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