# HG changeset patch # User Farmbuyer of US-Kilrogg # Date 1339142737 0 # Node ID 124da015c4a27f15c933f1df745e9afeb7fb1839 # Parent 676fb79a4ae25544186e1005b6810daa0d5e0007 - Some more debugging aids (logging error/assert, auto-enable of testing panel, reminder of GOP history mode) - Move (finally!) hypertext handling code out to each call site. - Fix some bugs in previous alpha code. - Initial-but-mostly-tested code to handle items that have a "unique" field which are in fact always the same (for example, elementium gem cluster). Still need to test the case in which a remote tracker sees them first. - The rest of the variable-cutoff history cleanup. diff -r 676fb79a4ae2 -r 124da015c4a2 core.lua --- a/core.lua Fri Jun 01 02:17:57 2012 +0000 +++ b/core.lua Fri Jun 08 08:05:37 2012 +0000 @@ -93,7 +93,7 @@ rewrite. Some variables are needlessly initialized to nil just to look uniform and -serve as a reminder. +serve as a spelling reminder. ]==] @@ -143,8 +143,8 @@ .." 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 Item '%s' was carrying unique tag <" - ..">, but that was already in use! (New sender was '%s', previous cache " +local unique_collision = "|cffff1010%s:|r Item '%s' was carrying unique tag " + .."<%s>, but that was already in use! (New sender was '%s', previous cache " .."entry was <%s/%s>.) This 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" @@ -159,7 +159,7 @@ --['heirloom'] = 7, } local my_name = UnitName('player') -local comm_cleanup_ttl = 4 -- seconds in the cache +local comm_cleanup_ttl = 4 -- seconds in the communications cache local revision_large = nil -- defaults to 1, possibly changed by revision local g_LOOT_ITEM_ss, g_LOOT_ITEM_MULTIPLE_sss, g_LOOT_ITEM_SELF_s, g_LOOT_ITEM_SELF_MULTIPLE_ss @@ -188,12 +188,12 @@ DEBUG_PRINT = false debug = { - comm = false, - loot = false, - flow = false, - notraid = false, - cache = false, - alsolog = false, + comm = false, + loot = false, + flow = false, + notraid = false, + cache = false, + alsolog = false, } -- This looks ugly, but it factors out the load-time decisions from -- the run-time ones. Args to [dp]print are concatenated with spaces. @@ -228,25 +228,41 @@ pprint = flib.nullfunc end + -- The same observable behavior as the Lua builtins, but with slightly + -- different hardcoded strings and, more importantly, implicit logging. + function error(txt,lvl) + pprint('ERROR()', txt) + pprint('DEBUGSTACK()', _G.debugstack()) + _G.error(txt,lvl) + end + function assert(cond,msg,...) + if cond then + return cond,msg,... + else + error('ASSERT() FAILED: '..tostring(msg or 'nil')) + end + end + enabled = false rebroadcast = false - display = nil -- display frame, when visible + display = nil -- reference to display frame iff visible loot_clean = nil -- index of last GUI entry with known-current visual data - sender_list = {active={},names={}} -- this should be reworked threshold = debug.loot and 0 or 3 -- rare by default sharder = nil -- name of person whose loot is marked as shards -- The rest is also used in the GUI: + sender_list = {active={},names={}} -- this should be reworked popped = nil -- non-nil when reminder has been shown, actual value unimportant bossmod_registered = nil - bossmods = {} + bossmods = {} - requesting = nil -- for prompting for additional rebroadcasters + requesting = nil -- prompting for additional rebroadcasters - -- don't use NUM_ITEM_QUALITIES as the upper bound unless we expect heirlooms to show up - thresholds = {} + -- don't use NUM_ITEM_QUALITIES as the upper loop bound unless we expect + -- heirlooms to show up + thresholds = {} for i = 0,6 do thresholds[i] = _G.ITEM_QUALITY_COLORS[i].hex .. _G["ITEM_QUALITY"..i.."_DESC"] .. "|r" end @@ -267,7 +283,7 @@ self:Printf([[|cffff1010ERROR:|r <|cff00ff00%s|r> Ouro Loot cannot finish loading. You will need to type |cff30adff%s|r once these problems are resolved, and try again.]], msg, _G.SLASH_RELOAD1) SLASH_ACECONSOLE_OUROLOOT1 = nil SLASH_ACECONSOLE_OUROLOOT2 = nil - _G.error (msg, --[[level=]]2) + self.error (msg, --[[level=]]2) end -- Seriously? ORLY? @@ -293,13 +309,17 @@ ------ Globals local g_loot = nil local g_restore_p = nil -local g_wafer_thin = nil -- for prompting for additional rebroadcasters +local g_wafer_thin = nil -- prompting for additional rebroadcasters local g_today = nil -- "today" entry in g_loot local g_boss_signpost = nil local g_seeing_oldsigs = nil local g_uniques = nil -- memoization of unique loot events +local g_unique_replace = nil local opts = nil +local error = addon.error +local assert = addon.assert + -- for speeding up local loads, not because I think _G will change local _G = _G local type = _G.type @@ -316,7 +336,7 @@ 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 +local _history_by_loot_id, _notify_about_remote, _setup_unique_replace -- 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 @@ -342,48 +362,63 @@ -- Hypertext support, inspired by DBM broadcast pizza timers do - local hypertext_format_str = "|HOuroRaid:%s|h%s[%s]|r|h" - local strsplit = _G.strsplit + local hypertext_format_str = "|HOuroLoot:%d|h%s[%s]|r|h" + local func_map = {} --_G.setmetatable({}, {__mode = 'k'}) + local text_map = {} --_G.setmetatable({}, {__mode = 'kv'}) + local base = _G.newproxy(true) + _G.getmetatable(base).__tostring = function(ud) return text_map[ud] end + --@debug@ + -- collecting these tokens is an interesting micro-optimization but not yet + _G.getmetatable(base).__gc = function(ud) + print("Collecting hyperlink object <",tostring(ud),">") + end + --@end-debug@ -- TEXT will automatically be surrounded by brackets - -- COLOR can be item quality code or a hex string - function addon.format_hypertext (code, text, color) - return hypertext_format_str:format (code, - type(color)=='number' and ITEM_QUALITY_COLORS[color].hex or color, - text) + -- COLOR can be ITEM_QUALITY_* or a formatting string ("|cff...") + -- FUNC can be "MethodName", "tab_title", or a function + -- + -- Returns an obaque token. Calling tostring() on the token will yield a + -- formatted clickable string that can be displayed in chat. This is + -- largely an excuse to fool around with Lua data constructs. + function addon.format_hypertext (text, color, func) + local ret = _G.newproxy(base) + local num = #text_map + 1 + text_map[ret] = hypertext_format_str:format (num, + type(color)=='number' and ITEM_QUALITY_COLORS[color].hex or color, + text) + text_map[num] = ret + func_map[ret] = func + return ret end - DEFAULT_CHAT_FRAME:HookScript("OnHyperlinkClick", function(self, link, string, mousebutton) + --[[ + link: OuroLoot:n + fullstring: |HOuroLoot:n|h|cff.....[foo]|r|h + mousebutton: "LeftButton", "MiddleButton", "RightButton" + + amusingly, print()'ing the fullstring below as a debugging aid yields + another clickable link, yay data reproducability + ]] + local strsplit = _G.strsplit + DEFAULT_CHAT_FRAME:HookScript("OnHyperlinkClick", function(self, link, fullstring, mousebutton) local ltype, arg = strsplit(":",link) - if ltype ~= "OuroRaid" then return end - -- XXX this is crap, redo this as a dispatch table with code at the call site - if arg == 'openloot' then - addon:BuildMainDisplay() - elseif arg == 'popupurl' then - -- Sadly, this is not generated by the packager, so hardcode it for now. - -- The 'data' field is handled differently for onshow than for other callbacks. - StaticPopup_Show("OUROL_URL", --[[text_arg1=]]nil, --[[text_arg2=]]nil, - --[[data=]][[http://www.curse.com/addons/wow/ouroloot]]) - elseif arg == 'doping' then - addon:DoPing() - elseif arg == 'help' then - addon:BuildMainDisplay('help') - elseif arg == 'bcaston' then - if not addon.rebroadcast then - addon:Activate(nil,true) + if ltype ~= "OuroLoot" then return end + local f = func_map[text_map[tonumber(arg)]] + if type(f) == 'function' then + f() + elseif type(f) == 'string' then + if type(addon[f]) == 'function' then + addon[f](addon) -- method name + else + addon:BuildMainDisplay(f) -- tab title fragment end - addon:broadcast('bcast_responder') - elseif arg == 'waferthin' then -- mint? it's wafer thin! - g_wafer_thin = true -- fuck off, I'm full - addon:broadcast('bcast_denied') -- remove once tested - elseif arg == 'reload' then - addon:BuildMainDisplay('opt') end end) local old = ItemRefTooltip.SetHyperlink function ItemRefTooltip:SetHyperlink (link, ...) - if link:match("^OuroRaid") then return end + if link:match("^OuroLoot") then return end return old (self, link, ...) end end @@ -416,9 +451,17 @@ if (GetLFGMode()) and (GetLFGModeType() == 'raid') then t,r = 'LFR', 25 elseif diffcode == 1 then - t,r = (GetNumRaidMembers()>0) and "10",10 or "5",5 + if GetNumRaidMembers() > 0 then + t,r = "10",10 + else + t,r = "5",5 + end elseif diffcode == 2 then - t,r = (GetNumRaidMembers()>0) and "25",25 or "5h",5 + if GetNumRaidMembers() > 0 then + t,r = "25",25 + else + t,r = "5h",5 + end elseif diffcode == 3 then t,r = "10h", 10 elseif diffcode == 4 then @@ -572,13 +615,14 @@ local function _test (cache, x) -- FIXME This can return false positives, if called after the onloop -- fifo has been removed but before the GC has removed the weak entry. - -- What to do, what to do... + -- What to do, what to do... try forcing a GC during alldone. return cache.hash[x] ~= nil end function create_new_cache (name, ttl, on_alldone) -- setting OnFinished for cleanup fires at the end of each inner loop, -- with no 'requested' argument to distinguish cases. thus, on_alldone. + -- FWIW, on_alldone is passed this table as its sole argument: local c = { ttl = ttl, name = name, @@ -612,8 +656,9 @@ if _G.OuroLootSV_opts == nil then _G.OuroLootSV_opts = {} + local vclick = self.format_hypertext ([[click here]], ITEM_QUALITY_UNCOMMON, 'help') self:ScheduleTimer(function(s) - s:Print(virgin, s.format_hypertext('help',"click here",ITEM_QUALITY_UNCOMMON)) + s:Print(virgin, tostring(vclick)) virgin = nil end,10,self) else @@ -695,7 +740,7 @@ end end -- format 3 to format 4 was a major revamp of per-player data - self:_uplift_history_format(player,rname) + self:_uplift_history_format(player) end end end @@ -803,29 +848,34 @@ -- There is no ITEM_QUALITY_LEGENDARY constant. Sigh. do local AC = LibStub("AceConsole-3.0") - local chat_prefix = self.format_hypertext('openloot',"Ouro Loot",--[[legendary]]5) + local chat_prefix = self.format_hypertext ("Ouro Loot", --[[legendary]]5, + --[[empty -> nil -> main tab]]'') + local chat_prefix_s = tostring(chat_prefix) function addon:Print (str, ...) if type(str) == 'string' and str:find("%", nil, --[[plainmatch=]]true) then - return AC:Print (chat_prefix, str:format(...)) + return AC:Print (chat_prefix_s, str:format(...)) else - return AC:Print (chat_prefix, str, ...) + return AC:Print (chat_prefix_s, str, ...) end end function addon:CFPrint (frame, str, ...) assert(type(frame)=='table' and frame.AddMessage) if type(str) == 'string' and str:find("%", nil, --[[plainmatch=]]true) then - return AC:Print (frame, chat_prefix, str:format(...)) + return AC:Print (frame, chat_prefix_s, str:format(...)) else - return AC:Print (frame, chat_prefix, str, ...) + return AC:Print (frame, chat_prefix_s, str, ...) end end end while opts.keybinding do if InCombatLockdown() then + local reload = self.format_hypertext ([[the options tab]], + ITEM_QUALITY_UNCOMMON, 'opt') self:Print("Cannot create '%s' as a keybinding while in combat!", opts.keybinding_text) - self:Print("The rest of the addon will continue to work, but you will need to reload out of combat to get the keybinding. Either type /reload or use the button on %s in the lower right.", self.format_hypertext('reload',"the options tab",ITEM_QUALITY_UNCOMMON)) + self:Print("The rest of the addon will continue to work, but you will need to reload out of combat to get the keybinding. Either type /reload or use the button on %s in the lower right.", + tostring(reload)) break end @@ -858,6 +908,8 @@ trigger on 'receive item' instead, which would detect extracting stuff from mail, or s/PUSHED/CREATED/ for things like healthstones and guild cauldron flasks. + + ??? do something with LOOT_ITEM_WHILE_PLAYER_INELIGIBLE for locked LFRs? ]] -- LOOT_ITEM = "%s receives loot: %s." --> (.+) receives loot: (.+)%. @@ -1200,22 +1252,44 @@ }) end + -- Alert other trackers that unique tag EXISTING in subsequent 'casts + -- should be replaced by REPLACE instead. If multiple players all saw + -- the same loot event, this will cause a flurry of cross-improvs. + local function _announce_unique_improvisation (existing, replace) + if not g_unique_replace then _setup_unique_replace() end + g_unique_replace.new_entry (g_unique_replace.me, existing, replace, 'improv') + addon:vbroadcast('improv', g_unique_replace.me, existing, replace) + end + local random = _G.math.random - local function many_uniques_handle_it (u, check_p) - if u and check_p then + local function _many_uniques_handle_it (u, prefix) + if u then -- Check and alert for an existing value. u = tostring(u) if g_uniques[u].history ~= g_uniques.NOTFOUND then - return nil, u + if not g_unique_replace then _setup_unique_replace() end + local maybe = g_unique_replace.get_previous_replacement (u) + if maybe then + addon.dprint('loot',"previous replaced tag ("..u + ..") with ("..maybe.."), using that instead") + return false, u, maybe + end + local can_replace_p,improv = _many_uniques_handle_it (nil, 'c') + if can_replace_p then + _announce_unique_improvisation (u, improv) + return false, u, improv + end + return false, u end addon.dprint('loot',"verified unique tag ("..u..")") else -- Need to *find* an unused value. For now use a range of -- J*10^4 where J is Jenny's Constant. Thank you, xkcd.com/1047. + prefix = prefix or 'n' repeat - u = 'n' .. random(8675309) - until g_uniques:TEST(u).history ~= g_uniques.NOTFOUND - addon.dprint('loot',"created unique tag", u) + u = prefix .. random(8675309) + until g_uniques:TEST(u).history == g_uniques.NOTFOUND + addon.dprint('loot',"created unique tag ("..u..")") end return true, u end @@ -1223,13 +1297,14 @@ -- Recent loot cache local candidates = {} local sigmap = {} -_G.sigmap = sigmap local function preempt_older_signature (oldersig, newersig) - local origin = candidates[oldersig] and candidates[oldersig].from +--pprint("preempt", oldersig, "::", newersig) + local origin = candidates[oldersig] and candidates[oldersig].bcast_from +--pprint("preempt", "candidate", candidates[oldersig], "bcast:", origin) if origin and g_seeing_oldsigs[origin] then -- replace entry from older client with this newer one candidates[oldersig] = nil - addon.dprint('cache', "preempting signature <", oldersig, "> from", origin) + addon.dprint('loot', "preempting signature <", oldersig, "> from", origin) end return false end @@ -1253,6 +1328,9 @@ and (not addon.history_suppress) then addon:_addHistoryEntry(looti) + elseif #loot.unique > 0 then + g_uniques[loot.unique] = -- stub entry + { loot = looti, history = g_uniques.NOTFOUND } end end end @@ -1285,21 +1363,28 @@ itemid = tonumber(ilink:match("item:(%d+)") or 0) -- This is only a 'while' to make jumping out of it easy. - local i, unique_okay, ret1, ret2 + local i, unique_okay, replacement, ret1, ret2 while local_override or ((iquality >= self.threshold) and not opts.itemfilter[itemid]) do - unique_okay, unique = many_uniques_handle_it (unique, not local_override) + unique_okay, unique, replacement = + _many_uniques_handle_it ((not local_override) and unique) if not unique_okay then - i = g_uniques[unique] - local err = unique_collision:format (ERROR_CAPS, iname, unique, - tostring(from), tostring(i.loot), tostring(i.history)) - self:Print(err) - _G.PlaySound("igQuestFailed", "master") - -- Make sure this is logged one way or another - ;(self.debug.loot and self.dprint or pprint)('loot', "COLLISION", prefix, err); - ret1, ret2 = nil, err - break + if replacement then + -- collision, but we've generated a placeholder for now + -- and broadcast the fact + self.dprint('loot', "substituting", unique, "with", replacement) + else + i = g_uniques[unique] + local err = unique_collision:format (ERROR_CAPS, iname, unique, + tostring(from), tostring(i.loot), tostring(i.history)) + self:Print(err) + _G.PlaySoundFile ([[Interface\AddOns\Ouro_Loot\sfrr.ogg]], "master") + -- Make sure this is logged one way or another + ;(self.debug.loot and self.dprint or pprint)('loot', "COLLISION", prefix, err); + ret1, ret2 = nil, err + break + end end if (self.rebroadcast and (not from)) and not local_override then @@ -1312,16 +1397,20 @@ if #unique > 0 then -- newer case signature = unique .. oldersig +--pprint("newer", "mapping older <", oldersig, "> to newer <", signature, ">") sigmap[oldersig] = signature - seenit = from and (recent_loot:test(signature) +--pprint("newer", "testing recent for", signature, "yields", recent_loot:test(signature)) + seenit = (from and recent_loot:test(signature)) -- The following clause is what handles older 'casts arriving -- earlier. All this is tested inside-out to maximize short - -- circuit avaluation. - or (g_seeing_oldsigs and preempt_older_signature(oldersig,signature))) + -- circuit evaluation; the preempt function always returns + -- false to force seenit off. + or (g_seeing_oldsigs and preempt_older_signature(oldersig,signature)) else -- older case, only remote assert(from) signature = sigmap[oldersig] or oldersig +--pprint("older", "testing signature will be", signature) seenit = recent_loot:test(signature) end @@ -1342,7 +1431,7 @@ id = itemid, itemlink = ilink, itexture = itexture, - unique = unique, + unique = replacement or unique, count = (count and count ~= "") and count or nil, bcast_from = from, extratext = extratext, @@ -1368,8 +1457,12 @@ and (not self.history_suppress) then self:_addHistoryEntry(looti) + else + g_uniques[i.unique] = -- stub entry + { loot = looti, history = g_uniques.NOTFOUND } end ret1 = looti -- return value mostly for gui's manual entry + self.dprint('loot', "manual", looti) else recent_loot:add(signature) candidates[signature] = i @@ -1448,88 +1541,92 @@ -- that would result in a pile of teensy functions, most of which would -- never be called. Too much overhead. (2.0: Most of these removed now -- that GUI is in place.) -function addon:OnSlash (txt) --, editbox) - txt = strtrim(txt:lower()) - local cmd, arg = "" - do - local s,e = txt:find("^%a+") - if s then - cmd = txt:sub(s,e) - s = txt:find("%S", e+2) - if s then arg = txt:sub(s,-1) end - end - end - - if cmd == "" then - if InCombatLockdown() then - return self:Print("Shouldn't display window in combat.") - else - return self:BuildMainDisplay() +do + local green_help_link = addon.format_hypertext ([[Click here]], + ITEM_QUALITY_UNCOMMON, 'help') + function addon:OnSlash (txt) --, editbox) + txt = strtrim(txt:lower()) + local cmd, arg = "" + do + local s,e = txt:find("^%a+") + if s then + cmd = txt:sub(s,e) + s = txt:find("%S", e+2) + if s then arg = txt:sub(s,-1) end + end end - elseif cmd:find("^thre") then - self:SetThreshold(arg) + if cmd == "" then + if InCombatLockdown() then + return self:Print("Shouldn't display window in combat.") + else + return self:BuildMainDisplay() + end - elseif cmd == "on" then self:Activate(arg) - elseif cmd == "off" then self:Deactivate() - elseif cmd == "broadcast" or cmd == "bcast" then self:Activate(nil,true) + elseif cmd:find("^thre") then + self:SetThreshold(arg) - elseif cmd == "toggle" then - if self.display then - self.display:Hide() + elseif cmd == "on" then self:Activate(arg) + elseif cmd == "off" then self:Deactivate() + elseif cmd == "broadcast" or cmd == "bcast" then self:Activate(nil,true) + + elseif cmd == "toggle" then + if self.display then + self.display:Hide() + else + return self:BuildMainDisplay() + end + + elseif cmd == "fake" then -- maybe comment this out for real users + self:_mark_boss_kill (self._addBossEntry{ + kind='boss',reason='kill',bossname="Baron Steamroller",duration=0 + }) + self:CHAT_MSG_LOOT ('manual', my_name, 54797) + if self.display then + self:redisplay() + end + self:Print "Baron Steamroller has been slain. Congratulations on your rug." + + elseif cmd == "debug" then + if arg then + self.debug[arg] = not self.debug[arg] + _G.print(arg,self.debug[arg]) + if self.debug[arg] then self.DEBUG_PRINT = true end + else + self.DEBUG_PRINT = not self.DEBUG_PRINT + end + + elseif cmd == "save" and arg and arg:len() > 0 then + self:save_saveas(arg) + elseif cmd == "list" then + self:save_list() + elseif cmd == "restore" and arg and arg:len() > 0 then + self:save_restore(tonumber(arg)) + elseif cmd == "delete" and arg and arg:len() > 0 then + self:save_delete(tonumber(arg)) + + elseif cmd == "help" then + self:BuildMainDisplay('help') + elseif cmd == "ping" then + self:DoPing() + + elseif cmd == "fixcache" then + self:do_item_cache_fixup() + else - return self:BuildMainDisplay() + if self:OpenMainDisplayToTab(cmd) then + return + end + self:Print("Unknown command '%s'. %s to see the help window.", + cmd, tostring(green_help_link)) end - - elseif cmd == "fake" then -- maybe comment this out for real users - self:_mark_boss_kill (self._addBossEntry{ - kind='boss',reason='kill',bossname="Baron Steamroller",duration=0 - }) - self:CHAT_MSG_LOOT ('manual', my_name, 54797) - if self.display then - self:redisplay() - end - self:Print "Baron Steamroller has been slain. Congratulations on your rug." - - elseif cmd == "debug" then - if arg then - self.debug[arg] = not self.debug[arg] - _G.print(arg,self.debug[arg]) - if self.debug[arg] then self.DEBUG_PRINT = true end - else - self.DEBUG_PRINT = not self.DEBUG_PRINT - end - - elseif cmd == "save" and arg and arg:len() > 0 then - self:save_saveas(arg) - elseif cmd == "list" then - self:save_list() - elseif cmd == "restore" and arg and arg:len() > 0 then - self:save_restore(tonumber(arg)) - elseif cmd == "delete" and arg and arg:len() > 0 then - self:save_delete(tonumber(arg)) - - elseif cmd == "help" then - self:BuildMainDisplay('help') - elseif cmd == "ping" then - self:DoPing() - - elseif cmd == "fixcache" then - self:do_item_cache_fixup() - - else - if self:OpenMainDisplayToTab(cmd) then - return - end - self:Print("Unknown command '%s'. %s to see the help window.", - cmd, self.format_hypertext('help',"Click here",ITEM_QUALITY_UNCOMMON)) end end function addon:SetThreshold (arg, quiet_p) local q = tonumber(arg) if q then - q = math.floor(q+0.001) + q = math.floor(q+0.1) if q<0 or q>6 then return self:Print("Threshold must be 0-6.") end @@ -1555,7 +1652,7 @@ self.dprint('flow', ">:Activate calling RRU") self:RAID_ROSTER_UPDATE("Activate") elseif self.debug.notraid then - self.dprint('flow', ">:Activate registering loot and bossmods") + self.dprint('flow', ">:(notraid) Activate registering loot and bossmods") self:RegisterEvent("CHAT_MSG_LOOT") _register_bossmod(self) elseif g_restore_p then @@ -1626,7 +1723,7 @@ ------ Behind the scenes routines -- Semi-experimental debugging aid. do - -- Putting _log local to here can result in this sequence: + -- Declaring _log as local to here can result in this sequence: -- 1) logging happens, followed by reload or logout/login -- 2) _log points to SV_log -- 3) VARIABLES_LOADED replaces SV_log pointer with restored version @@ -1707,6 +1804,8 @@ to_color = addon.disposition_colors[e.disposition or "normal"].hex end + addon.dprint ('loot', "notifying:", sender, index, + e.itemlink, from_color, from_text, to_color, to_text) addon:CFPrint (remote_change_chatframe, remote_chatty, sender, index, e.itemlink, from_color, from_text, to_color, to_text) end @@ -1755,11 +1854,17 @@ self:broadcast('revcheck',revision_large) else - self.dprint('comm', "ours is older, yammering") + self.dprint('comm', "ours is older, (possibly) yammering") if newer_warning then - self:Print(newer_warning, - self.format_hypertext('popupurl',"click here",ITEM_QUALITY_UNCOMMON), - self.format_hypertext('doping',"click here",ITEM_QUALITY_UNCOMMON)) + local pop = addon.format_hypertext ([[click here]], ITEM_QUALITY_UNCOMMON, + function() + -- Sadly, this is not generated by the packager, so hardcode it for now. + -- The 'data' field is handled differently for onshow than for other callbacks. + StaticPopup_Show("OUROL_URL", --[[text_arg1=]]nil, --[[text_arg2=]]nil, + --[[data=]][[http://www.curse.com/addons/wow/ouroloot]]) + end) + local ping = addon.format_hypertext ([[click here]], ITEM_QUALITY_UNCOMMON, 'DoPing') + self:Print(newer_warning, tostring(pop), tostring(ping)) newer_warning = nil end end @@ -2018,7 +2123,9 @@ function addon:register_boss_mod (name, registration_func, deregistration_func) assert(type(name)=='string') assert(type(registration_func)=='function') - if deregistration_func ~= nil then assert(type(deregistration_func)=='function') end + if deregistration_func ~= nil then + assert(type(deregistration_func)=='function') + end self.bossmods[#self.bossmods+1] = { n = name, r = registration_func, @@ -2235,7 +2342,9 @@ local gu = g_uniques[e.unique] local player_i, player_h, hist_i = _history_by_loot_id (e.unique, "fixcache") if gu.loot ~= i then -- is this an actual problem? - pprint('loot', ("Unique value '%s' had iterator value %d but g_uniques index %s."):format(e.unique,i,tostring(gu.loot))) + pprint ('loot', + ("Unique value '%s' had iterator value %d but g_uniques index %s."): + format(e.unique,i,tostring(gu.loot))) end if player_i then player_h.id[e.unique] = e.id @@ -2250,6 +2359,122 @@ self:Print("...finished. Found %d |4entry:entries; with weird data.", numfound) end +do + local gur + + -- Strictly speaking, we'd want to handle individual 'exist' entries + -- as they expire, rather then waiting for all of them to expire and then + -- doing them as a group. But, if there're more than one of these per + -- boss, something else is funky anyhow and certainly not a hurry. + local function fixup_unique_replacements() +--print("replacements fixup happening!") +--tabledump(g_unique_replace.replacements) +--_G.GRR = g_unique_replace.replacements + for exist,info in pairs(gur.replacements) do + local winning_index = 1 + local winner = info[1] + info[1] = nil + pprint('improv', "fixup for", exist, "starting with", winner[1], + "with", winner[2], "out of", #info, "total entries") + -- Lowest player GUID wins. Seniority gotta count for something. + for i = 2, #info do + if winner[1] <= info[i][1] then + pprint('improv', "champ wins against", i, info[i][1]) + flib.del(info[i]) + else + pprint('improv', "challenger wins with", i, info[i][1]) + flib.del(winner) + winner = info[i] + winning_index = i + end + info[i] = nil + end + pprint('improv', "final:", winner[1], winner[2]) + --[[ + A: winner was generated locally + >g_loot and history already has the replacement value + >winning_index == 1 + B: winner was generated remotely + >need to scan and replace + ]] + if winning_index ~= 1 then +--XXX still needs to be debugged: + local cache = g_uniques[exist] + local looti = assert(cache.loot) -- can't possibly be missing... + if g_loot[looti].unique ~= exist then + pprint('improv', "WTF. entry", looti, + "does not match original unique tag! instead", + g_loot[looti].unique) + else + pprint('improv', "found and replaced loot entry", looti) + g_loot[looti].unique = winner[2] + end + local hi,ui = cache.history, cache.history_may + if hi ~= g_uniques.NOTFOUND then + local hist = addon.history[hi] + if ui and hist.unique[ui] == exist then + -- ui is valid + else + ui = nil + for i,ui2 in ipairs(hist.unique) do + if ui2 == exist then + ui = i + break + end + end + end + if ui then + pprint('improv', "found and replacing history entry", hi, + ui, hist.name) + hist.when[winner[2]] = hist.when[exist] + hist.id[winner[2]] = hist.id[exist] + hist.count[winner[2]] = hist.count[exist] + hist.unique[ui] = winner[2] + hist.when[exist] = nil + hist.id[exist] = nil + hist.count[exist] = nil + end + end + end + pprint('improv', "finished with", exist, "into", winner[2]) + flib.del(winner) + flib.del(info) + gur.replacements[exist] = nil + end + end + + local function new_entry (id, exist, repl, is_local) + pprint('improv', "new_entry", id, exist, repl, is_local) + gur.replacements[exist] = gur.replacements[exist] or flib.new() + tinsert (gur.replacements[exist], flib.new (tonumber(id), repl)) + if is_local then + gur.replacements[exist].LOCAL = repl + end + gur.cache:add (exist) + end + + local function get_previous_replacement (exist) + local l = gur.replacements[exist] + if l and l.LOCAL then + pprint('improv', "check for previous", exist, "returns valid", + l.LOCAL) + return l.LOCAL + end + pprint('improv', "check for previous", exist, "returns nil") + end + + function _setup_unique_replace () + gur = {} + gur.cache = create_new_cache ('improv', 10, fixup_unique_replacements) + gur.me = tonumber(_G.UnitGUID('player'):sub(-7),16) + gur.replacements = {} + gur.new_entry = new_entry + gur.get_previous_replacement = get_previous_replacement + g_unique_replace = gur + _setup_unique_replace = nil + end +end + ------ Saved texts function addon:check_saved_table(silent_p) @@ -2345,13 +2570,16 @@ do -- 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 = {}, {}, {} for u,tstamp in pairs(p.when) do uniques_bywhen[tstamp] = u when_array[#when_array+1] = tstamp end - _G.table.sort(when_array) + _G.table.sort (when_array, compare_timestamps) for i,tstamp in ipairs(when_array) do new_uniques[i] = uniques_bywhen[tstamp] end @@ -2360,7 +2588,7 @@ -- 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, realmname) + function addon:_uplift_history_format (player) local unique, when, id, count = {}, {}, {}, {} local name = player.name @@ -2377,6 +2605,7 @@ player.id, player.when, player.unique, player.count = id, when, unique, count end + function addon:_cache_history_uniques() UpdateAddOnMemoryUsage() local before = GetAddOnMemoryUsage(nametag) @@ -2398,11 +2627,12 @@ count = count + 1 --print("Active loot", i, "INSERTED with tag", e.unique, "as", e.disposition) else - hmmm = "wonked data ("..i.."/"..tostring(e.unique)..") in precache loop!" - pprint(hmmm) + hmmm = "active data not found in history ("..i.."/"..tostring(e.unique) + ..") in precache loop! trying to fixup for this session" + pprint(hmmm) -- more? -- try to simply fix up errors as we go g_uniques[e.unique] = { loot = i, history = g_uniques.NOTFOUND } - trouble = true + --trouble = true end else trouble = true @@ -2517,21 +2747,34 @@ end end - -- Clears all but latest entry for each player. - function addon:preen_history (realmname) + -- Clears all but the most recent HOWMANY (optional, default 1) entries + -- for each player on REALMNAME. + -- This function's name is the legacy of the orignal fsck(8) "-p" option, + -- which has a similar feel. + function addon:preen_history (realmname, howmany) local r = assert(realmname) + howmany = tonumber(howmany) or 1 + if type(self.history_all[r]) ~= 'table' then + return + end g_uniques:RESET() - for i,h in ipairs(self.history) do + for i,h in ipairs(self.history_all[r]) do -- This is going to do horrible things to memory. The subtables -- after this step would be large and sparse, with no good way -- of shrinking the allocation... sort_player(h) -- ...so it's better in the long run to discard them. - local U = h.unique[1] - h.unique = { U } - h.id = { [U] = h.id[U] } - h.when = { [U] = h.when[U] } - h.count = { [U] = h.count[U] } + local new_unique, new_id, new_when, new_count = {}, {}, {}, {} + for ui = 1, howmany do + local U = h.unique[ui] + if not U then break end + new_unique[ui] = U + new_id[U] = h.id[U] + new_when[U] = h.when[U] + new_count[U] = h.count[U] + end + h.unique, h.id, h.when, h.count = + new_unique, new_id, new_when, new_count end end @@ -2744,7 +2987,7 @@ end else - return -- silently ignore newer cases from newer clients + return -- silently ignore future cases from future clients end if self.debug.loot then @@ -2752,7 +2995,7 @@ format(index, unique, id, tostring(olddisp), tostring(newdisp)) self.dprint('loot', m) if sender == my_name then - self.dprint('loot',"(Returning early from double self-mark.)") + self.dprint('loot',"(Returning early from debug mode's double self-mark.)") return index end end @@ -2780,7 +3023,7 @@ do local select, tconcat, strsplit, unpack = select, table.concat, strsplit, unpack --[[ old way: repeated string concatenations, BAD - new way: new table on every call, BAD + new way: new table on every broadcast, BAD local msg = ... for i = 2, select('#',...) do msg = msg .. '\a' .. (select(i,...) or "") @@ -2844,6 +3087,13 @@ addon:_check_revision (revlarge) end + OCR_funcs['17improv'] = function (sender, _, senderid, existing, replace) + addon.dprint('comm', "DOTimprov/17, sender", sender, "id", senderid, + "existing", existing, "replace", replace) + if not g_unique_replace then _setup_unique_replace() end + g_unique_replace.new_entry (senderid, existing, replace) + end + OCR_funcs['17mark'] = function (sender, _, unique, item, old, new) addon.dprint('comm', "DOTmark/17, sender", sender, "unique", unique, "item", item, "from old", old, "to new", new) @@ -2890,13 +3140,25 @@ end OCR_funcs['17boss'] = OCR_funcs['16boss'] + local bcast_on = addon.format_hypertext ([[the red pill]], '|cffff4040', + function() + if not addon.rebroadcast then + addon:Activate(nil,true) + end + addon:broadcast('bcast_responder') + end) + local waferthin = addon.format_hypertext ([[the blue pill]], '|cff0070dd', + function() + g_wafer_thin = true -- mint? it's wafer thin! + addon:broadcast('bcast_denied') -- fuck off, I'm full + end) OCR_funcs.bcast_req = function (sender) if addon.debug.comm or ((not g_wafer_thin) and (not addon.rebroadcast)) then addon:Print("%s has requested additional broadcasters! Choose %s to enable rebroadcasting, or %s to remain off and also ignore rebroadcast requests for as long as you're logged in.", sender, - addon.format_hypertext('bcaston',"the red pill",'|cffff4040'), - addon.format_hypertext('waferthin',"the blue pill",'|cff0070dd')) + tostring(bcast_on), + tostring(waferthin)) end addon.popped = true end diff -r 676fb79a4ae2 -r 124da015c4a2 gui.lua --- a/gui.lua Fri Jun 01 02:17:57 2012 +0000 +++ b/gui.lua Fri Jun 08 08:05:37 2012 +0000 @@ -58,6 +58,9 @@ local window_title = "Ouro Loot" local dirty_tabs = nil +local error = addon.error +local assert = addon.assert + local pairs, ipairs, tinsert, tremove, tostring, tonumber = pairs, ipairs, table.insert, table.remove, tostring, tonumber @@ -164,13 +167,14 @@ g_loot[text_type] = g_loot[text_type] or "" if g_loot.printed[text_type] >= #g_loot then return false end - assert(addon.loot_clean == #g_loot, tostring(addon.loot_clean) .. " ~= " .. #g_loot) + assert (addon.loot_clean == #g_loot, + tostring(addon.loot_clean) .. " ~= " .. #g_loot) -- if glc is nil, #==0 test already returned local ok,ret = pcall (f, text_type, g_loot, g_loot.printed[text_type], g_generated, accumulator) if not ok then error(("ERROR: text generator '%s' failed: %s"):format(text_type, ret)) - return false + return false -- why is this here again? end if ret then g_loot.printed[text_type] = #g_loot @@ -1888,30 +1892,35 @@ container:SetScroll(1000) -- scrollframe's max value end - -- Initial lower panel function - local function adv_lower (container, specials) - local spacer = GUI:Create("Spacer") - spacer:SetFullWidth(true) - spacer:SetHeight(5) - container:AddChild(spacer) - local speedbump = GUI:Create("InteractiveLabel") - speedbump:SetFullWidth(true) - speedbump:SetFontObject(GameFontHighlightLarge) - speedbump:SetImage("Interface\\DialogFrame\\DialogAlertIcon") - speedbump:SetImageSize(50,50) - speedbump:SetText("The debugging/testing settings on the rest of this panel can" - .." seriously bork up the addon if you make a mistake. If you're okay" - .." with the possibility of losing data, click this warning to load the panel.") - speedbump:SetCallback("OnClick", function (_sb) - adv_lower = adv_real - return addon:redisplay() - --return tabs_OnGroupSelected_func(container.parent,"OnGroupSelected","opt") - end) - container:AddChild(speedbump) - spacer = GUI:Create("Spacer") - spacer:SetFullWidth(true) - spacer:SetHeight(5) - container:AddChild(spacer) + -- Initial lower panel function (unless debug mode is on during load, which + -- means it was almost certainly hardcoded that way, which means it's + -- probably me testing). + local adv_lower + if addon.DEBUG_PRINT then + adv_lower = adv_real + else + function adv_lower (container, specials) + local spacer = GUI:Create("Spacer") + spacer:SetFullWidth(true) + spacer:SetHeight(5) + container:AddChild(spacer) + local speedbump = GUI:Create("InteractiveLabel") + speedbump:SetFullWidth(true) + speedbump:SetFontObject(GameFontHighlightLarge) + speedbump:SetImage("Interface\\DialogFrame\\DialogAlertIcon") + speedbump:SetImageSize(50,50) + speedbump:SetText("The debugging/testing settings on the rest of this panel can seriously bork up the addon if you make a mistake. If you're okay with the possibility of losing data, click this warning to load the panel.") + speedbump:SetCallback("OnClick", function (_sb) + adv_lower = adv_real + return addon:redisplay() + --return tabs_OnGroupSelected_func(container.parent,"OnGroupSelected","opt") + end) + container:AddChild(speedbump) + spacer = GUI:Create("Spacer") + spacer:SetFullWidth(true) + spacer:SetHeight(5) + container:AddChild(spacer) + end end tabs_OnGroupSelected["opt"] = function(container,specials) @@ -2233,6 +2242,15 @@ h:SetFullWidth(true) h:SetText(_tabtexts[group].title) spec:AddChild(h) + do + addon.sender_list.sort() + local fmt = "Received broadcast data from %d |4player:players;." + if addon.history_suppress then + -- this is the druid class color reworked into hex + fmt = fmt .. " |cffff7d0aHistory recording suppressed.|r" + end + tabs.titletext:SetFormattedText (fmt, addon.sender_list.activeI) + end return tabs_OnGroupSelected[group](tabs,spec,group) --[====[ Unfortunately, :GetHeight() called on anything useful out of a TabGroup @@ -2478,11 +2496,6 @@ tabs:SetCallback("OnRelease", function(_tabs) tabs.titletext:SetFontObject(titletext_orig_fo) end) - do - self.sender_list.sort() - tabs.titletext:SetFormattedText("Received broadcast data from %d |4player:players;.", - self.sender_list.activeI) - end tabs:SetRelativeWidth(0.99-rhs_width) tabs:SetFullHeight(true) tabs:SetTabs(tabgroup_tabs) @@ -2492,7 +2505,8 @@ end) tabs:SetCallback("OnTabLeave", statusy_OnLeave) tabs:SetUserData("special buttons group",tab_specials) - tabs:SelectTab(opt_tabselect or "eoi") + tabs:SelectTab((opt_tabselect and #opt_tabselect>0) + and opt_tabselect or "eoi") display:AddChildren (tabs, control) display:ApplyStatus() diff -r 676fb79a4ae2 -r 124da015c4a2 sfrr.ogg Binary file sfrr.ogg has changed diff -r 676fb79a4ae2 -r 124da015c4a2 verbage.lua --- a/verbage.lua Fri Jun 01 02:17:57 2012 +0000 +++ b/verbage.lua Fri Jun 08 08:05:37 2012 +0000 @@ -396,9 +396,9 @@ +Clear Realm History> and +Clear ALL History> are used to periodically wipe the slate clean. They do not generate any new entries from existing loot. -+Clear Older> deletes history information for all items not shown in the "most recent -loot" display. It is another good periodic maintenance step, but does not discard -as much data as the other actions. ++Clear Older> deletes history information older than a certain threshold (by +default, 5 loot events). It is another good periodic maintenance step, but +does not discard as much data as the other actions. Using +Reassign to...> will also move the item between player histories. The timestamp will not be changed; it will "always have been" received by the new recipient.