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 |
