comparison core.lua @ 114:67bf97136273

- Start paying attention to cross-realm names. New 'fname' and 'realm' keys in snapshot (now indexed by name only), 'person_realm' key in loot entries. Realm data not stored if it's the same as the player's realm. Manual rebroadcasting does not include realm data (do we care?). - New history_suppress_LFR and history_ignore_xrealm options. - This implementation depends on no two cross-realm players sharing the same player name. Is that guaranteed by Blizzard, or merely "unlikely"? - Gather history suppression knobs into a single function. - If restoring loot data, make sure the item cache has their values; fix up any missing data on load. - Memory tweaks for player history sorting. - Handle some long-standing FIXME's: reassigning loot to the same player, using expunge() for history, no partial duplication of effort for addHistoryEntry. - Try to be more graceful when discovering messed up history during column 3 display loops. Don't leave History tab in player-focused mode when all that player's data have been removed elsewhere.
author Farmbuyer of US-Kilrogg <farmbuyer@gmail.com>
date Mon, 13 Aug 2012 21:49:08 -0400
parents f93c1a93923b
children 289c7667adab
comparison
equal deleted inserted replaced
113:d1b914bbed8b 114:67bf97136273
6 - forum saved text from forum markup window, default nil 6 - forum saved text from forum markup window, default nil
7 - attend saved text from raid attendence window, default nil 7 - attend saved text from raid attendence window, default nil
8 - printed.FOO last loot index formatted into text window FOO, default 0 8 - printed.FOO last loot index formatted into text window FOO, default 0
9 - raiders accumulating raid roster data as we see raid members; indexed 9 - raiders accumulating raid roster data as we see raid members; indexed
10 by player name with subtable fields: 10 by player name with subtable fields:
11 - fname fully-qualified name; "PlayerName" or "PlayerName-RealmName"
11 - class capitalized English codename ("WARRIOR", "DEATHKNIGHT", etc) 12 - class capitalized English codename ("WARRIOR", "DEATHKNIGHT", etc)
12 - subgroup 1-8 (NUM_RAID_GROUPS), NRG+1 if something's wrong 13 - subgroup 1-8 (NUM_RAID_GROUPS), NRG+1 if something's wrong
13 - race English codename ("BloodElf", etc) 14 - race English codename ("BloodElf", etc)
14 - sex 1 = unknown/error, 2 = male, 3 = female 15 - sex 1 = unknown/error, 2 = male, 3 = female
15 - level can be 0 if player was offline at the time 16 - level can be 0 if player was offline at the time
16 - guild guild name, or missing if unguilded 17 - guild guild name, or missing if unguilded
18 - realm realm name, or missing if same realm as player at the time
17 - online 'online', 'offline', 'no_longer' [no longer in raid group] 19 - online 'online', 'offline', 'no_longer' [no longer in raid group]
18 [both of these next two fields use time_t values:] 20 [both of these next two fields use time_t values:]
19 - join time player joined the raid (or first time we've seen them) 21 - join time player joined the raid (or first time we've seen them)
20 - leave time player left the raid (or time we've left the raid, if 22 - leave time player left the raid (or time we've left the raid, if
21 'online' is not 'no_longer') 23 'online' is not 'no_longer')
46 48
47 Loot specific g_loot indices: 49 Loot specific g_loot indices:
48 - person recipient 50 - person recipient
49 - person_class class of recipient if available; may be missing; 51 - person_class class of recipient if available; may be missing;
50 will be classID-style (e.g., DEATHKNIGHT) 52 will be classID-style (e.g., DEATHKNIGHT)
53 - person_realm recipient's realm if different from the player's; missing
54 otherwise
51 - itemname not including square brackets 55 - itemname not including square brackets
52 - id itemID as number 56 - id itemID as number
53 - itemlink full clickable link 57 - itemlink full clickable link
54 - itexture icon path (e.g., Interface\Icons\INV_Misc_Rune_01) 58 - itexture icon path (e.g., Interface\Icons\INV_Misc_Rune_01)
55 - quality ITEM_QUALITY_* number 59 - quality ITEM_QUALITY_* number
114 --['datarev'] = 20, -- cheating, this isn't actually an option 118 --['datarev'] = 20, -- cheating, this isn't actually an option
115 ['popup_on_join'] = true, 119 ['popup_on_join'] = true,
116 ['register_slash_synonyms'] = false, 120 ['register_slash_synonyms'] = false,
117 ['slash_synonyms'] = '/ol,/oloot', 121 ['slash_synonyms'] = '/ol,/oloot',
118 ['scroll_to_bottom'] = true, 122 ['scroll_to_bottom'] = true,
123 ['history_suppress_LFR'] = false,
124 ['history_ignore_xrealm'] = true,
119 ['gui_noob'] = true, 125 ['gui_noob'] = true,
120 ['chatty_on_kill'] = false, 126 ['chatty_on_kill'] = false,
121 ['no_tracking_wipes'] = false, 127 ['no_tracking_wipes'] = false,
122 ['snarky_boss'] = true, 128 ['snarky_boss'] = true,
123 ['keybinding'] = false, 129 ['keybinding'] = false,
387 local GetNumRaidMembers = GetNumGroupMembers or GetNumRaidMembers 393 local GetNumRaidMembers = GetNumGroupMembers or GetNumRaidMembers
388 local IsInRaid = IsInRaid or (function() return GetNumRaidMembers() > 0 end) 394 local IsInRaid = IsInRaid or (function() return GetNumRaidMembers() > 0 end)
389 -- En masse forward decls of symbols defined inside local blocks 395 -- En masse forward decls of symbols defined inside local blocks
390 local _register_bossmod, makedate, create_new_cache, _init, _log, _do_loot_metas 396 local _register_bossmod, makedate, create_new_cache, _init, _log, _do_loot_metas
391 local _history_by_loot_id, _setup_unique_replace, _unavoidable_collision 397 local _history_by_loot_id, _setup_unique_replace, _unavoidable_collision
392 local _notify_about_change 398 local _notify_about_change, _LFR_suppressing
393 399
394 -- Try to extract numbers from the .toc "Version" and munge them into an 400 -- Try to extract numbers from the .toc "Version" and munge them into an
395 -- integral form for comparison. The result doesn't need to be meaningful as 401 -- integral form for comparison. The result doesn't need to be meaningful as
396 -- long as we can reliably feed two of them to "<" and get useful answers. 402 -- long as we can reliably feed two of them to "<" and get useful answers.
397 -- 403 --
509 local name, typeof, diffcode, diffstr, _, perbossheroic, isdynamic = GetInstanceInfo() 515 local name, typeof, diffcode, diffstr, _, perbossheroic, isdynamic = GetInstanceInfo()
510 local t, r 516 local t, r
511 name = addon.instance_abbrev[name] or name 517 name = addon.instance_abbrev[name] or name
512 if typeof == "none" then return name, MAX_RAID_MEMBERS end 518 if typeof == "none" then return name, MAX_RAID_MEMBERS end
513 -- diffstr is "5 Player", "10 Player (Heroic)", etc. ugh. 519 -- diffstr is "5 Player", "10 Player (Heroic)", etc. ugh.
514 if (GetLFGMode()) and (GetLFGModeType() == 'raid') then 520 if GetLFGMode() and (GetLFGModeType() == 'raid') then
515 t,r = 'LFR', 25 521 t,r = 'LFR', 25
516 elseif diffcode == 1 then 522 elseif diffcode == 1 then
517 if IsInRaid() then 523 if IsInRaid() then
518 t,r = "10",10 524 t,r = "10",10
519 else 525 else
536 end 542 end
537 return name .. "(" .. t .. ")", r 543 return name .. "(" .. t .. ")", r
538 end 544 end
539 addon.instance_tag = instance_tag -- grumble 545 addon.instance_tag = instance_tag -- grumble
540 addon.latest_instance = nil -- spelling reminder, assigned elsewhere 546 addon.latest_instance = nil -- spelling reminder, assigned elsewhere
547
548 -- Whether we're recording anything at all in the loot histories
549 local function _history_suppress()
550 return _LFR_suppressing or addon.history_suppress
551 end
541 552
542 -- Memoizing cache of unique IDs as we generate or search for them. Keys are 553 -- Memoizing cache of unique IDs as we generate or search for them. Keys are
543 -- the uniques, values are the following: 554 -- the uniques, values are the following:
544 -- 'history' active player name in self.history 555 -- 'history' active player name in self.history
545 -- 'history_may' index into player's uniques list, CAN QUICKLY BE OUTDATED 556 -- 'history_may' index into player's uniques list, CAN QUICKLY BE OUTDATED
1265 local time, difftime = time, difftime 1276 local time, difftime = time, difftime
1266 local R_ACTIVE, R_OFFLINE, R_LEFT = 'online', 'offline', 'no_longer' 1277 local R_ACTIVE, R_OFFLINE, R_LEFT = 'online', 'offline', 'no_longer'
1267 1278
1268 local lastevent, now = 0, 0 1279 local lastevent, now = 0, 0
1269 local redo_count = 0 1280 local redo_count = 0
1270 local redo, timer_handle 1281 local redo, timer_handle, my_realm
1271 1282
1272 function addon:CheckRoster (leaving_p, now_a) 1283 function addon:CheckRoster (leaving_p, now_a)
1273 if not g_loot.raiders then return end -- bad transition 1284 if not g_loot.raiders then return end -- bad transition
1274 1285
1275 now = now_a or time() 1286 now = now_a or time()
1299 end 1310 end
1300 redo = false 1311 redo = false
1301 for i = 1, GetNumRaidMembers() do 1312 for i = 1, GetNumRaidMembers() do
1302 local unit = 'raid'..i 1313 local unit = 'raid'..i
1303 -- We grab a bunch of return values here, but only pay attention to 1314 -- We grab a bunch of return values here, but only pay attention to
1304 -- them under specific circumstances. 1315 -- them under specific circumstances. Try to use as many of these
1305 local name, connected, subgroup, level, class, _ 1316 -- values as possible rather than multiple Unit* calls.
1306 name, _, subgroup, level, _, class, connected = GetRaidRosterInfo(i) 1317 local fname, connected, subgroup, level, class, _
1318 fname, _, subgroup, level, _, class, connected = GetRaidRosterInfo(i)
1307 -- No, that's not my typo, it really is "uknownbeing" in Blizzard's code. 1319 -- No, that's not my typo, it really is "uknownbeing" in Blizzard's code.
1308 if name and name ~= UNKNOWN and name ~= UNKNOWNOBJECT and name ~= UKNOWNBEING then 1320 if fname and fname ~= UNKNOWN and fname ~= UNKNOWNOBJECT and fname ~= UKNOWNBEING then
1321 local name,realm = fname:match("(%S+)-(%S+)")
1322 if realm and realm == my_realm then -- shouldn't happen
1323 realm = nil
1324 end
1325 if not name then
1326 assert(realm == nil)
1327 name = fname
1328 end
1309 if not g_loot.raiders[name] then 1329 if not g_loot.raiders[name] then
1310 g_loot.raiders[name] = { needinfo=true } 1330 g_loot.raiders[name] = { needinfo=true }
1311 end 1331 end
1312 local r = g_loot.raiders[name] 1332 local r = g_loot.raiders[name]
1313 r.subgroup = subgroup or (NUM_RAID_GROUPS+1) 1333 r.subgroup = subgroup or (NUM_RAID_GROUPS+1)
1314 if r.needinfo and UnitIsVisible(unit) then 1334 if r.needinfo and UnitIsVisible(unit) then
1315 r.needinfo = nil 1335 r.needinfo = nil
1336 r.fname = fname
1316 r.class = class --select(2,UnitClass(unit)) 1337 r.class = class --select(2,UnitClass(unit))
1317 r.race = select(2,UnitRace(unit)) 1338 r.race = select(2,UnitRace(unit))
1318 r.sex = UnitSex(unit) 1339 r.sex = UnitSex(unit)
1319 r.level = level --UnitLevel(unit) 1340 r.level = level --UnitLevel(unit)
1320 r.guild = GetGuildInfo(unit) 1341 r.guild = GetGuildInfo(unit)
1342 r.realm = realm
1321 end 1343 end
1322 --local connected = UnitIsConnected(unit) 1344 --local connected = UnitIsConnected(unit)
1323 if connected and r.online ~= R_ACTIVE then 1345 if connected and r.online ~= R_ACTIVE then
1324 r.join = r.join or now 1346 r.join = r.join or now
1325 r.online = R_ACTIVE 1347 r.online = R_ACTIVE
1353 self.dprint('flow', "enabled, leaving raid") 1375 self.dprint('flow', "enabled, leaving raid")
1354 self.popped = nil 1376 self.popped = nil
1355 self:Deactivate() 1377 self:Deactivate()
1356 self:CheckRoster(--[[leaving raid]]true) 1378 self:CheckRoster(--[[leaving raid]]true)
1357 end 1379 end
1380 _LFR_suppressing = nil
1358 return 1381 return
1359 end 1382 end
1360 1383
1361 local inside,whatkind = IsInInstance() 1384 local inside,whatkind = IsInInstance()
1362 if inside and (whatkind == "pvp" or whatkind == "arena") then 1385 if inside and (whatkind == "pvp" or whatkind == "arena") then
1364 return 1387 return
1365 end 1388 end
1366 1389
1367 local docheck = self.enabled 1390 local docheck = self.enabled
1368 if event == "Activate" then 1391 if event == "Activate" then
1369 -- dispatched manually from Activate 1392 -- dispatched from Activate
1393 if opts.history_suppress_LFR
1394 and GetLFGMode() and (GetLFGModeType() == 'raid')
1395 then
1396 _LFR_suppressing = true
1397 end
1398 my_realm = self.history.realm
1399 _register_bossmod(self)
1370 self:RegisterEvent("CHAT_MSG_LOOT") 1400 self:RegisterEvent("CHAT_MSG_LOOT")
1371 _register_bossmod(self)
1372 docheck = true 1401 docheck = true
1373 elseif event == RAID_ROSTER_UPDATE_EVENT then 1402 elseif event == RAID_ROSTER_UPDATE_EVENT then
1374 -- hot code path, be careful 1403 -- hot code path, be careful
1375 1404
1376 -- event registration from onload, joined a raid, maybe show popup 1405 -- event registration from onload, joined a raid, maybe show popup
1499 maybe_trash_kill_entry() -- Generate *some* kind of boss/location entry 1528 maybe_trash_kill_entry() -- Generate *some* kind of boss/location entry
1500 candidates[sig] = nil 1529 candidates[sig] = nil
1501 local looti = addon._addLootEntry(loot) 1530 local looti = addon._addLootEntry(loot)
1502 addon.dprint('loot', "entry", i, "was found, added at index", looti) 1531 addon.dprint('loot', "entry", i, "was found, added at index", looti)
1503 if addon:_test_disposition (loot.disposition, 'affects_history') 1532 if addon:_test_disposition (loot.disposition, 'affects_history')
1504 and (not addon.history_suppress) 1533 and not _history_suppress()
1505 then 1534 then
1506 addon:_addHistoryEntry(looti) 1535 addon:_addHistoryEntry(looti)
1507 elseif #loot.unique > 0 then 1536 elseif #loot.unique > 0 then
1508 g_uniques[loot.unique] = -- stub entry 1537 g_uniques[loot.unique] = -- stub entry
1509 { loot = looti, history = g_uniques.NOTFOUND } 1538 { loot = looti, history = g_uniques.NOTFOUND }
1589 self.dprint('loot', "remote", prefix, "<", signature, 1618 self.dprint('loot', "remote", prefix, "<", signature,
1590 "> already in cache, skipping from", from) 1619 "> already in cache, skipping from", from)
1591 break 1620 break
1592 end 1621 end
1593 1622
1623 -- Most of the time this will already be in place and filled out,
1624 -- but there are situations where not. Even if we can't get data
1625 -- back, at least avoid errors (or the need for existence checks).
1626 if not g_loot.raiders[recipient] then
1627 g_loot.raiders[recipient] = { needinfo=true }
1628 end
1629
1594 -- There is some redundancy in all this, in the interests of ease-of-coding 1630 -- There is some redundancy in all this, in the interests of ease-of-coding
1595 i = { 1631 i = {
1596 kind = 'loot', 1632 kind = 'loot',
1597 person = recipient, 1633 person = recipient,
1598 person_class= select(2,UnitClass(recipient)), 1634 person_class= g_loot.raiders[recipient].class,
1635 person_realm= g_loot.raiders[recipient].realm,
1599 cache_miss = cache_miss, 1636 cache_miss = cache_miss,
1600 quality = iquality, 1637 quality = iquality,
1601 itemname = iname, 1638 itemname = iname,
1602 id = itemid, 1639 id = itemid,
1603 itemlink = ilink, 1640 itemlink = ilink,
1624 end 1661 end
1625 end 1662 end
1626 end 1663 end
1627 local looti = self._addLootEntry(i) 1664 local looti = self._addLootEntry(i)
1628 if self:_test_disposition (i.disposition, 'affects_history') 1665 if self:_test_disposition (i.disposition, 'affects_history')
1629 and (not self.history_suppress) 1666 and not _history_suppress()
1630 then 1667 then
1631 self:_addHistoryEntry(looti) 1668 self:_addHistoryEntry(looti)
1632 else 1669 else
1633 g_uniques[i.unique] = -- stub entry 1670 g_uniques[i.unique] = -- stub entry
1634 { loot = looti, history = g_uniques.NOTFOUND } 1671 { loot = looti, history = g_uniques.NOTFOUND }
1798 self:Print[['/ouroloot fix cache' updates loot that wasn't in the cache]] 1835 self:Print[['/ouroloot fix cache' updates loot that wasn't in the cache]]
1799 self:Print[['/ouroloot fix history' repairs inconsistent data on the History tab]] 1836 self:Print[['/ouroloot fix history' repairs inconsistent data on the History tab]]
1800 self:Print[['/ouroloot fix' changes no stored data, only allows the window to be displayed again (this is built into all fixes above)]] 1837 self:Print[['/ouroloot fix' changes no stored data, only allows the window to be displayed again (this is built into all fixes above)]]
1801 return 1838 return
1802 elseif arg == "cache" then 1839 elseif arg == "cache" then
1803 self:do_item_cache_fixup() 1840 self:do_item_cache_fixup(--[[force_silent=]]false)
1804 elseif arg == "history" then 1841 elseif arg == "history" then
1805 self:repair_history_integrity() 1842 self:repair_history_integrity()
1806 end 1843 end
1807 self.NOLOAD = nil 1844 self.NOLOAD = nil
1808 self:Print("Window unlocked, best of luck.") 1845 self:Print("Window unlocked, best of luck.")
1841 self.dprint('flow', ":Activate is running") 1878 self.dprint('flow', ":Activate is running")
1842 self:RegisterEvent(RAID_ROSTER_UPDATE_EVENT,"RAID_ROSTER_UPDATE") 1879 self:RegisterEvent(RAID_ROSTER_UPDATE_EVENT,"RAID_ROSTER_UPDATE")
1843 self:RegisterEvent("PLAYER_ENTERING_WORLD", 1880 self:RegisterEvent("PLAYER_ENTERING_WORLD",
1844 function() self:ScheduleTimer("RAID_ROSTER_UPDATE", 5, "PLAYER_ENTERING_WORLD") end) 1881 function() self:ScheduleTimer("RAID_ROSTER_UPDATE", 5, "PLAYER_ENTERING_WORLD") end)
1845 self.popped = true 1882 self.popped = true
1883 if self.DO_ITEMID_FIX then
1884 self.DO_ITEMID_FIX = nil
1885 self:do_item_cache_fixup(--[[force_silent=]]not self.author_debug)
1886 end
1846 if IsInRaid() then 1887 if IsInRaid() then
1847 self.dprint('flow', ">:Activate calling RRU") 1888 self.dprint('flow', ">:Activate calling RRU")
1848 self:RAID_ROSTER_UPDATE("Activate") 1889 self:RAID_ROSTER_UPDATE("Activate")
1849 elseif self.debug.notraid then 1890 elseif self.debug.notraid then
1850 self.dprint('flow', ">:(notraid) Activate registering loot and bossmods") 1891 self.dprint('flow', ">:(notraid) Activate registering loot and bossmods")
1889 self.enabled = false 1930 self.enabled = false
1890 self.rebroadcast = false 1931 self.rebroadcast = false
1891 self:UnregisterEvent(RAID_ROSTER_UPDATE_EVENT) 1932 self:UnregisterEvent(RAID_ROSTER_UPDATE_EVENT)
1892 self:UnregisterEvent("PLAYER_ENTERING_WORLD") 1933 self:UnregisterEvent("PLAYER_ENTERING_WORLD")
1893 self:UnregisterEvent("CHAT_MSG_LOOT") 1934 self:UnregisterEvent("CHAT_MSG_LOOT")
1935 _LFR_suppressing = nil
1894 self:Fire ('Deactivate') 1936 self:Fire ('Deactivate')
1895 self:Print("Deactivated.") 1937 self:Print("Deactivated.")
1896 end 1938 end
1897 1939
1898 function addon:Clear(verbose_p) 1940 function addon:Clear(verbose_p)
2169 self.hist_clean = nil 2211 self.hist_clean = nil
2170 if g_restore_p then 2212 if g_restore_p then
2171 g_loot = _G.OuroLootSV 2213 g_loot = _G.OuroLootSV
2172 self.popped = #g_loot > 0 2214 self.popped = #g_loot > 0
2173 self.dprint('flow', "restoring", #g_loot, "entries") 2215 self.dprint('flow', "restoring", #g_loot, "entries")
2216 -- paranoia: make sure the GUI isn't stumbling over these later
2217 local dofix, GetItemInfo = false, GetItemInfo
2218 for i,e in self:filtered_loot_iter('loot') do
2219 local missing_data = not GetItemInfo(e.id)
2220 e.cache_miss = (e.cache_miss or missing_data) or nil
2221 dofix = dofix or e.cache_miss
2222 end
2223 self.DO_ITEMID_FIX = dofix or nil
2174 _do_loot_metas() 2224 _do_loot_metas()
2175 self:ScheduleTimer("Activate", 12, opts.threshold) 2225 self:ScheduleTimer("Activate", 12, opts.threshold)
2176 -- FIXME printed could be too large if entries were deleted, how much do we care? 2226 -- FIXME printed could be too large if entries were deleted, how much do we care?
2177 self.sharder = opts.autoshard 2227 self.sharder = opts.autoshard
2178 else 2228 else
2652 end 2702 end
2653 end 2703 end
2654 2704
2655 -- In the rare case of items getting put into the loot table without current 2705 -- In the rare case of items getting put into the loot table without current
2656 -- item cache data (which will have arrived by now). 2706 -- item cache data (which will have arrived by now).
2657 function addon:do_item_cache_fixup() 2707 function addon:do_item_cache_fixup (silent_p)
2658 self:Print("Fixing up missing item cache data...") 2708 if not silent_p then
2709 self:Print("Fixing up missing item cache data...")
2710 end
2659 2711
2660 local numfound = 0 2712 local numfound = 0
2713 local earliest_fixed
2661 local borkedpat = '^'..UNKNOWN..': (%S+)' 2714 local borkedpat = '^'..UNKNOWN..': (%S+)'
2662 2715
2663 -- 'while true' so that we can use (inner) break as (outer) continue 2716 -- 'while true' so that we can use (inner) break as (outer) continue
2664 for i,e in self:filtered_loot_iter('loot') do while true do 2717 for i,e in self:filtered_loot_iter('loot') do while true do
2665 if not e.cache_miss then break end 2718 if not e.cache_miss then break end
2666 local borked_id = e.itemname:match(borkedpat) 2719 local borked_id = e.itemname:match(borkedpat) or e.id
2667 if not borked_id then break end 2720 if not borked_id then break end
2668 numfound = numfound + 1 2721 numfound = numfound + 1
2669 -- Best to use the safest and most flexible API here, which is GII and 2722 -- Best to use the safest and most flexible API here, which is GII and
2670 -- its assload of return values. 2723 -- its assload of return values.
2671 local iname, ilink, iquality, _,_,_,_,_,_, itexture = GetItemInfo(borked_id) 2724 local iname, ilink, iquality, _,_,_,_,_,_, itexture = GetItemInfo(borked_id)
2688 if player_i then 2741 if player_i then
2689 player_h.id[e.unique] = e.id 2742 player_h.id[e.unique] = e.id
2690 msg = [[ Entry %d (and history) patched up with %s.]] 2743 msg = [[ Entry %d (and history) patched up with %s.]]
2691 end 2744 end
2692 end 2745 end
2693 self:Print(msg, i, ilink) 2746 earliest_fixed = earliest_fixed or i
2747 if not silent_p then self:Print(msg, i, ilink) end
2694 end 2748 end
2695 break 2749 break
2696 end end 2750 end end
2697 2751
2698 self:Print("...finished. Found %d |4entry:entries; with weird data.", numfound) 2752 if earliest_fixed then
2753 self.loot_clean = earliest_fixed-1 -- this works out even at i == 1
2754 end
2755 if not silent_p then
2756 self:Print("...finished. Found %d |4entry:entries; with weird data.", numfound)
2757 end
2699 end 2758 end
2700 2759
2701 do 2760 do
2702 local gur 2761 local gur
2703 2762
2947 -- }, ...... 3006 -- }, ......
2948 -- }, 3007 -- },
2949 -- ["OtherRealm"] = ...... 3008 -- ["OtherRealm"] = ......
2950 -- } 3009 -- }
2951 -- 3010 --
2952 -- Up through 2.81.4 (specifically through rev 95), an individual player's 3011 -- Up through 2.18.4 (specifically through rev 95), an individual player's
2953 -- table looked like this: 3012 -- table looked like this:
2954 -- ["name"] = "Farmbuyer", 3013 -- ["name"] = "Farmbuyer",
2955 -- [1] = { id = nnnnn, when = "formatted timestamp for displaying" } -- most recent loot 3014 -- -- most recent loot:
2956 -- [2] = { ......., [count = "x3"] } -- previous loot 3015 -- [1] = { id = nnnnn, when = "formatted timestamp for displaying" }
3016 -- -- previous loot:
3017 -- [2] = { ......., [count = "x3"] }
2957 -- which was much easier to manipulate, but had a ton of memory overhead. 3018 -- which was much easier to manipulate, but had a ton of memory overhead.
2958 do 3019 do
3020 local new, del, date = flib.new, flib.del, date
3021
2959 -- Sorts a player's history from newest to oldest, according to the 3022 -- Sorts a player's history from newest to oldest, according to the
2960 -- formatted timestamp. This is expensive, and destructive for P.unique. 3023 -- formatted timestamp. This is expensive, and destructive for P.unique.
2961 local function compare_timestamps (L, R) 3024 local function compare_timestamps (L, R)
2962 return L > R -- reverse of normal order, newest first 3025 return L > R -- reverse of normal order, newest first
2963 end 3026 end
2964 local function sort_player (p) 3027 local function sort_player (p)
2965 local new_uniques, uniques_bywhen, when_array = {}, {}, {} 3028 local new_uniques, uniques_bywhen, when_array = new(), new(), new()
2966 for u,tstamp in pairs(p.when) do 3029 for u,tstamp in pairs(p.when) do
2967 uniques_bywhen[tstamp] = u 3030 uniques_bywhen[tstamp] = u
2968 when_array[#when_array+1] = tstamp 3031 when_array[#when_array+1] = tstamp
2969 end 3032 end
2970 table.sort (when_array, compare_timestamps) 3033 table.sort (when_array, compare_timestamps)
2971 for i,tstamp in ipairs(when_array) do 3034 for i,tstamp in ipairs(when_array) do
2972 new_uniques[i] = uniques_bywhen[tstamp] 3035 new_uniques[i] = uniques_bywhen[tstamp]
2973 end 3036 end
2974 p.unique = new_uniques 3037 p.unique = new_uniques
3038 del(new_uniques) del(uniques_bywhen) del(when_array)
2975 end 3039 end
2976 3040
2977 function addon:repair_history_integrity() 3041 function addon:repair_history_integrity()
2978 local rcount, pcount, hcount, errors = 0, 0, 0, 0 3042 local rcount, pcount, hcount, errors = 0, 0, 0, 0
2979 local empties_to_delete = {} 3043 local empties_to_delete = {}
2996 end 3060 end
2997 hcount = hcount + 1 3061 hcount = hcount + 1
2998 end 3062 end
2999 player.id, player.when, player.unique, player.count = 3063 player.id, player.when, player.unique, player.count =
3000 id, when, unique, count 3064 id, when, unique, count
3001 player.person_class = g_loot.raiders[player.name] 3065 player.person_class = player.person_class or
3002 and g_loot.raiders[player.name].class 3066 (g_loot.raiders[player.name] and g_loot.raiders[player.name].class)
3003 if #player.unique > 1 then 3067 if #player.unique > 1 then
3004 sort_player(player) 3068 sort_player(player)
3005 elseif #player.unique == 0 then 3069 elseif #player.unique == 0 then
3006 tinsert (empties_to_delete, 1, pk) 3070 tinsert (empties_to_delete, 1, pk)
3007 end 3071 end
3149 self.history.byname[name] = i 3213 self.history.byname[name] = i
3150 end 3214 end
3151 return i, self.history[i] 3215 return i, self.history[i]
3152 end 3216 end
3153 3217
3154 -- Prepends data from the loot entry at LOOTINDEX to be the new most 3218 -- Prepends data from the loot entry at LOOTINDEX to the history for that
3155 -- recent history entry for that player. 3219 -- player, making it the "most recent" entry regardless of actual data.
3156 function addon:_addHistoryEntry (lootindex) 3220 -- In the usual case, builds a formatted timestamp string from g_today and
3221 -- the loot entry's recorded time (thus the formatted string really *will*
3222 -- be the most recent entry). If g_today has not been set, then falls
3223 -- back on formatting LOOTINDEX's time_t 'stamp' field.
3224 --
3225 -- If RESORT_P is true-valued, then re-sorts the player's history based on
3226 -- formatted timestmps, instead of leaving the new entry as the latest.
3227 function addon:_addHistoryEntry (lootindex, resort_p)
3157 local e = g_loot[lootindex] 3228 local e = g_loot[lootindex]
3158 if e.kind ~= 'loot' then return end 3229 if e.kind ~= 'loot' then return end
3159 3230
3231 if e.person_realm and opts.history_ignore_xrealm then
3232 return
3233 end
3234
3160 local i,h = self:get_loot_history(e.person) 3235 local i,h = self:get_loot_history(e.person)
3161 local when = self:format_timestamp (g_today, e) 3236 -- If we've added anything at all into g_loot this session, g_today
3237 -- will be set. If we've logged on simply to manipulate history, then
3238 -- try and fake a timestamp (it'll be "close enough").
3239 local when = g_today and self:format_timestamp (g_today,e)
3240 or date("%Y/%m/%d %H:%M",e.stamp)
3162 assert(h.name==e.person) 3241 assert(h.name==e.person)
3163 3242
3164 -- If any of these change, update the end of history_handle_disposition. 3243 -- Should rarely happen anymore:
3165 if (not e.unique) or (#e.unique==0) then 3244 if (not e.unique) or (#e.unique==0) then
3166 e.unique = e.id .. e.person .. when 3245 e.unique = e.id .. e.person .. when
3167 end 3246 end
3168 local U = e.unique 3247 local U = e.unique
3169 tinsert (h.unique, 1, U) 3248 tinsert (h.unique, 1, U)
3170 h.when[U] = when 3249 h.when[U] = when
3171 h.id[U] = e.id 3250 h.id[U] = e.id
3172 h.count[U] = e.count 3251 h.count[U] = e.count
3173 3252 if resort_p then
3253 sort_player(h)
3254 end
3174 g_uniques[U] = { loot = lootindex, history = e.person } 3255 g_uniques[U] = { loot = lootindex, history = e.person }
3175 self:Fire ('NewHistory', e.person, U) 3256 self:Fire ('NewHistory', e.person, U)
3176 end 3257 end
3177 3258
3178 -- Create new history table based on current loot. 3259 -- Create new history table based on current loot.
3274 end 3355 end
3275 end 3356 end
3276 3357
3277 if not errtxt then 3358 if not errtxt then
3278 -- The cache finder got a hit, but now it's gone? WTF? 3359 -- The cache finder got a hit, but now it's gone? WTF?
3279 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." 3360 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, with as many details as possible."
3280 end 3361 end
3281 return nil, errtxt 3362 return nil, errtxt
3282 end 3363 end
3283 3364
3284 -- Handles reassigning loot between players. Arguments depend on who's 3365 -- Handles reassigning loot between players. Arguments depend on who's
3287 -- "remote", sender, unique_id, item_id, old_recipient, new_recipient 3368 -- "remote", sender, unique_id, item_id, old_recipient, new_recipient
3288 -- In the local case, must also broadcast a trigger. In the remote case, 3369 -- In the local case, must also broadcast a trigger. In the remote case,
3289 -- must figure out the corresponding loot entry (if it exists). In both 3370 -- must figure out the corresponding loot entry (if it exists). In both
3290 -- cases, must update history appropriately. Returns nil if anything odd 3371 -- cases, must update history appropriately. Returns nil if anything odd
3291 -- happens; returns the affected loot index on success. 3372 -- happens; returns the affected loot index on success.
3292 --function addon:reassign_loot (index, to_name)
3293 function addon:reassign_loot (how, ...) 3373 function addon:reassign_loot (how, ...)
3294 -- This must all be filled out in all cases: 3374 -- This must all be filled out in all cases:
3295 local e, index, from_name, to_name, unique, id 3375 local e, index, from_name, to_name, unique, id
3296 -- Only set in remote case: 3376 -- Only set in remote case:
3297 local sender 3377 local sender
3353 end 3433 end
3354 else 3434 else
3355 -- XXX do some sanity checks here? from_name == from_h.name, etc? 3435 -- XXX do some sanity checks here? from_name == from_h.name, etc?
3356 -- If something were wrong at this point, what could we do? 3436 -- If something were wrong at this point, what could we do?
3357 3437
3358 local U = tremove (from_h.unique, hist_i) 3438 -- the Book of Job 1:21: "Naked I came from my faction capital
3359 -- The loot master giveth... 3439 -- city, and naked I shall return thither."
3360 to_h.unique[#to_h.unique+1] = U 3440 if from_h ~= to_h then
3361 to_h.when[U] = from_h.when[U] 3441 local U = tremove (from_h.unique, hist_i)
3362 to_h.id[U] = from_h.id[U] 3442 -- "The loot master giveth..."
3363 to_h.count[U] = from_h.count[U] 3443 to_h.unique[#to_h.unique+1] = U
3364 sort_player(to_h) 3444 to_h.when[U] = from_h.when[U]
3365 -- ...and the loot master taketh away. 3445 to_h.id[U] = from_h.id[U]
3366 from_h.when[U] = nil 3446 to_h.count[U] = from_h.count[U]
3367 from_h.id[U] = nil 3447 sort_player(to_h)
3368 from_h.count[U] = nil 3448 -- "...and the loot master taketh away."
3369 -- Blessed be the lookup cache of the loot master. 3449 from_h.when[U] = nil
3450 from_h.id[U] = nil
3451 from_h.count[U] = nil
3452 end
3453 -- "Blessed be the lookup cache of the loot master."
3370 g_uniques[U] = { loot = index, history = to_name } 3454 g_uniques[U] = { loot = index, history = to_name }
3371 end 3455 end
3372 local from_person_class = e.person_class or from_h.person_class 3456 local from_person_class = e.person_class or from_h.person_class
3373 or (g_loot.raiders[from_name] and g_loot.raiders[from_name].class) 3457 or (g_loot.raiders[from_name] and g_loot.raiders[from_name].class)
3374 or select(2,UnitClass(from_name)) 3458 or select(2,UnitClass(from_name))
3454 return 0 3538 return 0
3455 end 3539 end
3456 3540
3457 -- Any extra work for the "Mark as <x>" dropdown actions. The 3541 -- Any extra work for the "Mark as <x>" dropdown actions. The
3458 -- corresponding <x> will already have been assigned in the loot entry. 3542 -- corresponding <x> will already have been assigned in the loot entry.
3459 local deleted_cache = {}
3460 function addon:history_handle_disposition (index, olddisp) 3543 function addon:history_handle_disposition (index, olddisp)
3461 local e = g_loot[index] 3544 local e = g_loot[index]
3462 -- Standard disposition has a nil entry, but that's tedious in debug 3545 -- Standard disposition has a nil entry, but that's tedious in debug
3463 -- output, so force to a string instead. 3546 -- output, so force to a string instead.
3464 olddisp = olddisp or 'normal' 3547 olddisp = olddisp or 'normal'
3467 if olddisp == newdisp then return end 3550 if olddisp == newdisp then return end
3468 3551
3469 local name = e.person 3552 local name = e.person
3470 3553
3471 if not self:_test_disposition (newdisp, 'affects_history') then 3554 if not self:_test_disposition (newdisp, 'affects_history') then
3555 -- remove history entry if it exists
3472 local name_i, name_h, hist_i = _history_by_loot_id (e, "mark") 3556 local name_i, name_h, hist_i = _history_by_loot_id (e, "mark")
3473 -- remove history entry if it exists
3474 -- FIXME revist this and use expunge
3475 if hist_i then 3557 if hist_i then
3476 local c = flib.new() 3558 -- clears g_uniques and fires DelHistory
3477 local hist_u = tremove (name_h.unique, hist_i) 3559 expunge (name_h, hist_i)
3478 c.when = name_h.when[hist_u]
3479 c.id = name_h.id[hist_u]
3480 c.count = name_h.count[hist_u]
3481 deleted_cache[hist_u] = c
3482 name_h.when[hist_u] = nil
3483 name_h.id[hist_u] = nil
3484 name_h.count[hist_u] = nil
3485 self.hist_clean = nil
3486 elseif not self:_test_disposition (olddisp, 'affects_history') then 3560 elseif not self:_test_disposition (olddisp, 'affects_history') then
3487 -- Sharding a vault item, or giving the auto-sharder something to bank, 3561 -- Sharding a vault item, or giving the auto-sharder something to bank,
3488 -- etc, wouldn't necessarily have had a history entry to begin with. 3562 -- etc, wouldn't necessarily have had a history entry to begin with.
3489 -- So this isn't treated as an error. 3563 -- So this isn't treated as an error.
3490 else 3564 else
3494 end 3568 end
3495 3569
3496 if (not self:_test_disposition (olddisp, 'affects_history')) 3570 if (not self:_test_disposition (olddisp, 'affects_history'))
3497 and self:_test_disposition (newdisp, 'affects_history') 3571 and self:_test_disposition (newdisp, 'affects_history')
3498 then 3572 then
3573 -- Must create a new history entry.
3499 local name_i, name_h = self:get_loot_history(name) 3574 local name_i, name_h = self:get_loot_history(name)
3500 3575 -- puts entry into g_uniques and fires NewHistory
3501 -- Must create a new history entry. Could call '_addHistoryEntry(index)' 3576 self:_addHistoryEntry (index, --[[re-sort entries=]]true)
3502 -- but that would duplicate a lot of effort. To start with, check the
3503 -- cache of stuff we've already deleted; if it's not there then just do
3504 -- the same steps as _addHistoryEntry.
3505 -- FIXME The deleted cache isn't nearly as useful now with the new data structures.
3506 local when
3507 if (not e.unique) or (#e.unique==0) then
3508 when = g_today and self:format_timestamp (g_today, e) or date("%Y/%m/%d %H:%M",e.stamp)
3509 e.unique = e.id .. name .. when
3510 end
3511 local U = e.unique
3512 local c = deleted_cache[U]
3513 deleted_cache[U] = nil
3514 name_h.unique[#name_h.unique+1] = U
3515 name_h.when[U] = c and c.when or when or date("%Y/%m/%d %H:%M",e.stamp)
3516 name_h.id[U] = e.id -- c.id
3517 name_h.count[U] = c and c.count or e.count
3518 sort_player(name_h)
3519 g_uniques[U] = { loot = index, history = name }
3520 self.hist_clean = nil 3577 self.hist_clean = nil
3521
3522 if c then flib.del(c) end
3523
3524 return 3578 return
3525 end 3579 end
3526 end 3580 end
3527 3581
3528 -- This is not entirely "history" but not completely anything else either. 3582 -- This is not entirely "history" but not completely anything else either.