Mercurial > wow > ouroloot
comparison core.lua @ 73:32eb24fb2ebf
- This code is not quite ready for prime time. Do not run it yet.
- Loot events have associated unique IDs, enabling some new actions over
the network. These IDs are preserved as part of realm history. As a
result, the stored history format has completely changed (and requires
less memory as a bonus).
- "Prescan for faster handling" option, default off.
- "Mark as <x>" now broadcast to other trackers. Older versions can't
receive the message, of course. Future: Broadcast reassigning loot.
- New options controlling whether (and where) to print a message when
another player broadcasts those kinds of changes to existing loot.
- Names colored by class when that data is available; CUSTOM_CLASS_COLORS
supported.
- Metric boatloads of minor tweaks and optimizations throughout.
author | Farmbuyer of US-Kilrogg <farmbuyer@gmail.com> |
---|---|
date | Tue, 29 May 2012 22:50:09 +0000 |
parents | fb330a1fb6e9 |
children | 124da015c4a2 |
comparison
equal
deleted
inserted
replaced
72:bb19899c65a7 | 73:32eb24fb2ebf |
---|---|
90 After he retired, I began modifying the code. Eventually I set aside the | 90 After he retired, I began modifying the code. Eventually I set aside the |
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. | 95 Some variables are needlessly initialized to nil just to look uniform and |
96 serve as a reminder. | |
96 | 97 |
97 ]==] | 98 ]==] |
98 | 99 |
99 ------ Saved variables | 100 ------ Saved variables |
100 OuroLootSV = nil -- possible copy of g_loot | 101 OuroLootSV = nil -- possible copy of g_loot |
128 ['Custom...'] = '', | 129 ['Custom...'] = '', |
129 }, | 130 }, |
130 ['forum_current'] = '[item] by name', | 131 ['forum_current'] = '[item] by name', |
131 ['display_disabled_LODs'] = false, | 132 ['display_disabled_LODs'] = false, |
132 ['display_bcast_from'] = true, | 133 ['display_bcast_from'] = true, |
134 ['precache_history_uniques'] = false, | |
135 ['chatty_on_remote_changes'] = false, | |
136 ['chatty_on_remote_changes_frame'] = 1, | |
133 } | 137 } |
134 local virgin = "First time loaded? Hi! Use the /ouroloot or /loot command" | 138 local virgin = "First time loaded? Hi! Use the /ouroloot or /loot command" |
135 .." to show the main display. You should probably browse the instructions" | 139 .." to show the main display. You should probably browse the instructions" |
136 .." if you've never used this before; %s to display the help window. This" | 140 .." if you've never used this before; %s to display the help window. This" |
137 .." welcome message will not intrude again." | 141 .." welcome message will not intrude again." |
138 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" |
139 .." 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" |
140 .." for their installed versions (same as '/ouroloot ping' or clicking the" | 144 .." for their installed versions (same as '/ouroloot ping' or clicking the" |
141 .." '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 <" | |
147 ..">, 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 " | |
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" | |
142 local qualnames = { | 151 local qualnames = { |
143 ['gray'] = 0, ['grey'] = 0, ['poor'] = 0, ['trash'] = 0, | 152 ['gray'] = 0, ['grey'] = 0, ['poor'] = 0, ['trash'] = 0, |
144 ['white'] = 1, ['common'] = 1, | 153 ['white'] = 1, ['common'] = 1, |
145 ['green'] = 2, ['uncommon'] = 2, | 154 ['green'] = 2, ['uncommon'] = 2, |
146 ['blue'] = 3, ['rare'] = 3, | 155 ['blue'] = 3, ['rare'] = 3, |
185 notraid = false, | 194 notraid = false, |
186 cache = false, | 195 cache = false, |
187 alsolog = false, | 196 alsolog = false, |
188 } | 197 } |
189 -- 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 |
190 -- the run-time ones. | 199 -- the run-time ones. Args to [dp]print are concatenated with spaces. |
191 if tekdebug then | 200 if tekdebug then |
192 function dprint (t,...) | 201 function dprint (t,...) |
193 if DEBUG_PRINT and debug[t] then | 202 if DEBUG_PRINT and debug[t] then |
194 local text = flib.safefprint(tekdebug,"<"..t.."> ",...) | 203 local text = flib.safefprint(tekdebug,"<"..t.."> ",...) |
195 if debug.alsolog then | 204 if debug.alsolog then |
285 local g_loot = nil | 294 local g_loot = nil |
286 local g_restore_p = nil | 295 local g_restore_p = nil |
287 local g_wafer_thin = nil -- for prompting for additional rebroadcasters | 296 local g_wafer_thin = nil -- for prompting for additional rebroadcasters |
288 local g_today = nil -- "today" entry in g_loot | 297 local g_today = nil -- "today" entry in g_loot |
289 local g_boss_signpost = nil | 298 local g_boss_signpost = nil |
299 local g_seeing_oldsigs = nil | |
300 local g_uniques = nil -- memoization of unique loot events | |
290 local opts = nil | 301 local opts = nil |
291 | 302 |
292 local pairs, ipairs, tinsert, tremove, tostring, tonumber, wipe = | 303 -- for speeding up local loads, not because I think _G will change |
293 pairs, ipairs, table.insert, table.remove, tostring, tonumber, table.wipe | 304 local _G = _G |
305 local type = _G.type | |
306 local select = _G.select | |
307 local pairs = _G.pairs | |
308 local ipairs = _G.ipairs | |
309 local tinsert = _G.table.insert | |
310 local tremove = _G.table.remove | |
311 local tostring = _G.tostring | |
312 local tonumber = _G.tonumber | |
313 local wipe = _G.table.wipe | |
314 | |
294 local pprint, tabledump = addon.pprint, flib.tabledump | 315 local pprint, tabledump = addon.pprint, flib.tabledump |
295 local CopyTable, GetNumRaidMembers = CopyTable, GetNumRaidMembers | 316 local CopyTable, GetNumRaidMembers = _G.CopyTable, _G.GetNumRaidMembers |
296 -- En masse forward decls of symbols defined inside local blocks | 317 -- En masse forward decls of symbols defined inside local blocks |
297 local _register_bossmod, makedate, create_new_cache, _init, _log | 318 local _register_bossmod, makedate, create_new_cache, _init, _log |
319 local _history_by_loot_id, _notify_about_remote | |
298 | 320 |
299 -- Try to extract numbers from the .toc "Version" and munge them into an | 321 -- Try to extract numbers from the .toc "Version" and munge them into an |
300 -- integral form for comparison. The result doesn't need to be meaningful as | 322 -- integral form for comparison. The result doesn't need to be meaningful as |
301 -- long as we can reliably feed two of them to "<" and get useful answers. | 323 -- long as we can reliably feed two of them to "<" and get useful answers. |
302 -- | 324 -- |
319 end | 341 end |
320 | 342 |
321 -- Hypertext support, inspired by DBM broadcast pizza timers | 343 -- Hypertext support, inspired by DBM broadcast pizza timers |
322 do | 344 do |
323 local hypertext_format_str = "|HOuroRaid:%s|h%s[%s]|r|h" | 345 local hypertext_format_str = "|HOuroRaid:%s|h%s[%s]|r|h" |
346 local strsplit = _G.strsplit | |
324 | 347 |
325 -- TEXT will automatically be surrounded by brackets | 348 -- TEXT will automatically be surrounded by brackets |
326 -- COLOR can be item quality code or a hex string | 349 -- COLOR can be item quality code or a hex string |
327 function addon.format_hypertext (code, text, color) | 350 function addon.format_hypertext (code, text, color) |
328 return hypertext_format_str:format (code, | 351 return hypertext_format_str:format (code, |
403 end | 426 end |
404 -- dynamic difficulties always return normal "codes" | 427 -- dynamic difficulties always return normal "codes" |
405 if isdynamic and perbossheroic == 1 then | 428 if isdynamic and perbossheroic == 1 then |
406 t = t .. "h" | 429 t = t .. "h" |
407 end | 430 end |
408 pprint("instance_tag final", t, r) | |
409 return name .. "(" .. t .. ")", r | 431 return name .. "(" .. t .. ")", r |
410 end | 432 end |
411 addon.instance_tag = instance_tag -- grumble | 433 addon.instance_tag = instance_tag -- grumble |
412 addon.latest_instance = nil -- spelling reminder, assigned elsewhere | 434 addon.latest_instance = nil -- spelling reminder, assigned elsewhere |
413 | 435 |
436 -- Memoizing cache of unique IDs as we generate or search for them. Keys are | |
437 -- the uniques, values are the following: | |
438 -- 'history' active index into self.history | |
439 -- 'history_may' index into player's uniques list, CAN QUICKLY BE OUTDATED | |
440 -- and will instantly be wrong after manual insertion | |
441 -- 'loot' active index into g_loot | |
442 -- with all but the history entry optional. Values of g_uniqes.NOTFOUND | |
443 -- indicate a known missing status. Use g_uniques:RESET() to wipe the cache | |
444 -- and return to searching mode. | |
445 do | |
446 local notfound = -1 | |
447 local notfound_ret = { history = notfound } | |
448 local mt | |
449 | |
450 -- This can either be its own function or a slightly redundant __index. | |
451 local function m_probe_only (t, k) | |
452 return rawget(t,k) or notfound_ret | |
453 end | |
454 | |
455 -- Expensive search. | |
456 local function m_full_search (t, k) | |
457 local L, H, HU, loot | |
458 -- Try active loot entries first | |
459 for i,e in addon:filtered_loot_iter('loot') do | |
460 if k == e.unique then | |
461 L,loot = i,e | |
462 break | |
463 end | |
464 end | |
465 -- If it's active, try looking through that player's history first. | |
466 if L then | |
467 local hi,h = addon:get_loot_history (loot.person) | |
468 for ui,u in ipairs(h.unique) do | |
469 if k == u then | |
470 H, HU = hi, ui | |
471 break | |
472 end | |
473 end | |
474 else | |
475 -- No luck? Ugh, may have been reassigned and we're probing from | |
476 -- older data. Search the rest of current realm's history. | |
477 for hi,h in ipairs(addon.history) do | |
478 for ui,u in ipairs(h.unique) do | |
479 if k == u then | |
480 H, HU = hi, ui | |
481 break | |
482 end | |
483 end | |
484 end | |
485 end | |
486 local ret = { loot = L, history = H or notfound, history_may = HU } | |
487 t[k] = ret | |
488 return ret | |
489 end | |
490 | |
491 local function m_setmode (self, mode) | |
492 mt.__index = (mode == 'probe') and m_probe_only or | |
493 (mode == 'search') and m_full_search or | |
494 nil -- maybe error() here? | |
495 end | |
496 | |
497 local function m_reset (self) | |
498 wipe(self) | |
499 self[''] = notfound_ret -- special case for receiving older broadcast | |
500 self.NOTFOUND = notfound | |
501 self.RESET = m_reset | |
502 self.SEARCH = m_full_search | |
503 self.TEST = m_probe_only | |
504 self.SETMODE = m_setmode | |
505 mt.__index = m_full_search | |
506 return self | |
507 end | |
508 | |
509 -- If unique keys ever change into objects instead of strings, change | |
510 -- this into a weakly-keyed table. | |
511 mt = { __metatable = 'Should be using setmode.' } | |
512 | |
513 g_uniques = setmetatable (m_reset{}, mt) | |
514 end | |
515 | |
414 | 516 |
415 ------ Expiring caches | 517 ------ Expiring caches |
416 --[[ | 518 --[[ |
417 foo = create_new_cache("myfoo",15[,cleanup]) -- ttl | 519 cache = create_new_cache ("mycache", 15 [,cleanup]) |
418 foo:add("blah") | 520 cache:add(foo) |
419 foo:test("blah") -- returns true | 521 cache:test(foo) -- returns true |
522 ....5 seconds pass | |
523 cache:add(bar) | |
524 ....10 seconds pass | |
525 cache:test(foo) -- returns false | |
526 cache:test(bar) -- returns true | |
527 ....5 seconds pass | |
528 ....bar also gone, cleanup() called | |
420 ]] | 529 ]] |
421 do | 530 do |
422 local caches = {} | 531 local caches = {} |
423 local cleanup_group = _G.AnimTimerFrame:CreateAnimationGroup() | 532 local cleanup_group = _G.AnimTimerFrame:CreateAnimationGroup() |
424 local time = _G.time | 533 local time = _G.time |
429 local alldone = true | 538 local alldone = true |
430 -- this is ass-ugly | 539 -- this is ass-ugly |
431 for name,c in pairs(caches) do | 540 for name,c in pairs(caches) do |
432 local fifo = c.fifo | 541 local fifo = c.fifo |
433 local active = #fifo > 0 | 542 local active = #fifo > 0 |
434 while (#fifo > 0) and (now - fifo[1].t > c.ttl) do | 543 while (#fifo > 0) and (now > fifo[1].t) do |
435 addon.dprint('cache', name, "cache removing", fifo[1].t, "<", fifo[1].m, ">") | 544 addon.dprint('cache', name, "cache removing", fifo[1].t, "<", fifo[1].m, ">") |
436 tremove(fifo,1) | 545 tremove(fifo,1) |
437 end | 546 end |
438 if active and #fifo == 0 and c.func then | 547 if active and #fifo == 0 and c.func then |
439 addon.dprint('cache', name, "empty, firing cleanup") | 548 addon.dprint('cache', name, "empty, firing cleanup") |
442 alldone = alldone and (#fifo == 0) | 551 alldone = alldone and (#fifo == 0) |
443 end | 552 end |
444 if alldone then | 553 if alldone then |
445 addon.dprint('cache',"OnLoop FINISHING animation group") | 554 addon.dprint('cache',"OnLoop FINISHING animation group") |
446 cleanup_group:Finish() | 555 cleanup_group:Finish() |
556 _G.collectgarbage() | |
447 else | 557 else |
448 addon.dprint('cache',"OnLoop done, not yet finished") | 558 addon.dprint('cache',"OnLoop done, not yet finished") |
449 end | 559 end |
450 end) | 560 end) |
451 | 561 |
452 local function _add (cache, x) | 562 local function _add (cache, x) |
453 local datum = { t=time(), m=x } | 563 local datum = { t=time()+cache.ttl, m=x } |
454 cache.hash[x] = datum | 564 cache.hash[x] = datum |
455 tinsert (cache.fifo, datum) | 565 tinsert (cache.fifo, datum) |
456 if not cleanup_group:IsPlaying() then | 566 if not cleanup_group:IsPlaying() then |
457 addon.dprint('cache', cache.name, "with entry", datum.t, "<", datum.m, "> STARTING animation group") | 567 addon.dprint('cache', cache.name, "with entry", datum.t, "<", datum.m, "> STARTING animation group") |
458 cache.cleanup:SetDuration(1) -- hmmm | 568 cache.cleanup:SetDuration(1) -- hmmm |
488 | 598 |
489 ------ Ace3 framework stuff | 599 ------ Ace3 framework stuff |
490 function addon:OnInitialize() | 600 function addon:OnInitialize() |
491 if self.author_debug then | 601 if self.author_debug then |
492 _G.OL = self | 602 _G.OL = self |
493 end | 603 _G.g_uniques = g_uniques |
494 _log = OuroLootSV_log | 604 end |
605 _log = _G.OuroLootSV_log | |
495 | 606 |
496 -- VARIABLES_LOADED has fired by this point; test if we're doing something like | 607 -- VARIABLES_LOADED has fired by this point; test if we're doing something like |
497 -- relogging during a raid and already have collected loot data | 608 -- relogging during a raid and already have collected loot data |
609 local OuroLootSV = _G.OuroLootSV | |
498 g_restore_p = OuroLootSV ~= nil | 610 g_restore_p = OuroLootSV ~= nil |
499 self.dprint('flow', "oninit sets restore as", g_restore_p) | 611 self.dprint('flow', "oninit sets restore as", g_restore_p) |
500 | 612 |
501 if OuroLootSV_opts == nil then | 613 if _G.OuroLootSV_opts == nil then |
502 OuroLootSV_opts = {} | 614 _G.OuroLootSV_opts = {} |
503 self:ScheduleTimer(function(s) | 615 self:ScheduleTimer(function(s) |
504 s:Print(virgin, s.format_hypertext('help',"click here",ITEM_QUALITY_UNCOMMON)) | 616 s:Print(virgin, s.format_hypertext('help',"click here",ITEM_QUALITY_UNCOMMON)) |
505 virgin = nil | 617 virgin = nil |
506 end,10,self) | 618 end,10,self) |
507 else | 619 else |
508 virgin = nil | 620 virgin = nil |
509 end | 621 end |
510 opts = OuroLootSV_opts | 622 opts = _G.OuroLootSV_opts |
511 local stored_datarev = opts.datarev or 14 | 623 local stored_datarev = opts.datarev or 14 |
512 for opt,default in pairs(option_defaults) do | 624 for opt,default in pairs(option_defaults) do |
513 if opts[opt] == nil then | 625 if opts[opt] == nil then |
514 opts[opt] = default | 626 opts[opt] = default |
515 end | 627 end |
555 | 667 |
556 self:RegisterChatCommand("ouroloot", "OnSlash") | 668 self:RegisterChatCommand("ouroloot", "OnSlash") |
557 if opts.register_slashloot then | 669 if opts.register_slashloot then |
558 -- NOTA BENE: do not use /loot in the LoadOn list, ChatTypeInfo gets confused | 670 -- NOTA BENE: do not use /loot in the LoadOn list, ChatTypeInfo gets confused |
559 -- maybe try to detect if this command is already in use... | 671 -- maybe try to detect if this command is already in use... |
560 SLASH_ACECONSOLE_OUROLOOT2 = "/loot" | 672 _G.SLASH_ACECONSOLE_OUROLOOT2 = "/loot" |
561 end | 673 end |
562 | 674 |
563 self.history_all = self.history_all or OuroLootSV_hist or {} | 675 self.history_all = self.history_all or _G.OuroLootSV_hist or {} |
564 local r = self:load_assert (GetRealmName(), "how the freak does GetRealmName() fail?") | 676 local r = self:load_assert (_G.GetRealmName(), "how the freak does GetRealmName() fail?") |
565 self.history_all[r] = self:_prep_new_history_category (self.history_all[r], r) | 677 self.history_all[r] = self:_prep_new_history_category (self.history_all[r], r) |
566 self.history = self.history_all[r] | 678 self.history = self.history_all[r] |
567 | 679 |
568 local histformat = self.history_all.HISTFORMAT | 680 local histformat = self.history_all.HISTFORMAT |
569 self.history_all.HISTFORMAT = nil -- don't keep this in live data | 681 self.history_all.HISTFORMAT = nil -- don't keep this in live data |
570 if (not InCombatLockdown()) and OuroLootSV_hist and | 682 if _G.OuroLootSV_hist |
571 (histformat == nil or histformat < 3) -- restored data but it's older | 683 and (histformat == nil or histformat < 4) |
572 then | 684 then -- some big honkin' loops |
573 -- Big honkin' loop | |
574 for rname,realm in pairs(self.history_all) do | 685 for rname,realm in pairs(self.history_all) do |
575 for pk,player in ipairs(realm) do | 686 for pk,player in ipairs(realm) do |
576 for lk,loot in ipairs(player) do | 687 if histformat == nil or histformat < 3 then |
577 if loot.count == "" then | 688 for lk,loot in ipairs(player) do |
578 loot.count = nil | 689 if loot.count == "" then |
579 end | 690 loot.count = nil |
580 if not loot.unique then | 691 end |
581 loot.unique = loot.id .. ' ' .. loot.when | 692 if not loot.unique then |
693 loot.unique = loot.id .. ' ' .. loot.when | |
694 end | |
582 end | 695 end |
583 end | 696 end |
584 end | 697 -- format 3 to format 4 was a major revamp of per-player data |
585 end | 698 self:_uplift_history_format(player,rname) |
586 end | 699 end |
700 end | |
701 end | |
702 self._uplift_history_format = nil | |
587 --OuroLootSV_hist = nil | 703 --OuroLootSV_hist = nil |
588 | 704 |
589 -- Handle changes to the stored data format in stages from oldest to newest. | 705 -- Handle changes to the stored data format in stages from oldest to newest. |
706 -- bumpers[X] is responsible for updating from X to X+1. | |
707 -- (This is turning into a lot of loops over the same table. Consolidate?) | |
590 if OuroLootSV then | 708 if OuroLootSV then |
591 local dirty = false | 709 local dirty = false |
592 local bumpers = {} | 710 local bumpers = {} |
593 bumpers[14] = function() | 711 bumpers[14] = function() |
594 for i,e in ipairs(OuroLootSV) do | 712 for i,e in ipairs(OuroLootSV) do |
632 -- In the not-very-many days between 16 and 19, I managed to break | 750 -- In the not-very-many days between 16 and 19, I managed to break |
633 -- the exact same data in the exact same way. At least they're | 751 -- the exact same data in the exact same way. At least they're |
634 -- not actually running the same loop twice... probably... sigh. | 752 -- not actually running the same loop twice... probably... sigh. |
635 | 753 |
636 bumpers[19] = function() | 754 bumpers[19] = function() |
755 local date = _G.date | |
637 for i,e in ipairs(OuroLootSV) do | 756 for i,e in ipairs(OuroLootSV) do |
638 if e.kind == 'loot' and e.history_unique then | 757 if e.kind == 'loot' then |
639 e.unique, e.history_unique = e.history_unique, nil | 758 if e.history_unique then |
759 e.unique, e.history_unique = e.history_unique, nil | |
760 end | |
761 if e.unique == nil or #e.unique == 0 then | |
762 e.unique = e.id .. ' ' .. date("%Y/%m/%d %H:%M",e.stamp) | |
763 end | |
640 end | 764 end |
641 end | 765 end |
642 end | 766 end |
643 | 767 |
644 --[===[ | 768 --[===[ |
665 | 789 |
666 function addon:OnEnable() | 790 function addon:OnEnable() |
667 self:RegisterEvent("PLAYER_LOGOUT") | 791 self:RegisterEvent("PLAYER_LOGOUT") |
668 self:RegisterEvent("RAID_ROSTER_UPDATE") | 792 self:RegisterEvent("RAID_ROSTER_UPDATE") |
669 | 793 |
670 -- Cribbed from Talented. I like the way jerry thinks: the first argument | 794 -- Cribbed from Talented. I like the way jerry thinks: the first string |
671 -- can be a format spec for the remainder of the arguments. (The new | 795 -- argument can be a format spec for the remainder of the arguments. |
672 -- AceConsole:Printf isn't used because we can't specify a prefix without | 796 -- AceConsole:Printf isn't used because we can't specify a prefix without |
673 -- jumping through ridonkulous hoops.) The part about overriding :Print | 797 -- jumping through ridonkulous hoops. The part about overriding :Print |
674 -- with a version using prefix hyperlinks is my fault. | 798 -- with a version using prefix hyperlinks is my fault. |
799 -- | |
800 -- CFPrint added instead of the usual Print testing of the first arg for | |
801 -- frame-ness, which would slow down all printing and only rarely be useful. | |
675 -- | 802 -- |
676 -- There is no ITEM_QUALITY_LEGENDARY constant. Sigh. | 803 -- There is no ITEM_QUALITY_LEGENDARY constant. Sigh. |
677 do | 804 do |
678 local AC = LibStub("AceConsole-3.0") | 805 local AC = LibStub("AceConsole-3.0") |
679 local chat_prefix = self.format_hypertext('openloot',"Ouro Loot",--[[legendary]]5) | 806 local chat_prefix = self.format_hypertext('openloot',"Ouro Loot",--[[legendary]]5) |
682 return AC:Print (chat_prefix, str:format(...)) | 809 return AC:Print (chat_prefix, str:format(...)) |
683 else | 810 else |
684 return AC:Print (chat_prefix, str, ...) | 811 return AC:Print (chat_prefix, str, ...) |
685 end | 812 end |
686 end | 813 end |
814 function addon:CFPrint (frame, str, ...) | |
815 assert(type(frame)=='table' and frame.AddMessage) | |
816 if type(str) == 'string' and str:find("%", nil, --[[plainmatch=]]true) then | |
817 return AC:Print (frame, chat_prefix, str:format(...)) | |
818 else | |
819 return AC:Print (frame, chat_prefix, str, ...) | |
820 end | |
821 end | |
687 end | 822 end |
688 | 823 |
689 while opts.keybinding do | 824 while opts.keybinding do |
690 if InCombatLockdown() then | 825 if InCombatLockdown() then |
691 self:Print("Cannot create '%s' as a keybinding while in combat!", | 826 self:Print("Cannot create '%s' as a keybinding while in combat!", |
697 KeyBindingFrame_LoadUI() | 832 KeyBindingFrame_LoadUI() |
698 local btn = CreateFrame("Button", "OuroLootBindingOpen", nil, "SecureActionButtonTemplate") | 833 local btn = CreateFrame("Button", "OuroLootBindingOpen", nil, "SecureActionButtonTemplate") |
699 btn:SetAttribute("type", "macro") | 834 btn:SetAttribute("type", "macro") |
700 btn:SetAttribute("macrotext", "/ouroloot toggle") | 835 btn:SetAttribute("macrotext", "/ouroloot toggle") |
701 if SetBindingClick(opts.keybinding_text, "OuroLootBindingOpen") then | 836 if SetBindingClick(opts.keybinding_text, "OuroLootBindingOpen") then |
702 -- a simple SaveBindings(GetCurrentBindingSet()) occasionally fails when GCBS | 837 -- a simple SaveBindings(GetCurrentBindingSet()) occasionally fails when |
703 -- decides to return neither 1 nor 2 during load, for reasons nobody has ever learned | 838 -- GCBS() decides to return neither 1 nor 2 during load, for reasons nobody |
839 -- has ever learned | |
704 local c = GetCurrentBindingSet() | 840 local c = GetCurrentBindingSet() |
705 if c == ACCOUNT_BINDINGS or c == CHARACTER_BINDINGS then | 841 if c == ACCOUNT_BINDINGS or c == CHARACTER_BINDINGS then |
706 SaveBindings(c) | 842 SaveBindings(c) |
707 end | 843 end |
708 else | 844 else |
756 button:SetPoint("TOPLEFT",20,-20) | 892 button:SetPoint("TOPLEFT",20,-20) |
757 _b:SetScript("OnShow",nil) | 893 _b:SetScript("OnShow",nil) |
758 end) | 894 end) |
759 _G.InterfaceOptions_AddCategory(bliz) | 895 _G.InterfaceOptions_AddCategory(bliz) |
760 | 896 |
897 -- Maybe load up g_uniques now? | |
898 if opts.precache_history_uniques then | |
899 self:_cache_history_uniques() | |
900 end | |
901 | |
761 self:_scan_LOD_modules() | 902 self:_scan_LOD_modules() |
903 | |
904 self:_set_remote_change_chatframe (opts.chatty_on_remote_changes_frame, --[[silent_p=]]true) | |
762 | 905 |
763 if self.debug.flow then self:Print"is in control-flow debug mode." end | 906 if self.debug.flow then self:Print"is in control-flow debug mode." end |
764 end | 907 end |
765 --function addon:OnDisable() end | 908 --function addon:OnDisable() end |
766 | 909 |
835 | 978 |
836 | 979 |
837 ------ Event handlers | 980 ------ Event handlers |
838 function addon:_clear_SVs() | 981 function addon:_clear_SVs() |
839 g_loot = {} -- not saved, just fooling PLAYER_LOGOUT tests | 982 g_loot = {} -- not saved, just fooling PLAYER_LOGOUT tests |
840 OuroLootSV = nil | 983 _G.OuroLootSV = nil |
841 OuroLootSV_saved = nil | 984 _G.OuroLootSV_saved = nil |
842 OuroLootSV_opts = nil | 985 _G.OuroLootSV_opts = nil |
843 OuroLootSV_hist = nil | 986 _G.OuroLootSV_hist = nil |
844 OuroLootSV_log = nil | 987 _G.OuroLootSV_log = nil |
845 ReloadUI() | 988 _G.ReloadUI() |
846 end | 989 end |
847 function addon:PLAYER_LOGOUT() | 990 function addon:PLAYER_LOGOUT() |
848 self:UnregisterEvent("RAID_ROSTER_UPDATE") | 991 self:UnregisterEvent("RAID_ROSTER_UPDATE") |
849 self:UnregisterEvent("PLAYER_ENTERING_WORLD") | 992 self:UnregisterEvent("PLAYER_ENTERING_WORLD") |
850 | 993 |
851 local worth_saving = #g_loot > 0 or next(g_loot.raiders) | 994 local worth_saving = #g_loot > 0 or _G.next(g_loot.raiders) |
852 if not worth_saving then for text in self:registered_textgen_iter() do | 995 if not worth_saving then for text in self:registered_textgen_iter() do |
853 worth_saving = worth_saving or g_loot.printed[text] > 0 | 996 worth_saving = worth_saving or g_loot.printed[text] > 0 |
854 end end | 997 end end |
855 if worth_saving then | 998 if worth_saving then |
856 opts.autoshard = self.sharder | 999 opts.autoshard = self.sharder |
857 opts.threshold = self.threshold | 1000 opts.threshold = self.threshold |
858 for i,e in ipairs(g_loot) do | 1001 for i,e in ipairs(g_loot) do |
859 e.cols = nil | 1002 e.cols = nil |
860 end | 1003 end |
861 OuroLootSV = g_loot | 1004 _G.OuroLootSV = g_loot |
862 else | 1005 else |
863 OuroLootSV = nil | 1006 _G.OuroLootSV = nil |
864 end | 1007 end |
865 | 1008 |
866 worth_saving = false | 1009 worth_saving = false |
867 for r,t in pairs(self.history_all) do if type(t) == 'table' then | 1010 for r,t in pairs(self.history_all) do if type(t) == 'table' then |
868 if #t == 0 then | 1011 if #t == 0 then |
873 t.st = nil | 1016 t.st = nil |
874 t.byname = nil | 1017 t.byname = nil |
875 end | 1018 end |
876 end end | 1019 end end |
877 if worth_saving then | 1020 if worth_saving then |
878 OuroLootSV_hist = self.history_all | 1021 _G.OuroLootSV_hist = self.history_all |
879 OuroLootSV_hist.HISTFORMAT = 3 | 1022 _G.OuroLootSV_hist.HISTFORMAT = 4 |
880 else | 1023 else |
881 OuroLootSV_hist = nil | 1024 _G.OuroLootSV_hist = nil |
882 end | 1025 end |
883 OuroLootSV_log = #OuroLootSV_log > 0 and OuroLootSV_log or nil | 1026 _G.OuroLootSV_log = #_G.OuroLootSV_log > 0 and _G.OuroLootSV_log or nil |
884 end | 1027 end |
885 | 1028 |
886 do | 1029 do |
887 local IsInInstance, UnitIsConnected, UnitClass, UnitRace, UnitSex, | 1030 local IsInInstance, UnitIsConnected, UnitClass, UnitRace, UnitSex, |
888 UnitLevel, UnitInRaid, UnitIsVisible, GetGuildInfo, GetRaidRosterInfo = | 1031 UnitLevel, UnitInRaid, UnitIsVisible, GetGuildInfo, GetRaidRosterInfo = |
1014 end | 1157 end |
1015 end | 1158 end |
1016 end | 1159 end |
1017 end | 1160 end |
1018 | 1161 |
1019 -- helper for CHAT_MSG_LOOT handler | 1162 --[=[ CHAT_MSG_LOOT handler and its helpers. |
1163 Situations for "unique tag" generation, given N people seeing local loot | |
1164 events, M people seeing remote rebroadcasts, and player Z adding manually: | |
1165 | |
1166 + Local tracking: All LOCALs should see the same itemstring, thus the same | |
1167 unique ID stripped out of field #9. LOCALn includes this in the broadcast | |
1168 to REMOTEm. Tag is a large number, meaningless for clients and players. | |
1169 | |
1170 + Local broadcasting, remote tracking: same as local tracking. Possibly | |
1171 some weirdness if all local versions are significantly older than the remote | |
1172 versions; in this case each REMOTEn will generate their own tags of the form | |
1173 itemID+formatted_date, which will not be "unique" for the next 60 seconds. | |
1174 As long as at least one LOCALn is recent enough to strip and broadcast a | |
1175 proper ID, multiple items of the same visible name will not be "lost". | |
1176 | |
1177 + Z manually inserts a loot entry: Z generates a tag, preserved locally. | |
1178 If Z rebrodcasts that entry, all REMOTEs will see it. Tag is of the form | |
1179 "n" followed by a random number. | |
1180 ]=] | |
1020 do | 1181 do |
1182 local counter, _do_loot | |
1183 do | |
1184 local count = 0 | |
1185 function counter() count = count + 1; return count; end | |
1186 end | |
1187 | |
1021 local function maybe_trash_kill_entry() | 1188 local function maybe_trash_kill_entry() |
1022 -- this is set on various boss interactions, so we've got a kill/wipe | 1189 -- this is set on various boss interactions, so we've got a kill/wipe |
1023 -- entry already | 1190 -- entry already -- XXX maybe clear it after a delay, so that loot |
1191 -- from trash after a boss isn't grouped with that boss? | |
1024 if addon.latest_instance then return end | 1192 if addon.latest_instance then return end |
1025 --addon.latest_instance = instance_tag() | 1193 --addon.latest_instance = instance_tag() |
1026 local ss, max, inst = addon:snapshot_raid() | 1194 local ss, max, inst = addon:snapshot_raid() |
1027 addon.latest_instance = inst | 1195 addon.latest_instance = inst |
1028 addon:_mark_boss_kill (addon._addBossEntry{ | 1196 addon:_mark_boss_kill (addon._addBossEntry{ |
1030 instance=addon.latest_instance, duration=0, | 1198 instance=addon.latest_instance, duration=0, |
1031 raidersnap=ss, maxsize=max | 1199 raidersnap=ss, maxsize=max |
1032 }) | 1200 }) |
1033 end | 1201 end |
1034 | 1202 |
1203 local random = _G.math.random | |
1204 local function many_uniques_handle_it (u, check_p) | |
1205 if u and check_p then | |
1206 -- Check and alert for an existing value. | |
1207 u = tostring(u) | |
1208 if g_uniques[u].history ~= g_uniques.NOTFOUND then | |
1209 return nil, u | |
1210 end | |
1211 addon.dprint('loot',"verified unique tag ("..u..")") | |
1212 else | |
1213 -- 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. | |
1215 repeat | |
1216 u = 'n' .. random(8675309) | |
1217 until g_uniques:TEST(u).history ~= g_uniques.NOTFOUND | |
1218 addon.dprint('loot',"created unique tag", u) | |
1219 end | |
1220 return true, u | |
1221 end | |
1222 | |
1035 -- Recent loot cache | 1223 -- Recent loot cache |
1036 local candidates = {} | 1224 local candidates = {} |
1225 local sigmap = {} | |
1226 _G.sigmap = sigmap | |
1227 local function preempt_older_signature (oldersig, newersig) | |
1228 local origin = candidates[oldersig] and candidates[oldersig].from | |
1229 if origin and g_seeing_oldsigs[origin] then | |
1230 -- replace entry from older client with this newer one | |
1231 candidates[oldersig] = nil | |
1232 addon.dprint('cache', "preempting signature <", oldersig, "> from", origin) | |
1233 end | |
1234 return false | |
1235 end | |
1236 | |
1037 local function prefer_local_loots (cache) | 1237 local function prefer_local_loots (cache) |
1038 -- The function name is a bit of a misnomer, as local entries overwrite | 1238 -- The function name is a bit of a misnomer, as local entries overwrite |
1039 -- remote entries as the candidate table is populated. This routine is | 1239 -- remote entries as the candidate table is populated. This routine is |
1040 -- here to extract the final results once the cache timers have expired. | 1240 -- here to extract the final results once the cache timers have expired. |
1041 -- | 1241 -- |
1042 -- Keep this sync'd with the local_override branch below. | 1242 -- Keep this sync'd with the local_override branch below. |
1043 for i,sig in ipairs(candidates) do | 1243 for i,sig in ipairs(candidates) do |
1044 addon.dprint('loot', "processing candidate entry", i, sig) | 1244 addon.dprint('loot', "processing candidate entry", i, sig) |
1045 local loot = candidates[sig] | 1245 local loot = candidates[sig] |
1046 if loot then | 1246 if loot then |
1047 addon.dprint('loot', i, "was found") | |
1048 maybe_trash_kill_entry() -- Generate *some* kind of boss/location entry | 1247 maybe_trash_kill_entry() -- Generate *some* kind of boss/location entry |
1049 candidates[sig] = nil | 1248 candidates[sig] = nil |
1050 local looti = addon._addLootEntry(loot) | 1249 local looti = addon._addLootEntry(loot) |
1250 addon.dprint('loot', "entry", i, "was found, added at index", looti) | |
1051 if (loot.disposition ~= 'shard') | 1251 if (loot.disposition ~= 'shard') |
1052 and (loot.disposition ~= 'gvault') | 1252 and (loot.disposition ~= 'gvault') |
1053 and (not addon.history_suppress) | 1253 and (not addon.history_suppress) |
1054 then | 1254 then |
1055 addon:_addHistoryEntry(looti) | 1255 addon:_addHistoryEntry(looti) |
1059 | 1259 |
1060 if addon.display then | 1260 if addon.display then |
1061 addon:redisplay() | 1261 addon:redisplay() |
1062 end | 1262 end |
1063 wipe(candidates) | 1263 wipe(candidates) |
1064 end | 1264 wipe(sigmap) |
1065 addon.recent_loot = create_new_cache ('loot', comm_cleanup_ttl+3, prefer_local_loots) | 1265 end |
1066 | 1266 local recent_loot = create_new_cache ('loot', comm_cleanup_ttl+3, prefer_local_loots) |
1067 local GetItemInfo, GetItemIcon, random = GetItemInfo, GetItemIcon, math.random | 1267 |
1268 local strsplit, GetItemInfo, GetItemIcon, UnitClass = | |
1269 _G.strsplit, _G.GetItemInfo, _G.GetItemIcon, _G.UnitClass | |
1068 | 1270 |
1069 -- 'from' only present if this is triggered by a broadcast | 1271 -- 'from' only present if this is triggered by a broadcast |
1070 function addon:_do_loot (local_override, recipient, unique, itemid, count, from, extratext) | 1272 function _do_loot (self, local_override, recipient, unique, itemid, count, from, extratext) |
1273 local prefix = "_do_loot[" .. counter() .. "]" | |
1071 local itexture = GetItemIcon(itemid) | 1274 local itexture = GetItemIcon(itemid) |
1072 local iname, ilink, iquality = GetItemInfo(itemid) | 1275 local iname, ilink, iquality = GetItemInfo(itemid) |
1073 local i | 1276 local cache_miss |
1074 if (not iname) or (not itexture) then | 1277 if (not iname) or (not itexture) then |
1075 i = true | 1278 cache_miss = true |
1076 iname, ilink, iquality, itexture = | 1279 iname, ilink, iquality, itexture = |
1077 UNKNOWN..': '..itemid, 'item:6948', ITEM_QUALITY_COMMON, [[ICONS\INV_Misc_QuestionMark]] | 1280 UNKNOWN..': '..itemid, 'item:6948', ITEM_QUALITY_COMMON, [[ICONS\INV_Misc_QuestionMark]] |
1078 end | 1281 end |
1079 self.dprint('loot',">>_do_loot, R:", recipient, "U:", unique, "I:", | 1282 self.dprint('loot',">>"..prefix, "R:", recipient, "U:", unique, "I:", |
1080 itemid, "C:", count, "frm:", from, "ex:", extratext, "q:", iquality) | 1283 itemid, "C:", count, "frm:", from, "ex:", extratext, "q:", iquality) |
1081 | 1284 |
1082 itemid = tonumber(ilink:match("item:(%d+)") or 0) | 1285 itemid = tonumber(ilink:match("item:(%d+)") or 0) |
1083 unique = tostring(unique or random(8675309)) -- also, xkcd.com/1047 | 1286 |
1084 -- This is only a 'while' to make jumping out of it easy and still do cleanup below. | 1287 -- This is only a 'while' to make jumping out of it easy. |
1085 while local_override or ((iquality >= self.threshold) and not opts.itemfilter[itemid]) do | 1288 local i, unique_okay, ret1, ret2 |
1289 while local_override | |
1290 or ((iquality >= self.threshold) and not opts.itemfilter[itemid]) | |
1291 do | |
1292 unique_okay, unique = many_uniques_handle_it (unique, not local_override) | |
1293 if not unique_okay then | |
1294 i = g_uniques[unique] | |
1295 local err = unique_collision:format (ERROR_CAPS, iname, unique, | |
1296 tostring(from), tostring(i.loot), tostring(i.history)) | |
1297 self:Print(err) | |
1298 _G.PlaySound("igQuestFailed", "master") | |
1299 -- Make sure this is logged one way or another | |
1300 ;(self.debug.loot and self.dprint or pprint)('loot', "COLLISION", prefix, err); | |
1301 ret1, ret2 = nil, err | |
1302 break | |
1303 end | |
1304 | |
1086 if (self.rebroadcast and (not from)) and not local_override then | 1305 if (self.rebroadcast and (not from)) and not local_override then |
1087 self:vbroadcast('loot', recipient, unique, itemid, count) | 1306 self:vbroadcast('loot', recipient, unique, itemid, count) |
1088 end | 1307 end |
1089 if (not self.enabled) and (not local_override) then break end | 1308 if (not self.enabled) and (not local_override) then break end |
1090 local signature = unique .. recipient .. iname .. (count or "") | 1309 |
1091 if from and self.recent_loot:test(signature) then | 1310 local oldersig = recipient .. iname .. (count or "") |
1092 self.dprint('cache', "remote loot <",signature,"> already in cache, skipping") | 1311 local signature, seenit |
1312 if #unique > 0 then | |
1313 -- newer case | |
1314 signature = unique .. oldersig | |
1315 sigmap[oldersig] = signature | |
1316 seenit = from and (recent_loot:test(signature) | |
1317 -- The following clause is what handles older 'casts arriving | |
1318 -- earlier. All this is tested inside-out to maximize short | |
1319 -- circuit avaluation. | |
1320 or (g_seeing_oldsigs and preempt_older_signature(oldersig,signature))) | |
1093 else | 1321 else |
1094 -- There is some redundancy in all this, in the interests of ease-of-coding | 1322 -- older case, only remote |
1095 i = { | 1323 assert(from) |
1096 kind = 'loot', | 1324 signature = sigmap[oldersig] or oldersig |
1097 person = recipient, | 1325 seenit = recent_loot:test(signature) |
1098 person_class= select(2,UnitClass(recipient)), | 1326 end |
1099 cache_miss = i and true or nil, | 1327 |
1100 quality = iquality, | 1328 if seenit then |
1101 itemname = iname, | 1329 self.dprint('cache', "remote", prefix, "<", signature, |
1102 id = itemid, | 1330 "> already in cache, skipping from", from) |
1103 itemlink = ilink, | 1331 break |
1104 itexture = itexture, | 1332 end |
1105 unique = unique, | 1333 |
1106 count = (count and count ~= "") and count or nil, | 1334 -- There is some redundancy in all this, in the interests of ease-of-coding |
1107 bcast_from = from, | 1335 i = { |
1108 extratext = extratext, | 1336 kind = 'loot', |
1109 variant = self:is_variant_item(ilink), | 1337 person = recipient, |
1110 } | 1338 person_class= select(2,UnitClass(recipient)), |
1111 if opts.itemvault[itemid] then | 1339 cache_miss = cache_miss, |
1112 i.disposition = 'gvault' | 1340 quality = iquality, |
1113 elseif recipient == self.sharder then | 1341 itemname = iname, |
1114 i.disposition = 'shard' | 1342 id = itemid, |
1343 itemlink = ilink, | |
1344 itexture = itexture, | |
1345 unique = unique, | |
1346 count = (count and count ~= "") and count or nil, | |
1347 bcast_from = from, | |
1348 extratext = extratext, | |
1349 variant = self:is_variant_item(ilink), | |
1350 } | |
1351 if opts.itemvault[itemid] then | |
1352 i.disposition = 'gvault' | |
1353 elseif recipient == self.sharder then | |
1354 i.disposition = 'shard' | |
1355 end | |
1356 if local_override then | |
1357 -- player is adding loot by hand, don't wait for network cache timeouts | |
1358 -- keep this sync'd with prefer_local_loots above | |
1359 if i.extratext == 'shard' | |
1360 or i.extratext == 'gvault' | |
1361 or i.extratext == 'offspec' | |
1362 then | |
1363 i.disposition = i.extratext | |
1115 end | 1364 end |
1116 if local_override then | 1365 local looti = self._addLootEntry(i) |
1117 -- player is adding loot by hand, don't wait for network cache timeouts | 1366 if (i.disposition ~= 'shard') |
1118 -- keep this sync'd with prefer_local_loots above | 1367 and (i.disposition ~= 'gvault') |
1119 if i.extratext == 'shard' | 1368 and (not self.history_suppress) |
1120 or i.extratext == 'gvault' | 1369 then |
1121 or i.extratext == 'offspec' | 1370 self:_addHistoryEntry(looti) |
1122 then | |
1123 i.disposition = i.extratext | |
1124 end | |
1125 local looti = self._addLootEntry(i) | |
1126 if (i.disposition ~= 'shard') | |
1127 and (i.disposition ~= 'gvault') | |
1128 and (not self.history_suppress) | |
1129 then | |
1130 self:_addHistoryEntry(looti) | |
1131 end | |
1132 i = looti -- return value mostly for gui's manual entry | |
1133 else | |
1134 self.recent_loot:add(signature) | |
1135 candidates[signature] = i | |
1136 tinsert (candidates, signature) | |
1137 self.dprint('cache', "loot <",signature,"> added to cache as candidate", #candidates) | |
1138 end | 1371 end |
1372 ret1 = looti -- return value mostly for gui's manual entry | |
1373 else | |
1374 recent_loot:add(signature) | |
1375 candidates[signature] = i | |
1376 tinsert (candidates, signature) | |
1377 self.dprint('cache', prefix, "<", signature, | |
1378 "> added to cache as candidate", #candidates) | |
1139 end | 1379 end |
1140 break | 1380 break |
1141 end | 1381 end |
1142 self.dprint('loot',"<<_do_loot out") | 1382 self.dprint('loot',"<<"..prefix, "out") |
1143 return i | 1383 return ret1, ret2 |
1144 end | 1384 end |
1145 | 1385 |
1386 -- Returns the index of the resulting new loot entry, or nil after | |
1387 -- displaying any errors. | |
1146 function addon:CHAT_MSG_LOOT (event, ...) | 1388 function addon:CHAT_MSG_LOOT (event, ...) |
1147 if (not self.rebroadcast) and (not self.enabled) and (event ~= "manual") then return end | 1389 if (not self.rebroadcast) and (not self.enabled) and (event ~= "manual") then return end |
1148 | 1390 |
1149 --[[ | 1391 --[[ |
1150 iname: Hearthstone | 1392 iname: Hearthstone |
1171 end | 1413 end |
1172 end | 1414 end |
1173 | 1415 |
1174 self.dprint('loot', "CHAT_MSG_LOOT, person is", person, | 1416 self.dprint('loot', "CHAT_MSG_LOOT, person is", person, |
1175 ", itemstring is", itemstring, ", count is", count) | 1417 ", itemstring is", itemstring, ", count is", count) |
1176 if not itemstring then return end -- "So-and-So selected Greed", etc, not actual looting | 1418 if not itemstring then return end -- "PlayerX selected Greed", etc, not looting |
1177 | 1419 |
1178 -- Name might be colorized, remove the highlighting | 1420 -- Name might be colorized, remove the highlighting |
1179 if person then | 1421 if person then |
1180 person = person:match("|c%x%x%x%x%x%x%x%x(%S+)") or person | 1422 person = person:match("|c%x%x%x%x%x%x%x%x(%S+)") or person |
1181 else | 1423 else |
1185 --local id = tonumber(itemstring:match('|Hitem:(%d+):')) | 1427 --local id = tonumber(itemstring:match('|Hitem:(%d+):')) |
1186 local id,unique,_ | 1428 local id,unique,_ |
1187 _,id,_,_,_,_,_,_,unique = strsplit (":", itemstring) | 1429 _,id,_,_,_,_,_,_,unique = strsplit (":", itemstring) |
1188 if unique == 0 then unique = nil end | 1430 if unique == 0 then unique = nil end |
1189 | 1431 |
1190 return self:_do_loot (false, person, unique, id, count) | 1432 return _do_loot (self, false, person, unique, id, count) |
1191 | 1433 |
1192 elseif event == "broadcast" then | 1434 elseif event == "broadcast" then |
1193 return self:_do_loot(false, ...) | 1435 return _do_loot(self, false, ...) |
1194 | 1436 |
1195 elseif event == "manual" then | 1437 elseif event == "manual" then |
1196 local r,i,n = ... | 1438 local r,i,n = ... |
1197 return self:_do_loot(true, r, --[[unique=]]nil, i, | 1439 return _do_loot(self, true, r, --[[unique=]]nil, i, |
1198 --[[count=]]nil, --[[from=]]nil, n) | 1440 --[[count=]]nil, --[[from=]]nil, n) |
1199 end | 1441 end |
1200 end | 1442 end |
1201 end | 1443 end |
1202 | 1444 |
1330 "previous data, or 'help' to read about saved-texts commands.") | 1572 "previous data, or 'help' to read about saved-texts commands.") |
1331 return | 1573 return |
1332 end | 1574 end |
1333 self.rebroadcast = true -- hardcode to true; this used to be more complicated | 1575 self.rebroadcast = true -- hardcode to true; this used to be more complicated |
1334 self.enabled = not opt_bcast_only | 1576 self.enabled = not opt_bcast_only |
1577 g_seeing_oldsigs = nil | |
1335 if opt_threshold then | 1578 if opt_threshold then |
1336 self:SetThreshold (opt_threshold, --[[quiet_p=]]true) | 1579 self:SetThreshold (opt_threshold, --[[quiet_p=]]true) |
1337 end | 1580 end |
1338 self:Print("Ouro Raid Loot is %s. Threshold currently %s.", | 1581 self:Print("Ouro Raid Loot is %s. Threshold currently %s.", |
1339 self.enabled and "tracking" or "only broadcasting", | 1582 self.enabled and "tracking" or "only broadcasting", |
1362 self.dprint('flow', "Clear: display visible but eoiST not set??") | 1605 self.dprint('flow', "Clear: display visible but eoiST not set??") |
1363 end | 1606 end |
1364 self.display:Hide() | 1607 self.display:Hide() |
1365 end | 1608 end |
1366 g_restore_p = nil | 1609 g_restore_p = nil |
1367 OuroLootSV = nil | 1610 _G.OuroLootSV = nil |
1368 self:_reset_timestamps() | 1611 self:_reset_timestamps() |
1369 if verbose_p then | 1612 if verbose_p then |
1370 if (OuroLootSV_saved and #OuroLootSV_saved>0) then | 1613 if (_G.OuroLootSV_saved and #_G.OuroLootSV_saved>0) then |
1371 self:Print("Current loot data cleared, %d saved sets remaining.", #OuroLootSV_saved) | 1614 self:Print("Current loot data cleared, %d saved sets remaining.", #_G.OuroLootSV_saved) |
1372 else | 1615 else |
1373 self:Print("Current loot data cleared.") | 1616 self:Print("Current loot data cleared.") |
1374 end | 1617 end |
1375 end | 1618 end |
1376 _init(self,st) | 1619 _init(self,st) |
1389 -- 3) VARIABLES_LOADED replaces SV_log pointer with restored version | 1632 -- 3) VARIABLES_LOADED replaces SV_log pointer with restored version |
1390 -- 4) logging happens to _log table (now with no other references) | 1633 -- 4) logging happens to _log table (now with no other references) |
1391 -- 5) at logout, nothing new has been entered in the table being saved | 1634 -- 5) at logout, nothing new has been entered in the table being saved |
1392 local date = _G.date | 1635 local date = _G.date |
1393 function addon:log_with_timestamp (msg) | 1636 function addon:log_with_timestamp (msg) |
1394 tinsert (_log, date('%m:%d %H:%M:%S ')..msg) | 1637 tinsert (_log, date('%m/%d %H:%M:%S ')..msg) |
1395 end | 1638 end |
1396 end | 1639 end |
1397 | 1640 |
1398 -- Check for plugins which haven't already been loaded, and add hooks for | 1641 -- Check for plugins which haven't already been loaded, and add hooks for |
1399 -- them. Credit to DBM for the approach here. | 1642 -- them. Credit to DBM for the approach here. |
1410 end | 1653 end |
1411 end | 1654 end |
1412 end | 1655 end |
1413 end | 1656 end |
1414 | 1657 |
1658 -- Routines for printing changes made by remote users. | |
1659 do | |
1660 local remote_change_chatframe | |
1661 | |
1662 function addon:_set_remote_change_chatframe (arg, silent_p) | |
1663 local frame | |
1664 if type(arg) == 'number' then | |
1665 arg = _G.math.min (arg, _G.NUM_CHAT_WINDOWS) | |
1666 frame = _G['ChatFrame'..arg] | |
1667 elseif type(arg) == 'string' then | |
1668 frame = _G[arg] | |
1669 end | |
1670 if type(frame) == 'table' and type(frame.AddMessage) == 'function' then | |
1671 remote_change_chatframe = frame | |
1672 if not silent_p then | |
1673 local msg = "Now printing to chat frame " .. arg | |
1674 if frame.GetName then | |
1675 msg = msg .. " (" .. tostring(frame:GetName()) .. ")" | |
1676 end | |
1677 self:Print(msg) | |
1678 if frame ~= _G.DEFAULT_CHAT_FRAME then | |
1679 self:CFPrint (frame, msg) | |
1680 end | |
1681 end | |
1682 return frame | |
1683 else | |
1684 self:Print("'%s' was not a valid chat frame number/name, no change has been made.", arg) | |
1685 end | |
1686 end | |
1687 | |
1688 function _notify_about_remote (sender, index, from_whom, olddisp) | |
1689 local e = g_loot[index] | |
1690 local from_color, from_text, to_color, to_text | |
1691 if from_whom then | |
1692 -- FIXME need to return previous name/class from reassign_loot | |
1693 | |
1694 else | |
1695 if olddisp then | |
1696 from_text = addon.disposition_colors[olddisp].text | |
1697 else | |
1698 olddisp = "normal" | |
1699 from_text = "normal" | |
1700 end | |
1701 from_color = addon.disposition_colors[olddisp].hex | |
1702 if e.disposition then | |
1703 to_text = addon.disposition_colors[e.disposition].text | |
1704 else | |
1705 to_text = "normal" | |
1706 end | |
1707 to_color = addon.disposition_colors[e.disposition or "normal"].hex | |
1708 end | |
1709 | |
1710 addon:CFPrint (remote_change_chatframe, remote_chatty, sender, index, | |
1711 e.itemlink, from_color, from_text, to_color, to_text) | |
1712 end | |
1713 end | |
1714 | |
1415 -- Adds indices to traverse the tables in a nice sorted order. | 1715 -- Adds indices to traverse the tables in a nice sorted order. |
1416 do | 1716 do |
1417 local byindex, temp = {}, {} | 1717 local byindex, temp = {}, {} |
1418 local function sort (src, dest) | 1718 local function sort (src, dest) |
1419 for k in pairs(src) do | 1719 for k in pairs(src) do |
1420 temp[#temp+1] = k | 1720 temp[#temp+1] = k |
1421 end | 1721 end |
1422 table.sort(temp) | 1722 _G.table.sort(temp) |
1423 wipe(dest) | 1723 wipe(dest) |
1424 for i = 1, #temp do | 1724 for i = 1, #temp do |
1425 dest[i] = src[temp[i]] | 1725 dest[i] = src[temp[i]] |
1426 end | 1726 end |
1427 end | 1727 end |
1489 if not closest_boss then | 1789 if not closest_boss then |
1490 fencepost = closest_time | 1790 fencepost = closest_time |
1491 elseif not closest_time then | 1791 elseif not closest_time then |
1492 fencepost = closest_boss | 1792 fencepost = closest_boss |
1493 else | 1793 else |
1494 fencepost = math.min(closest_time,closest_boss) | 1794 fencepost = _G.math.min(closest_time,closest_boss) |
1495 end | 1795 end |
1496 end | 1796 end |
1497 return fencepost | 1797 return fencepost |
1498 end | 1798 end |
1499 | 1799 |
1536 function _init (self, possible_st) | 1836 function _init (self, possible_st) |
1537 self.dprint('flow',"_init running") | 1837 self.dprint('flow',"_init running") |
1538 self.loot_clean = nil | 1838 self.loot_clean = nil |
1539 self.hist_clean = nil | 1839 self.hist_clean = nil |
1540 if g_restore_p then | 1840 if g_restore_p then |
1541 g_loot = OuroLootSV | 1841 g_loot = _G.OuroLootSV |
1542 self.popped = #g_loot > 0 | 1842 self.popped = #g_loot > 0 |
1543 self.dprint('flow', "restoring", #g_loot, "entries") | 1843 self.dprint('flow', "restoring", #g_loot, "entries") |
1544 self:ScheduleTimer("Activate", 12, opts.threshold) | 1844 self:ScheduleTimer("Activate", 12, opts.threshold) |
1545 -- FIXME printed could be too large if entries were deleted, how much do we care? | 1845 -- FIXME printed could be too large if entries were deleted, how much do we care? |
1546 self.sharder = opts.autoshard | 1846 self.sharder = opts.autoshard |
1552 self:gui_init(g_loot) | 1852 self:gui_init(g_loot) |
1553 opts.autoshard = nil | 1853 opts.autoshard = nil |
1554 opts.threshold = nil | 1854 opts.threshold = nil |
1555 | 1855 |
1556 if g_restore_p then | 1856 if g_restore_p then |
1557 self:zero_printed_fenceposts() -- g_loot.printed.* = previous/safe values | 1857 self:zero_printed_fenceposts() -- g_loot.printed.* = previous/safe values |
1558 else | 1858 else |
1559 self:zero_printed_fenceposts(0) -- g_loot.printed.* = 0 | 1859 self:zero_printed_fenceposts(0) -- g_loot.printed.* = 0 |
1560 end | 1860 end |
1561 if possible_st then | 1861 if possible_st then |
1562 possible_st:SetData(g_loot) | 1862 possible_st:SetData(g_loot) |
1563 end | 1863 end |
1564 | 1864 |
1565 self.status_text = ("%s communicating as ident %s commrev %s"):format(self.revision,self.ident,self.commrev) | 1865 self.status_text = ("%s communicating as ident %s commrev %s"): |
1866 format (self.revision, self.ident, self.commrev) | |
1566 self:RegisterComm(self.ident) | 1867 self:RegisterComm(self.ident) |
1567 self:RegisterComm(self.identTg, "OnCommReceivedNocache") | 1868 self:RegisterComm(self.identTg, "OnCommReceivedNocache") |
1568 | 1869 |
1569 if self.author_debug then | 1870 if self.author_debug then |
1570 _G.Oloot = g_loot | 1871 _G.Oloot = g_loot |
1575 do | 1876 do |
1576 function addon:snapshot_raid (only_inraid_p) | 1877 function addon:snapshot_raid (only_inraid_p) |
1577 local ss = CopyTable(g_loot.raiders) | 1878 local ss = CopyTable(g_loot.raiders) |
1578 local instance,maxsize = instance_tag() | 1879 local instance,maxsize = instance_tag() |
1579 if only_inraid_p then | 1880 if only_inraid_p then |
1580 for name,info in next, ss do | 1881 for name,info in _G.next, ss do |
1581 if info.online == 3 then | 1882 if info.online == 3 then |
1582 ss[name] = nil | 1883 ss[name] = nil |
1583 end | 1884 end |
1584 end | 1885 end |
1585 end | 1886 end |
1586 return ss, maxsize, instance, time() | 1887 return ss, maxsize, instance, _G.time() |
1587 end | 1888 end |
1588 end | 1889 end |
1589 | 1890 |
1590 -- Tie-in with Deadly Boss Mods (or other such addons) | 1891 -- Tie-in with Deadly Boss Mods (or other such addons) |
1591 do | 1892 do |
1629 addon:Print("Registered kill for '%s' in %s!", boss.bossname, boss.instance) | 1930 addon:Print("Registered kill for '%s' in %s!", boss.bossname, boss.instance) |
1630 end | 1931 end |
1631 end | 1932 end |
1632 wipe(candidates) | 1933 wipe(candidates) |
1633 end | 1934 end |
1634 addon.recent_boss = create_new_cache ('boss', 10, fixup_durations) | 1935 local recent_boss = create_new_cache ('boss', 10, fixup_durations) |
1635 | 1936 |
1636 -- Similar to _do_loot, but duration+ parms only present when locally generated. | 1937 -- Similar to _do_loot, but duration+ parms only present when locally generated. |
1637 local function _do_boss (self, reason, bossname, intag, maxsize, duration) | 1938 local function _do_boss (self, reason, bossname, intag, maxsize, duration) |
1638 self.dprint('loot',">>_do_boss, R:", reason, "B:", bossname, | 1939 self.dprint('loot',">>_do_boss, R:", reason, "B:", bossname, |
1639 "T:", intag, "MS:", maxsize, "D:", duration) | 1940 "T:", intag, "MS:", maxsize, "D:", duration) |
1644 while self.enabled do | 1945 while self.enabled do |
1645 if reason == 'wipe' and opts.no_tracking_wipes then break end | 1946 if reason == 'wipe' and opts.no_tracking_wipes then break end |
1646 bossname = (opts.snarky_boss and self.boss_abbrev[bossname] or bossname) or bossname | 1947 bossname = (opts.snarky_boss and self.boss_abbrev[bossname] or bossname) or bossname |
1647 local not_from_local = duration == nil | 1948 local not_from_local = duration == nil |
1648 local signature = bossname .. reason | 1949 local signature = bossname .. reason |
1649 if not_from_local and self.recent_boss:test(signature) then | 1950 if not_from_local and recent_boss:test(signature) then |
1650 self.dprint('cache', "remote boss <",signature,"> already in cache, skipping") | 1951 self.dprint('cache', "remote boss <",signature,"> already in cache, skipping") |
1651 else | 1952 else |
1652 self.recent_boss:add(signature) | 1953 recent_boss:add(signature) |
1653 g_boss_signpost = #g_loot + 1 | 1954 g_boss_signpost = #g_loot + 1 |
1654 self.dprint('loot', "added boss signpost", g_boss_signpost) | 1955 self.dprint('loot', "added boss signpost", g_boss_signpost) |
1655 -- Possible scenarios: (1) we don't see a boss event at all (e.g., we're | 1956 -- Possible scenarios: (1) we don't see a boss event at all (e.g., we're |
1656 -- outside the instance) and so this only happens once as a non-local event, | 1957 -- outside the instance) and so this only happens once as a non-local event, |
1657 -- (2) we see a local event first and all non-local events are filtered | 1958 -- (2) we see a local event first and all non-local events are filtered |
1761 end | 2062 end |
1762 end | 2063 end |
1763 | 2064 |
1764 -- Adding entries to the loot record, and tracking the corresponding timestamp. | 2065 -- Adding entries to the loot record, and tracking the corresponding timestamp. |
1765 do | 2066 do |
2067 local rawget, setmetatable = _G.rawget, _G.setmetatable | |
2068 | |
1766 -- This shouldn't be required. /sadface | 2069 -- This shouldn't be required. /sadface |
1767 local loot_entry_mt = { | 2070 local loot_entry_mt = { |
1768 __index = function (e,key) | 2071 __index = function (e,key) |
1769 if key == 'cols' then | 2072 if key == 'cols' then |
1770 pprint('mt', e.kind, "key is", key) | 2073 pprint('mt', e.kind, "key is", key) |
1844 function addon._addLootEntry (e) | 2147 function addon._addLootEntry (e) |
1845 setmetatable(e,loot_entry_mt) | 2148 setmetatable(e,loot_entry_mt) |
1846 | 2149 |
1847 if not done_todays_date then do_todays_date() end | 2150 if not done_todays_date then do_todays_date() end |
1848 | 2151 |
1849 local h, m = GetGameTime() | 2152 local h, m = _G.GetGameTime() |
1850 --local localuptime = math.floor(GetTime()) | 2153 --local localuptime = math.floor(GetTime()) |
1851 local time_t = time() | 2154 local time_t = _G.time() |
1852 e.hour = h | 2155 e.hour = h |
1853 e.minute = m | 2156 e.minute = m |
1854 e.stamp = time_t --localuptime | 2157 e.stamp = time_t --localuptime |
1855 local index = #g_loot + 1 | 2158 local index = #g_loot + 1 |
1856 g_loot[index] = e | 2159 g_loot[index] = e |
1909 self:Print("Fixing up missing item cache data...") | 2212 self:Print("Fixing up missing item cache data...") |
1910 | 2213 |
1911 local numfound = 0 | 2214 local numfound = 0 |
1912 local borkedpat = '^'..UNKNOWN..': (%S+)' | 2215 local borkedpat = '^'..UNKNOWN..': (%S+)' |
1913 | 2216 |
1914 for i,e in self:filtered_loot_iter('loot') do | 2217 -- 'while true' so that we can use (inner) break as (outer) continue |
1915 if e.cache_miss then | 2218 for i,e in self:filtered_loot_iter('loot') do while true do |
1916 local borked_id = e.itemname:match(borkedpat) | 2219 if not e.cache_miss then break end |
1917 if borked_id then | 2220 local borked_id = e.itemname:match(borkedpat) |
1918 numfound = numfound + 1 | 2221 if not borked_id then break end |
1919 -- Best to use the safest and most flexible API here, which is GII and | 2222 numfound = numfound + 1 |
1920 -- its assload of return values. | 2223 -- Best to use the safest and most flexible API here, which is GII and |
1921 local iname, ilink, iquality, _,_,_,_,_,_, itexture = GetItemInfo(borked_id) | 2224 -- its assload of return values. |
1922 if iname then | 2225 local iname, ilink, iquality, _,_,_,_,_,_, itexture = GetItemInfo(borked_id) |
1923 self:Print(" Entry %d patched up with %s.", i, ilink) | 2226 if iname then |
1924 e.quality = iquality | 2227 local msg = [[ Entry %d patched up with %s.]] |
1925 e.itemname = iname | 2228 e.quality = iquality |
1926 e.id = tonumber(ilink:match("item:(%d+)")) | 2229 e.itemname = iname |
1927 e.itemlink = ilink | 2230 e.id = tonumber(ilink:match("item:(%d+)")) |
1928 e.itexture = itexture | 2231 e.itemlink = ilink |
1929 e.cache_miss = nil | 2232 e.itexture = itexture |
2233 e.cache_miss = nil | |
2234 if e.unique then | |
2235 local gu = g_uniques[e.unique] | |
2236 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? | |
2238 pprint('loot', ("Unique value '%s' had iterator value %d but g_uniques index %s."):format(e.unique,i,tostring(gu.loot))) | |
1930 end | 2239 end |
1931 end | 2240 if player_i then |
1932 end | 2241 player_h.id[e.unique] = e.id |
1933 end | 2242 msg = [[ Entry %d (and history) patched up with %s.]] |
2243 end | |
2244 end | |
2245 self:Print(msg, i, ilink) | |
2246 end | |
2247 break | |
2248 end end | |
1934 | 2249 |
1935 self:Print("...finished. Found %d |4entry:entries; with weird data.", numfound) | 2250 self:Print("...finished. Found %d |4entry:entries; with weird data.", numfound) |
1936 end | 2251 end |
1937 | 2252 |
1938 | 2253 |
1939 ------ Saved texts | 2254 ------ Saved texts |
1940 function addon:check_saved_table(silent_p) | 2255 function addon:check_saved_table(silent_p) |
1941 local s = OuroLootSV_saved | 2256 local s = _G.OuroLootSV_saved |
1942 if s and (#s > 0) then return s end | 2257 if s and (#s > 0) then return s end |
1943 OuroLootSV_saved = nil | 2258 _G.OuroLootSV_saved = nil |
1944 if not silent_p then self:Print("There are no saved loot texts.") end | 2259 if not silent_p then self:Print("There are no saved loot texts.") end |
1945 end | 2260 end |
1946 | 2261 |
1947 function addon:save_list() | 2262 function addon:save_list() |
1948 local s = self:check_saved_table(); if not s then return end; | 2263 local s = self:check_saved_table(); if not s then return end; |
1950 self:Print("#%d %s %d entries %s", i, t.date, t.count, t.name) | 2265 self:Print("#%d %s %d entries %s", i, t.date, t.count, t.name) |
1951 end | 2266 end |
1952 end | 2267 end |
1953 | 2268 |
1954 function addon:save_saveas(name) | 2269 function addon:save_saveas(name) |
1955 OuroLootSV_saved = OuroLootSV_saved or {} | 2270 _G.OuroLootSV_saved = _G.OuroLootSV_saved or {} |
1956 local SV = OuroLootSV_saved | 2271 local SV = _G.OuroLootSV_saved |
1957 local n = #SV + 1 | 2272 local n = #SV + 1 |
1958 local save = { | 2273 local save = { |
1959 name = name, | 2274 name = name, |
1960 date = makedate(), | 2275 date = makedate(), |
1961 count = #g_loot, | 2276 count = #g_loot, |
2003 -- ["OtherPlayer"] = 2, | 2318 -- ["OtherPlayer"] = 2, |
2004 -- ["Farmbuyer"] = 1, | 2319 -- ["Farmbuyer"] = 1, |
2005 -- } | 2320 -- } |
2006 -- [1] = { | 2321 -- [1] = { |
2007 -- ["name"] = "Farmbuyer", | 2322 -- ["name"] = "Farmbuyer", |
2008 -- [1] = { id = nnnnn, when = "formatted timestamp for displaying" } -- most recent loot | 2323 -- ["person_class"] = "PRIEST", -- may be missing, used in display only |
2009 -- [2] = { ......., [count = "x3"] } -- previous loot | 2324 -- -- sorted array: |
2325 -- ["unique"] = { most_recent_tag, previous_tag, .... }, | |
2326 -- -- these are indexed by unique tags, and 'count' may be missing: | |
2327 -- ["when"] = { ["tag"] = "formatted timestamp for displaying loot" }, | |
2328 -- ["id"] = { ["tag"] = 11111 }, | |
2329 -- ["count"] = { ["tag"] = "x3", .... }, | |
2010 -- }, | 2330 -- }, |
2011 -- [2] = { | 2331 -- [2] = { |
2012 -- ["name"] = "OtherPlayer", | 2332 -- ["name"] = "OtherPlayer", |
2013 -- ...... | 2333 -- ...... |
2014 -- }, ...... | 2334 -- }, ...... |
2015 -- }, | 2335 -- }, |
2016 -- ["OtherRealm"] = ...... | 2336 -- ["OtherRealm"] = ...... |
2017 -- } | 2337 -- } |
2338 -- | |
2339 -- Up through 2.81.4 (specifically through rev 95), an individual player's | |
2340 -- table looked like this: | |
2341 -- ["name"] = "Farmbuyer", | |
2342 -- [1] = { id = nnnnn, when = "formatted timestamp for displaying" } -- most recent loot | |
2343 -- [2] = { ......., [count = "x3"] } -- previous loot | |
2344 -- which was much easier to manipulate, but had a ton of memory overhead. | |
2018 do | 2345 do |
2019 local tsort = table.sort | 2346 -- Sorts a player's history from newest to oldest, according to the |
2020 local comp = function(L,R) return L.when > R.when end | 2347 -- formatted timestamp. This is expensive, and destructive for P.unique. |
2348 local function sort_player (p) | |
2349 local new_uniques, uniques_bywhen, when_array = {}, {}, {} | |
2350 for u,tstamp in pairs(p.when) do | |
2351 uniques_bywhen[tstamp] = u | |
2352 when_array[#when_array+1] = tstamp | |
2353 end | |
2354 _G.table.sort(when_array) | |
2355 for i,tstamp in ipairs(when_array) do | |
2356 new_uniques[i] = uniques_bywhen[tstamp] | |
2357 end | |
2358 p.unique = new_uniques | |
2359 end | |
2360 | |
2361 -- Possibly called during login. Cleared when no longer needed. | |
2362 -- Rewrites a PLAYER table from format 3 to format 4. | |
2363 function addon:_uplift_history_format (player, realmname) | |
2364 local unique, when, id, count = {}, {}, {}, {} | |
2365 local name = player.name | |
2366 | |
2367 for i,h in ipairs(player) do | |
2368 local U = h.unique | |
2369 unique[i] = U | |
2370 when[U] = h.when | |
2371 id[U] = h.id | |
2372 count[U] = h.count | |
2373 end | |
2374 | |
2375 wipe(player) | |
2376 player.name = name | |
2377 player.id, player.when, player.unique, player.count = | |
2378 id, when, unique, count | |
2379 end | |
2380 function addon:_cache_history_uniques() | |
2381 UpdateAddOnMemoryUsage() | |
2382 local before = GetAddOnMemoryUsage(nametag) | |
2383 local trouble | |
2384 local count = 0 | |
2385 for hi,player in ipairs(self.history) do | |
2386 for ui,u in ipairs(player.unique) do | |
2387 g_uniques[u] = { history = hi, history_may = ui } | |
2388 count = count + 1 | |
2389 end | |
2390 end | |
2391 for i,e in self:filtered_loot_iter('loot') do | |
2392 if e.unique and e.unique ~= "" then | |
2393 local hmmm = _G.rawget(g_uniques,e.unique) | |
2394 if hmmm then | |
2395 hmmm.loot = i --;print("Active loot", i, "found with tag", e.unique) | |
2396 elseif e.disposition == 'shard' or e.disposition == 'gvault' then | |
2397 g_uniques[e.unique] = { loot = i, history = g_uniques.NOTFOUND } | |
2398 count = count + 1 | |
2399 --print("Active loot", i, "INSERTED with tag", e.unique, "as", e.disposition) | |
2400 else | |
2401 hmmm = "wonked data ("..i.."/"..tostring(e.unique)..") in precache loop!" | |
2402 pprint(hmmm) | |
2403 -- try to simply fix up errors as we go | |
2404 g_uniques[e.unique] = { loot = i, history = g_uniques.NOTFOUND } | |
2405 trouble = true | |
2406 end | |
2407 else | |
2408 trouble = true | |
2409 pprint('loot', "ERROR precache loop found bad unique tag!", | |
2410 i, "tag", tostring(e.unique), "from?", tostring(e.bcast_from)) | |
2411 end | |
2412 end | |
2413 UpdateAddOnMemoryUsage() | |
2414 local after = GetAddOnMemoryUsage(nametag) | |
2415 self:Print("Pre-scanning history for faster loot handling on %s used %.2f MB of memory across %d entries.", | |
2416 self.history.realm, (after-before)/1024, count) | |
2417 if trouble then | |
2418 self:Print("Note that there were inconsistencies in the data;", | |
2419 "you should consider submitting a bug report (including your", | |
2420 "SavedVariables file), and regenerating or preening this", | |
2421 "realm's loot history.") | |
2422 end | |
2423 g_uniques:SETMODE('probe') | |
2424 self._cache_history_uniques = nil | |
2425 end | |
2021 | 2426 |
2022 -- Builds the map of names to array indices, using passed table or | 2427 -- Builds the map of names to array indices, using passed table or |
2023 -- self.history, and stores the result into its 'byname' field. Also | 2428 -- self.history, and stores the result into its 'byname' field. Also |
2024 -- called from the GUI code at least once. | 2429 -- called from the GUI code at least once. |
2025 function addon:_build_history_names (opt_hist) | 2430 function addon:_build_history_names (opt_hist) |
2026 local hist = opt_hist or self.history | 2431 local hist = opt_hist or self.history |
2027 local m = {} | 2432 local m = {} |
2028 for i = 1, #hist do | 2433 for i = 1, #hist do |
2029 m[hist[i].name] = i | 2434 m[hist[i].name] = i |
2030 end | 2435 end |
2436 -- why yes, I *did* spend many years as a YP/NIS admin, how did you know? | |
2031 hist.byname = m | 2437 hist.byname = m |
2032 end | 2438 end |
2033 | 2439 |
2034 -- Prepares and returns table to be used as self.history. | 2440 -- Prepares and returns table to be used as self.history. |
2035 function addon:_prep_new_history_category (prev_table, realmname) | 2441 function addon:_prep_new_history_category (prev_table, realmname) |
2057 function addon:get_loot_history (name) | 2463 function addon:get_loot_history (name) |
2058 local i | 2464 local i |
2059 i = self.history.byname[name] | 2465 i = self.history.byname[name] |
2060 if not i then | 2466 if not i then |
2061 i = #self.history + 1 | 2467 i = #self.history + 1 |
2062 self.history[i] = { name=name } | 2468 self.history[i] = { name=name, id={}, when={}, unique={}, count={} } |
2063 self.history.byname[name] = i | 2469 self.history.byname[name] = i |
2064 end | 2470 end |
2065 return i, self.history[i] | 2471 return i, self.history[i] |
2066 end | 2472 end |
2067 | 2473 |
2474 -- Prepends data from the loot entry at LOOTINDEX to be the new most | |
2475 -- recent history entry for that player. | |
2068 function addon:_addHistoryEntry (lootindex) | 2476 function addon:_addHistoryEntry (lootindex) |
2069 local e = g_loot[lootindex] | 2477 local e = g_loot[lootindex] |
2070 if e.kind ~= 'loot' then return end | 2478 if e.kind ~= 'loot' then return end |
2071 | 2479 |
2072 local i,h = self:get_loot_history(e.person) | 2480 local i,h = self:get_loot_history(e.person) |
2481 local when = self:format_timestamp (g_today, e) | |
2482 | |
2073 -- If any of these change, update the end of history_handle_disposition. | 2483 -- If any of these change, update the end of history_handle_disposition. |
2074 local n = { | |
2075 id = e.id, | |
2076 when = self:format_timestamp (g_today, e), | |
2077 count = e.count, | |
2078 } | |
2079 tinsert (h, 1, n) | |
2080 if (not e.unique) or (#e.unique==0) then | 2484 if (not e.unique) or (#e.unique==0) then |
2081 e.unique = n.id .. ' ' .. n.when | 2485 e.unique = e.id .. ' ' .. when |
2082 end | 2486 end |
2083 n.unique = e.unique | 2487 local U = e.unique |
2488 tinsert (h.unique, 1, U) | |
2489 h.when[U] = when | |
2490 h.id[U] = e.id | |
2491 h.count[U] = e.count | |
2492 | |
2493 g_uniques[U] = { loot = lootindex, history = i } | |
2084 end | 2494 end |
2085 | 2495 |
2086 -- Create new history table based on current loot. | 2496 -- Create new history table based on current loot. |
2087 function addon:rewrite_history (realmname) | 2497 function addon:rewrite_history (realmname) |
2088 local r = assert(realmname) | 2498 local r = assert(realmname) |
2089 self.history_all[r] = self:_prep_new_history_category (nil, r) | 2499 self.history_all[r] = self:_prep_new_history_category (nil, r) |
2090 self.history = self.history_all[r] | 2500 self.history = self.history_all[r] |
2501 g_uniques:RESET() | |
2091 | 2502 |
2092 local g_today_real = g_today | 2503 local g_today_real = g_today |
2093 for i,e in ipairs(g_loot) do | 2504 for i,e in ipairs(g_loot) do |
2094 if e.kind == 'time' then | 2505 if e.kind == 'time' then |
2095 g_today = e | 2506 g_today = e |
2100 g_today = g_today_real | 2511 g_today = g_today_real |
2101 self.hist_clean = nil | 2512 self.hist_clean = nil |
2102 | 2513 |
2103 -- safety measure: resort players' tables based on formatted timestamp | 2514 -- safety measure: resort players' tables based on formatted timestamp |
2104 for i,h in ipairs(self.history) do | 2515 for i,h in ipairs(self.history) do |
2105 tsort (h, comp) | 2516 sort_player(h) |
2106 end | 2517 end |
2107 end | 2518 end |
2108 | 2519 |
2109 -- Clears all but latest entry for each player. | 2520 -- Clears all but latest entry for each player. |
2110 function addon:preen_history (realmname) | 2521 function addon:preen_history (realmname) |
2111 local r = assert(realmname) | 2522 local r = assert(realmname) |
2523 g_uniques:RESET() | |
2112 for i,h in ipairs(self.history) do | 2524 for i,h in ipairs(self.history) do |
2113 tsort (h, comp) | 2525 -- This is going to do horrible things to memory. The subtables |
2114 while #h > 1 do | 2526 -- after this step would be large and sparse, with no good way |
2115 tremove (h) | 2527 -- of shrinking the allocation... |
2116 end | 2528 sort_player(h) |
2117 end | 2529 -- ...so it's better in the long run to discard them. |
2118 end | 2530 local U = h.unique[1] |
2119 | 2531 h.unique = { U } |
2120 -- Given an entry in a g_loot table, looks up the corresponding history | 2532 h.id = { [U] = h.id[U] } |
2121 -- entry. Returns the player's index and history table (as in get_loot_history) | 2533 h.when = { [U] = h.when[U] } |
2122 -- and the index into that table of the loot entry. On failure, returns nil | 2534 h.count = { [U] = h.count[U] } |
2123 -- and an error message ready to be formatted with the loot's name/itemlink. | 2535 end |
2124 function addon:_history_by_loot_id (loot, operation_text) | 2536 end |
2125 -- Using assert() here would be concatenating error strings that probably | 2537 |
2126 -- wouldn't be used. Do more verbose testing instead. | 2538 -- Given a unique tag OR an entry in a g_loot table, looks up the |
2127 if type(loot) ~= 'table' then | 2539 -- corresponding history entry. Returns the player's index and history |
2128 error("trying to "..operation_text.." nonexistant entry") | 2540 -- table (as in get_loot_history) and the index into that table of the |
2129 end | 2541 -- loot entry. On failure, returns nil and an error message ready to be |
2130 if loot.kind ~= 'loot' then | 2542 -- formatted with the loot's name/itemlink. |
2131 error("trying to "..operation_text.." something that isn't loot") | 2543 function _history_by_loot_id (needle, operation_text) |
2132 end | |
2133 | |
2134 local player = loot.person | |
2135 local tag = loot.unique | |
2136 local errtxt | 2544 local errtxt |
2137 local player_i, player_h, hist_i | 2545 if type(needle) == 'string' then |
2138 | 2546 -- unique tag |
2139 if not tag then | 2547 elseif type(needle) == 'table' then |
2140 errtxt = "Entry for %s is missing a history tag!" | 2548 if needle.kind ~= 'loot' then |
2549 error("trying to "..operation_text.." something that isn't loot") | |
2550 end | |
2551 needle = needle.unique | |
2552 if not needle then | |
2553 return nil, --[[errtxt=]]"Entry for %s is missing a history tag!" | |
2554 end | |
2141 else | 2555 else |
2142 player_i,player_h = self:get_loot_history(player) | 2556 error("'"..tostring(needle).."' is neither unique string nor loot entry!") |
2143 for i,h in ipairs(player_h) do | 2557 end |
2144 if h.unique == tag then | 2558 |
2145 hist_i = i | 2559 local player_i, player_h |
2146 break | 2560 local cache = g_uniques[needle] |
2561 | |
2562 if cache.history == g_uniques.NOTFOUND then | |
2563 -- 1) loot an item, 2) clear old history, 3) reassign from current loot | |
2564 -- Bah. Anybody that tricky is already recoding the tables directly anyhow. | |
2565 errtxt = "There is no record of %s ever having been assigned!" | |
2566 else | |
2567 player_i = cache.history | |
2568 player_h = addon.history[player_i] | |
2569 if cache.history_may | |
2570 and needle == player_h.unique[cache.history_may] | |
2571 then | |
2572 return player_i, player_h, cache.history_may | |
2573 end | |
2574 for i,u in ipairs(player_h.unique) do | |
2575 if needle == u then | |
2576 cache.history_may = i -- might help, might not | |
2577 return player_i, player_h, i | |
2147 end | 2578 end |
2148 end | 2579 end |
2149 if not hist_i then | 2580 end |
2150 -- 1) loot an item, 2) clear old history, 3) reassign from current loot | 2581 |
2151 -- Bah. Anybody that tricky is already recoding the tables directly anyhow. | 2582 if not errtxt then |
2152 errtxt = "There is no record of %s ever having been assigned!" | 2583 -- The cache finder got a hit, but now it's gone? WTF? |
2153 end | 2584 errtxt = "ZOMG! %s was in history but now is gone. Possibly your history tables have been corrupted and should be recreated. This is likely a bug. Tell Farmbuyer what steps you took to cause this." |
2154 end | 2585 end |
2155 | 2586 return nil, errtxt |
2156 if errtxt then | |
2157 return nil, errtxt | |
2158 end | |
2159 return player_i, player_h, hist_i | |
2160 end | 2587 end |
2161 | 2588 |
2162 function addon:reassign_loot (index, to_name) | 2589 function addon:reassign_loot (index, to_name) |
2163 assert(type(to_name)=='string' and to_name:len()>0) | 2590 assert(type(to_name)=='string' and to_name:len()>0) |
2164 local e = g_loot[index] | 2591 local e = g_loot[index] |
2165 local from_i, from_h, hist_i = self:_history_by_loot_id (e, "reassign") | 2592 local from_i, from_h, hist_i = _history_by_loot_id (e, "reassign") |
2166 local from_name = e.person | 2593 local from_name = e.person |
2167 local to_i,to_h = self:get_loot_history(to_name) | 2594 local to_i,to_h = self:get_loot_history(to_name) |
2168 | 2595 |
2169 if not from_i then | 2596 if not from_i then -- from_h here is the formatted error text |
2170 -- from_h is the formatted error text | |
2171 self:Print(from_h .. " Loot will be reassigned, but history will NOT be updated.", e.itemlink) | 2597 self:Print(from_h .. " Loot will be reassigned, but history will NOT be updated.", e.itemlink) |
2172 else | 2598 else |
2173 local hist_h = tremove (from_h, hist_i) | 2599 local U = tremove (from_h.unique, hist_i) |
2174 tinsert (to_h, 1, hist_h) | 2600 -- The loot master giveth... |
2175 tsort (from_h, comp) | 2601 to_h.unique[#to_h.unique+1] = U |
2176 tsort (to_h, comp) | 2602 to_h.when[U] = from_h.when[U] |
2603 to_h.id[U] = from_h.id[U] | |
2604 to_h.count[U] = from_h.count[U] | |
2605 sort_player(to_h) | |
2606 -- ...and the loot master taketh away. | |
2607 from_h.when[U] = nil | |
2608 from_h.id[U] = nil | |
2609 from_h.count[U] = nil | |
2610 -- Blessed be the lookup cache of the loot master. | |
2611 g_uniques[U] = { loot = index, history = to_i } | |
2177 end | 2612 end |
2178 e.person = to_name | 2613 e.person = to_name |
2179 e.person_class = select(2,UnitClass(to_name)) | 2614 e.person_class = select(2,_G.UnitClass(to_name)) |
2180 self.hist_clean = nil | 2615 self.hist_clean = nil |
2181 | 2616 |
2182 self:Print("Reassigned entry %d/%s from '%s' to '%s'.", index, e.itemlink, from_name, to_name) | 2617 self:Print("Reassigned entry %d/%s from '%s' to '%s'.", index, e.itemlink, from_name, to_name) |
2183 end | 2618 end |
2184 | 2619 |
2187 -- be pulled from LOOTINDEX instead). | 2622 -- be pulled from LOOTINDEX instead). |
2188 function addon:_delHistoryEntry (lootindex, opt_e) | 2623 function addon:_delHistoryEntry (lootindex, opt_e) |
2189 local e = opt_e or g_loot[lootindex] | 2624 local e = opt_e or g_loot[lootindex] |
2190 if e.kind ~= 'loot' then return end | 2625 if e.kind ~= 'loot' then return end |
2191 | 2626 |
2192 local from_i, from_h, hist_i = self:_history_by_loot_id (e, "delete") | 2627 local from_i, from_h, hist_i = _history_by_loot_id (e, "delete") |
2193 if not from_i then | 2628 if not from_i then |
2194 -- from_h is the formatted error text | 2629 -- from_h is the formatted error text |
2195 self:Print(from_h .. " Loot will be deleted, but history will NOT be updated.", e.itemlink) | 2630 self:Print(from_h .. " Loot will be deleted, but history will NOT be updated.", e.itemlink) |
2196 return | 2631 return |
2197 end | 2632 end |
2198 | 2633 |
2199 --[[local hist_h = ]]tremove (from_h, hist_i) | 2634 local hist_u = tremove (from_h.unique, hist_i) |
2200 tsort (from_h, comp) | 2635 from_h.when[hist_u] = nil |
2636 from_h.id[hist_u] = nil | |
2637 from_h.count[hist_u] = nil | |
2638 g_uniques[hist_u] = nil | |
2201 self.hist_clean = nil | 2639 self.hist_clean = nil |
2202 | 2640 |
2203 self:Print("Removed history entry %d/%s from '%s'.", lootindex, e.itemlink, e.person) | 2641 self:Print("Removed history entry %d/%s from '%s'.", lootindex, e.itemlink, e.person) |
2204 end | 2642 end |
2205 | 2643 |
2206 -- Any extra work for the "Mark as <x>" dropdown actions. The | 2644 -- Any extra work for the "Mark as <x>" dropdown actions. The |
2207 -- corresponding <x> will already have been assigned in the loot entry. | 2645 -- corresponding <x> will already have been assigned in the loot entry. |
2208 local deleted_cache = {} --setmetatable({}, {__mode='k'}) | 2646 local deleted_cache = {} |
2209 function addon:history_handle_disposition (index, olddisp) | 2647 function addon:history_handle_disposition (index, olddisp) |
2210 local e = g_loot[index] | 2648 local e = g_loot[index] |
2211 -- Standard disposition has a nil entry, but that's tedious in debug | 2649 -- Standard disposition has a nil entry, but that's tedious in debug |
2212 -- output, so force to a string instead. | 2650 -- output, so force to a string instead. |
2213 olddisp = olddisp or 'normal' | 2651 olddisp = olddisp or 'normal' |
2216 if olddisp == newdisp then return end | 2654 if olddisp == newdisp then return end |
2217 | 2655 |
2218 local name = e.person | 2656 local name = e.person |
2219 | 2657 |
2220 if (newdisp == 'shard' or newdisp == 'gvault') then | 2658 if (newdisp == 'shard' or newdisp == 'gvault') then |
2221 local name_i, name_h, hist_i = self:_history_by_loot_id (e, "mark") | 2659 local name_i, name_h, hist_i = _history_by_loot_id (e, "mark") |
2222 -- remove history entry | 2660 -- remove history entry if it exists |
2223 if hist_i then | 2661 if hist_i then |
2224 local hist_h = tremove (name_h, hist_i) | 2662 local c = flib.new() |
2225 deleted_cache[e.unique] = hist_h | 2663 local hist_u = tremove (name_h.unique, hist_i) |
2664 c.when = name_h.when[hist_u] | |
2665 c.id = name_h.id[hist_u] | |
2666 c.count = name_h.count[hist_u] | |
2667 deleted_cache[hist_u] = c | |
2668 name_h.when[hist_u] = nil | |
2669 name_h.id[hist_u] = nil | |
2670 name_h.count[hist_u] = nil | |
2226 self.hist_clean = nil | 2671 self.hist_clean = nil |
2227 elseif (olddisp == 'shard' or olddisp == 'gvault') then | 2672 elseif (olddisp == 'shard' or olddisp == 'gvault') then |
2228 -- Sharding a vault item, or giving the auto-sharder something to bank, | 2673 -- Sharding a vault item, or giving the auto-sharder something to bank, |
2229 -- etc, wouldn't necessarily have had a history entry to begin with. | 2674 -- etc, wouldn't necessarily have had a history entry to begin with. |
2675 -- So this isn't treated as an error. | |
2230 else | 2676 else |
2231 self:Print(name_h .. " Loot has been marked, but history will NOT be updated.", e.itemlink) | 2677 self:Print(name_h .. " Loot has been marked, but history will NOT be updated.", e.itemlink) |
2232 end | 2678 end |
2233 return | 2679 return |
2234 end | 2680 end |
2240 | 2686 |
2241 -- Must create a new history entry. Could call '_addHistoryEntry(index)' | 2687 -- Must create a new history entry. Could call '_addHistoryEntry(index)' |
2242 -- but that would duplicate a lot of effort. To start with, check the | 2688 -- but that would duplicate a lot of effort. To start with, check the |
2243 -- cache of stuff we've already deleted; if it's not there then just do | 2689 -- cache of stuff we've already deleted; if it's not there then just do |
2244 -- the same steps as _addHistoryEntry. | 2690 -- the same steps as _addHistoryEntry. |
2245 local entry | 2691 -- FIXME The deleted cache isn't nearly as useful now with the new data structures. |
2246 if e.unique and deleted_cache[e.unique] then | 2692 local when |
2247 entry = deleted_cache[e.unique] | |
2248 deleted_cache[e.unique] = nil | |
2249 end | |
2250 local when = g_today and self:format_timestamp (g_today, e) or tostring(e.stamp) | |
2251 entry = entry or { | |
2252 id = e.id, | |
2253 when = when, | |
2254 count = e.count, | |
2255 } | |
2256 tinsert (name_h, 1, entry) | |
2257 if (not e.unique) or (#e.unique==0) then | 2693 if (not e.unique) or (#e.unique==0) then |
2258 e.unique = entry.id .. ' ' .. entry.when | 2694 when = g_today and self:format_timestamp (g_today, e) or date("%Y/%m/%d %H:%M",e.stamp) |
2259 end | 2695 e.unique = e.id .. ' ' .. when |
2260 entry.unique = e.unique | 2696 end |
2697 local U = e.unique | |
2698 local c = deleted_cache[U] | |
2699 deleted_cache[U] = nil | |
2700 name_h.unique[#name_h.unique+1] = U | |
2701 name_h.when[U] = c and c.when or when or date("%Y/%m/%d %H:%M",e.stamp) | |
2702 name_h.id[U] = e.id -- c.id | |
2703 name_h.count[U] = c and c.count or e.count | |
2704 sort_player(name_h) | |
2705 g_uniques[U] = { loot = index, history = name_i } | |
2261 self.hist_clean = nil | 2706 self.hist_clean = nil |
2707 | |
2708 if c then flib.del(c) end | |
2709 | |
2262 return | 2710 return |
2263 end | 2711 end |
2712 end | |
2713 | |
2714 -- This is not entirely "history" but not completely anything else either. | |
2715 -- Handles the primary "Mark as <x>" action. Arguments depend on who's | |
2716 -- calling it: | |
2717 -- "local", row_index, new_disposition | |
2718 -- "remote", sender, unique_id, item_id, old_disposition, new_disposition | |
2719 -- In the local case, must also broadcast a trigger. In the remote case, | |
2720 -- must figure out the corresponding loot entry (if it exists). In both | |
2721 -- cases, must update history appropriately. Returns nil if anything odd | |
2722 -- happens (not necessarily an error!); returns the affected loot index | |
2723 -- on success. | |
2724 function addon:loot_mark_disposition (how, ...) | |
2725 -- This must all be filled out in all cases: | |
2726 local e, index, olddisp, newdisp, unique, id | |
2727 -- Only set in remote case: | |
2728 local sender | |
2729 | |
2730 if how == "local" then | |
2731 index, newdisp = ... | |
2732 index = assert(tonumber(index)) | |
2733 e = g_loot[index] | |
2734 id = e.id | |
2735 unique = e.unique -- can potentially still be nil at this step | |
2736 olddisp = e.disposition | |
2737 | |
2738 elseif how == "remote" then | |
2739 sender, unique, id, olddisp, newdisp = ... | |
2740 local cache = g_uniques[unique] | |
2741 if cache.loot then | |
2742 index = tonumber(cache.loot) | |
2743 e = g_loot[index] | |
2744 end | |
2745 | |
2746 else | |
2747 return -- silently ignore newer cases from newer clients | |
2748 end | |
2749 | |
2750 if self.debug.loot then | |
2751 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)) | |
2753 self.dprint('loot', m) | |
2754 if sender == my_name then | |
2755 self.dprint('loot',"(Returning early from double self-mark.)") | |
2756 return index | |
2757 end | |
2758 end | |
2759 | |
2760 if not e then | |
2761 -- say something? | |
2762 return | |
2763 end | |
2764 | |
2765 e.disposition = newdisp | |
2766 e.bcast_from = nil -- I actually don't remember now why this gets cleared... | |
2767 e.extratext = nil | |
2768 self:history_handle_disposition (index, olddisp) | |
2769 -- A unique tag has been set by this point. | |
2770 if how == "local" then | |
2771 unique = assert(e.unique) | |
2772 self:vbroadcast('mark', unique, id, olddisp, newdisp) | |
2773 end | |
2774 return index | |
2264 end | 2775 end |
2265 end | 2776 end |
2266 | 2777 |
2267 | 2778 |
2268 ------ Player communication | 2779 ------ Player communication |
2269 do | 2780 do |
2270 local select, tconcat, strsplit = select, table.concat, strsplit | 2781 local select, tconcat, strsplit, unpack = select, table.concat, strsplit, unpack |
2271 --[[ old way: repeated string concatenations, BAD | 2782 --[[ old way: repeated string concatenations, BAD |
2272 new way: new table on every call, BAD | 2783 new way: new table on every call, BAD |
2273 local msg = ... | 2784 local msg = ... |
2274 for i = 2, select('#',...) do | 2785 for i = 2, select('#',...) do |
2275 msg = msg .. '\a' .. (select(i,...) or "") | 2786 msg = msg .. '\a' .. (select(i,...) or "") |
2276 end | 2787 end |
2277 return msg | 2788 return msg |
2278 ]] | 2789 ]] |
2279 local function assemble(t,...) | 2790 local function assemble(t,...) |
2280 if select('#',...) > 0 then | 2791 local n = select('#',...) |
2792 if n > 0 then | |
2281 local msg = {t,...} | 2793 local msg = {t,...} |
2282 -- tconcat requires strings, but T is known to be one already | 2794 -- tconcat requires strings, but T is known to be one already |
2283 for i = 2, #msg do | 2795 -- can't use #msg since there might be holes |
2796 for i = 2, n+1 do | |
2284 msg[i] = tostring(msg[i] or "") | 2797 msg[i] = tostring(msg[i] or "") |
2285 end | 2798 end |
2286 return tconcat (msg, '\a') | 2799 return tconcat (msg, '\a') |
2287 end | 2800 end |
2288 return t | 2801 return t |
2329 OCR_funcs.revcheck = function (sender, _, revlarge) | 2842 OCR_funcs.revcheck = function (sender, _, revlarge) |
2330 addon.dprint('comm', "revcheck, sender", sender) | 2843 addon.dprint('comm', "revcheck, sender", sender) |
2331 addon:_check_revision (revlarge) | 2844 addon:_check_revision (revlarge) |
2332 end | 2845 end |
2333 | 2846 |
2847 OCR_funcs['17mark'] = function (sender, _, unique, item, old, new) | |
2848 addon.dprint('comm', "DOTmark/17, sender", sender, "unique", unique, | |
2849 "item", item, "from old", old, "to new", new) | |
2850 local index = addon:loot_mark_disposition ("remote", sender, unique, item, old, new) | |
2851 --if not addon.enabled then return end -- hmm | |
2852 if index and opts.chatty_on_remote_changes then | |
2853 _notify_about_remote (sender, index, --[[from_whom=]]nil, old) | |
2854 end | |
2855 end | |
2856 | |
2334 OCR_funcs['16loot'] = function (sender, _, recip, item, count, extratext) | 2857 OCR_funcs['16loot'] = function (sender, _, recip, item, count, extratext) |
2335 addon.dprint('comm', "DOTloot/16, sender", sender, "recip", recip, "item", item, "count", count) | 2858 addon.dprint('comm', "DOTloot/16, sender", sender, "recip", recip, "item", item, "count", count) |
2336 if not addon.enabled then return end | 2859 if not addon.enabled then return end |
2337 adduser (sender, nil, true) | 2860 adduser (sender, nil, true) |
2861 -- Empty unique string will pass through all of the loot handling, | |
2862 -- and then be rewritten by the history routine (into older string | |
2863 -- of ID+date). | |
2864 g_seeing_oldsigs = g_seeing_oldsigs or {} | |
2865 g_seeing_oldsigs[sender] = true | |
2338 addon:CHAT_MSG_LOOT ("broadcast", recip, --[[unique=]]"", item, count, sender, extratext) | 2866 addon:CHAT_MSG_LOOT ("broadcast", recip, --[[unique=]]"", item, count, sender, extratext) |
2339 end | 2867 end |
2340 OCR_funcs.loot = OCR_funcs['16loot'] -- old unversioned stuff goes to 16 | 2868 OCR_funcs.loot = OCR_funcs['16loot'] -- old unversioned stuff goes to 16 |
2341 OCR_funcs['17loot'] = function (sender, _, recip, unique, item, count, extratext) | 2869 OCR_funcs['17loot'] = function (sender, _, recip, unique, item, count, extratext) |
2342 addon.dprint('comm', "DOTloot, sender", sender, "recip", recip, | 2870 addon.dprint('comm', "DOTloot/17, sender", sender, "recip", recip, |
2343 "unique", unique, "item", item, "count", count) | 2871 "unique", unique, "item", item, "count", count, "extratext", extratext) |
2344 if not addon.enabled then return end | 2872 if not addon.enabled then return end |
2345 adduser (sender, nil, true) | 2873 adduser (sender, nil, true) |
2346 addon:CHAT_MSG_LOOT ("broadcast", recip, unique, item, count, sender, extratext) | 2874 addon:CHAT_MSG_LOOT ("broadcast", recip, unique, item, count, sender, extratext) |
2347 end | 2875 end |
2348 | 2876 |
2352 if not addon.enabled then return end | 2880 if not addon.enabled then return end |
2353 adduser (sender, nil, true) | 2881 adduser (sender, nil, true) |
2354 addon:on_boss_broadcast (reason, bossname, instancetag, --[[maxsize=]]0) | 2882 addon:on_boss_broadcast (reason, bossname, instancetag, --[[maxsize=]]0) |
2355 end | 2883 end |
2356 OCR_funcs['16boss'] = function (sender, _, reason, bossname, instancetag, maxsize) | 2884 OCR_funcs['16boss'] = function (sender, _, reason, bossname, instancetag, maxsize) |
2357 addon.dprint('comm', "DOTboss16, sender", sender, "reason", reason, | 2885 addon.dprint('comm', "DOTboss/16,17, sender", sender, "reason", reason, |
2358 "name", bossname, "it", instancetag, "size", maxsize) | 2886 "name", bossname, "it", instancetag, "size", maxsize) |
2359 if not addon.enabled then return end | 2887 if not addon.enabled then return end |
2360 adduser (sender, nil, true) | 2888 adduser (sender, nil, true) |
2361 addon:on_boss_broadcast (reason, bossname, instancetag, maxsize) | 2889 addon:on_boss_broadcast (reason, bossname, instancetag, maxsize) |
2362 end | 2890 end |
2383 -- remove this tag once it's all tested | 2911 -- remove this tag once it's all tested |
2384 OCR_funcs.bcast_denied = function (sender) | 2912 OCR_funcs.bcast_denied = function (sender) |
2385 if addon.requesting then addon:Print(sender, "declines futher broadcast requests.") end | 2913 if addon.requesting then addon:Print(sender, "declines futher broadcast requests.") end |
2386 end | 2914 end |
2387 | 2915 |
2388 -- Incoming message dispatcher | 2916 -- Incoming message disassembler and dispatcher. The static weak table |
2917 -- is not my favorite approach to handling ellipses, but it lets me loop | |
2918 -- through potential nils easily without creating a ton of garbage. | |
2919 local OCR_data = setmetatable({}, {__mode='v'}) | |
2389 local function dotdotdot (sender, tag, ...) | 2920 local function dotdotdot (sender, tag, ...) |
2390 local f = OCR_funcs[tag] | 2921 local f = OCR_funcs[tag] |
2391 addon.dprint('comm', ":... processing",tag,"from",sender) | 2922 if f then |
2392 if f then return f(sender,tag,...) end | 2923 --wipe(OCR_data) costs more than its worth here |
2393 addon.dprint('comm', "unknown comm message",tag",from", sender) | 2924 local n = select('#',...) |
2394 end | 2925 for i = 1, n do |
2395 -- Recent message cache | 2926 local d = select(i,...) |
2927 OCR_data[i] = (d ~= "") and d or nil | |
2928 end | |
2929 addon.dprint('comm', ":... processing", tag, "from", sender, "with arg count", n) | |
2930 return f(sender,tag,unpack(OCR_data,1,n)) | |
2931 end | |
2932 addon.dprint('comm', "unknown comm message", tag, "from", sender) | |
2933 end | |
2934 -- Recent message cache (this can be accessed via advanced options panel) | |
2396 addon.recent_messages = create_new_cache ('comm', comm_cleanup_ttl) | 2935 addon.recent_messages = create_new_cache ('comm', comm_cleanup_ttl) |
2397 | 2936 |
2398 function addon:OnCommReceived (prefix, msg, distribution, sender) | 2937 function addon:OnCommReceived (prefix, msg, distribution, sender) |
2399 if prefix ~= self.ident then return end | 2938 if prefix ~= self.ident then return end |
2400 if not self.debug.comm then | 2939 if not self.debug.comm then |