annotate core.lua @ 70:cdee65c1bd8c

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