farmbuyer@17: local nametag, addon = ... farmbuyer@1: farmbuyer@1: --[==[ farmbuyer@1: g_loot's numeric indices are loot entries (including titles, separators, farmbuyer@1: etc); its named indices are: farmbuyer@1: - forum: saved text from forum markup window, default nil farmbuyer@1: - attend: saved text from raid attendence window, default nil farmbuyer@16: - printed.FOO: last loot index formatted into text window FOO, default 0 farmbuyer@1: farmbuyer@1: Functions arranged like this, with these lables (for jumping to). As a farmbuyer@1: rule, member functions with UpperCamelCase names are called directly by farmbuyer@1: user-facing code, ones with lowercase names are "one step removed", and farmbuyer@1: names with leading underscores are strictly internal helper functions. farmbuyer@1: ------ Saved variables farmbuyer@1: ------ Constants farmbuyer@1: ------ Addon member data farmbuyer@6: ------ Globals farmbuyer@1: ------ Expiring caches farmbuyer@1: ------ Ace3 framework stuff farmbuyer@1: ------ Event handlers farmbuyer@1: ------ Slash command handler farmbuyer@1: ------ On/off farmbuyer@1: ------ Behind the scenes routines farmbuyer@1: ------ Saved texts farmbuyer@1: ------ Loot histories farmbuyer@1: ------ Player communication farmbuyer@1: farmbuyer@1: This started off as part of a raid addon package written by somebody else. farmbuyer@1: After he retired, I began modifying the code. Eventually I set aside the farmbuyer@1: entire package and rewrote the loot tracker module from scratch. Many of the farmbuyer@1: variable/function naming conventions (sv_*, g_*, and family) stayed across the farmbuyer@16: rewrite. farmbuyer@16: farmbuyer@16: Some variables are needlessly initialized to nil just to look uniform. farmbuyer@1: farmbuyer@1: ]==] farmbuyer@1: farmbuyer@1: ------ Saved variables farmbuyer@16: OuroLootSV = nil -- possible copy of g_loot farmbuyer@16: OuroLootSV_saved = nil -- table of copies of saved texts, default nil; keys farmbuyer@16: -- are numeric indices of tables, subkeys of those farmbuyer@16: -- are name/forum/attend/date farmbuyer@16: OuroLootSV_opts = nil -- same as option_defaults until changed farmbuyer@16: -- autoshard: optional name of disenchanting player, default nil farmbuyer@16: -- threshold: optional loot threshold, default nil farmbuyer@16: OuroLootSV_hist = nil farmbuyer@19: OuroLootSV_log = {} farmbuyer@1: farmbuyer@1: farmbuyer@1: ------ Constants farmbuyer@1: local option_defaults = { farmbuyer@1: ['popup_on_join'] = true, farmbuyer@1: ['register_slashloot'] = true, farmbuyer@1: ['scroll_to_bottom'] = true, farmbuyer@1: ['chatty_on_kill'] = false, farmbuyer@1: ['no_tracking_wipes'] = false, farmbuyer@1: ['snarky_boss'] = true, farmbuyer@1: ['keybinding'] = false, farmbuyer@2: ['bossmod'] = "DBM", farmbuyer@1: ['keybinding_text'] = 'CTRL-SHIFT-O', farmbuyer@1: ['forum'] = { farmbuyer@25: ['[url] Wowhead'] = '[url=http://www.wowhead.com/?item=$I]$N[/url]$X - $T', farmbuyer@25: ['[url] MMO/Wowstead'] = '[http://db.mmo-champion.com/i/$I]$X - $T', farmbuyer@1: ['[item] by name'] = '[item]$N[/item]$X - $T', farmbuyer@1: ['[item] by ID'] = '[item]$I[/item]$X - $T', farmbuyer@1: ['Custom...'] = '', farmbuyer@1: }, farmbuyer@1: ['forum_current'] = '[item] by name', farmbuyer@1: } farmbuyer@1: local virgin = "First time loaded? Hi! Use the /ouroloot or /loot command" farmbuyer@1: .." to show the main display. You should probably browse the instructions" farmbuyer@1: .." if you've never used this before; %s to display the help window. This" farmbuyer@1: .." welcome message will not intrude again." farmbuyer@27: local newer_warning = "A newer version has been released. You can %s to display" farmbuyer@27: .." a download URL for copy-and-pasting. You can %s to ping other raiders" farmbuyer@27: .." for their installed versions (same as '/ouroloot ping' or clicking the" farmbuyer@27: .." 'Ping!' button on the options panel)." farmbuyer@1: local qualnames = { farmbuyer@1: ['gray'] = 0, ['grey'] = 0, ['poor'] = 0, ['trash'] = 0, farmbuyer@1: ['white'] = 1, ['common'] = 1, farmbuyer@1: ['green'] = 2, ['uncommon'] = 2, farmbuyer@1: ['blue'] = 3, ['rare'] = 3, farmbuyer@1: ['epic'] = 4, ['purple'] = 4, farmbuyer@1: ['legendary'] = 5, ['orange'] = 5, farmbuyer@1: ['artifact'] = 6, farmbuyer@1: --['heirloom'] = 7, farmbuyer@1: } farmbuyer@1: local my_name = UnitName('player') farmbuyer@1: local comm_cleanup_ttl = 5 -- seconds in the cache farmbuyer@27: local revision_large = nil -- defaults to 1, possibly changed by revision farmbuyer@20: local g_LOOT_ITEM_ss, g_LOOT_ITEM_MULTIPLE_sss, g_LOOT_ITEM_SELF_s, g_LOOT_ITEM_SELF_MULTIPLE_ss farmbuyer@1: farmbuyer@1: farmbuyer@1: ------ Addon member data farmbuyer@1: local flib = LibStub("LibFarmbuyer") farmbuyer@1: addon.author_debug = flib.author_debug farmbuyer@1: farmbuyer@6: -- Play cute games with namespaces here just to save typing. WTB Lua 5.2 PST. farmbuyer@1: do local _G = _G setfenv (1, addon) farmbuyer@1: farmbuyer@17: commrev = 15 -- number farmbuyer@27: revision = _G.GetAddOnMetadata(nametag,"Version") or "?" -- "x.yy.z", etc farmbuyer@1: ident = "OuroLoot2" farmbuyer@1: identTg = "OuroLoot2Tg" farmbuyer@1: status_text = nil farmbuyer@1: farmbuyer@1: DEBUG_PRINT = false farmbuyer@1: debug = { farmbuyer@1: comm = false, farmbuyer@1: loot = false, farmbuyer@1: flow = false, farmbuyer@1: notraid = false, farmbuyer@1: cache = false, farmbuyer@19: alsolog = false, farmbuyer@1: } farmbuyer@1: function dprint (t,...) farmbuyer@19: if DEBUG_PRINT and debug[t] then farmbuyer@19: local text = flib.safeprint("<"..t.."> ",...) farmbuyer@19: if debug.alsolog then farmbuyer@19: addon:log_with_timestamp(text) farmbuyer@19: end farmbuyer@19: end farmbuyer@1: end farmbuyer@1: farmbuyer@1: if author_debug then farmbuyer@1: function pprint(t,...) farmbuyer@19: local text = flib.safeprint("<<"..t..">> ",...) farmbuyer@19: if debug.alsolog then farmbuyer@19: addon:log_with_timestamp(text) farmbuyer@19: end farmbuyer@1: end farmbuyer@1: else farmbuyer@1: pprint = flib.nullfunc farmbuyer@1: end farmbuyer@1: farmbuyer@1: enabled = false farmbuyer@1: rebroadcast = false farmbuyer@1: display = nil -- display frame, when visible farmbuyer@1: loot_clean = nil -- index of last GUI entry with known-current visual data farmbuyer@1: sender_list = {active={},names={}} -- this should be reworked farmbuyer@1: threshold = debug.loot and 0 or 3 -- rare by default farmbuyer@1: sharder = nil -- name of person whose loot is marked as shards farmbuyer@1: farmbuyer@1: -- The rest is also used in the GUI: farmbuyer@1: farmbuyer@1: popped = nil -- non-nil when reminder has been shown, actual value unimportant farmbuyer@1: farmbuyer@2: bossmod_registered = nil farmbuyer@2: bossmods = {} farmbuyer@2: farmbuyer@1: requesting = nil -- for prompting for additional rebroadcasters farmbuyer@1: farmbuyer@38: -- don't use NUM_ITEM_QUALITIES as the upper bound unless we expect heirlooms to show up farmbuyer@11: thresholds = {} farmbuyer@1: for i = 0,6 do farmbuyer@11: thresholds[i] = _G.ITEM_QUALITY_COLORS[i].hex .. _G["ITEM_QUALITY"..i.."_DESC"] .. "|r" farmbuyer@1: end farmbuyer@1: farmbuyer@1: _G.setfenv (1, _G) farmbuyer@1: end farmbuyer@1: farmbuyer@1: addon = LibStub("AceAddon-3.0"):NewAddon(addon, "Ouro Loot", farmbuyer@1: "AceTimer-3.0", "AceComm-3.0", "AceConsole-3.0", "AceEvent-3.0") farmbuyer@1: farmbuyer@1: farmbuyer@6: ------ Globals farmbuyer@1: local g_loot = nil farmbuyer@1: local g_restore_p = nil farmbuyer@1: local g_wafer_thin = nil -- for prompting for additional rebroadcasters farmbuyer@1: local g_today = nil -- "today" entry in g_loot farmbuyer@16: local g_boss_signpost = nil farmbuyer@1: local opts = nil farmbuyer@1: farmbuyer@1: local pairs, ipairs, tinsert, tremove, tonumber = pairs, ipairs, table.insert, table.remove, tonumber farmbuyer@1: local pprint, tabledump = addon.pprint, flib.tabledump farmbuyer@10: local GetNumRaidMembers = GetNumRaidMembers farmbuyer@1: -- En masse forward decls of symbols defined inside local blocks farmbuyer@2: local _register_bossmod farmbuyer@1: local makedate, create_new_cache, _init farmbuyer@1: farmbuyer@27: -- Try to extract numbers from the .toc "Version" and munge them into an farmbuyer@27: -- integral form for comparison. The result doesn't need to be meaningful as farmbuyer@38: -- long as we can reliably feed two of them to "<" and get useful answers. farmbuyer@29: -- farmbuyer@29: -- This makes/reinforces an assumption that revision_large of release packages farmbuyer@29: -- (e.g., 2016001) will always be higher than those of development packages farmbuyer@29: -- (e.g., 87), due to the tagging system versus subversion file revs. This farmbuyer@29: -- is good, as local dev code will never trigger a false positive update farmbuyer@38: -- warning for other users. The downside is that additional decimal places farmbuyer@38: -- in the Version field for bugfixes (e.g., "2.16.4.1") imposes a high-water farmbuyer@38: -- mark, as subsequent shorter strings ("2.16.5", "2.17") will never be larger. farmbuyer@27: do farmbuyer@27: local r = 0 farmbuyer@27: for d in addon.revision:gmatch("%d+") do farmbuyer@27: r = 1000*r + d farmbuyer@27: end farmbuyer@27: revision_large = math.max(r,1) farmbuyer@27: end farmbuyer@27: farmbuyer@1: -- Hypertext support, inspired by DBM broadcast pizza timers farmbuyer@1: do farmbuyer@1: local hypertext_format_str = "|HOuroRaid:%s|h%s[%s]|r|h" farmbuyer@1: farmbuyer@38: -- TEXT will automatically be surrounded by brackets farmbuyer@38: -- COLOR can be item quality code or a hex string farmbuyer@1: function addon.format_hypertext (code, text, color) farmbuyer@1: return hypertext_format_str:format (code, farmbuyer@11: type(color)=='number' and ITEM_QUALITY_COLORS[color].hex or color, farmbuyer@1: text) farmbuyer@1: end farmbuyer@1: farmbuyer@1: DEFAULT_CHAT_FRAME:HookScript("OnHyperlinkClick", function(self, link, string, mousebutton) farmbuyer@1: local ltype, arg = strsplit(":",link) farmbuyer@1: if ltype ~= "OuroRaid" then return end farmbuyer@1: if arg == 'openloot' then farmbuyer@1: addon:BuildMainDisplay() farmbuyer@27: elseif arg == 'popupurl' then farmbuyer@30: -- Sadly, this is not generated by the packager, so hardcode it for now. farmbuyer@30: -- The 'data' field is handled differently for onshow than for other callbacks. farmbuyer@30: StaticPopup_Show("OUROL_URL", --[[text_arg1=]]nil, --[[text_arg2=]]nil, farmbuyer@30: --[[data=]][[http://www.curse.com/addons/wow/ouroloot]]) farmbuyer@27: elseif arg == 'doping' then farmbuyer@27: addon:DoPing() farmbuyer@1: elseif arg == 'help' then farmbuyer@1: addon:BuildMainDisplay('help') farmbuyer@1: elseif arg == 'bcaston' then farmbuyer@1: if not addon.rebroadcast then farmbuyer@1: addon:Activate(nil,true) farmbuyer@1: end farmbuyer@1: addon:broadcast('bcast_responder') farmbuyer@1: elseif arg == 'waferthin' then -- mint? it's wafer thin! farmbuyer@1: g_wafer_thin = true -- fuck off, I'm full farmbuyer@1: addon:broadcast('bcast_denied') -- remove once tested farmbuyer@1: end farmbuyer@1: end) farmbuyer@1: farmbuyer@1: local old = ItemRefTooltip.SetHyperlink farmbuyer@1: function ItemRefTooltip:SetHyperlink (link, ...) farmbuyer@1: if link:match("^OuroRaid") then return end farmbuyer@1: return old (self, link, ...) farmbuyer@1: end farmbuyer@1: end farmbuyer@1: farmbuyer@1: do farmbuyer@1: -- copied here because it's declared local to the calendar ui, thanks blizz >< farmbuyer@1: local CALENDAR_FULLDATE_MONTH_NAMES = { farmbuyer@1: FULLDATE_MONTH_JANUARY, FULLDATE_MONTH_FEBRUARY, FULLDATE_MONTH_MARCH, farmbuyer@1: FULLDATE_MONTH_APRIL, FULLDATE_MONTH_MAY, FULLDATE_MONTH_JUNE, farmbuyer@1: FULLDATE_MONTH_JULY, FULLDATE_MONTH_AUGUST, FULLDATE_MONTH_SEPTEMBER, farmbuyer@1: FULLDATE_MONTH_OCTOBER, FULLDATE_MONTH_NOVEMBER, FULLDATE_MONTH_DECEMBER, farmbuyer@1: } farmbuyer@1: -- returns "dd Month yyyy", mm, dd, yyyy farmbuyer@1: function makedate() farmbuyer@1: Calendar_LoadUI() farmbuyer@1: local _, M, D, Y = CalendarGetDate() farmbuyer@1: local text = ("%d %s %d"):format(D, CALENDAR_FULLDATE_MONTH_NAMES[M], Y) farmbuyer@1: return text, M, D, Y farmbuyer@1: end farmbuyer@1: end farmbuyer@1: farmbuyer@1: -- Returns an instance name or abbreviation farmbuyer@1: local function instance_tag() farmbuyer@1: local name, typeof, diffcode, diffstr, _, perbossheroic, isdynamic = GetInstanceInfo() farmbuyer@1: local t farmbuyer@1: name = addon.instance_abbrev[name] or name farmbuyer@1: if typeof == "none" then return name end farmbuyer@1: -- diffstr is "5 Player", "10 Player (Heroic)", etc. ugh. farmbuyer@35: if (GetLFGMode()) and (GetLFGModeType() == 'raid') then farmbuyer@35: t = 'LFR' farmbuyer@35: elseif diffcode == 1 then farmbuyer@1: t = ((GetNumRaidMembers()>0) and "10" or "5") farmbuyer@1: elseif diffcode == 2 then farmbuyer@1: t = ((GetNumRaidMembers()>0) and "25" or "5h") farmbuyer@1: elseif diffcode == 3 then farmbuyer@1: t = "10h" farmbuyer@1: elseif diffcode == 4 then farmbuyer@1: t = "25h" farmbuyer@1: end farmbuyer@1: -- dynamic difficulties always return normal "codes" farmbuyer@1: if isdynamic and perbossheroic == 1 then farmbuyer@1: t = t .. "h" farmbuyer@1: end farmbuyer@1: return name .. "(" .. t .. ")" farmbuyer@1: end farmbuyer@1: addon.instance_tag = instance_tag -- grumble farmbuyer@1: farmbuyer@1: farmbuyer@1: ------ Expiring caches farmbuyer@1: --[[ farmbuyer@1: foo = create_new_cache("myfoo",15[,cleanup]) -- ttl farmbuyer@1: foo:add("blah") farmbuyer@1: foo:test("blah") -- returns true farmbuyer@1: ]] farmbuyer@1: do farmbuyer@1: local caches = {} farmbuyer@25: local cleanup_group = _G.AnimTimerFrame:CreateAnimationGroup() farmbuyer@10: local time = _G.time farmbuyer@1: cleanup_group:SetLooping("REPEAT") farmbuyer@1: cleanup_group:SetScript("OnLoop", function(cg) farmbuyer@1: addon.dprint('cache',"OnLoop firing") farmbuyer@10: local now = time() farmbuyer@1: local alldone = true farmbuyer@1: -- this is ass-ugly farmbuyer@25: for name,c in pairs(caches) do farmbuyer@25: local fifo = c.fifo farmbuyer@25: local active = #fifo > 0 farmbuyer@25: while (#fifo > 0) and (now - fifo[1].t > c.ttl) do farmbuyer@25: addon.dprint('cache', name, "cache removing",fifo[1].t, fifo[1].m) farmbuyer@25: tremove(fifo,1) farmbuyer@1: end farmbuyer@25: if active and #fifo == 0 and c.func then farmbuyer@25: addon.dprint('cache', name, "empty, firing cleanup") farmbuyer@25: c:func() farmbuyer@25: end farmbuyer@25: alldone = alldone and (#fifo == 0) farmbuyer@1: end farmbuyer@1: if alldone then farmbuyer@1: addon.dprint('cache',"OnLoop finishing animation group") farmbuyer@1: cleanup_group:Finish() farmbuyer@1: end farmbuyer@1: addon.dprint('cache',"OnLoop done") farmbuyer@1: end) farmbuyer@1: farmbuyer@1: local function _add (cache, x) farmbuyer@25: local datum = { t=time(), m=x } farmbuyer@25: cache.hash[x] = datum farmbuyer@25: tinsert (cache.fifo, datum) farmbuyer@1: if not cleanup_group:IsPlaying() then farmbuyer@1: addon.dprint('cache', cache.name, "STARTING animation group") farmbuyer@1: cache.cleanup:SetDuration(2) -- hmmm farmbuyer@1: cleanup_group:Play() farmbuyer@1: end farmbuyer@1: end farmbuyer@1: local function _test (cache, x) farmbuyer@25: return cache.hash[x] ~= nil farmbuyer@25: --[[for _,v in ipairs(cache) do farmbuyer@1: if v.m == x then return true end farmbuyer@25: end]] farmbuyer@1: end farmbuyer@25: farmbuyer@1: function create_new_cache (name, ttl, on_alldone) farmbuyer@25: -- setting OnFinished for cleanup fires at the end of each inner loop, farmbuyer@25: -- with no 'requested' argument to distinguish cases. thus, on_alldone. farmbuyer@1: local c = { farmbuyer@1: ttl = ttl, farmbuyer@1: name = name, farmbuyer@1: add = _add, farmbuyer@1: test = _test, farmbuyer@1: cleanup = cleanup_group:CreateAnimation("Animation"), farmbuyer@1: func = on_alldone, farmbuyer@25: fifo = {}, farmbuyer@25: hash = setmetatable({}, {__mode='kv'}), farmbuyer@1: } farmbuyer@1: c.cleanup:SetOrder(1) farmbuyer@25: caches[name] = c farmbuyer@1: return c farmbuyer@1: end farmbuyer@1: end farmbuyer@1: farmbuyer@1: farmbuyer@1: ------ Ace3 framework stuff farmbuyer@1: function addon:OnInitialize() farmbuyer@1: -- VARIABLES_LOADED has fired by this point; test if we're doing something like farmbuyer@1: -- relogging during a raid and already have collected loot data farmbuyer@1: g_restore_p = OuroLootSV ~= nil farmbuyer@1: self.dprint('flow', "oninit sets restore as", g_restore_p) farmbuyer@1: farmbuyer@1: if OuroLootSV_opts == nil then farmbuyer@1: OuroLootSV_opts = {} farmbuyer@1: self:ScheduleTimer(function(s) farmbuyer@1: s:Print(virgin, s.format_hypertext('help',"click here",ITEM_QUALITY_UNCOMMON)) farmbuyer@1: virgin = nil farmbuyer@1: end,10,self) farmbuyer@1: end farmbuyer@1: opts = OuroLootSV_opts farmbuyer@1: for opt,default in pairs(option_defaults) do farmbuyer@1: if opts[opt] == nil then farmbuyer@1: opts[opt] = default farmbuyer@1: end farmbuyer@1: end farmbuyer@38: farmbuyer@25: -- transition&remove old options farmbuyer@25: opts['forum_use_itemid'] = nil farmbuyer@25: if opts['forum_format'] then farmbuyer@25: opts.forum['Custom...'] = opts['forum_format'] farmbuyer@25: opts['forum_format'] = nil farmbuyer@25: end farmbuyer@25: if opts.forum['[url]'] then farmbuyer@25: opts.forum['[url] Wowhead'] = opts.forum['[url]'] farmbuyer@25: opts.forum['[url]'] = nil farmbuyer@25: opts.forum['[url] MMO/Wowstead'] = option_defaults.forum['[url] MMO/Wowstead'] farmbuyer@25: if opts['forum_current'] == '[url]' then farmbuyer@25: opts['forum_current'] = '[url] Wowhead' farmbuyer@25: end farmbuyer@25: end farmbuyer@1: option_defaults = nil farmbuyer@16: if OuroLootSV then -- may not be the same as testing g_restore_p soon farmbuyer@16: if OuroLootSV.saved then farmbuyer@16: OuroLootSV_saved = OuroLootSV.saved; OuroLootSV.saved = nil farmbuyer@16: end farmbuyer@16: if OuroLootSV.threshold then farmbuyer@16: opts.threshold = OuroLootSV.threshold; OuroLootSV.threshold = nil farmbuyer@16: end farmbuyer@16: if OuroLootSV.autoshard then farmbuyer@16: opts.autoshard = OuroLootSV.autoshard; OuroLootSV.autoshard = nil farmbuyer@16: end farmbuyer@16: end farmbuyer@38: farmbuyer@1: -- get item filter table if needed farmbuyer@1: if opts.itemfilter == nil then farmbuyer@1: opts.itemfilter = addon.default_itemfilter farmbuyer@1: end farmbuyer@1: addon.default_itemfilter = nil farmbuyer@1: farmbuyer@1: self:RegisterChatCommand("ouroloot", "OnSlash") farmbuyer@1: -- maybe try to detect if this command is already in use... farmbuyer@1: if opts.register_slashloot then farmbuyer@1: SLASH_ACECONSOLE_OUROLOOT2 = "/loot" farmbuyer@1: end farmbuyer@1: farmbuyer@1: self.history_all = self.history_all or OuroLootSV_hist or {} farmbuyer@4: local r = assert(GetRealmName()) farmbuyer@1: self.history_all[r] = self:_prep_new_history_category (self.history_all[r], r) farmbuyer@1: self.history = self.history_all[r] farmbuyer@38: if (not InCombatLockdown()) and OuroLootSV_hist and farmbuyer@38: (OuroLootSV_hist.HISTFORMAT == nil) -- restored data but it's older farmbuyer@38: then farmbuyer@38: -- Big honkin' loop farmbuyer@38: for rname,realm in pairs(self.history_all) do farmbuyer@38: for pk,player in ipairs(realm) do farmbuyer@38: for lk,loot in ipairs(player) do farmbuyer@38: if loot.count == "" then farmbuyer@38: loot.count = nil farmbuyer@38: end farmbuyer@38: end farmbuyer@38: end farmbuyer@38: end farmbuyer@38: end farmbuyer@38: self.history_all.HISTFORMAT = nil -- don't keep this in live data farmbuyer@6: --OuroLootSV_hist = nil farmbuyer@1: farmbuyer@1: _init(self) farmbuyer@27: self.dprint('flow', "version strings:", revision_large, self.status_text) farmbuyer@1: self.OnInitialize = nil farmbuyer@1: end farmbuyer@1: farmbuyer@1: function addon:OnEnable() farmbuyer@10: self:RegisterEvent("PLAYER_LOGOUT") farmbuyer@10: self:RegisterEvent("RAID_ROSTER_UPDATE") farmbuyer@1: farmbuyer@1: -- Cribbed from Talented. I like the way jerry thinks: the first argument farmbuyer@1: -- can be a format spec for the remainder of the arguments. (The new farmbuyer@1: -- AceConsole:Printf isn't used because we can't specify a prefix without farmbuyer@1: -- jumping through ridonkulous hoops.) The part about overriding :Print farmbuyer@1: -- with a version using prefix hyperlinks is my fault. farmbuyer@37: -- farmbuyer@37: -- There is no ITEM_QUALITY_LEGENDARY constant. Sigh. farmbuyer@1: do farmbuyer@1: local AC = LibStub("AceConsole-3.0") farmbuyer@1: local chat_prefix = self.format_hypertext('openloot',"Ouro Loot",--[[legendary]]5) farmbuyer@1: function addon:Print (str, ...) farmbuyer@1: if type(str) == 'string' and str:find("%", nil, --[[plainmatch=]]true) then farmbuyer@1: return AC:Print (chat_prefix, str:format(...)) farmbuyer@1: else farmbuyer@1: return AC:Print (chat_prefix, str, ...) farmbuyer@1: end farmbuyer@1: end farmbuyer@1: end farmbuyer@1: farmbuyer@1: if opts.keybinding then farmbuyer@15: KeyBindingFrame_LoadUI() farmbuyer@1: local btn = CreateFrame("Button", "OuroLootBindingOpen", nil, "SecureActionButtonTemplate") farmbuyer@1: btn:SetAttribute("type", "macro") farmbuyer@1: btn:SetAttribute("macrotext", "/ouroloot toggle") farmbuyer@1: if SetBindingClick(opts.keybinding_text, "OuroLootBindingOpen") then farmbuyer@15: -- a simple SaveBindings(GetCurrentBindingSet()) occasionally fails when GCBS farmbuyer@38: -- decides to return neither 1 nor 2 during load, for reasons nobody has ever learned farmbuyer@15: local c = GetCurrentBindingSet() farmbuyer@15: if c == ACCOUNT_BINDINGS or c == CHARACTER_BINDINGS then farmbuyer@15: SaveBindings(c) farmbuyer@15: end farmbuyer@1: else farmbuyer@2: self:Print("Error registering '%s' as a keybinding, check spelling!", opts.keybinding_text) farmbuyer@1: end farmbuyer@1: end farmbuyer@1: farmbuyer@20: --[[ farmbuyer@20: The four loot format patterns of interest, changed into relatively tight farmbuyer@20: string match patterns. Done at enable-time rather than load-time against farmbuyer@20: the slim chance that one of the non-US "delocalizers" needs to mess with farmbuyer@20: the global patterns before we transform them. farmbuyer@20: farmbuyer@20: The SELF variants can be replaced with LOOT_ITEM_PUSHED_SELF[_MULTIPLE] to farmbuyer@20: trigger on 'receive item' instead, which would detect extracting stuff farmbuyer@20: from mail, or s/PUSHED/CREATED/ for things like healthstones and guild farmbuyer@20: cauldron flasks. farmbuyer@20: ]] farmbuyer@20: farmbuyer@20: -- LOOT_ITEM = "%s receives loot: %s." --> (.+) receives loot: (.+)%. farmbuyer@20: g_LOOT_ITEM_ss = _G.LOOT_ITEM:gsub('%.$','%%.'):gsub('%%s','(.+)') farmbuyer@20: farmbuyer@20: -- LOOT_ITEM_MULTIPLE = "%s receives loot: %sx%d." --> (.+) receives loot: (.+)(x%d+)%. farmbuyer@20: g_LOOT_ITEM_MULTIPLE_sss = _G.LOOT_ITEM_MULTIPLE:gsub('%.$','%%.'):gsub('%%s','(.+)'):gsub('x%%d','(x%%d+)') farmbuyer@20: farmbuyer@20: -- LOOT_ITEM_SELF = "You receive loot: %s." --> You receive loot: (.+)%. farmbuyer@20: g_LOOT_ITEM_SELF_s = _G.LOOT_ITEM_SELF:gsub('%.$','%%.'):gsub('%%s','(.+)') farmbuyer@20: farmbuyer@20: -- LOOT_ITEM_SELF_MULTIPLE = "You receive loot: %sx%d." --> You receive loot: (.+)(x%d+)%. farmbuyer@20: g_LOOT_ITEM_SELF_MULTIPLE_ss = _G.LOOT_ITEM_SELF_MULTIPLE:gsub('%.$','%%.'):gsub('%%s','(.+)'):gsub('x%%d','(x%%d+)') farmbuyer@20: farmbuyer@1: if self.debug.flow then self:Print"is in control-flow debug mode." end farmbuyer@1: end farmbuyer@1: --function addon:OnDisable() end farmbuyer@1: farmbuyer@1: farmbuyer@1: ------ Event handlers farmbuyer@1: function addon:_clear_SVs() farmbuyer@1: g_loot = {} -- not saved, just fooling PLAYER_LOGOUT tests farmbuyer@1: OuroLootSV = nil farmbuyer@16: OuroLootSV_saved = nil farmbuyer@1: OuroLootSV_opts = nil farmbuyer@1: OuroLootSV_hist = nil farmbuyer@22: OuroLootSV_log = nil farmbuyer@8: ReloadUI() farmbuyer@1: end farmbuyer@1: function addon:PLAYER_LOGOUT() farmbuyer@16: self:UnregisterEvent("RAID_ROSTER_UPDATE") farmbuyer@16: self:UnregisterEvent("PLAYER_ENTERING_WORLD") farmbuyer@16: farmbuyer@16: local worth_saving = #g_loot > 0 or next(g_loot.raiders) farmbuyer@16: if not worth_saving then for text in self:registered_textgen_iter() do farmbuyer@16: worth_saving = worth_saving or g_loot.printed[text] > 0 farmbuyer@16: end end farmbuyer@16: if worth_saving then farmbuyer@16: opts.autoshard = self.sharder farmbuyer@16: opts.threshold = self.threshold farmbuyer@1: for i,e in ipairs(g_loot) do farmbuyer@1: e.cols = nil farmbuyer@1: end farmbuyer@1: OuroLootSV = g_loot farmbuyer@16: else farmbuyer@16: OuroLootSV = nil farmbuyer@1: end farmbuyer@16: farmbuyer@38: worth_saving = false farmbuyer@6: for r,t in pairs(self.history_all) do if type(t) == 'table' then farmbuyer@8: if #t == 0 then farmbuyer@8: self.history_all[r] = nil farmbuyer@8: else farmbuyer@38: worth_saving = true farmbuyer@8: t.realm = nil farmbuyer@8: t.st = nil farmbuyer@8: t.byname = nil farmbuyer@8: end farmbuyer@6: end end farmbuyer@38: if worth_saving then farmbuyer@38: OuroLootSV_hist = self.history_all farmbuyer@38: OuroLootSV_hist.HISTFORMAT = 2 farmbuyer@38: else farmbuyer@38: OuroLootSV_hist = nil farmbuyer@38: end farmbuyer@19: OuroLootSV_log = #OuroLootSV_log > 0 and OuroLootSV_log or nil farmbuyer@1: end farmbuyer@1: farmbuyer@10: do farmbuyer@10: local IsInInstance, UnitName, UnitIsConnected, UnitClass, UnitRace, UnitSex, farmbuyer@10: UnitLevel, UnitInRaid, UnitIsVisible, GetGuildInfo = farmbuyer@10: IsInInstance, UnitName, UnitIsConnected, UnitClass, UnitRace, UnitSex, farmbuyer@10: UnitLevel, UnitInRaid, UnitIsVisible, GetGuildInfo farmbuyer@10: local time, difftime = time, difftime farmbuyer@10: local R_ACTIVE, R_OFFLINE, R_LEFT = 1, 2, 3 farmbuyer@10: farmbuyer@10: local lastevent, now = 0, 0 farmbuyer@16: local redo_count = 0 farmbuyer@16: local redo, timer_handle farmbuyer@10: farmbuyer@10: function addon:CheckRoster (leaving_p, now_a) farmbuyer@10: if not g_loot.raiders then return end -- bad transition farmbuyer@10: farmbuyer@10: now = now_a or time() farmbuyer@10: farmbuyer@10: if leaving_p then farmbuyer@16: if timer_handle then farmbuyer@16: self:CancelTimer(timer_handle) farmbuyer@16: timer_handle = nil farmbuyer@16: end farmbuyer@10: for name,r in pairs(g_loot.raiders) do farmbuyer@10: r.leave = r.leave or now farmbuyer@10: end farmbuyer@10: return farmbuyer@10: end farmbuyer@10: farmbuyer@10: for name,r in pairs(g_loot.raiders) do farmbuyer@10: if r.online ~= R_LEFT and not UnitInRaid(name) then farmbuyer@10: r.online = R_LEFT farmbuyer@10: r.leave = now farmbuyer@10: end farmbuyer@10: end farmbuyer@10: farmbuyer@16: if redo then farmbuyer@16: redo_count = redo_count + 1 farmbuyer@16: end farmbuyer@16: redo = false farmbuyer@10: for i = 1, GetNumRaidMembers() do farmbuyer@10: local unit = 'raid'..i farmbuyer@10: local name = UnitName(unit) farmbuyer@10: -- No, that's not my typo, it really is "uknownbeing" in Blizzard's code. farmbuyer@10: if name and name ~= UNKNOWN and name ~= UNKNOWNOBJECT and name ~= UKNOWNBEING then farmbuyer@10: if not g_loot.raiders[name] then farmbuyer@10: g_loot.raiders[name] = { needinfo=true } farmbuyer@10: end farmbuyer@10: local r = g_loot.raiders[name] farmbuyer@10: if r.needinfo and UnitIsVisible(unit) then farmbuyer@10: r.needinfo = nil farmbuyer@10: r.class = select(2,UnitClass(unit)) farmbuyer@10: r.race = select(2,UnitRace(unit)) farmbuyer@10: r.sex = UnitSex(unit) farmbuyer@10: r.level = UnitLevel(unit) farmbuyer@10: r.guild = GetGuildInfo(unit) farmbuyer@10: end farmbuyer@10: local connected = UnitIsConnected(unit) farmbuyer@10: if connected and r.online ~= R_ACTIVE then farmbuyer@10: r.join = r.join or now farmbuyer@10: r.online = R_ACTIVE farmbuyer@10: elseif (not connected) and r.online ~= R_OFFLINE then farmbuyer@10: r.leave = now farmbuyer@10: r.online = R_OFFLINE farmbuyer@10: end farmbuyer@10: redo = redo or r.needinfo farmbuyer@10: end farmbuyer@10: end farmbuyer@16: if redo then -- XXX test redo_count here and eventually give up? farmbuyer@16: if not timer_handle then farmbuyer@16: timer_handle = self:ScheduleRepeatingTimer("RAID_ROSTER_UPDATE", 60) farmbuyer@16: end farmbuyer@16: else farmbuyer@16: redo_count = 0 farmbuyer@16: if timer_handle then farmbuyer@16: self:CancelTimer(timer_handle) farmbuyer@16: timer_handle = nil farmbuyer@16: end farmbuyer@10: end farmbuyer@10: end farmbuyer@10: farmbuyer@10: function addon:RAID_ROSTER_UPDATE (event) farmbuyer@10: if GetNumRaidMembers() == 0 then farmbuyer@16: -- Leaving a raid group. farmbuyer@10: -- Because of PLAYER_ENTERING_WORLD, this code also executes on load farmbuyer@10: -- screens while soloing and in regular groups. Take care. farmbuyer@16: self.dprint('flow', "GetNumRaidMembers == 0") farmbuyer@16: if self.enabled and not self.debug.notraid then farmbuyer@16: self.dprint('flow', "enabled, leaving raid") farmbuyer@10: self.popped = nil farmbuyer@16: self:Deactivate() -- self:UnregisterEvent("CHAT_MSG_LOOT") farmbuyer@10: self:CheckRoster(--[[leaving raid]]true) farmbuyer@10: end farmbuyer@10: return farmbuyer@10: end farmbuyer@10: farmbuyer@1: local inside,whatkind = IsInInstance() farmbuyer@1: if inside and (whatkind == "pvp" or whatkind == "arena") then farmbuyer@1: return self.dprint('flow', "got RRU event but in pvp zone, bailing") farmbuyer@1: end farmbuyer@10: farmbuyer@10: local docheck = self.enabled farmbuyer@1: if event == "Activate" then farmbuyer@1: -- dispatched manually from Activate farmbuyer@10: self:RegisterEvent("CHAT_MSG_LOOT") farmbuyer@2: _register_bossmod(self) farmbuyer@10: docheck = true farmbuyer@1: elseif event == "RAID_ROSTER_UPDATE" then farmbuyer@10: -- hot code path, be careful farmbuyer@10: farmbuyer@1: -- event registration from onload, joined a raid, maybe show popup farmbuyer@16: self.dprint('flow', "RRU check:", self.popped, opts.popup_on_join) farmbuyer@10: if (not self.popped) and opts.popup_on_join then farmbuyer@1: self.popped = StaticPopup_Show "OUROL_REMIND" farmbuyer@1: self.popped.data = self farmbuyer@10: return farmbuyer@1: end farmbuyer@1: end farmbuyer@11: -- Throttle the checks fired by common events. farmbuyer@10: if docheck and not InCombatLockdown() then farmbuyer@10: now = time() farmbuyer@10: if difftime(now,lastevent) > 45 then farmbuyer@10: lastevent = now farmbuyer@10: self:CheckRoster(false,now) farmbuyer@10: end farmbuyer@10: end farmbuyer@1: end farmbuyer@1: end farmbuyer@1: farmbuyer@1: -- helper for CHAT_MSG_LOOT handler farmbuyer@1: do farmbuyer@1: -- Recent loot cache farmbuyer@25: local candidates = {} farmbuyer@25: local function prefer_local_loots (cache) farmbuyer@25: -- The function name is a bit of a misnomer, as local entries overwrite farmbuyer@25: -- remote entries as the candidate table is populated. This routine is farmbuyer@39: -- here to extract the final results once the cache timers have expired. farmbuyer@38: -- farmbuyer@34: -- Keep this sync'd with the local_override branch below. farmbuyer@25: for i,sig in ipairs(candidates) do farmbuyer@25: addon.dprint('loot', "processing candidate entry", i, sig) farmbuyer@25: local loot = candidates[sig] farmbuyer@25: if loot then farmbuyer@25: addon.dprint('loot', i, "was found") farmbuyer@25: candidates[sig] = nil farmbuyer@25: local looti = addon._addLootEntry(loot) farmbuyer@25: if (loot.disposition ~= 'shard') farmbuyer@25: and (loot.disposition ~= 'gvault') farmbuyer@25: and (not addon.history_suppress) farmbuyer@25: then farmbuyer@25: addon:_addHistoryEntry(looti) farmbuyer@25: end farmbuyer@25: end farmbuyer@25: end farmbuyer@25: farmbuyer@25: if addon.display then farmbuyer@25: addon:redisplay() farmbuyer@25: end farmbuyer@25: table.wipe(candidates) farmbuyer@25: end farmbuyer@25: addon.recent_loot = create_new_cache ('loot', comm_cleanup_ttl, prefer_local_loots) farmbuyer@1: farmbuyer@6: local GetItemInfo, GetItemIcon = GetItemInfo, GetItemIcon farmbuyer@1: farmbuyer@1: -- 'from' and onwards only present if this is triggered by a broadcast farmbuyer@1: function addon:_do_loot (local_override, recipient, itemid, count, from, extratext) farmbuyer@6: local itexture = GetItemIcon(itemid) farmbuyer@6: local iname, ilink, iquality = GetItemInfo(itemid) farmbuyer@19: local i farmbuyer@6: if (not iname) or (not itexture) then farmbuyer@19: i = true farmbuyer@6: iname, ilink, iquality, itexture = farmbuyer@6: UNKNOWN..': '..itemid, 'item:6948', ITEM_QUALITY_COMMON, [[ICONS\INV_Misc_QuestionMark]] farmbuyer@6: end farmbuyer@19: self.dprint('loot',">>_do_loot, R:", recipient, "I:", itemid, "C:", count, "frm:", from, "ex:", extratext, "q:", iquality) farmbuyer@1: farmbuyer@19: itemid = tonumber(ilink:match("item:(%d+)") or 0) farmbuyer@39: -- This is only a 'while' to make jumping out of it easy and still do cleanup below. farmbuyer@25: while local_override or ((iquality >= self.threshold) and not opts.itemfilter[itemid]) do farmbuyer@1: if (self.rebroadcast and (not from)) and not local_override then farmbuyer@1: self:broadcast('loot', recipient, itemid, count) farmbuyer@1: end farmbuyer@25: if (not self.enabled) and (not local_override) then break end farmbuyer@25: local signature = recipient .. iname .. (count or "") farmbuyer@25: if from and self.recent_loot:test(signature) then farmbuyer@39: self.dprint('cache', "remote loot <",signature,"> already in cache, skipping") farmbuyer@25: else farmbuyer@25: -- There is some redundancy in all this, in the interests of ease-of-coding farmbuyer@25: i = { farmbuyer@25: kind = 'loot', farmbuyer@25: person = recipient, farmbuyer@25: person_class= select(2,UnitClass(recipient)), farmbuyer@25: cache_miss = i and true or nil, farmbuyer@25: quality = iquality, farmbuyer@25: itemname = iname, farmbuyer@25: id = itemid, farmbuyer@25: itemlink = ilink, farmbuyer@25: itexture = itexture, farmbuyer@25: disposition = (recipient == self.sharder) and 'shard' or nil, farmbuyer@38: count = (count and count ~= "") and count or nil, farmbuyer@25: bcast_from = from, farmbuyer@25: extratext = extratext, farmbuyer@25: is_heroic = self:is_heroic_item(ilink), farmbuyer@25: } farmbuyer@34: if local_override then farmbuyer@39: -- player is adding loot by hand, don't wait for network cache timeouts farmbuyer@39: -- keep this sync'd with prefer_local_loots above farmbuyer@34: if i.extratext == 'shard' farmbuyer@34: or i.extratext == 'gvault' farmbuyer@34: or i.extratext == 'offspec' farmbuyer@34: then farmbuyer@34: i.disposition = i.extratext farmbuyer@34: end farmbuyer@34: local looti = self._addLootEntry(i) farmbuyer@34: if (i.disposition ~= 'shard') farmbuyer@34: and (i.disposition ~= 'gvault') farmbuyer@34: and (not self.history_suppress) farmbuyer@34: then farmbuyer@34: self:_addHistoryEntry(looti) farmbuyer@34: end farmbuyer@39: i = looti -- return value mostly for gui's manual entry farmbuyer@34: else farmbuyer@34: self.recent_loot:add(signature) farmbuyer@34: candidates[signature] = i farmbuyer@34: tinsert (candidates, signature) farmbuyer@39: self.dprint('cache', "loot <",signature,"> added to cache as candidate", #candidates) farmbuyer@34: end farmbuyer@1: end farmbuyer@25: break farmbuyer@1: end farmbuyer@1: self.dprint('loot',"<<_do_loot out") farmbuyer@1: return i farmbuyer@1: end farmbuyer@1: farmbuyer@1: function addon:CHAT_MSG_LOOT (event, ...) farmbuyer@1: if (not self.rebroadcast) and (not self.enabled) and (event ~= "manual") then return end farmbuyer@1: farmbuyer@1: --[[ farmbuyer@1: iname: Hearthstone farmbuyer@1: iquality: integer farmbuyer@1: ilink: clickable formatted link farmbuyer@1: itemstring: item:6948:.... farmbuyer@1: itexture: inventory icon texture farmbuyer@1: ]] farmbuyer@1: farmbuyer@1: if event == "CHAT_MSG_LOOT" then farmbuyer@1: local msg = ... farmbuyer@20: local person, itemstring, count farmbuyer@21: --ChatFrame2:AddMessage("original string: >"..(msg:gsub("\124","\124\124")).."<") farmbuyer@20: farmbuyer@20: -- test in most likely order: other people get more loot than "you" do farmbuyer@20: person, itemstring, count = msg:match(g_LOOT_ITEM_MULTIPLE_sss) farmbuyer@20: if not person then farmbuyer@20: person, itemstring = msg:match(g_LOOT_ITEM_ss) farmbuyer@20: end farmbuyer@20: if not person then farmbuyer@20: itemstring, count = msg:match(g_LOOT_ITEM_SELF_MULTIPLE_ss) farmbuyer@20: if not itemstring then farmbuyer@20: itemstring = msg:match(g_LOOT_ITEM_SELF_s) farmbuyer@20: end farmbuyer@20: end farmbuyer@20: farmbuyer@20: self.dprint('loot', "CHAT_MSG_LOOT, person is", person, ", itemstring is", itemstring, ", count is", count) farmbuyer@20: if not itemstring then return end -- "So-and-So selected Greed", etc, not actual looting farmbuyer@1: farmbuyer@1: -- Name might be colorized, remove the highlighting farmbuyer@20: if person then farmbuyer@20: person = person:match("|c%x%x%x%x%x%x%x%x(%S+)") or person farmbuyer@20: else farmbuyer@38: person = my_name -- UNIT_YOU / You farmbuyer@20: end farmbuyer@1: farmbuyer@20: --local id = tonumber((select(2, strsplit(":", itemstring)))) farmbuyer@20: local id = tonumber(itemstring:match('|Hitem:(%d+):')) farmbuyer@1: farmbuyer@1: return self:_do_loot (false, person, id, count) farmbuyer@1: farmbuyer@1: elseif event == "broadcast" then farmbuyer@1: return self:_do_loot(false, ...) farmbuyer@1: farmbuyer@1: elseif event == "manual" then farmbuyer@1: local r,i,n = ... farmbuyer@1: return self:_do_loot(true, r,i,nil,nil,n) farmbuyer@1: end farmbuyer@1: end farmbuyer@1: end farmbuyer@1: farmbuyer@1: farmbuyer@1: ------ Slash command handler farmbuyer@1: -- Thought about breaking this up into a table-driven dispatcher. But farmbuyer@1: -- that would result in a pile of teensy functions, most of which would farmbuyer@1: -- never be called. Too much overhead. (2.0: Most of these removed now farmbuyer@1: -- that GUI is in place.) farmbuyer@1: function addon:OnSlash (txt) --, editbox) farmbuyer@1: txt = strtrim(txt:lower()) farmbuyer@1: local cmd, arg = "" farmbuyer@1: do farmbuyer@1: local s,e = txt:find("^%a+") farmbuyer@1: if s then farmbuyer@1: cmd = txt:sub(s,e) farmbuyer@1: s = txt:find("%S", e+2) farmbuyer@1: if s then arg = txt:sub(s,-1) end farmbuyer@1: end farmbuyer@1: end farmbuyer@1: farmbuyer@1: if cmd == "" then farmbuyer@1: if InCombatLockdown() then farmbuyer@1: return self:Print("Can't display window in combat.") farmbuyer@1: else farmbuyer@1: return self:BuildMainDisplay() farmbuyer@1: end farmbuyer@1: farmbuyer@1: elseif cmd:find("^thre") then farmbuyer@1: self:SetThreshold(arg) farmbuyer@1: farmbuyer@1: elseif cmd == "on" then self:Activate(arg) farmbuyer@1: elseif cmd == "off" then self:Deactivate() farmbuyer@1: elseif cmd == "broadcast" or cmd == "bcast" then self:Activate(nil,true) farmbuyer@1: farmbuyer@23: elseif cmd == "toggle" then farmbuyer@23: if self.display then farmbuyer@23: self.display:Hide() farmbuyer@23: else farmbuyer@23: return self:BuildMainDisplay() farmbuyer@23: end farmbuyer@23: farmbuyer@1: elseif cmd == "fake" then -- maybe comment this out for real users farmbuyer@1: self:_mark_boss_kill (self._addLootEntry{ farmbuyer@1: kind='boss',reason='kill',bosskill="Baron Steamroller",instance=instance_tag(),duration=0 farmbuyer@1: }) farmbuyer@1: self:CHAT_MSG_LOOT ('manual', my_name, 54797) farmbuyer@1: if self.display then farmbuyer@1: self:redisplay() farmbuyer@1: end farmbuyer@1: self:Print "Baron Steamroller has been slain. Congratulations on your rug." farmbuyer@1: farmbuyer@1: elseif cmd == "debug" then farmbuyer@1: if arg then farmbuyer@1: self.debug[arg] = not self.debug[arg] farmbuyer@1: _G.print(arg,self.debug[arg]) farmbuyer@1: if self.debug[arg] then self.DEBUG_PRINT = true end farmbuyer@1: else farmbuyer@1: self.DEBUG_PRINT = not self.DEBUG_PRINT farmbuyer@1: end farmbuyer@1: farmbuyer@1: elseif cmd == "save" and arg and arg:len() > 0 then farmbuyer@1: self:save_saveas(arg) farmbuyer@1: elseif cmd == "list" then farmbuyer@1: self:save_list() farmbuyer@1: elseif cmd == "restore" and arg and arg:len() > 0 then farmbuyer@1: self:save_restore(tonumber(arg)) farmbuyer@1: elseif cmd == "delete" and arg and arg:len() > 0 then farmbuyer@1: self:save_delete(tonumber(arg)) farmbuyer@1: farmbuyer@1: elseif cmd == "help" then farmbuyer@1: self:BuildMainDisplay('help') farmbuyer@23: elseif cmd == "ping" then farmbuyer@23: self:DoPing() farmbuyer@1: farmbuyer@19: elseif cmd == "fixcache" then farmbuyer@19: self:do_item_cache_fixup() farmbuyer@19: farmbuyer@1: else farmbuyer@1: if self:OpenMainDisplayToTab(cmd) then farmbuyer@1: return farmbuyer@1: end farmbuyer@1: self:Print("Unknown command '%s'. %s to see the help window.", farmbuyer@1: cmd, self.format_hypertext('help',"Click here",ITEM_QUALITY_UNCOMMON)) farmbuyer@1: end farmbuyer@1: end farmbuyer@1: farmbuyer@1: function addon:SetThreshold (arg, quiet_p) farmbuyer@1: local q = tonumber(arg) farmbuyer@1: if q then farmbuyer@1: q = math.floor(q+0.001) farmbuyer@1: if q<0 or q>6 then farmbuyer@1: return self:Print("Threshold must be 0-6.") farmbuyer@1: end farmbuyer@1: else farmbuyer@1: q = qualnames[arg] farmbuyer@1: if not q then farmbuyer@1: return self:Print("Unrecognized item quality argument.") farmbuyer@1: end farmbuyer@1: end farmbuyer@1: self.threshold = q farmbuyer@1: if not quiet_p then self:Print("Threshold now set to %s.", self.thresholds[q]) end farmbuyer@1: end farmbuyer@1: farmbuyer@1: farmbuyer@1: ------ On/off farmbuyer@1: function addon:Activate (opt_threshold, opt_bcast_only) farmbuyer@16: self.dprint('flow', ":Activate is running") farmbuyer@10: self:RegisterEvent("RAID_ROSTER_UPDATE") farmbuyer@16: self:RegisterEvent("PLAYER_ENTERING_WORLD", farmbuyer@16: function() self:ScheduleTimer("RAID_ROSTER_UPDATE", 5, "PLAYER_ENTERING_WORLD") end) farmbuyer@1: self.popped = true farmbuyer@1: if GetNumRaidMembers() > 0 then farmbuyer@16: self.dprint('flow', ">:Activate calling RRU") farmbuyer@1: self:RAID_ROSTER_UPDATE("Activate") farmbuyer@1: elseif self.debug.notraid then farmbuyer@16: self.dprint('flow', ">:Activate registering loot and bossmods") farmbuyer@10: self:RegisterEvent("CHAT_MSG_LOOT") farmbuyer@2: _register_bossmod(self) farmbuyer@1: elseif g_restore_p then farmbuyer@1: g_restore_p = nil farmbuyer@16: self.popped = nil -- get the reminder if later joining a raid farmbuyer@16: if #g_loot == 0 then farmbuyer@16: -- only generated text and raider join/leave data, not worth verbage farmbuyer@16: self.dprint('flow', ">:Activate restored generated texts, un-popping") farmbuyer@16: return farmbuyer@16: end farmbuyer@1: self:Print("Ouro Raid Loot restored previous data, but not in a raid", farmbuyer@1: "and 5-person mode not active. |cffff0505NOT tracking loot|r;", farmbuyer@1: "use 'enable' to activate loot tracking, or 'clear' to erase", farmbuyer@1: "previous data, or 'help' to read about saved-texts commands.") farmbuyer@1: return farmbuyer@1: end farmbuyer@1: self.rebroadcast = true -- hardcode to true; this used to be more complicated farmbuyer@1: self.enabled = not opt_bcast_only farmbuyer@1: if opt_threshold then farmbuyer@1: self:SetThreshold (opt_threshold, --[[quiet_p=]]true) farmbuyer@1: end farmbuyer@1: self:Print("Ouro Raid Loot is %s. Threshold currently %s.", farmbuyer@1: self.enabled and "tracking" or "only broadcasting", farmbuyer@1: self.thresholds[self.threshold]) farmbuyer@27: self:broadcast('revcheck',revision_large) farmbuyer@1: end farmbuyer@1: farmbuyer@1: -- Note: running '/loot off' will also avoid the popup reminder when farmbuyer@1: -- joining a raid, but will not change the saved option setting. farmbuyer@1: function addon:Deactivate() farmbuyer@1: self.enabled = false farmbuyer@1: self.rebroadcast = false farmbuyer@10: self:UnregisterEvent("RAID_ROSTER_UPDATE") farmbuyer@10: self:UnregisterEvent("PLAYER_ENTERING_WORLD") farmbuyer@10: self:UnregisterEvent("CHAT_MSG_LOOT") farmbuyer@1: self:Print("Ouro Raid Loot deactivated.") farmbuyer@1: end farmbuyer@1: farmbuyer@1: function addon:Clear(verbose_p) farmbuyer@1: local repopup, st farmbuyer@1: if self.display then farmbuyer@1: -- in the new version, this is likely to always be the case farmbuyer@1: repopup = true farmbuyer@1: st = self.display:GetUserData("eoiST") farmbuyer@1: if not st then farmbuyer@1: self.dprint('flow', "Clear: display visible but eoiST not set??") farmbuyer@1: end farmbuyer@1: self.display:Hide() farmbuyer@1: end farmbuyer@1: g_restore_p = nil farmbuyer@1: OuroLootSV = nil farmbuyer@1: self:_reset_timestamps() farmbuyer@1: if verbose_p then farmbuyer@16: if (OuroLootSV_saved and #OuroLootSV_saved>0) then farmbuyer@16: self:Print("Current loot data cleared, %d saved sets remaining.", #OuroLootSV_saved) farmbuyer@1: else farmbuyer@1: self:Print("Current loot data cleared.") farmbuyer@1: end farmbuyer@1: end farmbuyer@1: _init(self,st) farmbuyer@1: if repopup then farmbuyer@1: addon:BuildMainDisplay() farmbuyer@1: end farmbuyer@1: end farmbuyer@1: farmbuyer@1: farmbuyer@1: ------ Behind the scenes routines farmbuyer@19: -- Semi-experimental debugging aid. farmbuyer@19: do farmbuyer@19: local date = _G.date farmbuyer@19: local log = OuroLootSV_log farmbuyer@19: function addon:log_with_timestamp (msg) farmbuyer@19: tinsert (log, date('%m:%d %H:%M:%S ')..msg) farmbuyer@19: end farmbuyer@19: end farmbuyer@19: farmbuyer@1: -- Adds indices to traverse the tables in a nice sorted order. farmbuyer@1: do farmbuyer@1: local byindex, temp = {}, {} farmbuyer@1: local function sort (src, dest) farmbuyer@1: for k in pairs(src) do farmbuyer@1: temp[#temp+1] = k farmbuyer@1: end farmbuyer@1: table.sort(temp) farmbuyer@1: table.wipe(dest) farmbuyer@1: for i = 1, #temp do farmbuyer@1: dest[i] = src[temp[i]] farmbuyer@1: end farmbuyer@1: end farmbuyer@1: farmbuyer@1: function addon.sender_list.sort() farmbuyer@1: sort (addon.sender_list.active, byindex) farmbuyer@1: table.wipe(temp) farmbuyer@1: addon.sender_list.activeI = #byindex farmbuyer@1: sort (addon.sender_list.names, byindex) farmbuyer@1: table.wipe(temp) farmbuyer@1: end farmbuyer@1: addon.sender_list.namesI = byindex farmbuyer@1: end farmbuyer@1: farmbuyer@1: -- Message sending. farmbuyer@1: -- See OCR_funcs.tag at the end of this file for incoming message treatment. farmbuyer@1: do farmbuyer@1: local function assemble(...) farmbuyer@1: local msg = ... farmbuyer@1: for i = 2, select('#',...) do farmbuyer@1: msg = msg .. '\a' .. (select(i,...) or "") farmbuyer@1: end farmbuyer@1: return msg farmbuyer@1: end farmbuyer@1: farmbuyer@1: -- broadcast('tag', ) farmbuyer@1: function addon:broadcast(...) farmbuyer@1: local msg = assemble(...) farmbuyer@1: self.dprint('comm', ":", msg) farmbuyer@1: -- the "GUILD" here is just so that we can also pick up on it farmbuyer@1: self:SendCommMessage(self.ident, msg, self.debug.comm and "GUILD" or "RAID") farmbuyer@1: end farmbuyer@1: -- whispercast(, 'tag', ) farmbuyer@1: function addon:whispercast(to,...) farmbuyer@1: local msg = assemble(...) farmbuyer@1: self.dprint('comm', "@", to, ":", msg) farmbuyer@1: self:SendCommMessage(self.identTg, msg, "WHISPER", to) farmbuyer@1: end farmbuyer@1: end farmbuyer@1: farmbuyer@23: function addon:DoPing() farmbuyer@23: self:Print("Give me a ping, Vasili. One ping only, please.") farmbuyer@23: self.sender_list.active = {} farmbuyer@23: self.sender_list.names = {} farmbuyer@23: self:broadcast('ping') farmbuyer@27: self:broadcast('revcheck',revision_large) farmbuyer@27: end farmbuyer@27: farmbuyer@27: function addon:_check_revision (otherrev) farmbuyer@27: self.dprint('comm', "revchecking against", otherrev) farmbuyer@27: otherrev = tonumber(otherrev) farmbuyer@27: if otherrev == revision_large then farmbuyer@27: -- normal case farmbuyer@27: farmbuyer@27: elseif otherrev < revision_large then farmbuyer@27: self.dprint('comm', "ours is newer, notifying") farmbuyer@27: self:broadcast('revcheck',revision_large) farmbuyer@27: farmbuyer@27: else farmbuyer@27: self.dprint('comm', "ours is older, yammering") farmbuyer@27: if newer_warning then farmbuyer@27: self:Print(newer_warning, farmbuyer@29: self.format_hypertext('popupurl',"click here",ITEM_QUALITY_UNCOMMON), farmbuyer@27: self.format_hypertext('doping',"click here",ITEM_QUALITY_UNCOMMON)) farmbuyer@27: newer_warning = nil farmbuyer@27: end farmbuyer@27: end farmbuyer@23: end farmbuyer@23: farmbuyer@1: -- Generic helpers farmbuyer@28: -- Returns index and entry at that index, or nil if not found. farmbuyer@1: function addon._find_next_after (kind, index) farmbuyer@1: index = index + 1 farmbuyer@1: while index <= #g_loot do farmbuyer@1: if g_loot[index].kind == kind then farmbuyer@1: return index, g_loot[index] farmbuyer@1: end farmbuyer@1: index = index + 1 farmbuyer@1: end farmbuyer@1: end farmbuyer@28: -- Essentially a _find_next_after('time'-or-'boss'), but if KIND is farmbuyer@28: -- 'boss', will also stop upon finding a timestamp. Returns nil if farmbuyer@28: -- appropriate fencepost is not found. farmbuyer@28: function addon._find_timeboss_fencepost (kind, index) farmbuyer@28: local fencepost farmbuyer@28: local closest_time = addon._find_next_after('time',index) farmbuyer@28: if kind == 'time' then farmbuyer@28: fencepost = closest_time farmbuyer@28: elseif kind == 'boss' then farmbuyer@28: local closest_boss = addon._find_next_after('boss',index) farmbuyer@28: if not closest_boss then farmbuyer@28: fencepost = closest_time farmbuyer@28: elseif not closest_time then farmbuyer@28: fencepost = closest_boss farmbuyer@28: else farmbuyer@28: fencepost = math.min(closest_time,closest_boss) farmbuyer@28: end farmbuyer@28: end farmbuyer@28: return fencepost farmbuyer@28: end farmbuyer@1: farmbuyer@1: -- Iterate through g_loot entries according to the KIND field. Loop variables farmbuyer@1: -- are g_loot indices and the corresponding entries (essentially ipairs + some farmbuyer@1: -- conditionals). farmbuyer@1: function addon:filtered_loot_iter (filter_kind) farmbuyer@1: return self._find_next_after, filter_kind, 0 farmbuyer@1: end farmbuyer@1: farmbuyer@1: do farmbuyer@1: local itt farmbuyer@1: local function create() farmbuyer@1: local tip, lefts = CreateFrame("GameTooltip"), {} farmbuyer@1: for i = 1, 2 do -- scanning idea here also snagged from Talented farmbuyer@1: local L,R = tip:CreateFontString(), tip:CreateFontString() farmbuyer@1: L:SetFontObject(GameFontNormal) farmbuyer@1: R:SetFontObject(GameFontNormal) farmbuyer@1: tip:AddFontStrings(L,R) farmbuyer@1: lefts[i] = L farmbuyer@1: end farmbuyer@1: tip.lefts = lefts farmbuyer@1: return tip farmbuyer@1: end farmbuyer@1: function addon:is_heroic_item(item) -- returns true or *nil* farmbuyer@1: itt = itt or create() farmbuyer@1: itt:SetOwner(UIParent,"ANCHOR_NONE") farmbuyer@1: itt:ClearLines() farmbuyer@1: itt:SetHyperlink(item) farmbuyer@1: local t = itt.lefts[2]:GetText() farmbuyer@1: itt:Hide() farmbuyer@1: return (t == ITEM_HEROIC) or nil farmbuyer@1: end farmbuyer@1: end farmbuyer@1: farmbuyer@1: -- Called when first loading up, and then also when a 'clear' is being farmbuyer@16: -- performed. If SV's are present then g_restore_p will be true. farmbuyer@1: function _init (self, possible_st) farmbuyer@1: self.dprint('flow',"_init running") farmbuyer@1: self.loot_clean = nil farmbuyer@1: self.hist_clean = nil farmbuyer@1: if g_restore_p then farmbuyer@1: g_loot = OuroLootSV farmbuyer@16: self.popped = #g_loot > 0 farmbuyer@1: self.dprint('flow', "restoring", #g_loot, "entries") farmbuyer@16: self:ScheduleTimer("Activate", 12, opts.threshold) farmbuyer@1: -- FIXME printed could be too large if entries were deleted, how much do we care? farmbuyer@16: self.sharder = opts.autoshard farmbuyer@1: else farmbuyer@10: g_loot = { printed = {}, raiders = {} } farmbuyer@1: end farmbuyer@1: farmbuyer@16: self.threshold = opts.threshold or self.threshold -- in the case of restoring but not tracking farmbuyer@1: self:gui_init(g_loot) farmbuyer@16: opts.autoshard = nil farmbuyer@16: opts.threshold = nil farmbuyer@1: farmbuyer@1: if g_restore_p then farmbuyer@1: self:zero_printed_fenceposts() -- g_loot.printed.* = previous/safe values farmbuyer@1: else farmbuyer@1: self:zero_printed_fenceposts(0) -- g_loot.printed.* = 0 farmbuyer@1: end farmbuyer@1: if possible_st then farmbuyer@1: possible_st:SetData(g_loot) farmbuyer@1: end farmbuyer@1: farmbuyer@17: self.status_text = ("%s communicating as ident %s commrev %d"):format(self.revision,self.ident,self.commrev) farmbuyer@1: self:RegisterComm(self.ident) farmbuyer@1: self:RegisterComm(self.identTg, "OnCommReceivedNocache") farmbuyer@1: farmbuyer@1: if self.author_debug then farmbuyer@1: _G.OL = self farmbuyer@1: _G.Oloot = g_loot farmbuyer@1: end farmbuyer@1: end farmbuyer@1: farmbuyer@25: -- Tie-in with Deadly Boss Mods (or other such addons) farmbuyer@1: do farmbuyer@25: local candidates = {} farmbuyer@25: local location farmbuyer@1: local function fixup_durations (cache) farmbuyer@1: local boss, bossi farmbuyer@1: boss = candidates[1] farmbuyer@1: if #candidates == 1 then farmbuyer@1: -- (1) or (2) farmbuyer@1: boss.duration = boss.duration or 0 farmbuyer@19: addon.dprint('loot', "only one boss candidate") farmbuyer@1: else farmbuyer@1: -- (3), should only be one 'cast entry and our local entry farmbuyer@1: if #candidates ~= 2 then farmbuyer@1: -- could get a bunch of 'cast entries on the heels of one another farmbuyer@1: -- before the local one ever fires, apparently... sigh farmbuyer@1: --addon:Print(" s3 cache has %d entries, does that seem right to you?", #candidates) farmbuyer@1: end farmbuyer@1: if candidates[2].duration == nil then farmbuyer@1: --addon:Print(" s3's second entry is not the local trigger, does that seem right to you?") farmbuyer@1: end farmbuyer@1: -- try and be generic anyhow farmbuyer@1: for i,c in ipairs(candidates) do farmbuyer@1: if c.duration then farmbuyer@1: boss = c farmbuyer@19: addon.dprint('loot', "fixup found boss candidate", i, "duration", c.duration) farmbuyer@1: break farmbuyer@1: end farmbuyer@1: end farmbuyer@1: end farmbuyer@1: bossi = addon._addLootEntry(boss) farmbuyer@19: -- addon. farmbuyer@19: bossi = addon._adjustBossOrder (bossi, g_boss_signpost) or bossi farmbuyer@16: g_boss_signpost = nil farmbuyer@19: addon.dprint('loot', "added boss entry", bossi) farmbuyer@1: if boss.reason == 'kill' then farmbuyer@1: addon:_mark_boss_kill (bossi) farmbuyer@1: if opts.chatty_on_kill then farmbuyer@1: addon:Print("Registered kill for '%s' in %s!", boss.bosskill, boss.instance) farmbuyer@1: end farmbuyer@1: end farmbuyer@25: table.wipe(candidates) farmbuyer@1: end farmbuyer@1: addon.recent_boss = create_new_cache ('boss', 10, fixup_durations) farmbuyer@1: farmbuyer@1: -- Similar to _do_loot, but duration+ parms only present when locally generated. farmbuyer@1: local function _do_boss (self, reason, bossname, intag, duration, raiders) farmbuyer@1: self.dprint('loot',">>_do_boss, R:", reason, "B:", bossname, "T:", intag, farmbuyer@1: "D:", duration, "RL:", (raiders and #raiders or 'nil')) farmbuyer@1: if self.rebroadcast and duration then farmbuyer@1: self:broadcast('boss', reason, bossname, intag) farmbuyer@1: end farmbuyer@1: -- This is only a loop to make jumping out of it easy, and still do cleanup below. farmbuyer@1: while self.enabled do farmbuyer@1: if reason == 'wipe' and opts.no_tracking_wipes then break end farmbuyer@1: bossname = (opts.snarky_boss and self.boss_abbrev[bossname] or bossname) or bossname farmbuyer@1: local not_from_local = duration == nil farmbuyer@1: local signature = bossname .. reason farmbuyer@1: if not_from_local and self.recent_boss:test(signature) then farmbuyer@39: self.dprint('cache', "remote boss <",signature,"> already in cache, skipping") farmbuyer@1: else farmbuyer@1: self.recent_boss:add(signature) farmbuyer@16: g_boss_signpost = #g_loot + 1 farmbuyer@19: self.dprint('loot', "added boss signpost", g_boss_signpost) farmbuyer@1: -- Possible scenarios: (1) we don't see a boss event at all (e.g., we're farmbuyer@1: -- outside the instance) and so this only happens once as a non-local event, farmbuyer@1: -- (2) we see a local event first and all non-local events are filtered farmbuyer@1: -- by the cache, (3) we happen to get some non-local events before doing farmbuyer@1: -- our local event (not because of network weirdness but because our local farmbuyer@1: -- DBM might not trigger for a while). farmbuyer@1: local c = { farmbuyer@1: kind = 'boss', farmbuyer@1: bosskill = bossname, -- minor misnomer, might not actually be a kill farmbuyer@1: reason = reason, farmbuyer@1: instance = intag, farmbuyer@1: duration = duration, -- these two deliberately may be nil farmbuyer@1: raiderlist = raiders and table.concat(raiders, ", ") farmbuyer@1: } farmbuyer@1: tinsert(candidates,c) farmbuyer@1: end farmbuyer@17: break farmbuyer@1: end farmbuyer@1: self.dprint('loot',"<<_do_boss out") farmbuyer@1: end farmbuyer@1: -- No wrapping layer for now farmbuyer@1: addon.on_boss_broadcast = _do_boss farmbuyer@1: farmbuyer@1: function addon:_mark_boss_kill (index) farmbuyer@1: local e = g_loot[index] farmbuyer@17: if not e then farmbuyer@17: self:Print("Something horribly wrong;", index, "is not a valid entry!") farmbuyer@17: return farmbuyer@17: end farmbuyer@1: if not e.bosskill then farmbuyer@16: self:Print("Something horribly wrong;", index, "is not a boss entry!") farmbuyer@16: return farmbuyer@1: end farmbuyer@1: if e.reason ~= 'wipe' then farmbuyer@1: -- enh, bail farmbuyer@1: self.loot_clean = index-1 farmbuyer@1: end farmbuyer@1: local attempts = 1 farmbuyer@1: local first farmbuyer@1: farmbuyer@1: local i,d = 1,g_loot[1] farmbuyer@1: while d ~= e do farmbuyer@1: if d.bosskill and farmbuyer@1: d.bosskill == e.bosskill and farmbuyer@1: d.reason == 'wipe' farmbuyer@1: then farmbuyer@1: first = first or i farmbuyer@1: attempts = attempts + 1 farmbuyer@1: assert(tremove(g_loot,i)==d,"_mark_boss_kill screwed up data badly") farmbuyer@1: else farmbuyer@1: i = i + 1 farmbuyer@1: end farmbuyer@1: d = g_loot[i] farmbuyer@1: end farmbuyer@1: e.reason = 'kill' farmbuyer@1: e.attempts = attempts farmbuyer@1: self.loot_clean = first or index-1 farmbuyer@1: end farmbuyer@1: farmbuyer@2: function addon:register_boss_mod (name, registration_func, deregistration_func) farmbuyer@2: assert(type(name)=='string') farmbuyer@2: assert(type(registration_func)=='function') farmbuyer@2: if deregistration_func ~= nil then assert(type(deregistration_func)=='function') end farmbuyer@2: self.bossmods[#self.bossmods+1] = { farmbuyer@2: n = name, farmbuyer@2: r = registration_func, farmbuyer@2: d = deregistration_func, farmbuyer@2: } farmbuyer@5: self.bossmods[name] = self.bossmods[#self.bossmods] farmbuyer@5: assert(self.bossmods[name].n == self.bossmods[#self.bossmods].n) farmbuyer@2: end farmbuyer@1: farmbuyer@2: function _register_bossmod (self, force_p) farmbuyer@2: local x = self.bossmod_registered and self.bossmods[self.bossmod_registered] farmbuyer@2: if x then farmbuyer@2: if x.n == opts.bossmod and not force_p then farmbuyer@2: -- trying to register with already-registered boss mod farmbuyer@2: return farmbuyer@2: else farmbuyer@2: -- deregister farmbuyer@2: if x.d then x.d(self) end farmbuyer@2: end farmbuyer@1: end farmbuyer@1: farmbuyer@2: x = nil farmbuyer@2: for k,v in ipairs(self.bossmods) do farmbuyer@2: if v.n == opts.bossmod then farmbuyer@2: x = k farmbuyer@2: break farmbuyer@2: end farmbuyer@1: end farmbuyer@1: farmbuyer@2: if not x then farmbuyer@2: self.status_text = "|cffff1010No boss-mod found!|r" farmbuyer@2: self:Print(self.status_text) farmbuyer@2: return farmbuyer@1: end farmbuyer@1: farmbuyer@2: if self.bossmods[x].r (self, _do_boss) then farmbuyer@5: --self.bossmod_registered = x farmbuyer@5: self.bossmod_registered = self.bossmods[x].n farmbuyer@1: else farmbuyer@2: self:Print("|cffff1010Boss mod registration failed|r") farmbuyer@1: end farmbuyer@1: end farmbuyer@2: end farmbuyer@1: farmbuyer@1: -- Adding entries to the loot record, and tracking the corresponding timestamp. farmbuyer@1: do farmbuyer@1: -- This shouldn't be required. /sadface farmbuyer@1: local loot_entry_mt = { farmbuyer@1: __index = function (e,key) farmbuyer@1: if key == 'cols' then farmbuyer@15: pprint('mt', e.kind, "key is", key) farmbuyer@1: --tabledump(e) -- not actually that useful farmbuyer@1: addon:_fill_out_eoi_data(1) farmbuyer@1: end farmbuyer@1: return rawget(e,key) farmbuyer@1: end farmbuyer@1: } farmbuyer@1: farmbuyer@1: -- Given a loot index, searches backwards for a timestamp. Returns that farmbuyer@1: -- index and the time entry, or nil if it falls off the beginning. Pass an farmbuyer@1: -- optional second index to search no earlier than it. farmbuyer@1: -- May also be able to make good use of this in forum-generation routine. farmbuyer@1: function addon:find_previous_time_entry(i,stop) farmbuyer@17: stop = stop or 0 farmbuyer@1: while i > stop do farmbuyer@1: if g_loot[i].kind == 'time' then farmbuyer@1: return i, g_loot[i] farmbuyer@1: end farmbuyer@1: i = i - 1 farmbuyer@1: end farmbuyer@1: end farmbuyer@1: farmbuyer@1: -- format_timestamp (["format_string"], Day, [Loot]) farmbuyer@1: -- DAY is a loot entry with kind=='time', and controls the date printed. farmbuyer@1: -- LOOT may be any kind of entry in the g_loot table. If present, it farmbuyer@1: -- overrides the hour and minute printed; if absent, those values are farmbuyer@1: -- taken from the DAY entry. farmbuyer@1: -- FORMAT_STRING may contain $x (x in Y/M/D/h/m) tokens. farmbuyer@1: local format_timestamp_values, point2dee = {}, "%.2d" farmbuyer@1: function addon:format_timestamp (fmt_opt, day_entry, time_entry_opt) farmbuyer@1: if not time_entry_opt then farmbuyer@1: if type(fmt_opt) == 'table' then -- Two entries, default format farmbuyer@1: time_entry_opt, day_entry = day_entry, fmt_opt farmbuyer@1: fmt_opt = "$Y/$M/$D $h:$m" farmbuyer@1: --elseif type(fmt_opt) == "string" then -- Day entry only, specified format farmbuyer@1: end farmbuyer@1: end farmbuyer@1: --format_timestamp_values.Y = point2dee:format (day_entry.startday.year % 100) farmbuyer@1: format_timestamp_values.Y = ("%.4d"):format (day_entry.startday.year) farmbuyer@1: format_timestamp_values.M = point2dee:format (day_entry.startday.month) farmbuyer@1: format_timestamp_values.D = point2dee:format (day_entry.startday.day) farmbuyer@1: format_timestamp_values.h = point2dee:format ((time_entry_opt or day_entry).hour) farmbuyer@1: format_timestamp_values.m = point2dee:format ((time_entry_opt or day_entry).minute) farmbuyer@1: return fmt_opt:gsub ('%$([YMDhm])', format_timestamp_values) farmbuyer@1: end farmbuyer@1: farmbuyer@1: local done_todays_date farmbuyer@1: function addon:_reset_timestamps() farmbuyer@1: done_todays_date = nil farmbuyer@1: end farmbuyer@1: local function do_todays_date() farmbuyer@1: local text, M, D, Y = makedate() farmbuyer@1: local found,ts = #g_loot+1 farmbuyer@1: repeat farmbuyer@1: found,ts = addon:find_previous_time_entry(found-1) farmbuyer@1: if found and ts.startday.text == text then farmbuyer@1: done_todays_date = true farmbuyer@1: end farmbuyer@1: until done_todays_date or (not found) farmbuyer@1: if done_todays_date then farmbuyer@1: g_today = ts farmbuyer@1: else farmbuyer@1: done_todays_date = true farmbuyer@1: g_today = g_loot[addon._addLootEntry{ farmbuyer@1: kind = 'time', farmbuyer@1: startday = { farmbuyer@1: text = text, month = M, day = D, year = Y farmbuyer@1: } farmbuyer@1: }] farmbuyer@1: end farmbuyer@1: addon:_fill_out_eoi_data(1) farmbuyer@1: end farmbuyer@1: farmbuyer@1: -- Adding anything original to g_loot goes through this routine. farmbuyer@1: function addon._addLootEntry (e) farmbuyer@1: setmetatable(e,loot_entry_mt) farmbuyer@1: farmbuyer@1: if not done_todays_date then do_todays_date() end farmbuyer@1: farmbuyer@1: local h, m = GetGameTime() farmbuyer@10: --local localuptime = math.floor(GetTime()) farmbuyer@10: local time_t = time() farmbuyer@1: e.hour = h farmbuyer@1: e.minute = m farmbuyer@10: e.stamp = time_t --localuptime farmbuyer@1: local index = #g_loot + 1 farmbuyer@1: g_loot[index] = e farmbuyer@1: return index farmbuyer@1: end farmbuyer@16: farmbuyer@16: -- Problem: (1) boss kill happens, (2) fast looting happens, (3) boss farmbuyer@16: -- cache cleanup fires. Result: loot shows up before boss kill entry. farmbuyer@16: -- Solution: We need to shuffle the boss entry above any of the loot farmbuyer@16: -- from that boss. farmbuyer@16: function addon._adjustBossOrder (is, should_be) farmbuyer@16: --pprint('loot', is, should_be) farmbuyer@16: if is == should_be then --pprint('loot', "equal, yay") farmbuyer@16: return farmbuyer@16: end farmbuyer@17: if (type(is)~='number') or (type(should_be)~='number') or (is < should_be) then farmbuyer@19: pprint('loot', is, should_be, "...the hell? bailing") farmbuyer@16: return farmbuyer@16: end farmbuyer@16: if g_loot[should_be].kind == 'time' then farmbuyer@16: should_be = should_be + 1 farmbuyer@16: if is == should_be then farmbuyer@16: --pprint('loot', "needed to mark day entry, otherwise equal, yay") farmbuyer@16: return farmbuyer@16: end farmbuyer@16: end farmbuyer@16: farmbuyer@16: assert(g_loot[is].kind == 'boss') farmbuyer@16: local boss = tremove (g_loot, is) farmbuyer@16: --pprint('loot', "MOVING", boss.bosskill) farmbuyer@16: tinsert (g_loot, should_be, boss) farmbuyer@16: return should_be farmbuyer@16: end farmbuyer@1: end farmbuyer@1: farmbuyer@19: -- In the rare case of items getting put into the loot table without current farmbuyer@19: -- item cache data (which will have arrived by now). farmbuyer@19: function addon:do_item_cache_fixup() farmbuyer@19: self:Print("Fixing up missing item cache data...") farmbuyer@19: farmbuyer@19: local numfound = 0 farmbuyer@19: local borkedpat = '^'..UNKNOWN..': (%S+)' farmbuyer@19: farmbuyer@19: for i,e in self:filtered_loot_iter('loot') do farmbuyer@19: if e.cache_miss then farmbuyer@19: local borked_id = e.itemname:match(borkedpat) farmbuyer@19: if borked_id then farmbuyer@19: numfound = numfound + 1 farmbuyer@19: -- Best to use the safest and most flexible API here, which is GII and farmbuyer@19: -- its assload of return values. farmbuyer@19: local iname, ilink, iquality, _,_,_,_,_,_, itexture = GetItemInfo(borked_id) farmbuyer@19: if iname then farmbuyer@19: self:Print(" Entry %d patched up with %s.", i, ilink) farmbuyer@19: e.quality = iquality farmbuyer@19: e.itemname = iname farmbuyer@19: e.id = tonumber(ilink:match("item:(%d+)")) farmbuyer@19: e.itemlink = ilink farmbuyer@19: e.itexture = itexture farmbuyer@19: e.cache_miss = nil farmbuyer@19: end farmbuyer@19: end farmbuyer@19: end farmbuyer@19: end farmbuyer@19: farmbuyer@19: self:Print("...finished. Found %d |4entry:entries; with weird data.", numfound) farmbuyer@19: end farmbuyer@19: farmbuyer@1: farmbuyer@1: ------ Saved texts farmbuyer@1: function addon:check_saved_table(silent_p) farmbuyer@16: local s = OuroLootSV_saved farmbuyer@1: if s and (#s > 0) then return s end farmbuyer@16: OuroLootSV_saved = nil farmbuyer@1: if not silent_p then self:Print("There are no saved loot texts.") end farmbuyer@1: end farmbuyer@1: farmbuyer@1: function addon:save_list() farmbuyer@1: local s = self:check_saved_table(); if not s then return end; farmbuyer@1: for i,t in ipairs(s) do farmbuyer@1: self:Print("#%d %s %d entries %s", i, t.date, t.count, t.name) farmbuyer@1: end farmbuyer@1: end farmbuyer@1: farmbuyer@1: function addon:save_saveas(name) farmbuyer@16: OuroLootSV_saved = OuroLootSV_saved or {} farmbuyer@16: local SV = OuroLootSV_saved farmbuyer@16: local n = #SV + 1 farmbuyer@1: local save = { farmbuyer@1: name = name, farmbuyer@1: date = makedate(), farmbuyer@1: count = #g_loot, farmbuyer@1: } farmbuyer@10: for text in self:registered_textgen_iter() do farmbuyer@10: save[text] = g_loot[text] farmbuyer@10: end farmbuyer@1: self:Print("Saving current loot texts to #%d '%s'", n, name) farmbuyer@16: SV[n] = save farmbuyer@1: return self:save_list() farmbuyer@1: end farmbuyer@1: farmbuyer@1: function addon:save_restore(num) farmbuyer@1: local s = self:check_saved_table(); if not s then return end; farmbuyer@1: if (not num) or (num > #s) then farmbuyer@1: return self:Print("Saved text number must be 1 - "..#s) farmbuyer@1: end farmbuyer@1: local save = s[num] farmbuyer@1: self:Print("Overwriting current loot data with saved text #%d '%s'", num, save.name) farmbuyer@1: self:Clear(--[[verbose_p=]]false) farmbuyer@1: -- Clear will already have displayed the window, and re-selected the first farmbuyer@1: -- tab. Set these up for when the text tabs are clicked. farmbuyer@10: for text in self:registered_textgen_iter() do farmbuyer@10: g_loot[text] = save[text] farmbuyer@10: end farmbuyer@1: end farmbuyer@1: farmbuyer@1: function addon:save_delete(num) farmbuyer@1: local s = self:check_saved_table(); if not s then return end; farmbuyer@1: if (not num) or (num > #s) then farmbuyer@1: return self:Print("Saved text number must be 1 - "..#s) farmbuyer@1: end farmbuyer@1: self:Print("Deleting saved text #"..num) farmbuyer@1: tremove(s,num) farmbuyer@1: return self:save_list() farmbuyer@1: end farmbuyer@1: farmbuyer@1: farmbuyer@1: ------ Loot histories farmbuyer@1: -- history_all = { farmbuyer@1: -- ["Kilrogg"] = { farmbuyer@1: -- ["realm"] = "Kilrogg", -- not saved farmbuyer@1: -- ["st"] = { lib-st display table }, -- not saved farmbuyer@1: -- ["byname"] = { -- not saved farmbuyer@1: -- ["OtherPlayer"] = 2, farmbuyer@1: -- ["Farmbuyer"] = 1, farmbuyer@1: -- } farmbuyer@1: -- [1] = { farmbuyer@1: -- ["name"] = "Farmbuyer", farmbuyer@1: -- [1] = { id = nnnnn, when = "formatted timestamp for displaying" } -- most recent loot farmbuyer@1: -- [2] = { ......., [count = "x3"] } -- previous loot farmbuyer@1: -- }, farmbuyer@1: -- [2] = { farmbuyer@1: -- ["name"] = "OtherPlayer", farmbuyer@1: -- ...... farmbuyer@1: -- }, ...... farmbuyer@1: -- }, farmbuyer@1: -- ["OtherRealm"] = ...... farmbuyer@1: -- } farmbuyer@1: do farmbuyer@6: local tsort = table.sort farmbuyer@6: local comp = function(L,R) return L.when > R.when end farmbuyer@6: farmbuyer@4: -- Builds the map of names to array indices, using passed table or farmbuyer@4: -- self.history, and stores the result into its 'byname' field. Also farmbuyer@4: -- called from the GUI code at least once. farmbuyer@1: function addon:_build_history_names (opt_hist) farmbuyer@1: local hist = opt_hist or self.history farmbuyer@1: local m = {} farmbuyer@1: for i = 1, #hist do farmbuyer@1: m[hist[i].name] = i farmbuyer@1: end farmbuyer@1: hist.byname = m farmbuyer@1: end farmbuyer@1: farmbuyer@1: -- Prepares and returns table to be used as self.history. farmbuyer@1: function addon:_prep_new_history_category (prev_table, realmname) farmbuyer@1: local t = prev_table or { farmbuyer@1: --kind = 'realm', farmbuyer@6: --realm = realmname, farmbuyer@1: } farmbuyer@6: t.realm = realmname farmbuyer@1: farmbuyer@1: --[[ farmbuyer@1: t.cols = setmetatable({ farmbuyer@1: { value = realmname }, farmbuyer@1: }, self.time_column1_used_mt) farmbuyer@1: ]] farmbuyer@1: farmbuyer@1: if not t.byname then farmbuyer@1: self:_build_history_names (t) farmbuyer@1: end farmbuyer@1: farmbuyer@1: return t farmbuyer@1: end farmbuyer@1: farmbuyer@4: -- Maps a name to an array index, creating new tables if needed. Returns farmbuyer@6: -- the index and the table at that index. farmbuyer@4: function addon:get_loot_history (name) farmbuyer@4: local i farmbuyer@4: i = self.history.byname[name] farmbuyer@4: if not i then farmbuyer@4: i = #self.history + 1 farmbuyer@4: self.history[i] = { name=name } farmbuyer@4: self.history.byname[name] = i farmbuyer@4: end farmbuyer@6: return i, self.history[i] farmbuyer@4: end farmbuyer@4: farmbuyer@1: function addon:_addHistoryEntry (lootindex) farmbuyer@1: local e = g_loot[lootindex] farmbuyer@6: if e.kind ~= 'loot' then return end farmbuyer@6: farmbuyer@6: local i,h = self:get_loot_history(e.person) farmbuyer@25: -- If any of these change, update the end of history_handle_disposition. farmbuyer@1: local n = { farmbuyer@1: id = e.id, farmbuyer@1: when = self:format_timestamp (g_today, e), farmbuyer@1: count = e.count, farmbuyer@1: } farmbuyer@6: tinsert (h, 1, n) farmbuyer@24: e.history_unique = n.id .. ' ' .. n.when farmbuyer@6: end farmbuyer@6: farmbuyer@24: -- Create new history table based on current loot. farmbuyer@6: function addon:rewrite_history (realmname) farmbuyer@6: local r = assert(realmname) farmbuyer@6: self.history_all[r] = self:_prep_new_history_category (nil, r) farmbuyer@6: self.history = self.history_all[r] farmbuyer@6: farmbuyer@6: local g_today_real = g_today farmbuyer@6: for i,e in ipairs(g_loot) do farmbuyer@6: if e.kind == 'time' then farmbuyer@6: g_today = e farmbuyer@6: elseif e.kind == 'loot' then farmbuyer@6: self:_addHistoryEntry(i) farmbuyer@6: end farmbuyer@6: end farmbuyer@6: g_today = g_today_real farmbuyer@6: self.hist_clean = nil farmbuyer@6: farmbuyer@6: -- safety measure: resort players' tables based on formatted timestamp farmbuyer@6: for i,h in ipairs(self.history) do farmbuyer@6: tsort (h, comp) farmbuyer@6: end farmbuyer@6: end farmbuyer@6: farmbuyer@24: -- Clears all but latest entry for each player. farmbuyer@6: function addon:preen_history (realmname) farmbuyer@6: local r = assert(realmname) farmbuyer@6: for i,h in ipairs(self.history) do farmbuyer@6: tsort (h, comp) farmbuyer@6: while #h > 1 do farmbuyer@6: tremove (h) farmbuyer@6: end farmbuyer@6: end farmbuyer@1: end farmbuyer@24: farmbuyer@25: -- Given an entry in a g_loot table, looks up the corresponding history farmbuyer@25: -- entry. Returns the player's index and history table (as in get_loot_history) farmbuyer@25: -- and the index into that table of the loot entry. On failure, returns nil farmbuyer@25: -- and an error message ready to be formatted with the loot's name/itemlink. farmbuyer@25: function addon:_history_by_loot_id (loot, operation_text) farmbuyer@25: -- Using assert() here would be concatenating error strings that probably farmbuyer@25: -- wouldn't be used. Do more verbose testing instead. farmbuyer@25: if type(loot) ~= 'table' then farmbuyer@25: error("trying to "..operation_text.." nonexistant entry") farmbuyer@25: end farmbuyer@25: if loot.kind ~= 'loot' then farmbuyer@25: error("trying to "..operation_text.." something that isn't loot") farmbuyer@25: end farmbuyer@24: farmbuyer@25: local player = loot.person farmbuyer@25: local tag = loot.history_unique farmbuyer@24: local errtxt farmbuyer@25: local player_i, player_h, hist_i farmbuyer@24: farmbuyer@24: if not tag then farmbuyer@24: errtxt = "Entry for %s is missing a history tag!" farmbuyer@24: else farmbuyer@25: player_i,player_h = self:get_loot_history(player) farmbuyer@25: for i,h in ipairs(player_h) do farmbuyer@24: local unique = h.id .. ' ' .. h.when farmbuyer@24: if unique == tag then farmbuyer@25: hist_i = i farmbuyer@24: break farmbuyer@24: end farmbuyer@24: end farmbuyer@25: if not hist_i then farmbuyer@24: -- 1) loot an item, 2) clear old history, 3) reassign from current loot farmbuyer@24: -- Bah. Anybody that tricky is already recoding the tables directly anyhow. farmbuyer@24: errtxt = "There is no record of %s ever having been assigned!" farmbuyer@24: end farmbuyer@24: end farmbuyer@24: farmbuyer@24: if errtxt then farmbuyer@25: return nil, errtxt farmbuyer@24: end farmbuyer@25: return player_i, player_h, hist_i farmbuyer@25: end farmbuyer@24: farmbuyer@25: function addon:reassign_loot (index, to_name) farmbuyer@25: assert(type(to_name)=='string' and to_name:len()>0) farmbuyer@25: local e = g_loot[index] farmbuyer@25: local from_i, from_h, hist_i = self:_history_by_loot_id (e, "reassign") farmbuyer@25: local from_name = e.person farmbuyer@25: local to_i,to_h = self:get_loot_history(to_name) farmbuyer@25: farmbuyer@25: if not from_i then farmbuyer@25: -- from_h is the formatted error text farmbuyer@25: self:Print(from_h .. " Loot will be reassigned, but history will NOT be updated.", e.itemlink) farmbuyer@25: else farmbuyer@25: local hist_h = tremove (from_h, hist_i) farmbuyer@25: tinsert (to_h, 1, hist_h) farmbuyer@25: tsort (from_h, comp) farmbuyer@25: tsort (to_h, comp) farmbuyer@25: end farmbuyer@25: e.person = to_name farmbuyer@25: e.person_class = select(2,UnitClass(to_name)) farmbuyer@25: self.hist_clean = nil farmbuyer@25: farmbuyer@25: self:Print("Reassigned entry %d/%s from '%s' to '%s'.", index, e.itemlink, from_name, to_name) farmbuyer@25: end farmbuyer@25: farmbuyer@36: -- Similar to _addHistoryEntry. The second arg may be a loot entry farmbuyer@36: -- (which used to be at LOOTINDEX), or nil (and the loot entry will farmbuyer@36: -- be pulled from LOOTINDEX instead). farmbuyer@36: function addon:_delHistoryEntry (lootindex, opt_e) farmbuyer@36: local e = opt_e or g_loot[lootindex] farmbuyer@36: if e.kind ~= 'loot' then return end farmbuyer@36: farmbuyer@36: local from_i, from_h, hist_i = self:_history_by_loot_id (e, "delete") farmbuyer@36: if not from_i then farmbuyer@36: -- from_h is the formatted error text farmbuyer@36: self:Print(from_h .. " Loot will be deleted, but history will NOT be updated.", e.itemlink) farmbuyer@36: return farmbuyer@36: end farmbuyer@36: farmbuyer@36: --[[local hist_h = ]]tremove (from_h, hist_i) farmbuyer@36: tsort (from_h, comp) farmbuyer@36: self.hist_clean = nil farmbuyer@36: farmbuyer@36: self:Print("Removed history entry %d/%s from '%s'.", lootindex, e.itemlink, e.person) farmbuyer@36: end farmbuyer@36: farmbuyer@25: -- Any extra work for the "Mark as " dropdown actions. The farmbuyer@25: -- corresponding will already have been assigned in the loot entry. farmbuyer@25: local deleted_cache = {} --setmetatable({}, {__mode='k'}) farmbuyer@25: function addon:history_handle_disposition (index, olddisp) farmbuyer@25: local e = g_loot[index] farmbuyer@25: -- Standard disposition has a nil entry, but that's tedious in debug farmbuyer@25: -- output, so force to a string instead. farmbuyer@25: olddisp = olddisp or 'normal' farmbuyer@25: local newdisp = e.disposition or 'normal' farmbuyer@25: -- Ignore misclicks and the like farmbuyer@25: if olddisp == newdisp then return end farmbuyer@25: farmbuyer@25: local name = e.person farmbuyer@25: farmbuyer@25: if (newdisp == 'shard' or newdisp == 'gvault') then farmbuyer@25: local name_i, name_h, hist_i = self:_history_by_loot_id (e, "mark") farmbuyer@25: -- remove history entry farmbuyer@25: if hist_i then farmbuyer@25: local hist_h = tremove (name_h, hist_i) farmbuyer@25: deleted_cache[e.history_unique] = hist_h farmbuyer@25: self.hist_clean = nil farmbuyer@25: elseif (olddisp == 'shard' or olddisp == 'gvault') then farmbuyer@25: -- Sharding a vault item, or giving the auto-sharder something to bank, farmbuyer@25: -- etc, wouldn't necessarily have had a history entry to begin with. farmbuyer@25: else farmbuyer@25: self:Print(name_h .. " Loot has been marked, but history will NOT be updated.", e.itemlink) farmbuyer@25: end farmbuyer@25: return farmbuyer@25: end farmbuyer@25: farmbuyer@25: if (olddisp == 'shard' or olddisp == 'gvault') farmbuyer@25: and (newdisp == 'normal' or newdisp == 'offspec') farmbuyer@25: then farmbuyer@25: local name_i, name_h = self:get_loot_history(name) farmbuyer@25: farmbuyer@25: -- Must create a new history entry. Could call '_addHistoryEntry(index)' farmbuyer@25: -- but that would duplicate a lot of effort. To start with, check the farmbuyer@25: -- cache of stuff we've already deleted; if it's not there then just do farmbuyer@25: -- the same steps as _addHistoryEntry. farmbuyer@25: local entry farmbuyer@25: if e.history_unique and deleted_cache[e.history_unique] then farmbuyer@25: entry = deleted_cache[e.history_unique] farmbuyer@25: deleted_cache[e.history_unique] = nil farmbuyer@25: end farmbuyer@25: local when = g_today and self:format_timestamp (g_today, e) or tostring(e.stamp) farmbuyer@25: entry = entry or { farmbuyer@25: id = e.id, farmbuyer@25: when = when, farmbuyer@25: count = e.count, farmbuyer@25: } farmbuyer@25: tinsert (name_h, 1, entry) farmbuyer@25: e.history_unique = e.history_unique or (entry.id .. ' ' .. entry.when) farmbuyer@25: self.hist_clean = nil farmbuyer@25: return farmbuyer@25: end farmbuyer@24: end farmbuyer@1: end farmbuyer@1: farmbuyer@1: farmbuyer@1: ------ Player communication farmbuyer@1: do farmbuyer@1: local function adduser (name, status, active) farmbuyer@1: if status then addon.sender_list.names[name] = status end farmbuyer@1: if active then addon.sender_list.active[name] = active end farmbuyer@1: end farmbuyer@1: farmbuyer@1: -- Incoming handler functions. All take the sender name and the incoming farmbuyer@1: -- tag as the first two arguments. All of these are active even when the farmbuyer@1: -- player is not tracking loot, so test for that when appropriate. farmbuyer@1: local OCR_funcs = {} farmbuyer@1: farmbuyer@1: OCR_funcs.ping = function (sender) farmbuyer@1: pprint('comm', "incoming ping from", sender) farmbuyer@1: addon:whispercast (sender, 'pong', addon.revision, farmbuyer@1: addon.enabled and "tracking" or (addon.rebroadcast and "broadcasting" or "disabled")) farmbuyer@1: end farmbuyer@1: OCR_funcs.pong = function (sender, _, rev, status) farmbuyer@17: local s = ("|cff00ff00%s|r %s is |cff00ffff%s|r"):format(sender,rev,status) farmbuyer@1: addon:Print("Echo: ", s) farmbuyer@1: adduser (sender, s, status=="tracking" or status=="broadcasting" or nil) farmbuyer@1: end farmbuyer@27: OCR_funcs.revcheck = function (sender, _, revlarge) farmbuyer@27: addon.dprint('comm', "revcheck, sender", sender) farmbuyer@27: addon:_check_revision (revlarge) farmbuyer@27: end farmbuyer@1: farmbuyer@1: OCR_funcs.loot = function (sender, _, recip, item, count, extratext) farmbuyer@1: addon.dprint('comm', "DOTloot, sender", sender, "recip", recip, "item", item, "count", count) farmbuyer@1: if not addon.enabled then return end farmbuyer@1: adduser (sender, nil, true) farmbuyer@1: addon:CHAT_MSG_LOOT ("broadcast", recip, item, count, sender, extratext) farmbuyer@1: end farmbuyer@1: farmbuyer@1: OCR_funcs.boss = function (sender, _, reason, bossname, instancetag) farmbuyer@1: addon.dprint('comm', "DOTboss, sender", sender, "reason", reason, "name", bossname, "it", instancetag) farmbuyer@1: if not addon.enabled then return end farmbuyer@1: adduser (sender, nil, true) farmbuyer@1: addon:on_boss_broadcast (reason, bossname, instancetag) farmbuyer@1: end farmbuyer@1: farmbuyer@1: OCR_funcs.bcast_req = function (sender) farmbuyer@1: if addon.debug.comm or ((not g_wafer_thin) and (not addon.rebroadcast)) farmbuyer@1: then farmbuyer@38: 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.", farmbuyer@1: sender, farmbuyer@1: addon.format_hypertext('bcaston',"the red pill",'|cffff4040'), farmbuyer@1: addon.format_hypertext('waferthin',"the blue pill",'|cff0070dd')) farmbuyer@1: end farmbuyer@18: addon.popped = true farmbuyer@1: end farmbuyer@1: farmbuyer@1: OCR_funcs.bcast_responder = function (sender) farmbuyer@1: if addon.debug.comm or addon.requesting or farmbuyer@1: ((not g_wafer_thin) and (not addon.rebroadcast)) farmbuyer@1: then farmbuyer@1: addon:Print(sender, "has answered the call and is now broadcasting loot.") farmbuyer@1: end farmbuyer@1: end farmbuyer@1: -- remove this tag once it's all tested farmbuyer@1: OCR_funcs.bcast_denied = function (sender) farmbuyer@1: if addon.requesting then addon:Print(sender, "declines futher broadcast requests.") end farmbuyer@1: end farmbuyer@1: farmbuyer@1: -- Incoming message dispatcher farmbuyer@1: local function dotdotdot (sender, tag, ...) farmbuyer@1: local f = OCR_funcs[tag] farmbuyer@1: addon.dprint('comm', ":... processing",tag,"from",sender) farmbuyer@1: if f then return f(sender,tag,...) end farmbuyer@1: addon.dprint('comm', "unknown comm message",tag",from", sender) farmbuyer@1: end farmbuyer@1: -- Recent message cache farmbuyer@1: addon.recent_messages = create_new_cache ('comm', comm_cleanup_ttl) farmbuyer@1: farmbuyer@1: function addon:OnCommReceived (prefix, msg, distribution, sender) farmbuyer@1: if prefix ~= self.ident then return end farmbuyer@1: if not self.debug.comm then farmbuyer@1: if distribution ~= "RAID" and distribution ~= "WHISPER" then return end farmbuyer@1: if sender == my_name then return end farmbuyer@1: end farmbuyer@1: self.dprint('comm', ":OCR from", sender, "message is", msg) farmbuyer@1: farmbuyer@1: if self.recent_messages:test(msg) then farmbuyer@1: return self.dprint('cache', "message <",msg,"> already in cache, skipping") farmbuyer@1: end farmbuyer@1: self.recent_messages:add(msg) farmbuyer@1: farmbuyer@1: -- Nothing is actually returned, just (ab)using tail calls. farmbuyer@1: return dotdotdot(sender,strsplit('\a',msg)) farmbuyer@1: end farmbuyer@1: farmbuyer@1: function addon:OnCommReceivedNocache (prefix, msg, distribution, sender) farmbuyer@1: if prefix ~= self.identTg then return end farmbuyer@1: if not self.debug.comm then farmbuyer@1: if distribution ~= "WHISPER" then return end farmbuyer@1: if sender == my_name then return end farmbuyer@1: end farmbuyer@1: self.dprint('comm', ":OCRN from", sender, "message is", msg) farmbuyer@1: return dotdotdot(sender,strsplit('\a',msg)) farmbuyer@1: end farmbuyer@1: end farmbuyer@1: farmbuyer@1: -- vim:noet