Mercurial > wow > ouroloot
diff core.lua @ 25:cb9635999171
- Reassigning loot, and marking loot as disenchanted/vault, will update the
history entries also.
- Tooltip help on dropdown menus appears at mouse now, instead of depending
on Blizzard's "Beginner Tooltips" setting.
- Forum BBcode output includes an option for MMO-Champion/Wowstead formatting.
- Smarter cleanup functions for expiring caches.
- Properly prefer locally-generated loot events, even when other users can
see and rebroadcast the events back to you faster than you see them.
author | Farmbuyer of US-Kilrogg <farmbuyer@gmail.com> |
---|---|
date | Wed, 05 Oct 2011 02:14:07 +0000 |
parents | 61d932f0e8f2 |
children | 68d7b903ee17 |
line wrap: on
line diff
--- a/core.lua Wed Sep 21 06:21:48 2011 +0000 +++ b/core.lua Wed Oct 05 02:14:07 2011 +0000 @@ -59,7 +59,8 @@ ['bossmod'] = "DBM", ['keybinding_text'] = 'CTRL-SHIFT-O', ['forum'] = { - ['[url]'] = '[url=http://www.wowhead.com/?item=$I]$N[/url]$X - $T', + ['[url] Wowhead'] = '[url=http://www.wowhead.com/?item=$I]$N[/url]$X - $T', + ['[url] MMO/Wowstead'] = '[http://db.mmo-champion.com/i/$I]$X - $T', ['[item] by name'] = '[item]$N[/item]$X - $T', ['[item] by ID'] = '[item]$I[/item]$X - $T', ['Custom...'] = '', @@ -261,7 +262,7 @@ ]] do local caches = {} - local cleanup_group = AnimTimerFrame:CreateAnimationGroup() + local cleanup_group = _G.AnimTimerFrame:CreateAnimationGroup() local time = _G.time cleanup_group:SetLooping("REPEAT") cleanup_group:SetScript("OnLoop", function(cg) @@ -269,25 +270,30 @@ local now = time() local alldone = true -- this is ass-ugly - for _,c in ipairs(caches) do - while (#c > 0) and (now - c[1].t > c.ttl) do - addon.dprint('cache', c.name, "cache removing",c[1].t, c[1].m) - tremove(c,1) + for name,c in pairs(caches) do + local fifo = c.fifo + local active = #fifo > 0 + while (#fifo > 0) and (now - fifo[1].t > c.ttl) do + addon.dprint('cache', name, "cache removing",fifo[1].t, fifo[1].m) + tremove(fifo,1) end - alldone = alldone and (#c == 0) + if active and #fifo == 0 and c.func then + addon.dprint('cache', name, "empty, firing cleanup") + c:func() + end + alldone = alldone and (#fifo == 0) end if alldone then addon.dprint('cache',"OnLoop finishing animation group") cleanup_group:Finish() - for _,c in ipairs(caches) do - if c.func then c:func() end - end end addon.dprint('cache',"OnLoop done") end) local function _add (cache, x) - tinsert(cache, {t=time(),m=x}) + local datum = { t=time(), m=x } + cache.hash[x] = datum + tinsert (cache.fifo, datum) if not cleanup_group:IsPlaying() then addon.dprint('cache', cache.name, "STARTING animation group") cache.cleanup:SetDuration(2) -- hmmm @@ -295,11 +301,15 @@ end end local function _test (cache, x) - for _,v in ipairs(cache) do + return cache.hash[x] ~= nil + --[[for _,v in ipairs(cache) do if v.m == x then return true end - end + end]] 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. local c = { ttl = ttl, name = name, @@ -307,11 +317,11 @@ test = _test, cleanup = cleanup_group:CreateAnimation("Animation"), func = on_alldone, + fifo = {}, + hash = setmetatable({}, {__mode='kv'}), } c.cleanup:SetOrder(1) - -- setting OnFinished for cleanup fires at the end of each inner loop, - -- with no 'requested' argument to distinguish cases. thus, on_alldone. - tinsert (caches, c) + caches[name] = c return c end end @@ -337,13 +347,21 @@ opts[opt] = default end end + -- transition&remove old options + opts['forum_use_itemid'] = nil + if opts['forum_format'] then + opts.forum['Custom...'] = opts['forum_format'] + opts['forum_format'] = nil + end + if opts.forum['[url]'] then + opts.forum['[url] Wowhead'] = opts.forum['[url]'] + opts.forum['[url]'] = nil + opts.forum['[url] MMO/Wowstead'] = option_defaults.forum['[url] MMO/Wowstead'] + if opts['forum_current'] == '[url]' then + opts['forum_current'] = '[url] Wowhead' + end + end option_defaults = nil - -- transition&remove old options - opts["forum_use_itemid"] = nil - if opts["forum_format"] then - opts.forum["Custom..."] = opts["forum_format"] - opts["forum_format"] = nil - end if OuroLootSV then -- may not be the same as testing g_restore_p soon if OuroLootSV.saved then OuroLootSV_saved = OuroLootSV.saved; OuroLootSV.saved = nil @@ -617,7 +635,33 @@ -- helper for CHAT_MSG_LOOT handler do -- Recent loot cache - addon.recent_loot = create_new_cache ('loot', comm_cleanup_ttl) + local candidates = {} + local function prefer_local_loots (cache) + -- The function name is a bit of a misnomer, as local entries overwrite + -- remote entries as the candidate table is populated. This routine is + -- to extract the results once the cache timers have expired. + for i,sig in ipairs(candidates) do + addon.dprint('loot', "processing candidate entry", i, sig) + local loot = candidates[sig] + if loot then + addon.dprint('loot', i, "was found") + candidates[sig] = nil + local looti = addon._addLootEntry(loot) + if (loot.disposition ~= 'shard') + and (loot.disposition ~= 'gvault') + and (not addon.history_suppress) + then + addon:_addHistoryEntry(looti) + end + end + end + + if addon.display then + addon:redisplay() + end + table.wipe(candidates) + end + addon.recent_loot = create_new_cache ('loot', comm_cleanup_ttl, prefer_local_loots) local GetItemInfo, GetItemIcon = GetItemInfo, GetItemIcon @@ -634,47 +678,39 @@ self.dprint('loot',">>_do_loot, R:", recipient, "I:", itemid, "C:", count, "frm:", from, "ex:", extratext, "q:", iquality) itemid = tonumber(ilink:match("item:(%d+)") or 0) - if local_override or ((iquality >= self.threshold) and not opts.itemfilter[itemid]) then + -- This is only a loop to make jumping out of it easy, and still do cleanup below. + while local_override or ((iquality >= self.threshold) and not opts.itemfilter[itemid]) do if (self.rebroadcast and (not from)) and not local_override then self:broadcast('loot', recipient, itemid, count) end - if self.enabled or local_override then - local signature = recipient .. iname .. (count or "") - if self.recent_loot:test(signature) then - self.dprint('cache', "loot <",signature,"> already in cache, skipping") - else - self.recent_loot:add(signature) - i = self._addLootEntry{ -- There is some redundancy here... - kind = 'loot', - person = recipient, - person_class= select(2,UnitClass(recipient)), - cache_miss = i and true or nil, - quality = iquality, - itemname = iname, - id = itemid, - itemlink = ilink, - itexture = itexture, - disposition = (recipient == self.sharder) and 'shard' or nil, - count = count, - bcast_from = from, - extratext = extratext, - is_heroic = self:is_heroic_item(ilink), - } - self.dprint('loot', "added loot entry", i) - if not self.history_suppress then - self:_addHistoryEntry(i) - end - if self.display then - self:redisplay() - --[[ - local st = self.display:GetUserData("eoiST") - if st and st.frame:IsVisible() then - st:OuroLoot_Refresh() - end - ]] - end - end + if (not self.enabled) and (not local_override) then break end + local signature = recipient .. iname .. (count or "") + if from and self.recent_loot:test(signature) then + self.dprint('cache', "loot <",signature,"> already in cache, skipping") + else + self.recent_loot:add(signature) + -- 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)), + cache_miss = i and true or nil, + quality = iquality, + itemname = iname, + id = itemid, + itemlink = ilink, + itexture = itexture, + disposition = (recipient == self.sharder) and 'shard' or nil, + count = count, + bcast_from = from, + extratext = extratext, + is_heroic = self:is_heroic_item(ilink), + } + candidates[signature] = i + tinsert (candidates, signature) + self.dprint('cache', "loot <",signature,"> added to cache, candidate", #candidates) end + break end self.dprint('loot',"<<_do_loot out") return i @@ -1067,11 +1103,11 @@ end end --- Tie-ins with Deadly Boss Mods +-- Tie-in with Deadly Boss Mods (or other such addons) do - local candidates, location + local candidates = {} + local location local function fixup_durations (cache) - if candidates == nil then return end -- this is called for *all* cache expirations, including non-boss local boss, bossi boss = candidates[1] if #candidates == 1 then @@ -1108,7 +1144,7 @@ addon:Print("Registered kill for '%s' in %s!", boss.bosskill, boss.instance) end end - candidates = nil + table.wipe(candidates) end addon.recent_boss = create_new_cache ('boss', 10, fixup_durations) @@ -1145,7 +1181,6 @@ duration = duration, -- these two deliberately may be nil raiderlist = raiders and table.concat(raiders, ", ") } - candidates = candidates or {} tinsert(candidates,c) end break @@ -1531,6 +1566,7 @@ if e.kind ~= 'loot' then return end local i,h = self:get_loot_history(e.person) + -- If any of these change, update the end of history_handle_disposition. local n = { id = e.id, when = self:format_timestamp (g_today, e), @@ -1574,48 +1610,127 @@ end end - function addon:reassign_loot (index, name_to) - local e = assert(g_loot[index], "trying to reassign nonexistant entry") - assert(e.kind=='loot', "trying to reassign something that isn't loot") - assert(type(name_to)=='string' and name_to:len()>0) + -- Given an entry in a g_loot table, looks up the corresponding history + -- entry. Returns the player's index and history table (as in get_loot_history) + -- and the index into that table of the loot entry. On failure, returns nil + -- and an error message ready to be formatted with the loot's name/itemlink. + function addon:_history_by_loot_id (loot, operation_text) + -- Using assert() here would be concatenating error strings that probably + -- wouldn't be used. Do more verbose testing instead. + if type(loot) ~= 'table' then + error("trying to "..operation_text.." nonexistant entry") + end + if loot.kind ~= 'loot' then + error("trying to "..operation_text.." something that isn't loot") + end - local name_from = e.person - local tag = e.history_unique + local player = loot.person + local tag = loot.history_unique local errtxt + local player_i, player_h, hist_i if not tag then errtxt = "Entry for %s is missing a history tag!" else - local from_i,from_h = self:get_loot_history(name_from) - local to_i,to_h = self:get_loot_history(name_to) - - local hi - for i,h in ipairs(from_h) do + player_i,player_h = self:get_loot_history(player) + for i,h in ipairs(player_h) do local unique = h.id .. ' ' .. h.when if unique == tag then - hi = i + hist_i = i break end end - if not hi then + if not hist_i then -- 1) loot an item, 2) clear old history, 3) reassign from current loot -- Bah. Anybody that tricky is already recoding the tables directly anyhow. errtxt = "There is no record of %s ever having been assigned!" - else - hi = tremove (from_h, hi) - tinsert (to_h, 1, hi) - tsort (from_h, comp) - tsort (to_h, comp) end end if errtxt then - self:Print(errtxt .. " Loot will be reassigned but history will NOT be updated.", e.itemlink) + return nil, errtxt end - e.person = name_to - e.person_class = select(2,UnitClass(name_to)) + return player_i, player_h, hist_i + end - self:Print("Reassigned entry %d from '%s' to '%s'.", index, name_from, name_to) + function addon:reassign_loot (index, to_name) + assert(type(to_name)=='string' and to_name:len()>0) + local e = g_loot[index] + local from_i, from_h, hist_i = self:_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 is the formatted error text + self:Print(from_h .. " Loot will be reassigned, but history will NOT be updated.", e.itemlink) + else + local hist_h = tremove (from_h, hist_i) + tinsert (to_h, 1, hist_h) + tsort (from_h, comp) + tsort (to_h, comp) + end + e.person = to_name + e.person_class = select(2,UnitClass(to_name)) + self.hist_clean = nil + + self:Print("Reassigned entry %d/%s from '%s' to '%s'.", index, e.itemlink, from_name, to_name) + end + + -- 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 = {} --setmetatable({}, {__mode='k'}) + function addon:history_handle_disposition (index, olddisp) + local e = g_loot[index] + -- Standard disposition has a nil entry, but that's tedious in debug + -- output, so force to a string instead. + olddisp = olddisp or 'normal' + local newdisp = e.disposition or 'normal' + -- Ignore misclicks and the like + if olddisp == newdisp then return end + + local name = e.person + + if (newdisp == 'shard' or newdisp == 'gvault') then + local name_i, name_h, hist_i = self:_history_by_loot_id (e, "mark") + -- remove history entry + if hist_i then + local hist_h = tremove (name_h, hist_i) + deleted_cache[e.history_unique] = hist_h + self.hist_clean = nil + elseif (olddisp == 'shard' or olddisp == 'gvault') 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. + else + self:Print(name_h .. " Loot has been marked, but history will NOT be updated.", e.itemlink) + end + return + end + + if (olddisp == 'shard' or olddisp == 'gvault') + and (newdisp == 'normal' or newdisp == 'offspec') + then + 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. + local entry + if e.history_unique and deleted_cache[e.history_unique] then + entry = deleted_cache[e.history_unique] + deleted_cache[e.history_unique] = nil + end + local when = g_today and self:format_timestamp (g_today, e) or tostring(e.stamp) + entry = entry or { + id = e.id, + when = when, + count = e.count, + } + tinsert (name_h, 1, entry) + e.history_unique = e.history_unique or (entry.id .. ' ' .. entry.when) + self.hist_clean = nil + return + end end end