annotate core.lua @ 38:bb41be8f13b2

Remove some older commented code. Cosmetic comment changes. Clear stray empty 'count' strings out of rebroadcast loot histories, if restoring previous version's data (and not in combat).
author Farmbuyer of US-Kilrogg <farmbuyer@gmail.com>
date Fri, 23 Dec 2011 01:08:02 +0000
parents cd745a18d7c7
children 8f7ec6ccf5e3
rev   line source
farmbuyer@17 1 local nametag, addon = ...
farmbuyer@1 2
farmbuyer@1 3 --[==[
farmbuyer@1 4 g_loot's numeric indices are loot entries (including titles, separators,
farmbuyer@1 5 etc); its named indices are:
farmbuyer@1 6 - forum: saved text from forum markup window, default nil
farmbuyer@1 7 - attend: saved text from raid attendence window, default nil
farmbuyer@16 8 - printed.FOO: last loot index formatted into text window FOO, default 0
farmbuyer@1 9
farmbuyer@1 10 Functions arranged like this, with these lables (for jumping to). As a
farmbuyer@1 11 rule, member functions with UpperCamelCase names are called directly by
farmbuyer@1 12 user-facing code, ones with lowercase names are "one step removed", and
farmbuyer@1 13 names with leading underscores are strictly internal helper functions.
farmbuyer@1 14 ------ Saved variables
farmbuyer@1 15 ------ Constants
farmbuyer@1 16 ------ Addon member data
farmbuyer@6 17 ------ Globals
farmbuyer@1 18 ------ Expiring caches
farmbuyer@1 19 ------ Ace3 framework stuff
farmbuyer@1 20 ------ Event handlers
farmbuyer@1 21 ------ Slash command handler
farmbuyer@1 22 ------ On/off
farmbuyer@1 23 ------ Behind the scenes routines
farmbuyer@1 24 ------ Saved texts
farmbuyer@1 25 ------ Loot histories
farmbuyer@1 26 ------ Player communication
farmbuyer@1 27
farmbuyer@1 28 This started off as part of a raid addon package written by somebody else.
farmbuyer@1 29 After he retired, I began modifying the code. Eventually I set aside the
farmbuyer@1 30 entire package and rewrote the loot tracker module from scratch. Many of the
farmbuyer@1 31 variable/function naming conventions (sv_*, g_*, and family) stayed across the
farmbuyer@16 32 rewrite.
farmbuyer@16 33
farmbuyer@16 34 Some variables are needlessly initialized to nil just to look uniform.
farmbuyer@1 35
farmbuyer@1 36 ]==]
farmbuyer@1 37
farmbuyer@1 38 ------ Saved variables
farmbuyer@16 39 OuroLootSV = nil -- possible copy of g_loot
farmbuyer@16 40 OuroLootSV_saved = nil -- table of copies of saved texts, default nil; keys
farmbuyer@16 41 -- are numeric indices of tables, subkeys of those
farmbuyer@16 42 -- are name/forum/attend/date
farmbuyer@16 43 OuroLootSV_opts = nil -- same as option_defaults until changed
farmbuyer@16 44 -- autoshard: optional name of disenchanting player, default nil
farmbuyer@16 45 -- threshold: optional loot threshold, default nil
farmbuyer@16 46 OuroLootSV_hist = nil
farmbuyer@19 47 OuroLootSV_log = {}
farmbuyer@1 48
farmbuyer@1 49
farmbuyer@1 50 ------ Constants
farmbuyer@1 51 local option_defaults = {
farmbuyer@1 52 ['popup_on_join'] = true,
farmbuyer@1 53 ['register_slashloot'] = true,
farmbuyer@1 54 ['scroll_to_bottom'] = true,
farmbuyer@1 55 ['chatty_on_kill'] = false,
farmbuyer@1 56 ['no_tracking_wipes'] = false,
farmbuyer@1 57 ['snarky_boss'] = true,
farmbuyer@1 58 ['keybinding'] = false,
farmbuyer@2 59 ['bossmod'] = "DBM",
farmbuyer@1 60 ['keybinding_text'] = 'CTRL-SHIFT-O',
farmbuyer@1 61 ['forum'] = {
farmbuyer@25 62 ['[url] Wowhead'] = '[url=http://www.wowhead.com/?item=$I]$N[/url]$X - $T',
farmbuyer@25 63 ['[url] MMO/Wowstead'] = '[http://db.mmo-champion.com/i/$I]$X - $T',
farmbuyer@1 64 ['[item] by name'] = '[item]$N[/item]$X - $T',
farmbuyer@1 65 ['[item] by ID'] = '[item]$I[/item]$X - $T',
farmbuyer@1 66 ['Custom...'] = '',
farmbuyer@1 67 },
farmbuyer@1 68 ['forum_current'] = '[item] by name',
farmbuyer@1 69 }
farmbuyer@1 70 local virgin = "First time loaded? Hi! Use the /ouroloot or /loot command"
farmbuyer@1 71 .." to show the main display. You should probably browse the instructions"
farmbuyer@1 72 .." if you've never used this before; %s to display the help window. This"
farmbuyer@1 73 .." welcome message will not intrude again."
farmbuyer@27 74 local newer_warning = "A newer version has been released. You can %s to display"
farmbuyer@27 75 .." a download URL for copy-and-pasting. You can %s to ping other raiders"
farmbuyer@27 76 .." for their installed versions (same as '/ouroloot ping' or clicking the"
farmbuyer@27 77 .." 'Ping!' button on the options panel)."
farmbuyer@1 78 local qualnames = {
farmbuyer@1 79 ['gray'] = 0, ['grey'] = 0, ['poor'] = 0, ['trash'] = 0,
farmbuyer@1 80 ['white'] = 1, ['common'] = 1,
farmbuyer@1 81 ['green'] = 2, ['uncommon'] = 2,
farmbuyer@1 82 ['blue'] = 3, ['rare'] = 3,
farmbuyer@1 83 ['epic'] = 4, ['purple'] = 4,
farmbuyer@1 84 ['legendary'] = 5, ['orange'] = 5,
farmbuyer@1 85 ['artifact'] = 6,
farmbuyer@1 86 --['heirloom'] = 7,
farmbuyer@1 87 }
farmbuyer@1 88 local my_name = UnitName('player')
farmbuyer@1 89 local comm_cleanup_ttl = 5 -- seconds in the cache
farmbuyer@27 90 local revision_large = nil -- defaults to 1, possibly changed by revision
farmbuyer@20 91 local g_LOOT_ITEM_ss, g_LOOT_ITEM_MULTIPLE_sss, g_LOOT_ITEM_SELF_s, g_LOOT_ITEM_SELF_MULTIPLE_ss
farmbuyer@1 92
farmbuyer@1 93
farmbuyer@1 94 ------ Addon member data
farmbuyer@1 95 local flib = LibStub("LibFarmbuyer")
farmbuyer@1 96 addon.author_debug = flib.author_debug
farmbuyer@1 97
farmbuyer@6 98 -- Play cute games with namespaces here just to save typing. WTB Lua 5.2 PST.
farmbuyer@1 99 do local _G = _G setfenv (1, addon)
farmbuyer@1 100
farmbuyer@17 101 commrev = 15 -- number
farmbuyer@27 102 revision = _G.GetAddOnMetadata(nametag,"Version") or "?" -- "x.yy.z", etc
farmbuyer@1 103 ident = "OuroLoot2"
farmbuyer@1 104 identTg = "OuroLoot2Tg"
farmbuyer@1 105 status_text = nil
farmbuyer@1 106
farmbuyer@1 107 DEBUG_PRINT = false
farmbuyer@1 108 debug = {
farmbuyer@1 109 comm = false,
farmbuyer@1 110 loot = false,
farmbuyer@1 111 flow = false,
farmbuyer@1 112 notraid = false,
farmbuyer@1 113 cache = false,
farmbuyer@19 114 alsolog = false,
farmbuyer@1 115 }
farmbuyer@1 116 function dprint (t,...)
farmbuyer@19 117 if DEBUG_PRINT and debug[t] then
farmbuyer@19 118 local text = flib.safeprint("<"..t.."> ",...)
farmbuyer@19 119 if debug.alsolog then
farmbuyer@19 120 addon:log_with_timestamp(text)
farmbuyer@19 121 end
farmbuyer@19 122 end
farmbuyer@1 123 end
farmbuyer@1 124
farmbuyer@1 125 if author_debug then
farmbuyer@1 126 function pprint(t,...)
farmbuyer@19 127 local text = flib.safeprint("<<"..t..">> ",...)
farmbuyer@19 128 if debug.alsolog then
farmbuyer@19 129 addon:log_with_timestamp(text)
farmbuyer@19 130 end
farmbuyer@1 131 end
farmbuyer@1 132 else
farmbuyer@1 133 pprint = flib.nullfunc
farmbuyer@1 134 end
farmbuyer@1 135
farmbuyer@1 136 enabled = false
farmbuyer@1 137 rebroadcast = false
farmbuyer@1 138 display = nil -- display frame, when visible
farmbuyer@1 139 loot_clean = nil -- index of last GUI entry with known-current visual data
farmbuyer@1 140 sender_list = {active={},names={}} -- this should be reworked
farmbuyer@1 141 threshold = debug.loot and 0 or 3 -- rare by default
farmbuyer@1 142 sharder = nil -- name of person whose loot is marked as shards
farmbuyer@1 143
farmbuyer@1 144 -- The rest is also used in the GUI:
farmbuyer@1 145
farmbuyer@1 146 popped = nil -- non-nil when reminder has been shown, actual value unimportant
farmbuyer@1 147
farmbuyer@2 148 bossmod_registered = nil
farmbuyer@2 149 bossmods = {}
farmbuyer@2 150
farmbuyer@1 151 requesting = nil -- for prompting for additional rebroadcasters
farmbuyer@1 152
farmbuyer@38 153 -- don't use NUM_ITEM_QUALITIES as the upper bound unless we expect heirlooms to show up
farmbuyer@11 154 thresholds = {}
farmbuyer@1 155 for i = 0,6 do
farmbuyer@11 156 thresholds[i] = _G.ITEM_QUALITY_COLORS[i].hex .. _G["ITEM_QUALITY"..i.."_DESC"] .. "|r"
farmbuyer@1 157 end
farmbuyer@1 158
farmbuyer@1 159 _G.setfenv (1, _G)
farmbuyer@1 160 end
farmbuyer@1 161
farmbuyer@1 162 addon = LibStub("AceAddon-3.0"):NewAddon(addon, "Ouro Loot",
farmbuyer@1 163 "AceTimer-3.0", "AceComm-3.0", "AceConsole-3.0", "AceEvent-3.0")
farmbuyer@1 164
farmbuyer@1 165
farmbuyer@6 166 ------ Globals
farmbuyer@1 167 local g_loot = nil
farmbuyer@1 168 local g_restore_p = nil
farmbuyer@1 169 local g_wafer_thin = nil -- for prompting for additional rebroadcasters
farmbuyer@1 170 local g_today = nil -- "today" entry in g_loot
farmbuyer@16 171 local g_boss_signpost = nil
farmbuyer@1 172 local opts = nil
farmbuyer@1 173
farmbuyer@1 174 local pairs, ipairs, tinsert, tremove, tonumber = pairs, ipairs, table.insert, table.remove, tonumber
farmbuyer@1 175 local pprint, tabledump = addon.pprint, flib.tabledump
farmbuyer@10 176 local GetNumRaidMembers = GetNumRaidMembers
farmbuyer@1 177 -- En masse forward decls of symbols defined inside local blocks
farmbuyer@2 178 local _register_bossmod
farmbuyer@1 179 local makedate, create_new_cache, _init
farmbuyer@1 180
farmbuyer@27 181 -- Try to extract numbers from the .toc "Version" and munge them into an
farmbuyer@27 182 -- integral form for comparison. The result doesn't need to be meaningful as
farmbuyer@38 183 -- long as we can reliably feed two of them to "<" and get useful answers.
farmbuyer@29 184 --
farmbuyer@29 185 -- This makes/reinforces an assumption that revision_large of release packages
farmbuyer@29 186 -- (e.g., 2016001) will always be higher than those of development packages
farmbuyer@29 187 -- (e.g., 87), due to the tagging system versus subversion file revs. This
farmbuyer@29 188 -- is good, as local dev code will never trigger a false positive update
farmbuyer@38 189 -- warning for other users. The downside is that additional decimal places
farmbuyer@38 190 -- in the Version field for bugfixes (e.g., "2.16.4.1") imposes a high-water
farmbuyer@38 191 -- mark, as subsequent shorter strings ("2.16.5", "2.17") will never be larger.
farmbuyer@27 192 do
farmbuyer@27 193 local r = 0
farmbuyer@27 194 for d in addon.revision:gmatch("%d+") do
farmbuyer@27 195 r = 1000*r + d
farmbuyer@27 196 end
farmbuyer@27 197 revision_large = math.max(r,1)
farmbuyer@27 198 end
farmbuyer@27 199
farmbuyer@1 200 -- Hypertext support, inspired by DBM broadcast pizza timers
farmbuyer@1 201 do
farmbuyer@1 202 local hypertext_format_str = "|HOuroRaid:%s|h%s[%s]|r|h"
farmbuyer@1 203
farmbuyer@38 204 -- TEXT will automatically be surrounded by brackets
farmbuyer@38 205 -- COLOR can be item quality code or a hex string
farmbuyer@1 206 function addon.format_hypertext (code, text, color)
farmbuyer@1 207 return hypertext_format_str:format (code,
farmbuyer@11 208 type(color)=='number' and ITEM_QUALITY_COLORS[color].hex or color,
farmbuyer@1 209 text)
farmbuyer@1 210 end
farmbuyer@1 211
farmbuyer@1 212 DEFAULT_CHAT_FRAME:HookScript("OnHyperlinkClick", function(self, link, string, mousebutton)
farmbuyer@1 213 local ltype, arg = strsplit(":",link)
farmbuyer@1 214 if ltype ~= "OuroRaid" then return end
farmbuyer@1 215 if arg == 'openloot' then
farmbuyer@1 216 addon:BuildMainDisplay()
farmbuyer@27 217 elseif arg == 'popupurl' then
farmbuyer@30 218 -- Sadly, this is not generated by the packager, so hardcode it for now.
farmbuyer@30 219 -- The 'data' field is handled differently for onshow than for other callbacks.
farmbuyer@30 220 StaticPopup_Show("OUROL_URL", --[[text_arg1=]]nil, --[[text_arg2=]]nil,
farmbuyer@30 221 --[[data=]][[http://www.curse.com/addons/wow/ouroloot]])
farmbuyer@27 222 elseif arg == 'doping' then
farmbuyer@27 223 addon:DoPing()
farmbuyer@1 224 elseif arg == 'help' then
farmbuyer@1 225 addon:BuildMainDisplay('help')
farmbuyer@1 226 elseif arg == 'bcaston' then
farmbuyer@1 227 if not addon.rebroadcast then
farmbuyer@1 228 addon:Activate(nil,true)
farmbuyer@1 229 end
farmbuyer@1 230 addon:broadcast('bcast_responder')
farmbuyer@1 231 elseif arg == 'waferthin' then -- mint? it's wafer thin!
farmbuyer@1 232 g_wafer_thin = true -- fuck off, I'm full
farmbuyer@1 233 addon:broadcast('bcast_denied') -- remove once tested
farmbuyer@1 234 end
farmbuyer@1 235 end)
farmbuyer@1 236
farmbuyer@1 237 local old = ItemRefTooltip.SetHyperlink
farmbuyer@1 238 function ItemRefTooltip:SetHyperlink (link, ...)
farmbuyer@1 239 if link:match("^OuroRaid") then return end
farmbuyer@1 240 return old (self, link, ...)
farmbuyer@1 241 end
farmbuyer@1 242 end
farmbuyer@1 243
farmbuyer@1 244 do
farmbuyer@1 245 -- copied here because it's declared local to the calendar ui, thanks blizz ><
farmbuyer@1 246 local CALENDAR_FULLDATE_MONTH_NAMES = {
farmbuyer@1 247 FULLDATE_MONTH_JANUARY, FULLDATE_MONTH_FEBRUARY, FULLDATE_MONTH_MARCH,
farmbuyer@1 248 FULLDATE_MONTH_APRIL, FULLDATE_MONTH_MAY, FULLDATE_MONTH_JUNE,
farmbuyer@1 249 FULLDATE_MONTH_JULY, FULLDATE_MONTH_AUGUST, FULLDATE_MONTH_SEPTEMBER,
farmbuyer@1 250 FULLDATE_MONTH_OCTOBER, FULLDATE_MONTH_NOVEMBER, FULLDATE_MONTH_DECEMBER,
farmbuyer@1 251 }
farmbuyer@1 252 -- returns "dd Month yyyy", mm, dd, yyyy
farmbuyer@1 253 function makedate()
farmbuyer@1 254 Calendar_LoadUI()
farmbuyer@1 255 local _, M, D, Y = CalendarGetDate()
farmbuyer@1 256 local text = ("%d %s %d"):format(D, CALENDAR_FULLDATE_MONTH_NAMES[M], Y)
farmbuyer@1 257 return text, M, D, Y
farmbuyer@1 258 end
farmbuyer@1 259 end
farmbuyer@1 260
farmbuyer@1 261 -- Returns an instance name or abbreviation
farmbuyer@1 262 local function instance_tag()
farmbuyer@1 263 local name, typeof, diffcode, diffstr, _, perbossheroic, isdynamic = GetInstanceInfo()
farmbuyer@1 264 local t
farmbuyer@1 265 name = addon.instance_abbrev[name] or name
farmbuyer@1 266 if typeof == "none" then return name end
farmbuyer@1 267 -- diffstr is "5 Player", "10 Player (Heroic)", etc. ugh.
farmbuyer@35 268 if (GetLFGMode()) and (GetLFGModeType() == 'raid') then
farmbuyer@35 269 t = 'LFR'
farmbuyer@35 270 elseif diffcode == 1 then
farmbuyer@1 271 t = ((GetNumRaidMembers()>0) and "10" or "5")
farmbuyer@1 272 elseif diffcode == 2 then
farmbuyer@1 273 t = ((GetNumRaidMembers()>0) and "25" or "5h")
farmbuyer@1 274 elseif diffcode == 3 then
farmbuyer@1 275 t = "10h"
farmbuyer@1 276 elseif diffcode == 4 then
farmbuyer@1 277 t = "25h"
farmbuyer@1 278 end
farmbuyer@1 279 -- dynamic difficulties always return normal "codes"
farmbuyer@1 280 if isdynamic and perbossheroic == 1 then
farmbuyer@1 281 t = t .. "h"
farmbuyer@1 282 end
farmbuyer@1 283 return name .. "(" .. t .. ")"
farmbuyer@1 284 end
farmbuyer@1 285 addon.instance_tag = instance_tag -- grumble
farmbuyer@1 286
farmbuyer@1 287
farmbuyer@1 288 ------ Expiring caches
farmbuyer@1 289 --[[
farmbuyer@1 290 foo = create_new_cache("myfoo",15[,cleanup]) -- ttl
farmbuyer@1 291 foo:add("blah")
farmbuyer@1 292 foo:test("blah") -- returns true
farmbuyer@1 293 ]]
farmbuyer@1 294 do
farmbuyer@1 295 local caches = {}
farmbuyer@25 296 local cleanup_group = _G.AnimTimerFrame:CreateAnimationGroup()
farmbuyer@10 297 local time = _G.time
farmbuyer@1 298 cleanup_group:SetLooping("REPEAT")
farmbuyer@1 299 cleanup_group:SetScript("OnLoop", function(cg)
farmbuyer@1 300 addon.dprint('cache',"OnLoop firing")
farmbuyer@10 301 local now = time()
farmbuyer@1 302 local alldone = true
farmbuyer@1 303 -- this is ass-ugly
farmbuyer@25 304 for name,c in pairs(caches) do
farmbuyer@25 305 local fifo = c.fifo
farmbuyer@25 306 local active = #fifo > 0
farmbuyer@25 307 while (#fifo > 0) and (now - fifo[1].t > c.ttl) do
farmbuyer@25 308 addon.dprint('cache', name, "cache removing",fifo[1].t, fifo[1].m)
farmbuyer@25 309 tremove(fifo,1)
farmbuyer@1 310 end
farmbuyer@25 311 if active and #fifo == 0 and c.func then
farmbuyer@25 312 addon.dprint('cache', name, "empty, firing cleanup")
farmbuyer@25 313 c:func()
farmbuyer@25 314 end
farmbuyer@25 315 alldone = alldone and (#fifo == 0)
farmbuyer@1 316 end
farmbuyer@1 317 if alldone then
farmbuyer@1 318 addon.dprint('cache',"OnLoop finishing animation group")
farmbuyer@1 319 cleanup_group:Finish()
farmbuyer@1 320 end
farmbuyer@1 321 addon.dprint('cache',"OnLoop done")
farmbuyer@1 322 end)
farmbuyer@1 323
farmbuyer@1 324 local function _add (cache, x)
farmbuyer@25 325 local datum = { t=time(), m=x }
farmbuyer@25 326 cache.hash[x] = datum
farmbuyer@25 327 tinsert (cache.fifo, datum)
farmbuyer@1 328 if not cleanup_group:IsPlaying() then
farmbuyer@1 329 addon.dprint('cache', cache.name, "STARTING animation group")
farmbuyer@1 330 cache.cleanup:SetDuration(2) -- hmmm
farmbuyer@1 331 cleanup_group:Play()
farmbuyer@1 332 end
farmbuyer@1 333 end
farmbuyer@1 334 local function _test (cache, x)
farmbuyer@25 335 return cache.hash[x] ~= nil
farmbuyer@25 336 --[[for _,v in ipairs(cache) do
farmbuyer@1 337 if v.m == x then return true end
farmbuyer@25 338 end]]
farmbuyer@1 339 end
farmbuyer@25 340
farmbuyer@1 341 function create_new_cache (name, ttl, on_alldone)
farmbuyer@25 342 -- setting OnFinished for cleanup fires at the end of each inner loop,
farmbuyer@25 343 -- with no 'requested' argument to distinguish cases. thus, on_alldone.
farmbuyer@1 344 local c = {
farmbuyer@1 345 ttl = ttl,
farmbuyer@1 346 name = name,
farmbuyer@1 347 add = _add,
farmbuyer@1 348 test = _test,
farmbuyer@1 349 cleanup = cleanup_group:CreateAnimation("Animation"),
farmbuyer@1 350 func = on_alldone,
farmbuyer@25 351 fifo = {},
farmbuyer@25 352 hash = setmetatable({}, {__mode='kv'}),
farmbuyer@1 353 }
farmbuyer@1 354 c.cleanup:SetOrder(1)
farmbuyer@25 355 caches[name] = c
farmbuyer@1 356 return c
farmbuyer@1 357 end
farmbuyer@1 358 end
farmbuyer@1 359
farmbuyer@1 360
farmbuyer@1 361 ------ Ace3 framework stuff
farmbuyer@1 362 function addon:OnInitialize()
farmbuyer@1 363 -- VARIABLES_LOADED has fired by this point; test if we're doing something like
farmbuyer@1 364 -- relogging during a raid and already have collected loot data
farmbuyer@1 365 g_restore_p = OuroLootSV ~= nil
farmbuyer@1 366 self.dprint('flow', "oninit sets restore as", g_restore_p)
farmbuyer@1 367
farmbuyer@1 368 if OuroLootSV_opts == nil then
farmbuyer@1 369 OuroLootSV_opts = {}
farmbuyer@1 370 self:ScheduleTimer(function(s)
farmbuyer@1 371 s:Print(virgin, s.format_hypertext('help',"click here",ITEM_QUALITY_UNCOMMON))
farmbuyer@1 372 virgin = nil
farmbuyer@1 373 end,10,self)
farmbuyer@1 374 end
farmbuyer@1 375 opts = OuroLootSV_opts
farmbuyer@1 376 for opt,default in pairs(option_defaults) do
farmbuyer@1 377 if opts[opt] == nil then
farmbuyer@1 378 opts[opt] = default
farmbuyer@1 379 end
farmbuyer@1 380 end
farmbuyer@38 381
farmbuyer@25 382 -- transition&remove old options
farmbuyer@25 383 opts['forum_use_itemid'] = nil
farmbuyer@25 384 if opts['forum_format'] then
farmbuyer@25 385 opts.forum['Custom...'] = opts['forum_format']
farmbuyer@25 386 opts['forum_format'] = nil
farmbuyer@25 387 end
farmbuyer@25 388 if opts.forum['[url]'] then
farmbuyer@25 389 opts.forum['[url] Wowhead'] = opts.forum['[url]']
farmbuyer@25 390 opts.forum['[url]'] = nil
farmbuyer@25 391 opts.forum['[url] MMO/Wowstead'] = option_defaults.forum['[url] MMO/Wowstead']
farmbuyer@25 392 if opts['forum_current'] == '[url]' then
farmbuyer@25 393 opts['forum_current'] = '[url] Wowhead'
farmbuyer@25 394 end
farmbuyer@25 395 end
farmbuyer@1 396 option_defaults = nil
farmbuyer@16 397 if OuroLootSV then -- may not be the same as testing g_restore_p soon
farmbuyer@16 398 if OuroLootSV.saved then
farmbuyer@16 399 OuroLootSV_saved = OuroLootSV.saved; OuroLootSV.saved = nil
farmbuyer@16 400 end
farmbuyer@16 401 if OuroLootSV.threshold then
farmbuyer@16 402 opts.threshold = OuroLootSV.threshold; OuroLootSV.threshold = nil
farmbuyer@16 403 end
farmbuyer@16 404 if OuroLootSV.autoshard then
farmbuyer@16 405 opts.autoshard = OuroLootSV.autoshard; OuroLootSV.autoshard = nil
farmbuyer@16 406 end
farmbuyer@16 407 end
farmbuyer@38 408
farmbuyer@1 409 -- get item filter table if needed
farmbuyer@1 410 if opts.itemfilter == nil then
farmbuyer@1 411 opts.itemfilter = addon.default_itemfilter
farmbuyer@1 412 end
farmbuyer@1 413 addon.default_itemfilter = nil
farmbuyer@1 414
farmbuyer@1 415 self:RegisterChatCommand("ouroloot", "OnSlash")
farmbuyer@1 416 -- maybe try to detect if this command is already in use...
farmbuyer@1 417 if opts.register_slashloot then
farmbuyer@1 418 SLASH_ACECONSOLE_OUROLOOT2 = "/loot"
farmbuyer@1 419 end
farmbuyer@1 420
farmbuyer@1 421 self.history_all = self.history_all or OuroLootSV_hist or {}
farmbuyer@4 422 local r = assert(GetRealmName())
farmbuyer@1 423 self.history_all[r] = self:_prep_new_history_category (self.history_all[r], r)
farmbuyer@1 424 self.history = self.history_all[r]
farmbuyer@38 425 if (not InCombatLockdown()) and OuroLootSV_hist and
farmbuyer@38 426 (OuroLootSV_hist.HISTFORMAT == nil) -- restored data but it's older
farmbuyer@38 427 then
farmbuyer@38 428 -- Big honkin' loop
farmbuyer@38 429 for rname,realm in pairs(self.history_all) do
farmbuyer@38 430 for pk,player in ipairs(realm) do
farmbuyer@38 431 for lk,loot in ipairs(player) do
farmbuyer@38 432 if loot.count == "" then
farmbuyer@38 433 loot.count = nil
farmbuyer@38 434 end
farmbuyer@38 435 end
farmbuyer@38 436 end
farmbuyer@38 437 end
farmbuyer@38 438 end
farmbuyer@38 439 self.history_all.HISTFORMAT = nil -- don't keep this in live data
farmbuyer@6 440 --OuroLootSV_hist = nil
farmbuyer@1 441
farmbuyer@1 442 _init(self)
farmbuyer@27 443 self.dprint('flow', "version strings:", revision_large, self.status_text)
farmbuyer@1 444 self.OnInitialize = nil
farmbuyer@1 445 end
farmbuyer@1 446
farmbuyer@1 447 function addon:OnEnable()
farmbuyer@10 448 self:RegisterEvent("PLAYER_LOGOUT")
farmbuyer@10 449 self:RegisterEvent("RAID_ROSTER_UPDATE")
farmbuyer@1 450
farmbuyer@1 451 -- Cribbed from Talented. I like the way jerry thinks: the first argument
farmbuyer@1 452 -- can be a format spec for the remainder of the arguments. (The new
farmbuyer@1 453 -- AceConsole:Printf isn't used because we can't specify a prefix without
farmbuyer@1 454 -- jumping through ridonkulous hoops.) The part about overriding :Print
farmbuyer@1 455 -- with a version using prefix hyperlinks is my fault.
farmbuyer@37 456 --
farmbuyer@37 457 -- There is no ITEM_QUALITY_LEGENDARY constant. Sigh.
farmbuyer@1 458 do
farmbuyer@1 459 local AC = LibStub("AceConsole-3.0")
farmbuyer@1 460 local chat_prefix = self.format_hypertext('openloot',"Ouro Loot",--[[legendary]]5)
farmbuyer@1 461 function addon:Print (str, ...)
farmbuyer@1 462 if type(str) == 'string' and str:find("%", nil, --[[plainmatch=]]true) then
farmbuyer@1 463 return AC:Print (chat_prefix, str:format(...))
farmbuyer@1 464 else
farmbuyer@1 465 return AC:Print (chat_prefix, str, ...)
farmbuyer@1 466 end
farmbuyer@1 467 end
farmbuyer@1 468 end
farmbuyer@1 469
farmbuyer@1 470 if opts.keybinding then
farmbuyer@15 471 KeyBindingFrame_LoadUI()
farmbuyer@1 472 local btn = CreateFrame("Button", "OuroLootBindingOpen", nil, "SecureActionButtonTemplate")
farmbuyer@1 473 btn:SetAttribute("type", "macro")
farmbuyer@1 474 btn:SetAttribute("macrotext", "/ouroloot toggle")
farmbuyer@1 475 if SetBindingClick(opts.keybinding_text, "OuroLootBindingOpen") then
farmbuyer@15 476 -- a simple SaveBindings(GetCurrentBindingSet()) occasionally fails when GCBS
farmbuyer@38 477 -- decides to return neither 1 nor 2 during load, for reasons nobody has ever learned
farmbuyer@15 478 local c = GetCurrentBindingSet()
farmbuyer@15 479 if c == ACCOUNT_BINDINGS or c == CHARACTER_BINDINGS then
farmbuyer@15 480 SaveBindings(c)
farmbuyer@15 481 end
farmbuyer@1 482 else
farmbuyer@2 483 self:Print("Error registering '%s' as a keybinding, check spelling!", opts.keybinding_text)
farmbuyer@1 484 end
farmbuyer@1 485 end
farmbuyer@1 486
farmbuyer@20 487 --[[
farmbuyer@20 488 The four loot format patterns of interest, changed into relatively tight
farmbuyer@20 489 string match patterns. Done at enable-time rather than load-time against
farmbuyer@20 490 the slim chance that one of the non-US "delocalizers" needs to mess with
farmbuyer@20 491 the global patterns before we transform them.
farmbuyer@20 492
farmbuyer@20 493 The SELF variants can be replaced with LOOT_ITEM_PUSHED_SELF[_MULTIPLE] to
farmbuyer@20 494 trigger on 'receive item' instead, which would detect extracting stuff
farmbuyer@20 495 from mail, or s/PUSHED/CREATED/ for things like healthstones and guild
farmbuyer@20 496 cauldron flasks.
farmbuyer@20 497 ]]
farmbuyer@20 498
farmbuyer@20 499 -- LOOT_ITEM = "%s receives loot: %s." --> (.+) receives loot: (.+)%.
farmbuyer@20 500 g_LOOT_ITEM_ss = _G.LOOT_ITEM:gsub('%.$','%%.'):gsub('%%s','(.+)')
farmbuyer@20 501
farmbuyer@20 502 -- LOOT_ITEM_MULTIPLE = "%s receives loot: %sx%d." --> (.+) receives loot: (.+)(x%d+)%.
farmbuyer@20 503 g_LOOT_ITEM_MULTIPLE_sss = _G.LOOT_ITEM_MULTIPLE:gsub('%.$','%%.'):gsub('%%s','(.+)'):gsub('x%%d','(x%%d+)')
farmbuyer@20 504
farmbuyer@20 505 -- LOOT_ITEM_SELF = "You receive loot: %s." --> You receive loot: (.+)%.
farmbuyer@20 506 g_LOOT_ITEM_SELF_s = _G.LOOT_ITEM_SELF:gsub('%.$','%%.'):gsub('%%s','(.+)')
farmbuyer@20 507
farmbuyer@20 508 -- LOOT_ITEM_SELF_MULTIPLE = "You receive loot: %sx%d." --> You receive loot: (.+)(x%d+)%.
farmbuyer@20 509 g_LOOT_ITEM_SELF_MULTIPLE_ss = _G.LOOT_ITEM_SELF_MULTIPLE:gsub('%.$','%%.'):gsub('%%s','(.+)'):gsub('x%%d','(x%%d+)')
farmbuyer@20 510
farmbuyer@1 511 if self.debug.flow then self:Print"is in control-flow debug mode." end
farmbuyer@1 512 end
farmbuyer@1 513 --function addon:OnDisable() end
farmbuyer@1 514
farmbuyer@1 515
farmbuyer@1 516 ------ Event handlers
farmbuyer@1 517 function addon:_clear_SVs()
farmbuyer@1 518 g_loot = {} -- not saved, just fooling PLAYER_LOGOUT tests
farmbuyer@1 519 OuroLootSV = nil
farmbuyer@16 520 OuroLootSV_saved = nil
farmbuyer@1 521 OuroLootSV_opts = nil
farmbuyer@1 522 OuroLootSV_hist = nil
farmbuyer@22 523 OuroLootSV_log = nil
farmbuyer@8 524 ReloadUI()
farmbuyer@1 525 end
farmbuyer@1 526 function addon:PLAYER_LOGOUT()
farmbuyer@16 527 self:UnregisterEvent("RAID_ROSTER_UPDATE")
farmbuyer@16 528 self:UnregisterEvent("PLAYER_ENTERING_WORLD")
farmbuyer@16 529
farmbuyer@16 530 local worth_saving = #g_loot > 0 or next(g_loot.raiders)
farmbuyer@16 531 if not worth_saving then for text in self:registered_textgen_iter() do
farmbuyer@16 532 worth_saving = worth_saving or g_loot.printed[text] > 0
farmbuyer@16 533 end end
farmbuyer@16 534 if worth_saving then
farmbuyer@16 535 opts.autoshard = self.sharder
farmbuyer@16 536 opts.threshold = self.threshold
farmbuyer@1 537 for i,e in ipairs(g_loot) do
farmbuyer@1 538 e.cols = nil
farmbuyer@1 539 end
farmbuyer@1 540 OuroLootSV = g_loot
farmbuyer@16 541 else
farmbuyer@16 542 OuroLootSV = nil
farmbuyer@1 543 end
farmbuyer@16 544
farmbuyer@38 545 worth_saving = false
farmbuyer@6 546 for r,t in pairs(self.history_all) do if type(t) == 'table' then
farmbuyer@8 547 if #t == 0 then
farmbuyer@8 548 self.history_all[r] = nil
farmbuyer@8 549 else
farmbuyer@38 550 worth_saving = true
farmbuyer@8 551 t.realm = nil
farmbuyer@8 552 t.st = nil
farmbuyer@8 553 t.byname = nil
farmbuyer@8 554 end
farmbuyer@6 555 end end
farmbuyer@38 556 if worth_saving then
farmbuyer@38 557 OuroLootSV_hist = self.history_all
farmbuyer@38 558 OuroLootSV_hist.HISTFORMAT = 2
farmbuyer@38 559 else
farmbuyer@38 560 OuroLootSV_hist = nil
farmbuyer@38 561 end
farmbuyer@19 562 OuroLootSV_log = #OuroLootSV_log > 0 and OuroLootSV_log or nil
farmbuyer@1 563 end
farmbuyer@1 564
farmbuyer@10 565 do
farmbuyer@10 566 local IsInInstance, UnitName, UnitIsConnected, UnitClass, UnitRace, UnitSex,
farmbuyer@10 567 UnitLevel, UnitInRaid, UnitIsVisible, GetGuildInfo =
farmbuyer@10 568 IsInInstance, UnitName, UnitIsConnected, UnitClass, UnitRace, UnitSex,
farmbuyer@10 569 UnitLevel, UnitInRaid, UnitIsVisible, GetGuildInfo
farmbuyer@10 570 local time, difftime = time, difftime
farmbuyer@10 571 local R_ACTIVE, R_OFFLINE, R_LEFT = 1, 2, 3
farmbuyer@10 572
farmbuyer@10 573 local lastevent, now = 0, 0
farmbuyer@16 574 local redo_count = 0
farmbuyer@16 575 local redo, timer_handle
farmbuyer@10 576
farmbuyer@10 577 function addon:CheckRoster (leaving_p, now_a)
farmbuyer@10 578 if not g_loot.raiders then return end -- bad transition
farmbuyer@10 579
farmbuyer@10 580 now = now_a or time()
farmbuyer@10 581
farmbuyer@10 582 if leaving_p then
farmbuyer@16 583 if timer_handle then
farmbuyer@16 584 self:CancelTimer(timer_handle)
farmbuyer@16 585 timer_handle = nil
farmbuyer@16 586 end
farmbuyer@10 587 for name,r in pairs(g_loot.raiders) do
farmbuyer@10 588 r.leave = r.leave or now
farmbuyer@10 589 end
farmbuyer@10 590 return
farmbuyer@10 591 end
farmbuyer@10 592
farmbuyer@10 593 for name,r in pairs(g_loot.raiders) do
farmbuyer@10 594 if r.online ~= R_LEFT and not UnitInRaid(name) then
farmbuyer@10 595 r.online = R_LEFT
farmbuyer@10 596 r.leave = now
farmbuyer@10 597 end
farmbuyer@10 598 end
farmbuyer@10 599
farmbuyer@16 600 if redo then
farmbuyer@16 601 redo_count = redo_count + 1
farmbuyer@16 602 end
farmbuyer@16 603 redo = false
farmbuyer@10 604 for i = 1, GetNumRaidMembers() do
farmbuyer@10 605 local unit = 'raid'..i
farmbuyer@10 606 local name = UnitName(unit)
farmbuyer@10 607 -- No, that's not my typo, it really is "uknownbeing" in Blizzard's code.
farmbuyer@10 608 if name and name ~= UNKNOWN and name ~= UNKNOWNOBJECT and name ~= UKNOWNBEING then
farmbuyer@10 609 if not g_loot.raiders[name] then
farmbuyer@10 610 g_loot.raiders[name] = { needinfo=true }
farmbuyer@10 611 end
farmbuyer@10 612 local r = g_loot.raiders[name]
farmbuyer@10 613 if r.needinfo and UnitIsVisible(unit) then
farmbuyer@10 614 r.needinfo = nil
farmbuyer@10 615 r.class = select(2,UnitClass(unit))
farmbuyer@10 616 r.race = select(2,UnitRace(unit))
farmbuyer@10 617 r.sex = UnitSex(unit)
farmbuyer@10 618 r.level = UnitLevel(unit)
farmbuyer@10 619 r.guild = GetGuildInfo(unit)
farmbuyer@10 620 end
farmbuyer@10 621 local connected = UnitIsConnected(unit)
farmbuyer@10 622 if connected and r.online ~= R_ACTIVE then
farmbuyer@10 623 r.join = r.join or now
farmbuyer@10 624 r.online = R_ACTIVE
farmbuyer@10 625 elseif (not connected) and r.online ~= R_OFFLINE then
farmbuyer@10 626 r.leave = now
farmbuyer@10 627 r.online = R_OFFLINE
farmbuyer@10 628 end
farmbuyer@10 629 redo = redo or r.needinfo
farmbuyer@10 630 end
farmbuyer@10 631 end
farmbuyer@16 632 if redo then -- XXX test redo_count here and eventually give up?
farmbuyer@16 633 if not timer_handle then
farmbuyer@16 634 timer_handle = self:ScheduleRepeatingTimer("RAID_ROSTER_UPDATE", 60)
farmbuyer@16 635 end
farmbuyer@16 636 else
farmbuyer@16 637 redo_count = 0
farmbuyer@16 638 if timer_handle then
farmbuyer@16 639 self:CancelTimer(timer_handle)
farmbuyer@16 640 timer_handle = nil
farmbuyer@16 641 end
farmbuyer@10 642 end
farmbuyer@10 643 end
farmbuyer@10 644
farmbuyer@10 645 function addon:RAID_ROSTER_UPDATE (event)
farmbuyer@10 646 if GetNumRaidMembers() == 0 then
farmbuyer@16 647 -- Leaving a raid group.
farmbuyer@10 648 -- Because of PLAYER_ENTERING_WORLD, this code also executes on load
farmbuyer@10 649 -- screens while soloing and in regular groups. Take care.
farmbuyer@16 650 self.dprint('flow', "GetNumRaidMembers == 0")
farmbuyer@16 651 if self.enabled and not self.debug.notraid then
farmbuyer@16 652 self.dprint('flow', "enabled, leaving raid")
farmbuyer@10 653 self.popped = nil
farmbuyer@16 654 self:Deactivate() -- self:UnregisterEvent("CHAT_MSG_LOOT")
farmbuyer@10 655 self:CheckRoster(--[[leaving raid]]true)
farmbuyer@10 656 end
farmbuyer@10 657 return
farmbuyer@10 658 end
farmbuyer@10 659
farmbuyer@1 660 local inside,whatkind = IsInInstance()
farmbuyer@1 661 if inside and (whatkind == "pvp" or whatkind == "arena") then
farmbuyer@1 662 return self.dprint('flow', "got RRU event but in pvp zone, bailing")
farmbuyer@1 663 end
farmbuyer@10 664
farmbuyer@10 665 local docheck = self.enabled
farmbuyer@1 666 if event == "Activate" then
farmbuyer@1 667 -- dispatched manually from Activate
farmbuyer@10 668 self:RegisterEvent("CHAT_MSG_LOOT")
farmbuyer@2 669 _register_bossmod(self)
farmbuyer@10 670 docheck = true
farmbuyer@1 671 elseif event == "RAID_ROSTER_UPDATE" then
farmbuyer@10 672 -- hot code path, be careful
farmbuyer@10 673
farmbuyer@1 674 -- event registration from onload, joined a raid, maybe show popup
farmbuyer@16 675 self.dprint('flow', "RRU check:", self.popped, opts.popup_on_join)
farmbuyer@10 676 if (not self.popped) and opts.popup_on_join then
farmbuyer@1 677 self.popped = StaticPopup_Show "OUROL_REMIND"
farmbuyer@1 678 self.popped.data = self
farmbuyer@10 679 return
farmbuyer@1 680 end
farmbuyer@1 681 end
farmbuyer@11 682 -- Throttle the checks fired by common events.
farmbuyer@10 683 if docheck and not InCombatLockdown() then
farmbuyer@10 684 now = time()
farmbuyer@10 685 if difftime(now,lastevent) > 45 then
farmbuyer@10 686 lastevent = now
farmbuyer@10 687 self:CheckRoster(false,now)
farmbuyer@10 688 end
farmbuyer@10 689 end
farmbuyer@1 690 end
farmbuyer@1 691 end
farmbuyer@1 692
farmbuyer@1 693 -- helper for CHAT_MSG_LOOT handler
farmbuyer@1 694 do
farmbuyer@1 695 -- Recent loot cache
farmbuyer@25 696 local candidates = {}
farmbuyer@25 697 local function prefer_local_loots (cache)
farmbuyer@25 698 -- The function name is a bit of a misnomer, as local entries overwrite
farmbuyer@25 699 -- remote entries as the candidate table is populated. This routine is
farmbuyer@38 700 -- here to extract the results once the cache timers have expired.
farmbuyer@38 701 --
farmbuyer@34 702 -- Keep this sync'd with the local_override branch below.
farmbuyer@25 703 for i,sig in ipairs(candidates) do
farmbuyer@25 704 addon.dprint('loot', "processing candidate entry", i, sig)
farmbuyer@25 705 local loot = candidates[sig]
farmbuyer@25 706 if loot then
farmbuyer@25 707 addon.dprint('loot', i, "was found")
farmbuyer@25 708 candidates[sig] = nil
farmbuyer@25 709 local looti = addon._addLootEntry(loot)
farmbuyer@25 710 if (loot.disposition ~= 'shard')
farmbuyer@25 711 and (loot.disposition ~= 'gvault')
farmbuyer@25 712 and (not addon.history_suppress)
farmbuyer@25 713 then
farmbuyer@25 714 addon:_addHistoryEntry(looti)
farmbuyer@25 715 end
farmbuyer@25 716 end
farmbuyer@25 717 end
farmbuyer@25 718
farmbuyer@25 719 if addon.display then
farmbuyer@25 720 addon:redisplay()
farmbuyer@25 721 end
farmbuyer@25 722 table.wipe(candidates)
farmbuyer@25 723 end
farmbuyer@25 724 addon.recent_loot = create_new_cache ('loot', comm_cleanup_ttl, prefer_local_loots)
farmbuyer@1 725
farmbuyer@6 726 local GetItemInfo, GetItemIcon = GetItemInfo, GetItemIcon
farmbuyer@1 727
farmbuyer@1 728 -- 'from' and onwards only present if this is triggered by a broadcast
farmbuyer@1 729 function addon:_do_loot (local_override, recipient, itemid, count, from, extratext)
farmbuyer@6 730 local itexture = GetItemIcon(itemid)
farmbuyer@6 731 local iname, ilink, iquality = GetItemInfo(itemid)
farmbuyer@19 732 local i
farmbuyer@6 733 if (not iname) or (not itexture) then
farmbuyer@19 734 i = true
farmbuyer@6 735 iname, ilink, iquality, itexture =
farmbuyer@6 736 UNKNOWN..': '..itemid, 'item:6948', ITEM_QUALITY_COMMON, [[ICONS\INV_Misc_QuestionMark]]
farmbuyer@6 737 end
farmbuyer@19 738 self.dprint('loot',">>_do_loot, R:", recipient, "I:", itemid, "C:", count, "frm:", from, "ex:", extratext, "q:", iquality)
farmbuyer@1 739
farmbuyer@19 740 itemid = tonumber(ilink:match("item:(%d+)") or 0)
farmbuyer@25 741 -- This is only a loop to make jumping out of it easy, and still do cleanup below.
farmbuyer@25 742 while local_override or ((iquality >= self.threshold) and not opts.itemfilter[itemid]) do
farmbuyer@1 743 if (self.rebroadcast and (not from)) and not local_override then
farmbuyer@1 744 self:broadcast('loot', recipient, itemid, count)
farmbuyer@1 745 end
farmbuyer@25 746 if (not self.enabled) and (not local_override) then break end
farmbuyer@25 747 local signature = recipient .. iname .. (count or "")
farmbuyer@25 748 if from and self.recent_loot:test(signature) then
farmbuyer@25 749 self.dprint('cache', "loot <",signature,"> already in cache, skipping")
farmbuyer@25 750 else
farmbuyer@25 751 -- There is some redundancy in all this, in the interests of ease-of-coding
farmbuyer@25 752 i = {
farmbuyer@25 753 kind = 'loot',
farmbuyer@25 754 person = recipient,
farmbuyer@25 755 person_class= select(2,UnitClass(recipient)),
farmbuyer@25 756 cache_miss = i and true or nil,
farmbuyer@25 757 quality = iquality,
farmbuyer@25 758 itemname = iname,
farmbuyer@25 759 id = itemid,
farmbuyer@25 760 itemlink = ilink,
farmbuyer@25 761 itexture = itexture,
farmbuyer@25 762 disposition = (recipient == self.sharder) and 'shard' or nil,
farmbuyer@38 763 count = (count and count ~= "") and count or nil,
farmbuyer@25 764 bcast_from = from,
farmbuyer@25 765 extratext = extratext,
farmbuyer@25 766 is_heroic = self:is_heroic_item(ilink),
farmbuyer@25 767 }
farmbuyer@34 768 if local_override then
farmbuyer@34 769 -- adding things by hand, don't wait for network cache timeouts
farmbuyer@34 770 if i.extratext == 'shard'
farmbuyer@34 771 or i.extratext == 'gvault'
farmbuyer@34 772 or i.extratext == 'offspec'
farmbuyer@34 773 then
farmbuyer@34 774 i.disposition = i.extratext
farmbuyer@34 775 end
farmbuyer@34 776 local looti = self._addLootEntry(i)
farmbuyer@34 777 if (i.disposition ~= 'shard')
farmbuyer@34 778 and (i.disposition ~= 'gvault')
farmbuyer@34 779 and (not self.history_suppress)
farmbuyer@34 780 then
farmbuyer@34 781 self:_addHistoryEntry(looti)
farmbuyer@34 782 end
farmbuyer@34 783 i = looti -- mostly for gui's manual entry
farmbuyer@34 784 else
farmbuyer@34 785 self.recent_loot:add(signature)
farmbuyer@34 786 candidates[signature] = i
farmbuyer@34 787 tinsert (candidates, signature)
farmbuyer@34 788 self.dprint('cache', "loot <",signature,"> added to cache, candidate", #candidates)
farmbuyer@34 789 end
farmbuyer@1 790 end
farmbuyer@25 791 break
farmbuyer@1 792 end
farmbuyer@1 793 self.dprint('loot',"<<_do_loot out")
farmbuyer@1 794 return i
farmbuyer@1 795 end
farmbuyer@1 796
farmbuyer@1 797 function addon:CHAT_MSG_LOOT (event, ...)
farmbuyer@1 798 if (not self.rebroadcast) and (not self.enabled) and (event ~= "manual") then return end
farmbuyer@1 799
farmbuyer@1 800 --[[
farmbuyer@1 801 iname: Hearthstone
farmbuyer@1 802 iquality: integer
farmbuyer@1 803 ilink: clickable formatted link
farmbuyer@1 804 itemstring: item:6948:....
farmbuyer@1 805 itexture: inventory icon texture
farmbuyer@1 806 ]]
farmbuyer@1 807
farmbuyer@1 808 if event == "CHAT_MSG_LOOT" then
farmbuyer@1 809 local msg = ...
farmbuyer@20 810 local person, itemstring, count
farmbuyer@21 811 --ChatFrame2:AddMessage("original string: >"..(msg:gsub("\124","\124\124")).."<")
farmbuyer@20 812
farmbuyer@20 813 -- test in most likely order: other people get more loot than "you" do
farmbuyer@20 814 person, itemstring, count = msg:match(g_LOOT_ITEM_MULTIPLE_sss)
farmbuyer@20 815 if not person then
farmbuyer@20 816 person, itemstring = msg:match(g_LOOT_ITEM_ss)
farmbuyer@20 817 end
farmbuyer@20 818 if not person then
farmbuyer@20 819 itemstring, count = msg:match(g_LOOT_ITEM_SELF_MULTIPLE_ss)
farmbuyer@20 820 if not itemstring then
farmbuyer@20 821 itemstring = msg:match(g_LOOT_ITEM_SELF_s)
farmbuyer@20 822 end
farmbuyer@20 823 end
farmbuyer@20 824
farmbuyer@20 825 self.dprint('loot', "CHAT_MSG_LOOT, person is", person, ", itemstring is", itemstring, ", count is", count)
farmbuyer@20 826 if not itemstring then return end -- "So-and-So selected Greed", etc, not actual looting
farmbuyer@1 827
farmbuyer@1 828 -- Name might be colorized, remove the highlighting
farmbuyer@20 829 if person then
farmbuyer@20 830 person = person:match("|c%x%x%x%x%x%x%x%x(%S+)") or person
farmbuyer@20 831 else
farmbuyer@38 832 person = my_name -- UNIT_YOU / You
farmbuyer@20 833 end
farmbuyer@1 834
farmbuyer@20 835 --local id = tonumber((select(2, strsplit(":", itemstring))))
farmbuyer@20 836 local id = tonumber(itemstring:match('|Hitem:(%d+):'))
farmbuyer@1 837
farmbuyer@1 838 return self:_do_loot (false, person, id, count)
farmbuyer@1 839
farmbuyer@1 840 elseif event == "broadcast" then
farmbuyer@1 841 return self:_do_loot(false, ...)
farmbuyer@1 842
farmbuyer@1 843 elseif event == "manual" then
farmbuyer@1 844 local r,i,n = ...
farmbuyer@1 845 return self:_do_loot(true, r,i,nil,nil,n)
farmbuyer@1 846 end
farmbuyer@1 847 end
farmbuyer@1 848 end
farmbuyer@1 849
farmbuyer@1 850
farmbuyer@1 851 ------ Slash command handler
farmbuyer@1 852 -- Thought about breaking this up into a table-driven dispatcher. But
farmbuyer@1 853 -- that would result in a pile of teensy functions, most of which would
farmbuyer@1 854 -- never be called. Too much overhead. (2.0: Most of these removed now
farmbuyer@1 855 -- that GUI is in place.)
farmbuyer@1 856 function addon:OnSlash (txt) --, editbox)
farmbuyer@1 857 txt = strtrim(txt:lower())
farmbuyer@1 858 local cmd, arg = ""
farmbuyer@1 859 do
farmbuyer@1 860 local s,e = txt:find("^%a+")
farmbuyer@1 861 if s then
farmbuyer@1 862 cmd = txt:sub(s,e)
farmbuyer@1 863 s = txt:find("%S", e+2)
farmbuyer@1 864 if s then arg = txt:sub(s,-1) end
farmbuyer@1 865 end
farmbuyer@1 866 end
farmbuyer@1 867
farmbuyer@1 868 if cmd == "" then
farmbuyer@1 869 if InCombatLockdown() then
farmbuyer@1 870 return self:Print("Can't display window in combat.")
farmbuyer@1 871 else
farmbuyer@1 872 return self:BuildMainDisplay()
farmbuyer@1 873 end
farmbuyer@1 874
farmbuyer@1 875 elseif cmd:find("^thre") then
farmbuyer@1 876 self:SetThreshold(arg)
farmbuyer@1 877
farmbuyer@1 878 elseif cmd == "on" then self:Activate(arg)
farmbuyer@1 879 elseif cmd == "off" then self:Deactivate()
farmbuyer@1 880 elseif cmd == "broadcast" or cmd == "bcast" then self:Activate(nil,true)
farmbuyer@1 881
farmbuyer@23 882 elseif cmd == "toggle" then
farmbuyer@23 883 if self.display then
farmbuyer@23 884 self.display:Hide()
farmbuyer@23 885 else
farmbuyer@23 886 return self:BuildMainDisplay()
farmbuyer@23 887 end
farmbuyer@23 888
farmbuyer@1 889 elseif cmd == "fake" then -- maybe comment this out for real users
farmbuyer@1 890 self:_mark_boss_kill (self._addLootEntry{
farmbuyer@1 891 kind='boss',reason='kill',bosskill="Baron Steamroller",instance=instance_tag(),duration=0
farmbuyer@1 892 })
farmbuyer@1 893 self:CHAT_MSG_LOOT ('manual', my_name, 54797)
farmbuyer@1 894 if self.display then
farmbuyer@1 895 self:redisplay()
farmbuyer@1 896 end
farmbuyer@1 897 self:Print "Baron Steamroller has been slain. Congratulations on your rug."
farmbuyer@1 898
farmbuyer@1 899 elseif cmd == "debug" then
farmbuyer@1 900 if arg then
farmbuyer@1 901 self.debug[arg] = not self.debug[arg]
farmbuyer@1 902 _G.print(arg,self.debug[arg])
farmbuyer@1 903 if self.debug[arg] then self.DEBUG_PRINT = true end
farmbuyer@1 904 else
farmbuyer@1 905 self.DEBUG_PRINT = not self.DEBUG_PRINT
farmbuyer@1 906 end
farmbuyer@1 907
farmbuyer@1 908 elseif cmd == "save" and arg and arg:len() > 0 then
farmbuyer@1 909 self:save_saveas(arg)
farmbuyer@1 910 elseif cmd == "list" then
farmbuyer@1 911 self:save_list()
farmbuyer@1 912 elseif cmd == "restore" and arg and arg:len() > 0 then
farmbuyer@1 913 self:save_restore(tonumber(arg))
farmbuyer@1 914 elseif cmd == "delete" and arg and arg:len() > 0 then
farmbuyer@1 915 self:save_delete(tonumber(arg))
farmbuyer@1 916
farmbuyer@1 917 elseif cmd == "help" then
farmbuyer@1 918 self:BuildMainDisplay('help')
farmbuyer@23 919 elseif cmd == "ping" then
farmbuyer@23 920 self:DoPing()
farmbuyer@1 921
farmbuyer@19 922 elseif cmd == "fixcache" then
farmbuyer@19 923 self:do_item_cache_fixup()
farmbuyer@19 924
farmbuyer@1 925 else
farmbuyer@1 926 if self:OpenMainDisplayToTab(cmd) then
farmbuyer@1 927 return
farmbuyer@1 928 end
farmbuyer@1 929 self:Print("Unknown command '%s'. %s to see the help window.",
farmbuyer@1 930 cmd, self.format_hypertext('help',"Click here",ITEM_QUALITY_UNCOMMON))
farmbuyer@1 931 end
farmbuyer@1 932 end
farmbuyer@1 933
farmbuyer@1 934 function addon:SetThreshold (arg, quiet_p)
farmbuyer@1 935 local q = tonumber(arg)
farmbuyer@1 936 if q then
farmbuyer@1 937 q = math.floor(q+0.001)
farmbuyer@1 938 if q<0 or q>6 then
farmbuyer@1 939 return self:Print("Threshold must be 0-6.")
farmbuyer@1 940 end
farmbuyer@1 941 else
farmbuyer@1 942 q = qualnames[arg]
farmbuyer@1 943 if not q then
farmbuyer@1 944 return self:Print("Unrecognized item quality argument.")
farmbuyer@1 945 end
farmbuyer@1 946 end
farmbuyer@1 947 self.threshold = q
farmbuyer@1 948 if not quiet_p then self:Print("Threshold now set to %s.", self.thresholds[q]) end
farmbuyer@1 949 end
farmbuyer@1 950
farmbuyer@1 951
farmbuyer@1 952 ------ On/off
farmbuyer@1 953 function addon:Activate (opt_threshold, opt_bcast_only)
farmbuyer@16 954 self.dprint('flow', ":Activate is running")
farmbuyer@10 955 self:RegisterEvent("RAID_ROSTER_UPDATE")
farmbuyer@16 956 self:RegisterEvent("PLAYER_ENTERING_WORLD",
farmbuyer@16 957 function() self:ScheduleTimer("RAID_ROSTER_UPDATE", 5, "PLAYER_ENTERING_WORLD") end)
farmbuyer@1 958 self.popped = true
farmbuyer@1 959 if GetNumRaidMembers() > 0 then
farmbuyer@16 960 self.dprint('flow', ">:Activate calling RRU")
farmbuyer@1 961 self:RAID_ROSTER_UPDATE("Activate")
farmbuyer@1 962 elseif self.debug.notraid then
farmbuyer@16 963 self.dprint('flow', ">:Activate registering loot and bossmods")
farmbuyer@10 964 self:RegisterEvent("CHAT_MSG_LOOT")
farmbuyer@2 965 _register_bossmod(self)
farmbuyer@1 966 elseif g_restore_p then
farmbuyer@1 967 g_restore_p = nil
farmbuyer@16 968 self.popped = nil -- get the reminder if later joining a raid
farmbuyer@16 969 if #g_loot == 0 then
farmbuyer@16 970 -- only generated text and raider join/leave data, not worth verbage
farmbuyer@16 971 self.dprint('flow', ">:Activate restored generated texts, un-popping")
farmbuyer@16 972 return
farmbuyer@16 973 end
farmbuyer@1 974 self:Print("Ouro Raid Loot restored previous data, but not in a raid",
farmbuyer@1 975 "and 5-person mode not active. |cffff0505NOT tracking loot|r;",
farmbuyer@1 976 "use 'enable' to activate loot tracking, or 'clear' to erase",
farmbuyer@1 977 "previous data, or 'help' to read about saved-texts commands.")
farmbuyer@1 978 return
farmbuyer@1 979 end
farmbuyer@1 980 self.rebroadcast = true -- hardcode to true; this used to be more complicated
farmbuyer@1 981 self.enabled = not opt_bcast_only
farmbuyer@1 982 if opt_threshold then
farmbuyer@1 983 self:SetThreshold (opt_threshold, --[[quiet_p=]]true)
farmbuyer@1 984 end
farmbuyer@1 985 self:Print("Ouro Raid Loot is %s. Threshold currently %s.",
farmbuyer@1 986 self.enabled and "tracking" or "only broadcasting",
farmbuyer@1 987 self.thresholds[self.threshold])
farmbuyer@27 988 self:broadcast('revcheck',revision_large)
farmbuyer@1 989 end
farmbuyer@1 990
farmbuyer@1 991 -- Note: running '/loot off' will also avoid the popup reminder when
farmbuyer@1 992 -- joining a raid, but will not change the saved option setting.
farmbuyer@1 993 function addon:Deactivate()
farmbuyer@1 994 self.enabled = false
farmbuyer@1 995 self.rebroadcast = false
farmbuyer@10 996 self:UnregisterEvent("RAID_ROSTER_UPDATE")
farmbuyer@10 997 self:UnregisterEvent("PLAYER_ENTERING_WORLD")
farmbuyer@10 998 self:UnregisterEvent("CHAT_MSG_LOOT")
farmbuyer@1 999 self:Print("Ouro Raid Loot deactivated.")
farmbuyer@1 1000 end
farmbuyer@1 1001
farmbuyer@1 1002 function addon:Clear(verbose_p)
farmbuyer@1 1003 local repopup, st
farmbuyer@1 1004 if self.display then
farmbuyer@1 1005 -- in the new version, this is likely to always be the case
farmbuyer@1 1006 repopup = true
farmbuyer@1 1007 st = self.display:GetUserData("eoiST")
farmbuyer@1 1008 if not st then
farmbuyer@1 1009 self.dprint('flow', "Clear: display visible but eoiST not set??")
farmbuyer@1 1010 end
farmbuyer@1 1011 self.display:Hide()
farmbuyer@1 1012 end
farmbuyer@1 1013 g_restore_p = nil
farmbuyer@1 1014 OuroLootSV = nil
farmbuyer@1 1015 self:_reset_timestamps()
farmbuyer@1 1016 if verbose_p then
farmbuyer@16 1017 if (OuroLootSV_saved and #OuroLootSV_saved>0) then
farmbuyer@16 1018 self:Print("Current loot data cleared, %d saved sets remaining.", #OuroLootSV_saved)
farmbuyer@1 1019 else
farmbuyer@1 1020 self:Print("Current loot data cleared.")
farmbuyer@1 1021 end
farmbuyer@1 1022 end
farmbuyer@1 1023 _init(self,st)
farmbuyer@1 1024 if repopup then
farmbuyer@1 1025 addon:BuildMainDisplay()
farmbuyer@1 1026 end
farmbuyer@1 1027 end
farmbuyer@1 1028
farmbuyer@1 1029
farmbuyer@1 1030 ------ Behind the scenes routines
farmbuyer@19 1031 -- Semi-experimental debugging aid.
farmbuyer@19 1032 do
farmbuyer@19 1033 local date = _G.date
farmbuyer@19 1034 local log = OuroLootSV_log
farmbuyer@19 1035 function addon:log_with_timestamp (msg)
farmbuyer@19 1036 tinsert (log, date('%m:%d %H:%M:%S ')..msg)
farmbuyer@19 1037 end
farmbuyer@19 1038 end
farmbuyer@19 1039
farmbuyer@1 1040 -- Adds indices to traverse the tables in a nice sorted order.
farmbuyer@1 1041 do
farmbuyer@1 1042 local byindex, temp = {}, {}
farmbuyer@1 1043 local function sort (src, dest)
farmbuyer@1 1044 for k in pairs(src) do
farmbuyer@1 1045 temp[#temp+1] = k
farmbuyer@1 1046 end
farmbuyer@1 1047 table.sort(temp)
farmbuyer@1 1048 table.wipe(dest)
farmbuyer@1 1049 for i = 1, #temp do
farmbuyer@1 1050 dest[i] = src[temp[i]]
farmbuyer@1 1051 end
farmbuyer@1 1052 end
farmbuyer@1 1053
farmbuyer@1 1054 function addon.sender_list.sort()
farmbuyer@1 1055 sort (addon.sender_list.active, byindex)
farmbuyer@1 1056 table.wipe(temp)
farmbuyer@1 1057 addon.sender_list.activeI = #byindex
farmbuyer@1 1058 sort (addon.sender_list.names, byindex)
farmbuyer@1 1059 table.wipe(temp)
farmbuyer@1 1060 end
farmbuyer@1 1061 addon.sender_list.namesI = byindex
farmbuyer@1 1062 end
farmbuyer@1 1063
farmbuyer@1 1064 -- Message sending.
farmbuyer@1 1065 -- See OCR_funcs.tag at the end of this file for incoming message treatment.
farmbuyer@1 1066 do
farmbuyer@1 1067 local function assemble(...)
farmbuyer@1 1068 local msg = ...
farmbuyer@1 1069 for i = 2, select('#',...) do
farmbuyer@1 1070 msg = msg .. '\a' .. (select(i,...) or "")
farmbuyer@1 1071 end
farmbuyer@1 1072 return msg
farmbuyer@1 1073 end
farmbuyer@1 1074
farmbuyer@1 1075 -- broadcast('tag', <stuff>)
farmbuyer@1 1076 function addon:broadcast(...)
farmbuyer@1 1077 local msg = assemble(...)
farmbuyer@1 1078 self.dprint('comm', "<broadcast>:", msg)
farmbuyer@1 1079 -- the "GUILD" here is just so that we can also pick up on it
farmbuyer@1 1080 self:SendCommMessage(self.ident, msg, self.debug.comm and "GUILD" or "RAID")
farmbuyer@1 1081 end
farmbuyer@1 1082 -- whispercast(<to>, 'tag', <stuff>)
farmbuyer@1 1083 function addon:whispercast(to,...)
farmbuyer@1 1084 local msg = assemble(...)
farmbuyer@1 1085 self.dprint('comm', "<whispercast>@", to, ":", msg)
farmbuyer@1 1086 self:SendCommMessage(self.identTg, msg, "WHISPER", to)
farmbuyer@1 1087 end
farmbuyer@1 1088 end
farmbuyer@1 1089
farmbuyer@23 1090 function addon:DoPing()
farmbuyer@23 1091 self:Print("Give me a ping, Vasili. One ping only, please.")
farmbuyer@23 1092 self.sender_list.active = {}
farmbuyer@23 1093 self.sender_list.names = {}
farmbuyer@23 1094 self:broadcast('ping')
farmbuyer@27 1095 self:broadcast('revcheck',revision_large)
farmbuyer@27 1096 end
farmbuyer@27 1097
farmbuyer@27 1098 function addon:_check_revision (otherrev)
farmbuyer@27 1099 self.dprint('comm', "revchecking against", otherrev)
farmbuyer@27 1100 otherrev = tonumber(otherrev)
farmbuyer@27 1101 if otherrev == revision_large then
farmbuyer@27 1102 -- normal case
farmbuyer@27 1103
farmbuyer@27 1104 elseif otherrev < revision_large then
farmbuyer@27 1105 self.dprint('comm', "ours is newer, notifying")
farmbuyer@27 1106 self:broadcast('revcheck',revision_large)
farmbuyer@27 1107
farmbuyer@27 1108 else
farmbuyer@27 1109 self.dprint('comm', "ours is older, yammering")
farmbuyer@27 1110 if newer_warning then
farmbuyer@27 1111 self:Print(newer_warning,
farmbuyer@29 1112 self.format_hypertext('popupurl',"click here",ITEM_QUALITY_UNCOMMON),
farmbuyer@27 1113 self.format_hypertext('doping',"click here",ITEM_QUALITY_UNCOMMON))
farmbuyer@27 1114 newer_warning = nil
farmbuyer@27 1115 end
farmbuyer@27 1116 end
farmbuyer@23 1117 end
farmbuyer@23 1118
farmbuyer@1 1119 -- Generic helpers
farmbuyer@28 1120 -- Returns index and entry at that index, or nil if not found.
farmbuyer@1 1121 function addon._find_next_after (kind, index)
farmbuyer@1 1122 index = index + 1
farmbuyer@1 1123 while index <= #g_loot do
farmbuyer@1 1124 if g_loot[index].kind == kind then
farmbuyer@1 1125 return index, g_loot[index]
farmbuyer@1 1126 end
farmbuyer@1 1127 index = index + 1
farmbuyer@1 1128 end
farmbuyer@1 1129 end
farmbuyer@28 1130 -- Essentially a _find_next_after('time'-or-'boss'), but if KIND is
farmbuyer@28 1131 -- 'boss', will also stop upon finding a timestamp. Returns nil if
farmbuyer@28 1132 -- appropriate fencepost is not found.
farmbuyer@28 1133 function addon._find_timeboss_fencepost (kind, index)
farmbuyer@28 1134 local fencepost
farmbuyer@28 1135 local closest_time = addon._find_next_after('time',index)
farmbuyer@28 1136 if kind == 'time' then
farmbuyer@28 1137 fencepost = closest_time
farmbuyer@28 1138 elseif kind == 'boss' then
farmbuyer@28 1139 local closest_boss = addon._find_next_after('boss',index)
farmbuyer@28 1140 if not closest_boss then
farmbuyer@28 1141 fencepost = closest_time
farmbuyer@28 1142 elseif not closest_time then
farmbuyer@28 1143 fencepost = closest_boss
farmbuyer@28 1144 else
farmbuyer@28 1145 fencepost = math.min(closest_time,closest_boss)
farmbuyer@28 1146 end
farmbuyer@28 1147 end
farmbuyer@28 1148 return fencepost
farmbuyer@28 1149 end
farmbuyer@1 1150
farmbuyer@1 1151 -- Iterate through g_loot entries according to the KIND field. Loop variables
farmbuyer@1 1152 -- are g_loot indices and the corresponding entries (essentially ipairs + some
farmbuyer@1 1153 -- conditionals).
farmbuyer@1 1154 function addon:filtered_loot_iter (filter_kind)
farmbuyer@1 1155 return self._find_next_after, filter_kind, 0
farmbuyer@1 1156 end
farmbuyer@1 1157
farmbuyer@1 1158 do
farmbuyer@1 1159 local itt
farmbuyer@1 1160 local function create()
farmbuyer@1 1161 local tip, lefts = CreateFrame("GameTooltip"), {}
farmbuyer@1 1162 for i = 1, 2 do -- scanning idea here also snagged from Talented
farmbuyer@1 1163 local L,R = tip:CreateFontString(), tip:CreateFontString()
farmbuyer@1 1164 L:SetFontObject(GameFontNormal)
farmbuyer@1 1165 R:SetFontObject(GameFontNormal)
farmbuyer@1 1166 tip:AddFontStrings(L,R)
farmbuyer@1 1167 lefts[i] = L
farmbuyer@1 1168 end
farmbuyer@1 1169 tip.lefts = lefts
farmbuyer@1 1170 return tip
farmbuyer@1 1171 end
farmbuyer@1 1172 function addon:is_heroic_item(item) -- returns true or *nil*
farmbuyer@1 1173 itt = itt or create()
farmbuyer@1 1174 itt:SetOwner(UIParent,"ANCHOR_NONE")
farmbuyer@1 1175 itt:ClearLines()
farmbuyer@1 1176 itt:SetHyperlink(item)
farmbuyer@1 1177 local t = itt.lefts[2]:GetText()
farmbuyer@1 1178 itt:Hide()
farmbuyer@1 1179 return (t == ITEM_HEROIC) or nil
farmbuyer@1 1180 end
farmbuyer@1 1181 end
farmbuyer@1 1182
farmbuyer@1 1183 -- Called when first loading up, and then also when a 'clear' is being
farmbuyer@16 1184 -- performed. If SV's are present then g_restore_p will be true.
farmbuyer@1 1185 function _init (self, possible_st)
farmbuyer@1 1186 self.dprint('flow',"_init running")
farmbuyer@1 1187 self.loot_clean = nil
farmbuyer@1 1188 self.hist_clean = nil
farmbuyer@1 1189 if g_restore_p then
farmbuyer@1 1190 g_loot = OuroLootSV
farmbuyer@16 1191 self.popped = #g_loot > 0
farmbuyer@1 1192 self.dprint('flow', "restoring", #g_loot, "entries")
farmbuyer@16 1193 self:ScheduleTimer("Activate", 12, opts.threshold)
farmbuyer@1 1194 -- FIXME printed could be too large if entries were deleted, how much do we care?
farmbuyer@16 1195 self.sharder = opts.autoshard
farmbuyer@1 1196 else
farmbuyer@10 1197 g_loot = { printed = {}, raiders = {} }
farmbuyer@1 1198 end
farmbuyer@1 1199
farmbuyer@16 1200 self.threshold = opts.threshold or self.threshold -- in the case of restoring but not tracking
farmbuyer@1 1201 self:gui_init(g_loot)
farmbuyer@16 1202 opts.autoshard = nil
farmbuyer@16 1203 opts.threshold = nil
farmbuyer@1 1204
farmbuyer@1 1205 if g_restore_p then
farmbuyer@1 1206 self:zero_printed_fenceposts() -- g_loot.printed.* = previous/safe values
farmbuyer@1 1207 else
farmbuyer@1 1208 self:zero_printed_fenceposts(0) -- g_loot.printed.* = 0
farmbuyer@1 1209 end
farmbuyer@1 1210 if possible_st then
farmbuyer@1 1211 possible_st:SetData(g_loot)
farmbuyer@1 1212 end
farmbuyer@1 1213
farmbuyer@17 1214 self.status_text = ("%s communicating as ident %s commrev %d"):format(self.revision,self.ident,self.commrev)
farmbuyer@1 1215 self:RegisterComm(self.ident)
farmbuyer@1 1216 self:RegisterComm(self.identTg, "OnCommReceivedNocache")
farmbuyer@1 1217
farmbuyer@1 1218 if self.author_debug then
farmbuyer@1 1219 _G.OL = self
farmbuyer@1 1220 _G.Oloot = g_loot
farmbuyer@1 1221 end
farmbuyer@1 1222 end
farmbuyer@1 1223
farmbuyer@25 1224 -- Tie-in with Deadly Boss Mods (or other such addons)
farmbuyer@1 1225 do
farmbuyer@25 1226 local candidates = {}
farmbuyer@25 1227 local location
farmbuyer@1 1228 local function fixup_durations (cache)
farmbuyer@1 1229 local boss, bossi
farmbuyer@1 1230 boss = candidates[1]
farmbuyer@1 1231 if #candidates == 1 then
farmbuyer@1 1232 -- (1) or (2)
farmbuyer@1 1233 boss.duration = boss.duration or 0
farmbuyer@19 1234 addon.dprint('loot', "only one boss candidate")
farmbuyer@1 1235 else
farmbuyer@1 1236 -- (3), should only be one 'cast entry and our local entry
farmbuyer@1 1237 if #candidates ~= 2 then
farmbuyer@1 1238 -- could get a bunch of 'cast entries on the heels of one another
farmbuyer@1 1239 -- before the local one ever fires, apparently... sigh
farmbuyer@1 1240 --addon:Print("<warning> s3 cache has %d entries, does that seem right to you?", #candidates)
farmbuyer@1 1241 end
farmbuyer@1 1242 if candidates[2].duration == nil then
farmbuyer@1 1243 --addon:Print("<warning> s3's second entry is not the local trigger, does that seem right to you?")
farmbuyer@1 1244 end
farmbuyer@1 1245 -- try and be generic anyhow
farmbuyer@1 1246 for i,c in ipairs(candidates) do
farmbuyer@1 1247 if c.duration then
farmbuyer@1 1248 boss = c
farmbuyer@19 1249 addon.dprint('loot', "fixup found boss candidate", i, "duration", c.duration)
farmbuyer@1 1250 break
farmbuyer@1 1251 end
farmbuyer@1 1252 end
farmbuyer@1 1253 end
farmbuyer@1 1254 bossi = addon._addLootEntry(boss)
farmbuyer@19 1255 -- addon.
farmbuyer@19 1256 bossi = addon._adjustBossOrder (bossi, g_boss_signpost) or bossi
farmbuyer@16 1257 g_boss_signpost = nil
farmbuyer@19 1258 addon.dprint('loot', "added boss entry", bossi)
farmbuyer@1 1259 if boss.reason == 'kill' then
farmbuyer@1 1260 addon:_mark_boss_kill (bossi)
farmbuyer@1 1261 if opts.chatty_on_kill then
farmbuyer@1 1262 addon:Print("Registered kill for '%s' in %s!", boss.bosskill, boss.instance)
farmbuyer@1 1263 end
farmbuyer@1 1264 end
farmbuyer@25 1265 table.wipe(candidates)
farmbuyer@1 1266 end
farmbuyer@1 1267 addon.recent_boss = create_new_cache ('boss', 10, fixup_durations)
farmbuyer@1 1268
farmbuyer@1 1269 -- Similar to _do_loot, but duration+ parms only present when locally generated.
farmbuyer@1 1270 local function _do_boss (self, reason, bossname, intag, duration, raiders)
farmbuyer@1 1271 self.dprint('loot',">>_do_boss, R:", reason, "B:", bossname, "T:", intag,
farmbuyer@1 1272 "D:", duration, "RL:", (raiders and #raiders or 'nil'))
farmbuyer@1 1273 if self.rebroadcast and duration then
farmbuyer@1 1274 self:broadcast('boss', reason, bossname, intag)
farmbuyer@1 1275 end
farmbuyer@1 1276 -- This is only a loop to make jumping out of it easy, and still do cleanup below.
farmbuyer@1 1277 while self.enabled do
farmbuyer@1 1278 if reason == 'wipe' and opts.no_tracking_wipes then break end
farmbuyer@1 1279 bossname = (opts.snarky_boss and self.boss_abbrev[bossname] or bossname) or bossname
farmbuyer@1 1280 local not_from_local = duration == nil
farmbuyer@1 1281 local signature = bossname .. reason
farmbuyer@1 1282 if not_from_local and self.recent_boss:test(signature) then
farmbuyer@1 1283 self.dprint('cache', "boss <",signature,"> already in cache, skipping")
farmbuyer@1 1284 else
farmbuyer@1 1285 self.recent_boss:add(signature)
farmbuyer@16 1286 g_boss_signpost = #g_loot + 1
farmbuyer@19 1287 self.dprint('loot', "added boss signpost", g_boss_signpost)
farmbuyer@1 1288 -- Possible scenarios: (1) we don't see a boss event at all (e.g., we're
farmbuyer@1 1289 -- outside the instance) and so this only happens once as a non-local event,
farmbuyer@1 1290 -- (2) we see a local event first and all non-local events are filtered
farmbuyer@1 1291 -- by the cache, (3) we happen to get some non-local events before doing
farmbuyer@1 1292 -- our local event (not because of network weirdness but because our local
farmbuyer@1 1293 -- DBM might not trigger for a while).
farmbuyer@1 1294 local c = {
farmbuyer@1 1295 kind = 'boss',
farmbuyer@1 1296 bosskill = bossname, -- minor misnomer, might not actually be a kill
farmbuyer@1 1297 reason = reason,
farmbuyer@1 1298 instance = intag,
farmbuyer@1 1299 duration = duration, -- these two deliberately may be nil
farmbuyer@1 1300 raiderlist = raiders and table.concat(raiders, ", ")
farmbuyer@1 1301 }
farmbuyer@1 1302 tinsert(candidates,c)
farmbuyer@1 1303 end
farmbuyer@17 1304 break
farmbuyer@1 1305 end
farmbuyer@1 1306 self.dprint('loot',"<<_do_boss out")
farmbuyer@1 1307 end
farmbuyer@1 1308 -- No wrapping layer for now
farmbuyer@1 1309 addon.on_boss_broadcast = _do_boss
farmbuyer@1 1310
farmbuyer@1 1311 function addon:_mark_boss_kill (index)
farmbuyer@1 1312 local e = g_loot[index]
farmbuyer@17 1313 if not e then
farmbuyer@17 1314 self:Print("Something horribly wrong;", index, "is not a valid entry!")
farmbuyer@17 1315 return
farmbuyer@17 1316 end
farmbuyer@1 1317 if not e.bosskill then
farmbuyer@16 1318 self:Print("Something horribly wrong;", index, "is not a boss entry!")
farmbuyer@16 1319 return
farmbuyer@1 1320 end
farmbuyer@1 1321 if e.reason ~= 'wipe' then
farmbuyer@1 1322 -- enh, bail
farmbuyer@1 1323 self.loot_clean = index-1
farmbuyer@1 1324 end
farmbuyer@1 1325 local attempts = 1
farmbuyer@1 1326 local first
farmbuyer@1 1327
farmbuyer@1 1328 local i,d = 1,g_loot[1]
farmbuyer@1 1329 while d ~= e do
farmbuyer@1 1330 if d.bosskill and
farmbuyer@1 1331 d.bosskill == e.bosskill and
farmbuyer@1 1332 d.reason == 'wipe'
farmbuyer@1 1333 then
farmbuyer@1 1334 first = first or i
farmbuyer@1 1335 attempts = attempts + 1
farmbuyer@1 1336 assert(tremove(g_loot,i)==d,"_mark_boss_kill screwed up data badly")
farmbuyer@1 1337 else
farmbuyer@1 1338 i = i + 1
farmbuyer@1 1339 end
farmbuyer@1 1340 d = g_loot[i]
farmbuyer@1 1341 end
farmbuyer@1 1342 e.reason = 'kill'
farmbuyer@1 1343 e.attempts = attempts
farmbuyer@1 1344 self.loot_clean = first or index-1
farmbuyer@1 1345 end
farmbuyer@1 1346
farmbuyer@2 1347 function addon:register_boss_mod (name, registration_func, deregistration_func)
farmbuyer@2 1348 assert(type(name)=='string')
farmbuyer@2 1349 assert(type(registration_func)=='function')
farmbuyer@2 1350 if deregistration_func ~= nil then assert(type(deregistration_func)=='function') end
farmbuyer@2 1351 self.bossmods[#self.bossmods+1] = {
farmbuyer@2 1352 n = name,
farmbuyer@2 1353 r = registration_func,
farmbuyer@2 1354 d = deregistration_func,
farmbuyer@2 1355 }
farmbuyer@5 1356 self.bossmods[name] = self.bossmods[#self.bossmods]
farmbuyer@5 1357 assert(self.bossmods[name].n == self.bossmods[#self.bossmods].n)
farmbuyer@2 1358 end
farmbuyer@1 1359
farmbuyer@2 1360 function _register_bossmod (self, force_p)
farmbuyer@2 1361 local x = self.bossmod_registered and self.bossmods[self.bossmod_registered]
farmbuyer@2 1362 if x then
farmbuyer@2 1363 if x.n == opts.bossmod and not force_p then
farmbuyer@2 1364 -- trying to register with already-registered boss mod
farmbuyer@2 1365 return
farmbuyer@2 1366 else
farmbuyer@2 1367 -- deregister
farmbuyer@2 1368 if x.d then x.d(self) end
farmbuyer@2 1369 end
farmbuyer@1 1370 end
farmbuyer@1 1371
farmbuyer@2 1372 x = nil
farmbuyer@2 1373 for k,v in ipairs(self.bossmods) do
farmbuyer@2 1374 if v.n == opts.bossmod then
farmbuyer@2 1375 x = k
farmbuyer@2 1376 break
farmbuyer@2 1377 end
farmbuyer@1 1378 end
farmbuyer@1 1379
farmbuyer@2 1380 if not x then
farmbuyer@2 1381 self.status_text = "|cffff1010No boss-mod found!|r"
farmbuyer@2 1382 self:Print(self.status_text)
farmbuyer@2 1383 return
farmbuyer@1 1384 end
farmbuyer@1 1385
farmbuyer@2 1386 if self.bossmods[x].r (self, _do_boss) then
farmbuyer@5 1387 --self.bossmod_registered = x
farmbuyer@5 1388 self.bossmod_registered = self.bossmods[x].n
farmbuyer@1 1389 else
farmbuyer@2 1390 self:Print("|cffff1010Boss mod registration failed|r")
farmbuyer@1 1391 end
farmbuyer@1 1392 end
farmbuyer@2 1393 end
farmbuyer@1 1394
farmbuyer@1 1395 -- Adding entries to the loot record, and tracking the corresponding timestamp.
farmbuyer@1 1396 do
farmbuyer@1 1397 -- This shouldn't be required. /sadface
farmbuyer@1 1398 local loot_entry_mt = {
farmbuyer@1 1399 __index = function (e,key)
farmbuyer@1 1400 if key == 'cols' then
farmbuyer@15 1401 pprint('mt', e.kind, "key is", key)
farmbuyer@1 1402 --tabledump(e) -- not actually that useful
farmbuyer@1 1403 addon:_fill_out_eoi_data(1)
farmbuyer@1 1404 end
farmbuyer@1 1405 return rawget(e,key)
farmbuyer@1 1406 end
farmbuyer@1 1407 }
farmbuyer@1 1408
farmbuyer@1 1409 -- Given a loot index, searches backwards for a timestamp. Returns that
farmbuyer@1 1410 -- index and the time entry, or nil if it falls off the beginning. Pass an
farmbuyer@1 1411 -- optional second index to search no earlier than it.
farmbuyer@1 1412 -- May also be able to make good use of this in forum-generation routine.
farmbuyer@1 1413 function addon:find_previous_time_entry(i,stop)
farmbuyer@17 1414 stop = stop or 0
farmbuyer@1 1415 while i > stop do
farmbuyer@1 1416 if g_loot[i].kind == 'time' then
farmbuyer@1 1417 return i, g_loot[i]
farmbuyer@1 1418 end
farmbuyer@1 1419 i = i - 1
farmbuyer@1 1420 end
farmbuyer@1 1421 end
farmbuyer@1 1422
farmbuyer@1 1423 -- format_timestamp (["format_string"], Day, [Loot])
farmbuyer@1 1424 -- DAY is a loot entry with kind=='time', and controls the date printed.
farmbuyer@1 1425 -- LOOT may be any kind of entry in the g_loot table. If present, it
farmbuyer@1 1426 -- overrides the hour and minute printed; if absent, those values are
farmbuyer@1 1427 -- taken from the DAY entry.
farmbuyer@1 1428 -- FORMAT_STRING may contain $x (x in Y/M/D/h/m) tokens.
farmbuyer@1 1429 local format_timestamp_values, point2dee = {}, "%.2d"
farmbuyer@1 1430 function addon:format_timestamp (fmt_opt, day_entry, time_entry_opt)
farmbuyer@1 1431 if not time_entry_opt then
farmbuyer@1 1432 if type(fmt_opt) == 'table' then -- Two entries, default format
farmbuyer@1 1433 time_entry_opt, day_entry = day_entry, fmt_opt
farmbuyer@1 1434 fmt_opt = "$Y/$M/$D $h:$m"
farmbuyer@1 1435 --elseif type(fmt_opt) == "string" then -- Day entry only, specified format
farmbuyer@1 1436 end
farmbuyer@1 1437 end
farmbuyer@1 1438 --format_timestamp_values.Y = point2dee:format (day_entry.startday.year % 100)
farmbuyer@1 1439 format_timestamp_values.Y = ("%.4d"):format (day_entry.startday.year)
farmbuyer@1 1440 format_timestamp_values.M = point2dee:format (day_entry.startday.month)
farmbuyer@1 1441 format_timestamp_values.D = point2dee:format (day_entry.startday.day)
farmbuyer@1 1442 format_timestamp_values.h = point2dee:format ((time_entry_opt or day_entry).hour)
farmbuyer@1 1443 format_timestamp_values.m = point2dee:format ((time_entry_opt or day_entry).minute)
farmbuyer@1 1444 return fmt_opt:gsub ('%$([YMDhm])', format_timestamp_values)
farmbuyer@1 1445 end
farmbuyer@1 1446
farmbuyer@1 1447 local done_todays_date
farmbuyer@1 1448 function addon:_reset_timestamps()
farmbuyer@1 1449 done_todays_date = nil
farmbuyer@1 1450 end
farmbuyer@1 1451 local function do_todays_date()
farmbuyer@1 1452 local text, M, D, Y = makedate()
farmbuyer@1 1453 local found,ts = #g_loot+1
farmbuyer@1 1454 repeat
farmbuyer@1 1455 found,ts = addon:find_previous_time_entry(found-1)
farmbuyer@1 1456 if found and ts.startday.text == text then
farmbuyer@1 1457 done_todays_date = true
farmbuyer@1 1458 end
farmbuyer@1 1459 until done_todays_date or (not found)
farmbuyer@1 1460 if done_todays_date then
farmbuyer@1 1461 g_today = ts
farmbuyer@1 1462 else
farmbuyer@1 1463 done_todays_date = true
farmbuyer@1 1464 g_today = g_loot[addon._addLootEntry{
farmbuyer@1 1465 kind = 'time',
farmbuyer@1 1466 startday = {
farmbuyer@1 1467 text = text, month = M, day = D, year = Y
farmbuyer@1 1468 }
farmbuyer@1 1469 }]
farmbuyer@1 1470 end
farmbuyer@1 1471 addon:_fill_out_eoi_data(1)
farmbuyer@1 1472 end
farmbuyer@1 1473
farmbuyer@1 1474 -- Adding anything original to g_loot goes through this routine.
farmbuyer@1 1475 function addon._addLootEntry (e)
farmbuyer@1 1476 setmetatable(e,loot_entry_mt)
farmbuyer@1 1477
farmbuyer@1 1478 if not done_todays_date then do_todays_date() end
farmbuyer@1 1479
farmbuyer@1 1480 local h, m = GetGameTime()
farmbuyer@10 1481 --local localuptime = math.floor(GetTime())
farmbuyer@10 1482 local time_t = time()
farmbuyer@1 1483 e.hour = h
farmbuyer@1 1484 e.minute = m
farmbuyer@10 1485 e.stamp = time_t --localuptime
farmbuyer@1 1486 local index = #g_loot + 1
farmbuyer@1 1487 g_loot[index] = e
farmbuyer@1 1488 return index
farmbuyer@1 1489 end
farmbuyer@16 1490
farmbuyer@16 1491 -- Problem: (1) boss kill happens, (2) fast looting happens, (3) boss
farmbuyer@16 1492 -- cache cleanup fires. Result: loot shows up before boss kill entry.
farmbuyer@16 1493 -- Solution: We need to shuffle the boss entry above any of the loot
farmbuyer@16 1494 -- from that boss.
farmbuyer@16 1495 function addon._adjustBossOrder (is, should_be)
farmbuyer@16 1496 --pprint('loot', is, should_be)
farmbuyer@16 1497 if is == should_be then --pprint('loot', "equal, yay")
farmbuyer@16 1498 return
farmbuyer@16 1499 end
farmbuyer@17 1500 if (type(is)~='number') or (type(should_be)~='number') or (is < should_be) then
farmbuyer@19 1501 pprint('loot', is, should_be, "...the hell? bailing")
farmbuyer@16 1502 return
farmbuyer@16 1503 end
farmbuyer@16 1504 if g_loot[should_be].kind == 'time' then
farmbuyer@16 1505 should_be = should_be + 1
farmbuyer@16 1506 if is == should_be then
farmbuyer@16 1507 --pprint('loot', "needed to mark day entry, otherwise equal, yay")
farmbuyer@16 1508 return
farmbuyer@16 1509 end
farmbuyer@16 1510 end
farmbuyer@16 1511
farmbuyer@16 1512 assert(g_loot[is].kind == 'boss')
farmbuyer@16 1513 local boss = tremove (g_loot, is)
farmbuyer@16 1514 --pprint('loot', "MOVING", boss.bosskill)
farmbuyer@16 1515 tinsert (g_loot, should_be, boss)
farmbuyer@16 1516 return should_be
farmbuyer@16 1517 end
farmbuyer@1 1518 end
farmbuyer@1 1519
farmbuyer@19 1520 -- In the rare case of items getting put into the loot table without current
farmbuyer@19 1521 -- item cache data (which will have arrived by now).
farmbuyer@19 1522 function addon:do_item_cache_fixup()
farmbuyer@19 1523 self:Print("Fixing up missing item cache data...")
farmbuyer@19 1524
farmbuyer@19 1525 local numfound = 0
farmbuyer@19 1526 local borkedpat = '^'..UNKNOWN..': (%S+)'
farmbuyer@19 1527
farmbuyer@19 1528 for i,e in self:filtered_loot_iter('loot') do
farmbuyer@19 1529 if e.cache_miss then
farmbuyer@19 1530 local borked_id = e.itemname:match(borkedpat)
farmbuyer@19 1531 if borked_id then
farmbuyer@19 1532 numfound = numfound + 1
farmbuyer@19 1533 -- Best to use the safest and most flexible API here, which is GII and
farmbuyer@19 1534 -- its assload of return values.
farmbuyer@19 1535 local iname, ilink, iquality, _,_,_,_,_,_, itexture = GetItemInfo(borked_id)
farmbuyer@19 1536 if iname then
farmbuyer@19 1537 self:Print(" Entry %d patched up with %s.", i, ilink)
farmbuyer@19 1538 e.quality = iquality
farmbuyer@19 1539 e.itemname = iname
farmbuyer@19 1540 e.id = tonumber(ilink:match("item:(%d+)"))
farmbuyer@19 1541 e.itemlink = ilink
farmbuyer@19 1542 e.itexture = itexture
farmbuyer@19 1543 e.cache_miss = nil
farmbuyer@19 1544 end
farmbuyer@19 1545 end
farmbuyer@19 1546 end
farmbuyer@19 1547 end
farmbuyer@19 1548
farmbuyer@19 1549 self:Print("...finished. Found %d |4entry:entries; with weird data.", numfound)
farmbuyer@19 1550 end
farmbuyer@19 1551
farmbuyer@1 1552
farmbuyer@1 1553 ------ Saved texts
farmbuyer@1 1554 function addon:check_saved_table(silent_p)
farmbuyer@16 1555 local s = OuroLootSV_saved
farmbuyer@1 1556 if s and (#s > 0) then return s end
farmbuyer@16 1557 OuroLootSV_saved = nil
farmbuyer@1 1558 if not silent_p then self:Print("There are no saved loot texts.") end
farmbuyer@1 1559 end
farmbuyer@1 1560
farmbuyer@1 1561 function addon:save_list()
farmbuyer@1 1562 local s = self:check_saved_table(); if not s then return end;
farmbuyer@1 1563 for i,t in ipairs(s) do
farmbuyer@1 1564 self:Print("#%d %s %d entries %s", i, t.date, t.count, t.name)
farmbuyer@1 1565 end
farmbuyer@1 1566 end
farmbuyer@1 1567
farmbuyer@1 1568 function addon:save_saveas(name)
farmbuyer@16 1569 OuroLootSV_saved = OuroLootSV_saved or {}
farmbuyer@16 1570 local SV = OuroLootSV_saved
farmbuyer@16 1571 local n = #SV + 1
farmbuyer@1 1572 local save = {
farmbuyer@1 1573 name = name,
farmbuyer@1 1574 date = makedate(),
farmbuyer@1 1575 count = #g_loot,
farmbuyer@1 1576 }
farmbuyer@10 1577 for text in self:registered_textgen_iter() do
farmbuyer@10 1578 save[text] = g_loot[text]
farmbuyer@10 1579 end
farmbuyer@1 1580 self:Print("Saving current loot texts to #%d '%s'", n, name)
farmbuyer@16 1581 SV[n] = save
farmbuyer@1 1582 return self:save_list()
farmbuyer@1 1583 end
farmbuyer@1 1584
farmbuyer@1 1585 function addon:save_restore(num)
farmbuyer@1 1586 local s = self:check_saved_table(); if not s then return end;
farmbuyer@1 1587 if (not num) or (num > #s) then
farmbuyer@1 1588 return self:Print("Saved text number must be 1 - "..#s)
farmbuyer@1 1589 end
farmbuyer@1 1590 local save = s[num]
farmbuyer@1 1591 self:Print("Overwriting current loot data with saved text #%d '%s'", num, save.name)
farmbuyer@1 1592 self:Clear(--[[verbose_p=]]false)
farmbuyer@1 1593 -- Clear will already have displayed the window, and re-selected the first
farmbuyer@1 1594 -- tab. Set these up for when the text tabs are clicked.
farmbuyer@10 1595 for text in self:registered_textgen_iter() do
farmbuyer@10 1596 g_loot[text] = save[text]
farmbuyer@10 1597 end
farmbuyer@1 1598 end
farmbuyer@1 1599
farmbuyer@1 1600 function addon:save_delete(num)
farmbuyer@1 1601 local s = self:check_saved_table(); if not s then return end;
farmbuyer@1 1602 if (not num) or (num > #s) then
farmbuyer@1 1603 return self:Print("Saved text number must be 1 - "..#s)
farmbuyer@1 1604 end
farmbuyer@1 1605 self:Print("Deleting saved text #"..num)
farmbuyer@1 1606 tremove(s,num)
farmbuyer@1 1607 return self:save_list()
farmbuyer@1 1608 end
farmbuyer@1 1609
farmbuyer@1 1610
farmbuyer@1 1611 ------ Loot histories
farmbuyer@1 1612 -- history_all = {
farmbuyer@1 1613 -- ["Kilrogg"] = {
farmbuyer@1 1614 -- ["realm"] = "Kilrogg", -- not saved
farmbuyer@1 1615 -- ["st"] = { lib-st display table }, -- not saved
farmbuyer@1 1616 -- ["byname"] = { -- not saved
farmbuyer@1 1617 -- ["OtherPlayer"] = 2,
farmbuyer@1 1618 -- ["Farmbuyer"] = 1,
farmbuyer@1 1619 -- }
farmbuyer@1 1620 -- [1] = {
farmbuyer@1 1621 -- ["name"] = "Farmbuyer",
farmbuyer@1 1622 -- [1] = { id = nnnnn, when = "formatted timestamp for displaying" } -- most recent loot
farmbuyer@1 1623 -- [2] = { ......., [count = "x3"] } -- previous loot
farmbuyer@1 1624 -- },
farmbuyer@1 1625 -- [2] = {
farmbuyer@1 1626 -- ["name"] = "OtherPlayer",
farmbuyer@1 1627 -- ......
farmbuyer@1 1628 -- }, ......
farmbuyer@1 1629 -- },
farmbuyer@1 1630 -- ["OtherRealm"] = ......
farmbuyer@1 1631 -- }
farmbuyer@1 1632 do
farmbuyer@6 1633 local tsort = table.sort
farmbuyer@6 1634 local comp = function(L,R) return L.when > R.when end
farmbuyer@6 1635
farmbuyer@4 1636 -- Builds the map of names to array indices, using passed table or
farmbuyer@4 1637 -- self.history, and stores the result into its 'byname' field. Also
farmbuyer@4 1638 -- called from the GUI code at least once.
farmbuyer@1 1639 function addon:_build_history_names (opt_hist)
farmbuyer@1 1640 local hist = opt_hist or self.history
farmbuyer@1 1641 local m = {}
farmbuyer@1 1642 for i = 1, #hist do
farmbuyer@1 1643 m[hist[i].name] = i
farmbuyer@1 1644 end
farmbuyer@1 1645 hist.byname = m
farmbuyer@1 1646 end
farmbuyer@1 1647
farmbuyer@1 1648 -- Prepares and returns table to be used as self.history.
farmbuyer@1 1649 function addon:_prep_new_history_category (prev_table, realmname)
farmbuyer@1 1650 local t = prev_table or {
farmbuyer@1 1651 --kind = 'realm',
farmbuyer@6 1652 --realm = realmname,
farmbuyer@1 1653 }
farmbuyer@6 1654 t.realm = realmname
farmbuyer@1 1655
farmbuyer@1 1656 --[[
farmbuyer@1 1657 t.cols = setmetatable({
farmbuyer@1 1658 { value = realmname },
farmbuyer@1 1659 }, self.time_column1_used_mt)
farmbuyer@1 1660 ]]
farmbuyer@1 1661
farmbuyer@1 1662 if not t.byname then
farmbuyer@1 1663 self:_build_history_names (t)
farmbuyer@1 1664 end
farmbuyer@1 1665
farmbuyer@1 1666 return t
farmbuyer@1 1667 end
farmbuyer@1 1668
farmbuyer@4 1669 -- Maps a name to an array index, creating new tables if needed. Returns
farmbuyer@6 1670 -- the index and the table at that index.
farmbuyer@4 1671 function addon:get_loot_history (name)
farmbuyer@4 1672 local i
farmbuyer@4 1673 i = self.history.byname[name]
farmbuyer@4 1674 if not i then
farmbuyer@4 1675 i = #self.history + 1
farmbuyer@4 1676 self.history[i] = { name=name }
farmbuyer@4 1677 self.history.byname[name] = i
farmbuyer@4 1678 end
farmbuyer@6 1679 return i, self.history[i]
farmbuyer@4 1680 end
farmbuyer@4 1681
farmbuyer@1 1682 function addon:_addHistoryEntry (lootindex)
farmbuyer@1 1683 local e = g_loot[lootindex]
farmbuyer@6 1684 if e.kind ~= 'loot' then return end
farmbuyer@6 1685
farmbuyer@6 1686 local i,h = self:get_loot_history(e.person)
farmbuyer@25 1687 -- If any of these change, update the end of history_handle_disposition.
farmbuyer@1 1688 local n = {
farmbuyer@1 1689 id = e.id,
farmbuyer@1 1690 when = self:format_timestamp (g_today, e),
farmbuyer@1 1691 count = e.count,
farmbuyer@1 1692 }
farmbuyer@6 1693 tinsert (h, 1, n)
farmbuyer@24 1694 e.history_unique = n.id .. ' ' .. n.when
farmbuyer@6 1695 end
farmbuyer@6 1696
farmbuyer@24 1697 -- Create new history table based on current loot.
farmbuyer@6 1698 function addon:rewrite_history (realmname)
farmbuyer@6 1699 local r = assert(realmname)
farmbuyer@6 1700 self.history_all[r] = self:_prep_new_history_category (nil, r)
farmbuyer@6 1701 self.history = self.history_all[r]
farmbuyer@6 1702
farmbuyer@6 1703 local g_today_real = g_today
farmbuyer@6 1704 for i,e in ipairs(g_loot) do
farmbuyer@6 1705 if e.kind == 'time' then
farmbuyer@6 1706 g_today = e
farmbuyer@6 1707 elseif e.kind == 'loot' then
farmbuyer@6 1708 self:_addHistoryEntry(i)
farmbuyer@6 1709 end
farmbuyer@6 1710 end
farmbuyer@6 1711 g_today = g_today_real
farmbuyer@6 1712 self.hist_clean = nil
farmbuyer@6 1713
farmbuyer@6 1714 -- safety measure: resort players' tables based on formatted timestamp
farmbuyer@6 1715 for i,h in ipairs(self.history) do
farmbuyer@6 1716 tsort (h, comp)
farmbuyer@6 1717 end
farmbuyer@6 1718 end
farmbuyer@6 1719
farmbuyer@24 1720 -- Clears all but latest entry for each player.
farmbuyer@6 1721 function addon:preen_history (realmname)
farmbuyer@6 1722 local r = assert(realmname)
farmbuyer@6 1723 for i,h in ipairs(self.history) do
farmbuyer@6 1724 tsort (h, comp)
farmbuyer@6 1725 while #h > 1 do
farmbuyer@6 1726 tremove (h)
farmbuyer@6 1727 end
farmbuyer@6 1728 end
farmbuyer@1 1729 end
farmbuyer@24 1730
farmbuyer@25 1731 -- Given an entry in a g_loot table, looks up the corresponding history
farmbuyer@25 1732 -- entry. Returns the player's index and history table (as in get_loot_history)
farmbuyer@25 1733 -- and the index into that table of the loot entry. On failure, returns nil
farmbuyer@25 1734 -- and an error message ready to be formatted with the loot's name/itemlink.
farmbuyer@25 1735 function addon:_history_by_loot_id (loot, operation_text)
farmbuyer@25 1736 -- Using assert() here would be concatenating error strings that probably
farmbuyer@25 1737 -- wouldn't be used. Do more verbose testing instead.
farmbuyer@25 1738 if type(loot) ~= 'table' then
farmbuyer@25 1739 error("trying to "..operation_text.." nonexistant entry")
farmbuyer@25 1740 end
farmbuyer@25 1741 if loot.kind ~= 'loot' then
farmbuyer@25 1742 error("trying to "..operation_text.." something that isn't loot")
farmbuyer@25 1743 end
farmbuyer@24 1744
farmbuyer@25 1745 local player = loot.person
farmbuyer@25 1746 local tag = loot.history_unique
farmbuyer@24 1747 local errtxt
farmbuyer@25 1748 local player_i, player_h, hist_i
farmbuyer@24 1749
farmbuyer@24 1750 if not tag then
farmbuyer@24 1751 errtxt = "Entry for %s is missing a history tag!"
farmbuyer@24 1752 else
farmbuyer@25 1753 player_i,player_h = self:get_loot_history(player)
farmbuyer@25 1754 for i,h in ipairs(player_h) do
farmbuyer@24 1755 local unique = h.id .. ' ' .. h.when
farmbuyer@24 1756 if unique == tag then
farmbuyer@25 1757 hist_i = i
farmbuyer@24 1758 break
farmbuyer@24 1759 end
farmbuyer@24 1760 end
farmbuyer@25 1761 if not hist_i then
farmbuyer@24 1762 -- 1) loot an item, 2) clear old history, 3) reassign from current loot
farmbuyer@24 1763 -- Bah. Anybody that tricky is already recoding the tables directly anyhow.
farmbuyer@24 1764 errtxt = "There is no record of %s ever having been assigned!"
farmbuyer@24 1765 end
farmbuyer@24 1766 end
farmbuyer@24 1767
farmbuyer@24 1768 if errtxt then
farmbuyer@25 1769 return nil, errtxt
farmbuyer@24 1770 end
farmbuyer@25 1771 return player_i, player_h, hist_i
farmbuyer@25 1772 end
farmbuyer@24 1773
farmbuyer@25 1774 function addon:reassign_loot (index, to_name)
farmbuyer@25 1775 assert(type(to_name)=='string' and to_name:len()>0)
farmbuyer@25 1776 local e = g_loot[index]
farmbuyer@25 1777 local from_i, from_h, hist_i = self:_history_by_loot_id (e, "reassign")
farmbuyer@25 1778 local from_name = e.person
farmbuyer@25 1779 local to_i,to_h = self:get_loot_history(to_name)
farmbuyer@25 1780
farmbuyer@25 1781 if not from_i then
farmbuyer@25 1782 -- from_h is the formatted error text
farmbuyer@25 1783 self:Print(from_h .. " Loot will be reassigned, but history will NOT be updated.", e.itemlink)
farmbuyer@25 1784 else
farmbuyer@25 1785 local hist_h = tremove (from_h, hist_i)
farmbuyer@25 1786 tinsert (to_h, 1, hist_h)
farmbuyer@25 1787 tsort (from_h, comp)
farmbuyer@25 1788 tsort (to_h, comp)
farmbuyer@25 1789 end
farmbuyer@25 1790 e.person = to_name
farmbuyer@25 1791 e.person_class = select(2,UnitClass(to_name))
farmbuyer@25 1792 self.hist_clean = nil
farmbuyer@25 1793
farmbuyer@25 1794 self:Print("Reassigned entry %d/%s from '%s' to '%s'.", index, e.itemlink, from_name, to_name)
farmbuyer@25 1795 end
farmbuyer@25 1796
farmbuyer@36 1797 -- Similar to _addHistoryEntry. The second arg may be a loot entry
farmbuyer@36 1798 -- (which used to be at LOOTINDEX), or nil (and the loot entry will
farmbuyer@36 1799 -- be pulled from LOOTINDEX instead).
farmbuyer@36 1800 function addon:_delHistoryEntry (lootindex, opt_e)
farmbuyer@36 1801 local e = opt_e or g_loot[lootindex]
farmbuyer@36 1802 if e.kind ~= 'loot' then return end
farmbuyer@36 1803
farmbuyer@36 1804 local from_i, from_h, hist_i = self:_history_by_loot_id (e, "delete")
farmbuyer@36 1805 if not from_i then
farmbuyer@36 1806 -- from_h is the formatted error text
farmbuyer@36 1807 self:Print(from_h .. " Loot will be deleted, but history will NOT be updated.", e.itemlink)
farmbuyer@36 1808 return
farmbuyer@36 1809 end
farmbuyer@36 1810
farmbuyer@36 1811 --[[local hist_h = ]]tremove (from_h, hist_i)
farmbuyer@36 1812 tsort (from_h, comp)
farmbuyer@36 1813 self.hist_clean = nil
farmbuyer@36 1814
farmbuyer@36 1815 self:Print("Removed history entry %d/%s from '%s'.", lootindex, e.itemlink, e.person)
farmbuyer@36 1816 end
farmbuyer@36 1817
farmbuyer@25 1818 -- Any extra work for the "Mark as <x>" dropdown actions. The
farmbuyer@25 1819 -- corresponding <x> will already have been assigned in the loot entry.
farmbuyer@25 1820 local deleted_cache = {} --setmetatable({}, {__mode='k'})
farmbuyer@25 1821 function addon:history_handle_disposition (index, olddisp)
farmbuyer@25 1822 local e = g_loot[index]
farmbuyer@25 1823 -- Standard disposition has a nil entry, but that's tedious in debug
farmbuyer@25 1824 -- output, so force to a string instead.
farmbuyer@25 1825 olddisp = olddisp or 'normal'
farmbuyer@25 1826 local newdisp = e.disposition or 'normal'
farmbuyer@25 1827 -- Ignore misclicks and the like
farmbuyer@25 1828 if olddisp == newdisp then return end
farmbuyer@25 1829
farmbuyer@25 1830 local name = e.person
farmbuyer@25 1831
farmbuyer@25 1832 if (newdisp == 'shard' or newdisp == 'gvault') then
farmbuyer@25 1833 local name_i, name_h, hist_i = self:_history_by_loot_id (e, "mark")
farmbuyer@25 1834 -- remove history entry
farmbuyer@25 1835 if hist_i then
farmbuyer@25 1836 local hist_h = tremove (name_h, hist_i)
farmbuyer@25 1837 deleted_cache[e.history_unique] = hist_h
farmbuyer@25 1838 self.hist_clean = nil
farmbuyer@25 1839 elseif (olddisp == 'shard' or olddisp == 'gvault') then
farmbuyer@25 1840 -- Sharding a vault item, or giving the auto-sharder something to bank,
farmbuyer@25 1841 -- etc, wouldn't necessarily have had a history entry to begin with.
farmbuyer@25 1842 else
farmbuyer@25 1843 self:Print(name_h .. " Loot has been marked, but history will NOT be updated.", e.itemlink)
farmbuyer@25 1844 end
farmbuyer@25 1845 return
farmbuyer@25 1846 end
farmbuyer@25 1847
farmbuyer@25 1848 if (olddisp == 'shard' or olddisp == 'gvault')
farmbuyer@25 1849 and (newdisp == 'normal' or newdisp == 'offspec')
farmbuyer@25 1850 then
farmbuyer@25 1851 local name_i, name_h = self:get_loot_history(name)
farmbuyer@25 1852
farmbuyer@25 1853 -- Must create a new history entry. Could call '_addHistoryEntry(index)'
farmbuyer@25 1854 -- but that would duplicate a lot of effort. To start with, check the
farmbuyer@25 1855 -- cache of stuff we've already deleted; if it's not there then just do
farmbuyer@25 1856 -- the same steps as _addHistoryEntry.
farmbuyer@25 1857 local entry
farmbuyer@25 1858 if e.history_unique and deleted_cache[e.history_unique] then
farmbuyer@25 1859 entry = deleted_cache[e.history_unique]
farmbuyer@25 1860 deleted_cache[e.history_unique] = nil
farmbuyer@25 1861 end
farmbuyer@25 1862 local when = g_today and self:format_timestamp (g_today, e) or tostring(e.stamp)
farmbuyer@25 1863 entry = entry or {
farmbuyer@25 1864 id = e.id,
farmbuyer@25 1865 when = when,
farmbuyer@25 1866 count = e.count,
farmbuyer@25 1867 }
farmbuyer@25 1868 tinsert (name_h, 1, entry)
farmbuyer@25 1869 e.history_unique = e.history_unique or (entry.id .. ' ' .. entry.when)
farmbuyer@25 1870 self.hist_clean = nil
farmbuyer@25 1871 return
farmbuyer@25 1872 end
farmbuyer@24 1873 end
farmbuyer@1 1874 end
farmbuyer@1 1875
farmbuyer@1 1876
farmbuyer@1 1877 ------ Player communication
farmbuyer@1 1878 do
farmbuyer@1 1879 local function adduser (name, status, active)
farmbuyer@1 1880 if status then addon.sender_list.names[name] = status end
farmbuyer@1 1881 if active then addon.sender_list.active[name] = active end
farmbuyer@1 1882 end
farmbuyer@1 1883
farmbuyer@1 1884 -- Incoming handler functions. All take the sender name and the incoming
farmbuyer@1 1885 -- tag as the first two arguments. All of these are active even when the
farmbuyer@1 1886 -- player is not tracking loot, so test for that when appropriate.
farmbuyer@1 1887 local OCR_funcs = {}
farmbuyer@1 1888
farmbuyer@1 1889 OCR_funcs.ping = function (sender)
farmbuyer@1 1890 pprint('comm', "incoming ping from", sender)
farmbuyer@1 1891 addon:whispercast (sender, 'pong', addon.revision,
farmbuyer@1 1892 addon.enabled and "tracking" or (addon.rebroadcast and "broadcasting" or "disabled"))
farmbuyer@1 1893 end
farmbuyer@1 1894 OCR_funcs.pong = function (sender, _, rev, status)
farmbuyer@17 1895 local s = ("|cff00ff00%s|r %s is |cff00ffff%s|r"):format(sender,rev,status)
farmbuyer@1 1896 addon:Print("Echo: ", s)
farmbuyer@1 1897 adduser (sender, s, status=="tracking" or status=="broadcasting" or nil)
farmbuyer@1 1898 end
farmbuyer@27 1899 OCR_funcs.revcheck = function (sender, _, revlarge)
farmbuyer@27 1900 addon.dprint('comm', "revcheck, sender", sender)
farmbuyer@27 1901 addon:_check_revision (revlarge)
farmbuyer@27 1902 end
farmbuyer@1 1903
farmbuyer@1 1904 OCR_funcs.loot = function (sender, _, recip, item, count, extratext)
farmbuyer@1 1905 addon.dprint('comm', "DOTloot, sender", sender, "recip", recip, "item", item, "count", count)
farmbuyer@1 1906 if not addon.enabled then return end
farmbuyer@1 1907 adduser (sender, nil, true)
farmbuyer@1 1908 addon:CHAT_MSG_LOOT ("broadcast", recip, item, count, sender, extratext)
farmbuyer@1 1909 end
farmbuyer@1 1910
farmbuyer@1 1911 OCR_funcs.boss = function (sender, _, reason, bossname, instancetag)
farmbuyer@1 1912 addon.dprint('comm', "DOTboss, sender", sender, "reason", reason, "name", bossname, "it", instancetag)
farmbuyer@1 1913 if not addon.enabled then return end
farmbuyer@1 1914 adduser (sender, nil, true)
farmbuyer@1 1915 addon:on_boss_broadcast (reason, bossname, instancetag)
farmbuyer@1 1916 end
farmbuyer@1 1917
farmbuyer@1 1918 OCR_funcs.bcast_req = function (sender)
farmbuyer@1 1919 if addon.debug.comm or ((not g_wafer_thin) and (not addon.rebroadcast))
farmbuyer@1 1920 then
farmbuyer@38 1921 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 1922 sender,
farmbuyer@1 1923 addon.format_hypertext('bcaston',"the red pill",'|cffff4040'),
farmbuyer@1 1924 addon.format_hypertext('waferthin',"the blue pill",'|cff0070dd'))
farmbuyer@1 1925 end
farmbuyer@18 1926 addon.popped = true
farmbuyer@1 1927 end
farmbuyer@1 1928
farmbuyer@1 1929 OCR_funcs.bcast_responder = function (sender)
farmbuyer@1 1930 if addon.debug.comm or addon.requesting or
farmbuyer@1 1931 ((not g_wafer_thin) and (not addon.rebroadcast))
farmbuyer@1 1932 then
farmbuyer@1 1933 addon:Print(sender, "has answered the call and is now broadcasting loot.")
farmbuyer@1 1934 end
farmbuyer@1 1935 end
farmbuyer@1 1936 -- remove this tag once it's all tested
farmbuyer@1 1937 OCR_funcs.bcast_denied = function (sender)
farmbuyer@1 1938 if addon.requesting then addon:Print(sender, "declines futher broadcast requests.") end
farmbuyer@1 1939 end
farmbuyer@1 1940
farmbuyer@1 1941 -- Incoming message dispatcher
farmbuyer@1 1942 local function dotdotdot (sender, tag, ...)
farmbuyer@1 1943 local f = OCR_funcs[tag]
farmbuyer@1 1944 addon.dprint('comm', ":... processing",tag,"from",sender)
farmbuyer@1 1945 if f then return f(sender,tag,...) end
farmbuyer@1 1946 addon.dprint('comm', "unknown comm message",tag",from", sender)
farmbuyer@1 1947 end
farmbuyer@1 1948 -- Recent message cache
farmbuyer@1 1949 addon.recent_messages = create_new_cache ('comm', comm_cleanup_ttl)
farmbuyer@1 1950
farmbuyer@1 1951 function addon:OnCommReceived (prefix, msg, distribution, sender)
farmbuyer@1 1952 if prefix ~= self.ident then return end
farmbuyer@1 1953 if not self.debug.comm then
farmbuyer@1 1954 if distribution ~= "RAID" and distribution ~= "WHISPER" then return end
farmbuyer@1 1955 if sender == my_name then return end
farmbuyer@1 1956 end
farmbuyer@1 1957 self.dprint('comm', ":OCR from", sender, "message is", msg)
farmbuyer@1 1958
farmbuyer@1 1959 if self.recent_messages:test(msg) then
farmbuyer@1 1960 return self.dprint('cache', "message <",msg,"> already in cache, skipping")
farmbuyer@1 1961 end
farmbuyer@1 1962 self.recent_messages:add(msg)
farmbuyer@1 1963
farmbuyer@1 1964 -- Nothing is actually returned, just (ab)using tail calls.
farmbuyer@1 1965 return dotdotdot(sender,strsplit('\a',msg))
farmbuyer@1 1966 end
farmbuyer@1 1967
farmbuyer@1 1968 function addon:OnCommReceivedNocache (prefix, msg, distribution, sender)
farmbuyer@1 1969 if prefix ~= self.identTg then return end
farmbuyer@1 1970 if not self.debug.comm then
farmbuyer@1 1971 if distribution ~= "WHISPER" then return end
farmbuyer@1 1972 if sender == my_name then return end
farmbuyer@1 1973 end
farmbuyer@1 1974 self.dprint('comm', ":OCRN from", sender, "message is", msg)
farmbuyer@1 1975 return dotdotdot(sender,strsplit('\a',msg))
farmbuyer@1 1976 end
farmbuyer@1 1977 end
farmbuyer@1 1978
farmbuyer@1 1979 -- vim:noet