annotate core.lua @ 66:43913e02a1ef

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