diff core.lua @ 81:0f6355bcfe68

Initial version of reassign_loot that handles remote broadcasting, tested in sandbox. Catch tab generation errors at a higher level and take apart the window (including STs) rather than propagating Lua errors upwards.
author Farmbuyer of US-Kilrogg <farmbuyer@gmail.com>
date Fri, 15 Jun 2012 20:04:05 +0000
parents f01d8d1b8f82
children ae17128ef3f2
line wrap: on
line diff
--- a/core.lua	Thu Jun 14 04:48:13 2012 +0000
+++ b/core.lua	Fri Jun 15 20:04:05 2012 +0000
@@ -143,7 +143,14 @@
 	.." a download URL for copy-and-pasting. You can %s to ping other raiders"
 	.." for their installed versions (same as '/ouroloot ping' or clicking the"
 	.." 'Ping!' button on the options panel)."
-local unique_collision = "|cffff1010%s:|r|nItem '%s' was carrying unique tag <%s>, but that was already in use; tried to generate a new tag and failed!|n|nRemote sender was '%s', previous cache entry was <%s/%s>.|n|nThis may require a live human to figure out; the loot in question has not been stored."
+local horrible_error_text = [[|cffff1010]] .. ERROR_CAPS
+	..[[:|n|cffffff00Something unrecoverable has happened.  The error message]]
+	..[[ which was provided follows in white:|r|n|n%s|n|n|cffffff00Ouro Loot]]
+	..[[ will not display a window until this situation is corrected. ]]
+	..[[ You can try typing|n|cff00ff40/ouroloot fix ?|n]]
+	..[[|cffffff00to see what can be done by software alone.  You may still]]
+	..[[ need to do a "/reload" afterwards, or even restart the game client.]]
+local unique_collision = "Item '%s' was carrying unique tag <%s>, but that was already in use; tried to generate a new tag and failed!|n|nRemote sender was '%s', previous cache entry was <%s/%s>.|n|nThis may require a live human to figure out; the loot in question has not been stored."
 local remote_chatty = "|cff00ff00%s|r changed %d/%s from %s%s|r to %s%s|r"
 local qualnames = {
 	['gray'] = 0, ['grey'] = 0, ['poor'] = 0, ['trash'] = 0,
@@ -339,8 +346,8 @@
 local CopyTable, GetNumRaidMembers = _G.CopyTable, _G.GetNumRaidMembers
 -- En masse forward decls of symbols defined inside local blocks
 local _register_bossmod, makedate, create_new_cache, _init, _log
-local _history_by_loot_id, _notify_about_remote, _setup_unique_replace
-local _unavoidable_collision
+local _history_by_loot_id, _setup_unique_replace, _unavoidable_collision
+local _notify_about_change, _notify_about_remote
 
 -- 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
@@ -1382,7 +1389,7 @@
 					self.dprint('loot', "substituting", unique, "with", replacement)
 				else
 					i = g_uniques[unique]
-					local err = unique_collision:format (ERROR_CAPS, iname, unique,
+					local err = unique_collision:format (iname, unique,
 						tostring(from), tostring(i.loot), tostring(i.history))
 					_unavoidable_collision (err)
 					-- Make sure this is logged one way or another
@@ -1625,8 +1632,19 @@
 		elseif cmd == "ping" then
 			self:DoPing()
 
-		elseif cmd == "fixcache" then
-			self:do_item_cache_fixup()
+		elseif cmd == "fix" then
+			if arg == "?" then
+				self:Print[['/loot fix cache' updates loot that wasn't in the cache]]
+				self:Print[['/loot fix history' repairs inconsistent data on the History tab]]
+				self:Print[['/loot 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()
+			elseif arg == "history" then
+				self:repair_history_integrity()
+			end
+			self.NOLOAD = nil
+			self:Print("Window unlocked, best of luck.")
 
 		else
 			if self:OpenMainDisplayToTab(cmd) then
@@ -1798,7 +1816,15 @@
 		end
 	end
 
-	function _notify_about_remote (sender, index, from_whom, olddisp)
+	local hexes = _G.setmetatable({}, {__index = function (t, k)
+		local r = _G.math.floor(255*k.r+0.5)
+		local g = _G.math.floor(255*k.g+0.5)
+		local b = _G.math.floor(255*k.b+0.5)
+		local hex = ("|cff%.2x%.2x%.2x"):format(r,g,b)
+		t[k] = hex
+		return hex
+	end})
+	function _notify_about_change (chatframe, source, index, olddisp, from_whom, from_class)
 		local e = g_loot[index]
 		if not e then
 			-- how did this happen?
@@ -1806,8 +1832,16 @@
 		end
 		local from_color, from_text, to_color, to_text
 		if from_whom then
-			-- FIXME need to return previous name/class from reassign_loot
-
+			from_color = from_class
+				and addon.class_colors[from_class]
+				or _G.NORMAL_FONT_COLOR
+			from_color = hexes[from_color]
+			from_text = from_whom
+			to_color = e.person_class
+				and addon.class_colors[e.person_class]
+				or _G.NORMAL_FONT_COLOR
+			to_color = hexes[to_color]
+			to_text = e.person
 		else
 			if olddisp then
 				from_text = addon.disposition_colors[olddisp].text
@@ -1824,11 +1858,15 @@
 			to_color = addon.disposition_colors[e.disposition or "normal"].hex
 		end
 
-		addon.dprint ('loot', "notifying:", sender, index,
+		addon.dprint ('loot', "notification:", source, index,
 			e.itemlink, from_color, from_text, to_color, to_text)
-		addon:CFPrint (remote_change_chatframe, remote_chatty, sender, index,
+		addon:CFPrint (chatframe, remote_chatty, source, index,
 			e.itemlink, from_color, from_text, to_color, to_text)
 	end
+
+	function _notify_about_remote (sender, index, olddisp, from_whom, from_class)
+		_notify_about_change (remote_change_chatframe, sender, index, olddisp, from_whom, from_class)
+	end
 end
 
 -- Adds indices to traverse the tables in a nice sorted order.
@@ -2505,25 +2543,42 @@
 
 do
 	local clicky
-	function _unavoidable_collision (err)
+	function addon:horrible_horrible_error (err_msg)
+		if self.display then
+			local d = self.display
+			if d then
+				local eoist = d:GetUserData("eoiST")
+				if eoist then eoist:Hide() end
+				local histst = d:GetUserData("histST")
+				if histst then histst:Hide() end
+				d:Hide()
+			end
+		end
+		self.NOLOAD = err_msg
 		-- This should happen so rarely that it's not worth moving into gui.lua
-		if not StaticPopupDialogs["OUROL_COLLISION"] then
-			StaticPopupDialogs["OUROL_COLLISION"] = flib.StaticPopup{
+		if not StaticPopupDialogs["OUROL_ARGH"] then
+			StaticPopupDialogs["OUROL_ARGH"] = flib.StaticPopup{
 				button1 = OKAY,
 			}
 			clicky = addon.format_hypertext(
 				[[ SYSTEM FAILURE -- RELEASE RINZLER ]], "|cffff0000",
-				function() StaticPopup_Show "OUROL_COLLISION" end)
+				function() StaticPopup_Show "OUROL_ARGH" end)
 		end
-		StaticPopupDialogs["OUROL_COLLISION"].text = err
+		StaticPopupDialogs["OUROL_ARGH"].text = horrible_error_text:format(err_msg)
 		_G.PlaySoundFile ([[Interface\AddOns\Ouro_Loot\sfrr.ogg]], "Master")
 		addon:Print ("        ")
 		addon:Print ("        ", clicky)
 		addon:Print ("        ")
 	end
+
+	function _unavoidable_collision (err)
+		addon:horrible_horrible_error (err)
+		-- we don't actually need to kill the GUI in this case
+		addon.NOLOAD = nil
+	end
 end
 --function DOTEST()
---	local err = unique_collision:format (ERROR_CAPS,
+--	local err = unique_collision:format (
 --		"Codpiece of the Grimacing Lunatic",
 --		'n3183021', 'Farmbuyer', '14', '78')
 --	_unavoidable_collision (err)
@@ -2540,7 +2595,7 @@
 end
 
 function addon:save_list()
-	local s = self:check_saved_table(); if not s then return end;
+	local s = self:check_saved_table(); if not s then return end
 	for i,t in ipairs(s) do
 		self:Print("#%d   %s    %d entries     %s", i, t.date, t.count, t.name)
 	end
@@ -2564,7 +2619,7 @@
 end
 
 function addon:save_restore(num)
-	local s = self:check_saved_table(); if not s then return end;
+	local s = self:check_saved_table(); if not s then return end
 	if (not num) or (num > #s) then
 		return self:Print("Saved text number must be 1 - "..#s)
 	end
@@ -2579,7 +2634,7 @@
 end
 
 function addon:save_delete(num)
-	local s = self:check_saved_table(); if not s then return end;
+	local s = self:check_saved_table(); if not s then return end
 	if (not num) or (num > #s) then
 		return self:Print("Saved text number must be 1 - "..#s)
 	end
@@ -2641,6 +2696,58 @@
 		p.unique = new_uniques
 	end
 
+	function addon:repair_history_integrity()
+		local rcount, pcount, hcount, errors = 0, 0, 0, 0
+		local empties_to_delete = {}
+
+		for rname,realm in pairs(self.history_all) do
+			for pk,player in ipairs(realm) do
+				local id, when, unique, count = {}, {}, {}, {}
+				for i,h in ipairs(player.unique) do
+					h = tostring(h)
+					if player.when[h] and player.id[h] then
+						unique[#unique+1] = h
+						id[h] = player.id[h]
+						when[h] = player.when[h]
+						count[h] = player.count[h]
+					else
+						self:Print("Realm %s, player %s, entry %d:  tag <%s>, id <%s>, time <%s>, count <%s>",
+							rname, player.name, i, h, tostring(player.id[h]),
+							tostring(player.when[h]), tostring(player.count[h]))
+						errors = errors + 1
+					end
+					hcount = hcount + 1
+				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
+				if #player.unique > 1 then
+					sort_player(player)
+				elseif #player.unique == 0 then
+					tinsert (empties_to_delete, 1, pk)
+				end
+				pcount = pcount + 1
+			end
+			if #empties_to_delete > 0 then
+				for _,pk in ipairs(empties_to_delete) do
+					local player = tremove (realm, pk)
+					self:Print("Realm %s, player %s, is empty", rname, player.name)
+				end
+				wipe(empties_to_delete)
+			end
+			if #realm == 0 then
+				self.history_all[rname] = nil
+				self:Print("Realm %s is empty", rname)
+			end
+			rcount = rcount + 1
+		end
+		self:_build_history_names()
+		if errors > 0 then
+			self:Print("The listed entries have been erased from history.")
+		end
+	end
+
 	-- Possibly called during login.  Cleared when no longer needed.
 	-- Rewrites a PLAYER table from format 3 to format 4.
 	function addon:_uplift_history_format (player)
@@ -2884,16 +2991,79 @@
 		return nil, errtxt
 	end
 
-	function addon:reassign_loot (index, to_name)
-		assert(type(to_name)=='string' and to_name:len()>0)
-		local e = g_loot[index]
+	-- Handles reassigning loot between players.  Arguments depend on who's
+	-- calling it:
+	--     "local", row_index, new_recipient
+	--     "remote", sender, unique_id, item_id, old_recipient, new_recipient
+	-- In the local case, must also broadcast a trigger.  In the remote case,
+	-- 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
+		-- Only set in remote case:
+		local sender
+
+		if how == "local" then
+			-- GUI doesn't allow reassignment unless the item is not-shard,
+			-- so we can assume the presence of a unique tag in this function.
+			index, to_name = ...
+			assert(type(to_name)=='string' and to_name:len()>0)
+			index = assert(tonumber(index))
+			e = g_loot[index]
+			id = e.id
+			unique = assert(e.unique)
+			from_name = e.person
+
+		elseif how == "remote" then
+			sender, unique, id, from_name, to_name = ...
+			id = tonumber(id)
+			local cache
+			local loop = 0
+			repeat    -- wtb continue statement pst
+				if loop > 1 then break end
+				e = nil
+				cache = cache and g_uniques:SEARCH(unique) or g_uniques[unique]
+				index = tonumber(cache.loot)
+				if index then
+					e = g_loot[index]
+				else
+				end
+				loop = loop + 1
+			until e and (e.id == id)
+
+		else
+			return  -- silently ignore future cases from future clients
+		end
+
+		if self.debug.loot then
+			local m = ("Reassign index %d (pre-unique %s) with id %d from '%s' to '%s'."):
+				format(index, unique, id, tostring(from_name), tostring(to_name))
+			self.dprint('loot', m)
+			if sender == my_name then
+				self.dprint('loot',"(Returning early from debug mode's double self-reassign.)")
+				return index
+			end
+		end
+
+		if not e then
+			-- say something?
+			return
+		end
+
 		local from_i, from_h, hist_i = _history_by_loot_id (e, "reassign")
-		local from_name = e.person
 		local to_i,to_h = self:get_loot_history(to_name)
 
-		if not from_i then   -- from_h here is the formatted error text
-			self:Print(from_h .. "  Loot will be reassigned, but history will NOT be updated.", e.itemlink)
+		if not from_i then
+			if how == "local" then
+				-- from_h here is the formatted error text
+				self:Print(from_h .. "  Loot will be reassigned, but history will NOT be updated.", e.itemlink)
+			end
 		else
+			-- XXX do some sanity checks here?  from_name == from_h.name, etc?
+
 			local U = tremove (from_h.unique, hist_i)
 			-- The loot master giveth...
 			to_h.unique[#to_h.unique+1] = U
@@ -2908,11 +3078,29 @@
 			-- Blessed be the lookup cache of the loot master.
 			g_uniques[U] = { loot = index, history = to_i }
 		end
+		local from_person_class = e.person_class or from_h.person_class
+			or (g_loot.raiders[from_name] and g_loot.raiders[from_name].class)
+			or select(2,_G.UnitClass(from_name))
 		e.person = to_name
-		e.person_class = select(2,_G.UnitClass(to_name))
+		e.person_class = to_h.person_class
+			or (g_loot.raiders[to_name] and g_loot.raiders[to_name].class)
+			or select(2,_G.UnitClass(to_name))
 		self.hist_clean = nil
-
-		self:Print("Reassigned entry %d/%s from '%s' to '%s'.", index, e.itemlink, from_name, to_name)
+		self.loot_clean = nil
+
+		if how == "local" then
+			--self:Print("Reassigned entry %d/%s from '%s' to '%s'.", index, e.itemlink, from_name, to_name)
+			_notify_about_change (_G.DEFAULT_CHAT_FRAME, _G.UNIT_YOU, index,
+				nil, from_name, from_person_class)
+			self:vbroadcast('reassign', unique, id, from_name, to_name)
+		elseif opts.chatty_on_remote_changes then
+			_notify_about_remote (sender, index, nil, from_name, from_person_class)
+		end
+		if self.display then
+			self.display:GetUserData("eoiST"):OuroLoot_Refresh(index)
+			self:redisplay()
+		end
+		return index
 	end
 
 	-- Similar to _addHistoryEntry.  The second arg may be a loot entry
@@ -3035,11 +3223,20 @@
 
 		elseif how == "remote" then
 			sender, unique, id, olddisp, newdisp = ...
-			local cache = g_uniques[unique]
-			if cache.loot then
+			id = tonumber(id)
+			local cache
+			local loop = 0
+			repeat    -- wtb continue statement pst
+				if loop > 1 then break end
+				e = nil
+				cache = cache and g_uniques:SEARCH(unique) or g_uniques[unique]
 				index = tonumber(cache.loot)
-				e = g_loot[index]
-			end
+				if index then
+					e = g_loot[index]
+				else
+				end
+				loop = loop + 1
+			until e and (e.id == id)
 
 		else
 			return  -- silently ignore future cases from future clients
@@ -3064,6 +3261,8 @@
 		e.bcast_from = nil  -- I actually don't remember now why this gets cleared...
 		e.extratext = nil
 		self:history_handle_disposition (index, olddisp)
+		self.hist_clean = nil
+		self.loot_clean = nil
 		-- A unique tag has been set by this point.
 		if how == "local" then
 			unique = assert(e.unique)
@@ -3155,10 +3354,17 @@
 		local index = addon:loot_mark_disposition ("remote", sender, unique, item, old, new)
 		--if not addon.enabled then return end   -- hmm
 		if index and opts.chatty_on_remote_changes then
-			_notify_about_remote (sender, index, --[[from_whom=]]nil, old)
+			_notify_about_remote (sender, index, old)
 		end
 	end
 
+	OCR_funcs['17reassign'] = function (sender, _, unique, item, from, to)
+		addon.dprint('comm', "DOTreassign/17, sender", sender, "unique", unique,
+			"item", item, "from", from, "to", to)
+		--[[local index =]] addon:reassign_loot ("remote", sender, unique, item, from, to)
+		-- Notification handled inside reassign_loot.
+	end
+
 	OCR_funcs['16loot'] = function (sender, _, recip, item, count, extratext)
 		addon.dprint('comm', "DOTloot/16, sender", sender, "recip", recip, "item", item, "count", count)
 		if not addon.enabled then return end