Mercurial > wow > ouroloot
changeset 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 | d1b914bbed8b |
children | 289c7667adab |
files | core.lua gui.lua options.lua text_tabs.lua |
diffstat | 4 files changed, 181 insertions(+), 86 deletions(-) [+] |
line wrap: on
line diff
--- a/core.lua Fri Aug 10 00:07:35 2012 -0400 +++ b/core.lua Mon Aug 13 21:49:08 2012 -0400 @@ -8,12 +8,14 @@ - printed.FOO last loot index formatted into text window FOO, default 0 - raiders accumulating raid roster data as we see raid members; indexed by player name with subtable fields: +- fname fully-qualified name; "PlayerName" or "PlayerName-RealmName" - class capitalized English codename ("WARRIOR", "DEATHKNIGHT", etc) - subgroup 1-8 (NUM_RAID_GROUPS), NRG+1 if something's wrong - race English codename ("BloodElf", etc) - sex 1 = unknown/error, 2 = male, 3 = female - level can be 0 if player was offline at the time - guild guild name, or missing if unguilded +- realm realm name, or missing if same realm as player at the time - online 'online', 'offline', 'no_longer' [no longer in raid group] [both of these next two fields use time_t values:] - join time player joined the raid (or first time we've seen them) @@ -48,6 +50,8 @@ - person recipient - person_class class of recipient if available; may be missing; will be classID-style (e.g., DEATHKNIGHT) +- person_realm recipient's realm if different from the player's; missing + otherwise - itemname not including square brackets - id itemID as number - itemlink full clickable link @@ -116,6 +120,8 @@ ['register_slash_synonyms'] = false, ['slash_synonyms'] = '/ol,/oloot', ['scroll_to_bottom'] = true, + ['history_suppress_LFR'] = false, + ['history_ignore_xrealm'] = true, ['gui_noob'] = true, ['chatty_on_kill'] = false, ['no_tracking_wipes'] = false, @@ -389,7 +395,7 @@ -- En masse forward decls of symbols defined inside local blocks local _register_bossmod, makedate, create_new_cache, _init, _log, _do_loot_metas local _history_by_loot_id, _setup_unique_replace, _unavoidable_collision -local _notify_about_change +local _notify_about_change, _LFR_suppressing -- Try to extract numbers from the .toc "Version" and munge them into an -- integral form for comparison. The result doesn't need to be meaningful as @@ -511,7 +517,7 @@ name = addon.instance_abbrev[name] or name if typeof == "none" then return name, MAX_RAID_MEMBERS end -- diffstr is "5 Player", "10 Player (Heroic)", etc. ugh. - if (GetLFGMode()) and (GetLFGModeType() == 'raid') then + if GetLFGMode() and (GetLFGModeType() == 'raid') then t,r = 'LFR', 25 elseif diffcode == 1 then if IsInRaid() then @@ -539,6 +545,11 @@ addon.instance_tag = instance_tag -- grumble addon.latest_instance = nil -- spelling reminder, assigned elsewhere +-- Whether we're recording anything at all in the loot histories +local function _history_suppress() + return _LFR_suppressing or addon.history_suppress +end + -- Memoizing cache of unique IDs as we generate or search for them. Keys are -- the uniques, values are the following: -- 'history' active player name in self.history @@ -1267,7 +1278,7 @@ local lastevent, now = 0, 0 local redo_count = 0 - local redo, timer_handle + local redo, timer_handle, my_realm function addon:CheckRoster (leaving_p, now_a) if not g_loot.raiders then return end -- bad transition @@ -1301,11 +1312,20 @@ for i = 1, GetNumRaidMembers() do local unit = 'raid'..i -- We grab a bunch of return values here, but only pay attention to - -- them under specific circumstances. - local name, connected, subgroup, level, class, _ - name, _, subgroup, level, _, class, connected = GetRaidRosterInfo(i) + -- them under specific circumstances. Try to use as many of these + -- values as possible rather than multiple Unit* calls. + local fname, connected, subgroup, level, class, _ + fname, _, subgroup, level, _, class, connected = GetRaidRosterInfo(i) -- No, that's not my typo, it really is "uknownbeing" in Blizzard's code. - if name and name ~= UNKNOWN and name ~= UNKNOWNOBJECT and name ~= UKNOWNBEING then + if fname and fname ~= UNKNOWN and fname ~= UNKNOWNOBJECT and fname ~= UKNOWNBEING then + local name,realm = fname:match("(%S+)-(%S+)") + if realm and realm == my_realm then -- shouldn't happen + realm = nil + end + if not name then + assert(realm == nil) + name = fname + end if not g_loot.raiders[name] then g_loot.raiders[name] = { needinfo=true } end @@ -1313,11 +1333,13 @@ r.subgroup = subgroup or (NUM_RAID_GROUPS+1) if r.needinfo and UnitIsVisible(unit) then r.needinfo = nil + r.fname = fname r.class = class --select(2,UnitClass(unit)) r.race = select(2,UnitRace(unit)) r.sex = UnitSex(unit) r.level = level --UnitLevel(unit) r.guild = GetGuildInfo(unit) + r.realm = realm end --local connected = UnitIsConnected(unit) if connected and r.online ~= R_ACTIVE then @@ -1355,6 +1377,7 @@ self:Deactivate() self:CheckRoster(--[[leaving raid]]true) end + _LFR_suppressing = nil return end @@ -1366,9 +1389,15 @@ local docheck = self.enabled if event == "Activate" then - -- dispatched manually from Activate + -- dispatched from Activate + if opts.history_suppress_LFR + and GetLFGMode() and (GetLFGModeType() == 'raid') + then + _LFR_suppressing = true + end + my_realm = self.history.realm + _register_bossmod(self) self:RegisterEvent("CHAT_MSG_LOOT") - _register_bossmod(self) docheck = true elseif event == RAID_ROSTER_UPDATE_EVENT then -- hot code path, be careful @@ -1501,7 +1530,7 @@ local looti = addon._addLootEntry(loot) addon.dprint('loot', "entry", i, "was found, added at index", looti) if addon:_test_disposition (loot.disposition, 'affects_history') - and (not addon.history_suppress) + and not _history_suppress() then addon:_addHistoryEntry(looti) elseif #loot.unique > 0 then @@ -1591,11 +1620,19 @@ break end + -- Most of the time this will already be in place and filled out, + -- but there are situations where not. Even if we can't get data + -- back, at least avoid errors (or the need for existence checks). + if not g_loot.raiders[recipient] then + g_loot.raiders[recipient] = { needinfo=true } + end + -- There is some redundancy in all this, in the interests of ease-of-coding i = { kind = 'loot', person = recipient, - person_class= select(2,UnitClass(recipient)), + person_class= g_loot.raiders[recipient].class, + person_realm= g_loot.raiders[recipient].realm, cache_miss = cache_miss, quality = iquality, itemname = iname, @@ -1626,7 +1663,7 @@ end local looti = self._addLootEntry(i) if self:_test_disposition (i.disposition, 'affects_history') - and (not self.history_suppress) + and not _history_suppress() then self:_addHistoryEntry(looti) else @@ -1800,7 +1837,7 @@ self:Print[['/ouroloot fix' changes no stored data, only allows the window to be displayed again (this is built into all fixes above)]] return elseif arg == "cache" then - self:do_item_cache_fixup() + self:do_item_cache_fixup(--[[force_silent=]]false) elseif arg == "history" then self:repair_history_integrity() end @@ -1843,6 +1880,10 @@ self:RegisterEvent("PLAYER_ENTERING_WORLD", function() self:ScheduleTimer("RAID_ROSTER_UPDATE", 5, "PLAYER_ENTERING_WORLD") end) self.popped = true + if self.DO_ITEMID_FIX then + self.DO_ITEMID_FIX = nil + self:do_item_cache_fixup(--[[force_silent=]]not self.author_debug) + end if IsInRaid() then self.dprint('flow', ">:Activate calling RRU") self:RAID_ROSTER_UPDATE("Activate") @@ -1891,6 +1932,7 @@ self:UnregisterEvent(RAID_ROSTER_UPDATE_EVENT) self:UnregisterEvent("PLAYER_ENTERING_WORLD") self:UnregisterEvent("CHAT_MSG_LOOT") + _LFR_suppressing = nil self:Fire ('Deactivate') self:Print("Deactivated.") end @@ -2171,6 +2213,14 @@ g_loot = _G.OuroLootSV self.popped = #g_loot > 0 self.dprint('flow', "restoring", #g_loot, "entries") + -- paranoia: make sure the GUI isn't stumbling over these later + local dofix, GetItemInfo = false, GetItemInfo + for i,e in self:filtered_loot_iter('loot') do + local missing_data = not GetItemInfo(e.id) + e.cache_miss = (e.cache_miss or missing_data) or nil + dofix = dofix or e.cache_miss + end + self.DO_ITEMID_FIX = dofix or nil _do_loot_metas() self:ScheduleTimer("Activate", 12, opts.threshold) -- FIXME printed could be too large if entries were deleted, how much do we care? @@ -2654,16 +2704,19 @@ -- In the rare case of items getting put into the loot table without current -- item cache data (which will have arrived by now). -function addon:do_item_cache_fixup() - self:Print("Fixing up missing item cache data...") +function addon:do_item_cache_fixup (silent_p) + if not silent_p then + self:Print("Fixing up missing item cache data...") + end local numfound = 0 + local earliest_fixed local borkedpat = '^'..UNKNOWN..': (%S+)' -- 'while true' so that we can use (inner) break as (outer) continue for i,e in self:filtered_loot_iter('loot') do while true do if not e.cache_miss then break end - local borked_id = e.itemname:match(borkedpat) + local borked_id = e.itemname:match(borkedpat) or e.id if not borked_id then break end numfound = numfound + 1 -- Best to use the safest and most flexible API here, which is GII and @@ -2690,12 +2743,18 @@ msg = [[ Entry %d (and history) patched up with %s.]] end end - self:Print(msg, i, ilink) + earliest_fixed = earliest_fixed or i + if not silent_p then self:Print(msg, i, ilink) end end break end end - self:Print("...finished. Found %d |4entry:entries; with weird data.", numfound) + if earliest_fixed then + self.loot_clean = earliest_fixed-1 -- this works out even at i == 1 + end + if not silent_p then + self:Print("...finished. Found %d |4entry:entries; with weird data.", numfound) + end end do @@ -2949,20 +3008,24 @@ -- ["OtherRealm"] = ...... -- } -- --- Up through 2.81.4 (specifically through rev 95), an individual player's +-- Up through 2.18.4 (specifically through rev 95), an individual player's -- table looked like this: -- ["name"] = "Farmbuyer", --- [1] = { id = nnnnn, when = "formatted timestamp for displaying" } -- most recent loot --- [2] = { ......., [count = "x3"] } -- previous loot +-- -- most recent loot: +-- [1] = { id = nnnnn, when = "formatted timestamp for displaying" } +-- -- previous loot: +-- [2] = { ......., [count = "x3"] } -- which was much easier to manipulate, but had a ton of memory overhead. do + local new, del, date = flib.new, flib.del, date + -- Sorts a player's history from newest to oldest, according to the -- formatted timestamp. This is expensive, and destructive for P.unique. local function compare_timestamps (L, R) return L > R -- reverse of normal order, newest first end local function sort_player (p) - local new_uniques, uniques_bywhen, when_array = {}, {}, {} + local new_uniques, uniques_bywhen, when_array = new(), new(), new() for u,tstamp in pairs(p.when) do uniques_bywhen[tstamp] = u when_array[#when_array+1] = tstamp @@ -2972,6 +3035,7 @@ new_uniques[i] = uniques_bywhen[tstamp] end p.unique = new_uniques + del(new_uniques) del(uniques_bywhen) del(when_array) end function addon:repair_history_integrity() @@ -2998,8 +3062,8 @@ end player.id, player.when, player.unique, player.count = id, when, unique, count - player.person_class = g_loot.raiders[player.name] - and g_loot.raiders[player.name].class + player.person_class = player.person_class or + (g_loot.raiders[player.name] and g_loot.raiders[player.name].class) if #player.unique > 1 then sort_player(player) elseif #player.unique == 0 then @@ -3151,17 +3215,32 @@ return i, self.history[i] end - -- Prepends data from the loot entry at LOOTINDEX to be the new most - -- recent history entry for that player. - function addon:_addHistoryEntry (lootindex) + -- Prepends data from the loot entry at LOOTINDEX to the history for that + -- player, making it the "most recent" entry regardless of actual data. + -- In the usual case, builds a formatted timestamp string from g_today and + -- the loot entry's recorded time (thus the formatted string really *will* + -- be the most recent entry). If g_today has not been set, then falls + -- back on formatting LOOTINDEX's time_t 'stamp' field. + -- + -- If RESORT_P is true-valued, then re-sorts the player's history based on + -- formatted timestmps, instead of leaving the new entry as the latest. + function addon:_addHistoryEntry (lootindex, resort_p) local e = g_loot[lootindex] if e.kind ~= 'loot' then return end + if e.person_realm and opts.history_ignore_xrealm then + return + end + local i,h = self:get_loot_history(e.person) - local when = self:format_timestamp (g_today, e) + -- If we've added anything at all into g_loot this session, g_today + -- will be set. If we've logged on simply to manipulate history, then + -- try and fake a timestamp (it'll be "close enough"). + local when = g_today and self:format_timestamp (g_today,e) + or date("%Y/%m/%d %H:%M",e.stamp) assert(h.name==e.person) - -- If any of these change, update the end of history_handle_disposition. + -- Should rarely happen anymore: if (not e.unique) or (#e.unique==0) then e.unique = e.id .. e.person .. when end @@ -3170,7 +3249,9 @@ h.when[U] = when h.id[U] = e.id h.count[U] = e.count - + if resort_p then + sort_player(h) + end g_uniques[U] = { loot = lootindex, history = e.person } self:Fire ('NewHistory', e.person, U) end @@ -3276,7 +3357,7 @@ if not errtxt then -- The cache finder got a hit, but now it's gone? WTF? - 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." + 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." end return nil, errtxt end @@ -3289,7 +3370,6 @@ -- must figure out the corresponding loot entry (if it exists). In both -- cases, must update history appropriately. Returns nil if anything odd -- happens; returns the affected loot index on success. - --function addon:reassign_loot (index, to_name) function addon:reassign_loot (how, ...) -- This must all be filled out in all cases: local e, index, from_name, to_name, unique, id @@ -3355,18 +3435,22 @@ -- XXX do some sanity checks here? from_name == from_h.name, etc? -- If something were wrong at this point, what could we do? - local U = tremove (from_h.unique, hist_i) - -- The loot master giveth... - to_h.unique[#to_h.unique+1] = U - to_h.when[U] = from_h.when[U] - to_h.id[U] = from_h.id[U] - to_h.count[U] = from_h.count[U] - sort_player(to_h) - -- ...and the loot master taketh away. - from_h.when[U] = nil - from_h.id[U] = nil - from_h.count[U] = nil - -- Blessed be the lookup cache of the loot master. + -- the Book of Job 1:21: "Naked I came from my faction capital + -- city, and naked I shall return thither." + if from_h ~= to_h then + local U = tremove (from_h.unique, hist_i) + -- "The loot master giveth..." + to_h.unique[#to_h.unique+1] = U + to_h.when[U] = from_h.when[U] + to_h.id[U] = from_h.id[U] + to_h.count[U] = from_h.count[U] + sort_player(to_h) + -- "...and the loot master taketh away." + from_h.when[U] = nil + from_h.id[U] = nil + from_h.count[U] = nil + end + -- "Blessed be the lookup cache of the loot master." g_uniques[U] = { loot = index, history = to_name } end local from_person_class = e.person_class or from_h.person_class @@ -3456,7 +3540,6 @@ -- Any extra work for the "Mark as <x>" dropdown actions. The -- corresponding <x> will already have been assigned in the loot entry. - local deleted_cache = {} function addon:history_handle_disposition (index, olddisp) local e = g_loot[index] -- Standard disposition has a nil entry, but that's tedious in debug @@ -3469,20 +3552,11 @@ local name = e.person if not self:_test_disposition (newdisp, 'affects_history') then + -- remove history entry if it exists local name_i, name_h, hist_i = _history_by_loot_id (e, "mark") - -- remove history entry if it exists - -- FIXME revist this and use expunge if hist_i then - local c = flib.new() - local hist_u = tremove (name_h.unique, hist_i) - c.when = name_h.when[hist_u] - c.id = name_h.id[hist_u] - c.count = name_h.count[hist_u] - deleted_cache[hist_u] = c - name_h.when[hist_u] = nil - name_h.id[hist_u] = nil - name_h.count[hist_u] = nil - self.hist_clean = nil + -- clears g_uniques and fires DelHistory + expunge (name_h, hist_i) elseif not self:_test_disposition (olddisp, 'affects_history') then -- Sharding a vault item, or giving the auto-sharder something to bank, -- etc, wouldn't necessarily have had a history entry to begin with. @@ -3496,31 +3570,11 @@ if (not self:_test_disposition (olddisp, 'affects_history')) and self:_test_disposition (newdisp, 'affects_history') then + -- Must create a new history entry. local name_i, name_h = self:get_loot_history(name) - - -- Must create a new history entry. Could call '_addHistoryEntry(index)' - -- but that would duplicate a lot of effort. To start with, check the - -- cache of stuff we've already deleted; if it's not there then just do - -- the same steps as _addHistoryEntry. - -- FIXME The deleted cache isn't nearly as useful now with the new data structures. - local when - if (not e.unique) or (#e.unique==0) then - when = g_today and self:format_timestamp (g_today, e) or date("%Y/%m/%d %H:%M",e.stamp) - e.unique = e.id .. name .. when - end - local U = e.unique - local c = deleted_cache[U] - deleted_cache[U] = nil - name_h.unique[#name_h.unique+1] = U - name_h.when[U] = c and c.when or when or date("%Y/%m/%d %H:%M",e.stamp) - name_h.id[U] = e.id -- c.id - name_h.count[U] = c and c.count or e.count - sort_player(name_h) - g_uniques[U] = { loot = index, history = name } + -- puts entry into g_uniques and fires NewHistory + self:_addHistoryEntry (index, --[[re-sort entries=]]true) self.hist_clean = nil - - if c then flib.del(c) end - return end end
--- a/gui.lua Fri Aug 10 00:07:35 2012 -0400 +++ b/gui.lua Mon Aug 13 21:49:08 2012 -0400 @@ -340,7 +340,8 @@ if e == nil then self.loot_clean = nil pprint('_f_o_e_d', "index",i,"somehow still in loop past",#g_loot,"bailing") - return + -- hmm. used to bail here. does restarting cause problems? + return self:_fill_out_eoi_data(1) end local display_bcast_from = self.db.profile.display_bcast_from @@ -350,9 +351,11 @@ -- garbage for now. if e.kind == 'loot' then local textured = eoi_st_textured_item_format:format (e.itexture, ITEM_QUALITY_COLORS[e.quality].hex, e.itemname, e.count or "") + local pdisplay = e.person_realm + and (e.person .. FOREIGN_SERVER_LABEL) or e.person e.cols = { {value = textured}, - {value = e.person}, + {value = pdisplay}, {} } -- This is horrible. Must do better. @@ -460,7 +463,12 @@ col2.OLi = li col2.OLu = unique local col3 = new() - col3.value = assert(player.when[unique]) + col3.value = player.when[unique] + + if not col3.value then + col3.hist_miss = true + col3.value = '??' + end local id = assert(player.id[unique]) local itexture = GetItemIcon(id) @@ -1055,6 +1063,7 @@ local e = data[realrow] if e == nil then return end -- something horrible has happened local kind = e.kind + local cell = e.cols[column] local tt = GameTooltip -- can this be hoisted? does GT ever get securely replaced? if gui._do_debugging_tooltip and column == 1 and kind ~= 'hist' then @@ -1076,7 +1085,8 @@ elseif kind == 'loot' and column == 2 then tt:SetOwner (cellFrame, "ANCHOR_BOTTOMRIGHT", -50, 5) tt:ClearLines() - tt:AddLine(e.person.." Loot:") + tt:AddLine(("%s Loot:"):format(e.person_realm + and (e.person .. "-" .. e.person_realm) or e.person)) local counter = 0 for i,e2 in ipairs(data) do if e2.person == e.person then -- would be awesome to test for alts @@ -1095,7 +1105,16 @@ tt:Show() elseif kind == 'loot' and column == 3 then - setstatus(e.cols[column].value) + setstatus(cell.value) + + elseif kind == 'hist' and column == 3 and cell.hist_miss then + tt:SetOwner (cellFrame, "ANCHOR_RIGHT", -20, 0) + tt:ClearLines() + tt:AddLine("Corrupted History Data") + tt:AddLine([[Close this window, then type]], 0.8, 0.8, 0.8, 1) + tt:AddLine([[/ouroloot fix history]], 0, 1, 64/255, nil) + tt:AddLine([[and redisplay this window.]], 0.8, 0.8, 0.8, 1) + tt:Show() end @@ -1144,7 +1163,7 @@ elseif kind == 'loot' and column == 2 then local ddep = gui.dropdown.eoi_player - ddep[1].text = e.person + ddep[1].text = e.person -- FIXME realm this too local raiders = {} for i = 1, GetNumRaidMembers() do tinsert (raiders, (GetRaidRosterInfo(i))) @@ -1812,7 +1831,15 @@ st_widget.tail_offset = 0 container:SetLayout("Fill") container:AddChild(st_widget) - setstatus(hist_normal_status) + -- If we're focused on one player, but have deleted all entries for + -- that player, don't sit there stuck on a blank grid. + if history_filter_who and #histST.filtered < 1 then + history_filter_who = nil + histST:SetFilter(history_filter_by_recent) + setstatus(hist_normal_status) + else + setstatus(hist_name_status) + end local b do
--- a/options.lua Fri Aug 10 00:07:35 2012 -0400 +++ b/options.lua Mon Aug 13 21:49:08 2012 -0400 @@ -161,6 +161,17 @@ [[Irreverent replacement names for boss events. See abbreviations.lua for details.]]) container:AddChild(w) + -- auto-GOP mode when in LFR + w = mktoggle('history_suppress_LFR', "Suppress history in LFR", stdw, + [[Do not record anything at all in the History tab while in an LFR raid. Changes only take effect outside of LFR.]]) + container:AddChild(w) + + -- ignore cross realm players in history + w = mktoggle('history_ignore_xrealm', "Suppress history for cross-realm players", + stdw, + [[Do not record anything in the History tab for players from other realms.]]) + container:AddChild(w) + -- LOD plugins in all cases w = mktoggle('display_disabled_LODs', "Include disabled plugins", stdw, [[Show loadable plugins even if they've been disabled (and offer to enable them if clicked). Relog to take effect.]])
--- a/text_tabs.lua Fri Aug 10 00:07:35 2012 -0400 +++ b/text_tabs.lua Mon Aug 13 21:49:08 2012 -0400 @@ -154,9 +154,9 @@ if raidertable then for name,info in pairs(raidertable) do if info.online ~= 'no_longer' then -- 'no_longer' == left the raid if (info.subgroup or (NUM_RAID_GROUPS+1)) <= max_group_number then - tins (ingroups, name) + tins (ingroups, info.fname) else - tins (outgroups, name) + tins (outgroups, info.fname) end end end end