annotate core.lua @ 95:3546c7b55986

Move generation of the Help tab to the same file as the help text. Allow registration of arbitrary tab controls, not just text generation. Move some shared data out of the addon table into a 'gui' state table in preparation for more of the same. Clean some cruft out of the default itemfilter list.
author Farmbuyer of US-Kilrogg <farmbuyer@gmail.com>
date Thu, 26 Jul 2012 03:34:56 +0000
parents db1d5d09e5f5
children 780b7e0eeeeb
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@71 56 - unique an almost-certainly-unique string, content meaningless
farmbuyer@61 57 - disposition offspec/gvault/shard; missing otherwise; can be set from
farmbuyer@57 58 the extratext field
farmbuyer@61 59 - count e.g., "x3"; missing otherwise; can be set/removed from
farmbuyer@57 60 extratext; triggers only for a stack of items, not "the boss
farmbuyer@57 61 dropped double axes today"
farmbuyer@66 62 - variant 1 = heroic item, 2 = LFR item; missing otherwise
farmbuyer@61 63 - cache_miss if GetItemInfo failed; SHOULD be missing (changes other fields)
farmbuyer@65 64 - bcast_from player's name if received rebroadcast from that player;
farmbuyer@65 65 missing otherwise; can be deleted as a result of in-game
farmbuyer@65 66 fiddling of loot data
farmbuyer@61 67 - extratext text in Note column, including disposition and rebroadcasting
farmbuyer@61 68 - extratext_byhand true if text edited by player directly; missing otherwise
farmbuyer@50 69
farmbuyer@1 70
farmbuyer@1 71 Functions arranged like this, with these lables (for jumping to). As a
farmbuyer@1 72 rule, member functions with UpperCamelCase names are called directly by
farmbuyer@1 73 user-facing code, ones with lowercase names are "one step removed", and
farmbuyer@1 74 names with leading underscores are strictly internal helper functions.
farmbuyer@1 75 ------ Saved variables
farmbuyer@1 76 ------ Constants
farmbuyer@1 77 ------ Addon member data
farmbuyer@6 78 ------ Globals
farmbuyer@1 79 ------ Expiring caches
farmbuyer@1 80 ------ Ace3 framework stuff
farmbuyer@1 81 ------ Event handlers
farmbuyer@1 82 ------ Slash command handler
farmbuyer@1 83 ------ On/off
farmbuyer@1 84 ------ Behind the scenes routines
farmbuyer@1 85 ------ Saved texts
farmbuyer@1 86 ------ Loot histories
farmbuyer@1 87 ------ Player communication
farmbuyer@1 88
farmbuyer@1 89 This started off as part of a raid addon package written by somebody else.
farmbuyer@1 90 After he retired, I began modifying the code. Eventually I set aside the
farmbuyer@1 91 entire package and rewrote the loot tracker module from scratch. Many of the
farmbuyer@1 92 variable/function naming conventions (sv_*, g_*, and family) stayed across the
farmbuyer@16 93 rewrite.
farmbuyer@16 94
farmbuyer@73 95 Some variables are needlessly initialized to nil just to look uniform and
farmbuyer@76 96 serve as a spelling reminder.
farmbuyer@1 97
farmbuyer@1 98 ]==]
farmbuyer@1 99
farmbuyer@1 100 ------ Saved variables
farmbuyer@16 101 OuroLootSV = nil -- possible copy of g_loot
farmbuyer@16 102 OuroLootSV_saved = nil -- table of copies of saved texts, default nil; keys
farmbuyer@16 103 -- are numeric indices of tables, subkeys of those
farmbuyer@16 104 -- are name/forum/attend/date
farmbuyer@16 105 OuroLootSV_opts = nil -- same as option_defaults until changed
farmbuyer@16 106 -- autoshard: optional name of disenchanting player, default nil
farmbuyer@16 107 -- threshold: optional loot threshold, default nil
farmbuyer@16 108 OuroLootSV_hist = nil
farmbuyer@19 109 OuroLootSV_log = {}
farmbuyer@1 110
farmbuyer@1 111
farmbuyer@1 112 ------ Constants
farmbuyer@90 113 local RAID_ROSTER_UPDATE_EVENT =
farmbuyer@90 114 (select(4,GetBuildInfo()) >= 50000) and 'GROUP_ROSTER_UPDATE' or 'RAID_ROSTER_UPDATE'
farmbuyer@1 115 local option_defaults = {
farmbuyer@71 116 ['datarev'] = 20, -- cheating, this isn't actually an option
farmbuyer@1 117 ['popup_on_join'] = true,
farmbuyer@94 118 ['register_slash_synonyms'] = false,
farmbuyer@94 119 ['slash_synonyms'] = '/ol,/oloot',
farmbuyer@1 120 ['scroll_to_bottom'] = true,
farmbuyer@83 121 ['gui_noob'] = true,
farmbuyer@1 122 ['chatty_on_kill'] = false,
farmbuyer@1 123 ['no_tracking_wipes'] = false,
farmbuyer@1 124 ['snarky_boss'] = true,
farmbuyer@1 125 ['keybinding'] = false,
farmbuyer@2 126 ['bossmod'] = "DBM",
farmbuyer@1 127 ['keybinding_text'] = 'CTRL-SHIFT-O',
farmbuyer@1 128 ['forum'] = {
farmbuyer@25 129 ['[url] Wowhead'] = '[url=http://www.wowhead.com/?item=$I]$N[/url]$X - $T',
farmbuyer@25 130 ['[url] MMO/Wowstead'] = '[http://db.mmo-champion.com/i/$I]$X - $T',
farmbuyer@1 131 ['[item] by name'] = '[item]$N[/item]$X - $T',
farmbuyer@1 132 ['[item] by ID'] = '[item]$I[/item]$X - $T',
farmbuyer@1 133 ['Custom...'] = '',
farmbuyer@1 134 },
farmbuyer@1 135 ['forum_current'] = '[item] by name',
farmbuyer@57 136 ['display_disabled_LODs'] = false,
farmbuyer@65 137 ['display_bcast_from'] = true,
farmbuyer@73 138 ['precache_history_uniques'] = false,
farmbuyer@73 139 ['chatty_on_remote_changes'] = false,
farmbuyer@73 140 ['chatty_on_remote_changes_frame'] = 1,
farmbuyer@1 141 }
farmbuyer@95 142 local virgin = "First time loaded? Hi! Use the /ouroloot command"
farmbuyer@1 143 .." to show the main display. You should probably browse the instructions"
farmbuyer@1 144 .." if you've never used this before; %s to display the help window. This"
farmbuyer@1 145 .." welcome message will not intrude again."
farmbuyer@27 146 local newer_warning = "A newer version has been released. You can %s to display"
farmbuyer@27 147 .." a download URL for copy-and-pasting. You can %s to ping other raiders"
farmbuyer@27 148 .." for their installed versions (same as '/ouroloot ping' or clicking the"
farmbuyer@27 149 .." 'Ping!' button on the options panel)."
farmbuyer@81 150 local horrible_error_text = [[|cffff1010]] .. ERROR_CAPS
farmbuyer@81 151 ..[[:|n|cffffff00Something unrecoverable has happened. The error message]]
farmbuyer@81 152 ..[[ which was provided follows in white:|r|n|n%s|n|n|cffffff00Ouro Loot]]
farmbuyer@81 153 ..[[ will not display a window until this situation is corrected. ]]
farmbuyer@81 154 ..[[ You can try typing|n|cff00ff40/ouroloot fix ?|n]]
farmbuyer@81 155 ..[[|cffffff00to see what can be done by software alone. You may still]]
farmbuyer@81 156 ..[[ need to do a "/reload" afterwards, or even restart the game client.]]
farmbuyer@81 157 local unique_collision = "Item '%s' was carrying unique tag <%s>, but that was already in use; tried to generate a new tag and failed!|n|nRemote sender was '%s', previous cache entry was <%s/%s>.|n|nThis may require a live human to figure out; the loot in question has not been stored."
farmbuyer@86 158 local remote_chatty = "|cff00ff00%s|r changed %d/%s from %s to %s"
farmbuyer@1 159 local qualnames = {
farmbuyer@1 160 ['gray'] = 0, ['grey'] = 0, ['poor'] = 0, ['trash'] = 0,
farmbuyer@1 161 ['white'] = 1, ['common'] = 1,
farmbuyer@1 162 ['green'] = 2, ['uncommon'] = 2,
farmbuyer@1 163 ['blue'] = 3, ['rare'] = 3,
farmbuyer@1 164 ['epic'] = 4, ['purple'] = 4,
farmbuyer@1 165 ['legendary'] = 5, ['orange'] = 5,
farmbuyer@1 166 ['artifact'] = 6,
farmbuyer@1 167 --['heirloom'] = 7,
farmbuyer@1 168 }
farmbuyer@1 169 local my_name = UnitName('player')
farmbuyer@76 170 local comm_cleanup_ttl = 4 -- seconds in the communications cache
farmbuyer@89 171 local version_large = nil -- defaults to 1, possibly changed by version
farmbuyer@20 172 local g_LOOT_ITEM_ss, g_LOOT_ITEM_MULTIPLE_sss, g_LOOT_ITEM_SELF_s, g_LOOT_ITEM_SELF_MULTIPLE_ss
farmbuyer@1 173
farmbuyer@1 174
farmbuyer@1 175 ------ Addon member data
farmbuyer@1 176 local flib = LibStub("LibFarmbuyer")
farmbuyer@1 177 addon.author_debug = flib.author_debug
farmbuyer@1 178
farmbuyer@6 179 -- Play cute games with namespaces here just to save typing. WTB Lua 5.2 PST.
farmbuyer@1 180 do local _G = _G setfenv (1, addon)
farmbuyer@1 181
farmbuyer@71 182 commrev = '17'
farmbuyer@89 183 version = _G.GetAddOnMetadata(nametag,"Version") or "?" -- "x.yy.z", etc
farmbuyer@1 184 ident = "OuroLoot2"
farmbuyer@1 185 identTg = "OuroLoot2Tg"
farmbuyer@1 186 status_text = nil
farmbuyer@89 187 revision = "@project-revision@"
farmbuyer@89 188 --@debug@
farmbuyer@89 189 revision = "DEVEL"
farmbuyer@89 190 --@end-debug@
farmbuyer@1 191
farmbuyer@45 192 tekdebug = nil
farmbuyer@45 193 if _G.tekDebug then
farmbuyer@45 194 local tdframe = _G.tekDebug:GetFrame("Ouro Loot")
farmbuyer@45 195 function tekdebug (txt)
farmbuyer@45 196 -- tekDebug notices "<name passed to getframe>|r:"
farmbuyer@45 197 tdframe:AddMessage('|cff17ff0dOuro Loot|r:'..txt,1,1,1)
farmbuyer@45 198 end
farmbuyer@45 199 end
farmbuyer@45 200
farmbuyer@1 201 DEBUG_PRINT = false
farmbuyer@1 202 debug = {
farmbuyer@76 203 comm = false,
farmbuyer@76 204 loot = false,
farmbuyer@76 205 flow = false,
farmbuyer@76 206 notraid = false,
farmbuyer@76 207 cache = false,
farmbuyer@76 208 alsolog = false,
farmbuyer@1 209 }
farmbuyer@77 210 --@debug@
farmbuyer@77 211 DEBUG_PRINT = true
farmbuyer@77 212 debug.loot = true
farmbuyer@77 213 debug.comm = true
farmbuyer@77 214 --@end-debug@
farmbuyer@77 215
farmbuyer@45 216 -- This looks ugly, but it factors out the load-time decisions from
farmbuyer@73 217 -- the run-time ones. Args to [dp]print are concatenated with spaces.
farmbuyer@45 218 if tekdebug then
farmbuyer@45 219 function dprint (t,...)
farmbuyer@45 220 if DEBUG_PRINT and debug[t] then
farmbuyer@45 221 local text = flib.safefprint(tekdebug,"<"..t.."> ",...)
farmbuyer@45 222 if debug.alsolog then
farmbuyer@45 223 addon:log_with_timestamp(text)
farmbuyer@45 224 end
farmbuyer@45 225 end
farmbuyer@45 226 end
farmbuyer@45 227 else
farmbuyer@45 228 function dprint (t,...)
farmbuyer@45 229 if DEBUG_PRINT and debug[t] then
farmbuyer@45 230 local text = flib.safeprint("<"..t.."> ",...)
farmbuyer@45 231 if debug.alsolog then
farmbuyer@45 232 addon:log_with_timestamp(text)
farmbuyer@45 233 end
farmbuyer@19 234 end
farmbuyer@19 235 end
farmbuyer@1 236 end
farmbuyer@1 237
farmbuyer@45 238 if author_debug and tekdebug then
farmbuyer@45 239 function pprint (t,...)
farmbuyer@45 240 local text = flib.safefprint(tekdebug,"<<"..t..">> ",...)
farmbuyer@19 241 if debug.alsolog then
farmbuyer@19 242 addon:log_with_timestamp(text)
farmbuyer@19 243 end
farmbuyer@1 244 end
farmbuyer@1 245 else
farmbuyer@1 246 pprint = flib.nullfunc
farmbuyer@1 247 end
farmbuyer@1 248
farmbuyer@76 249 -- The same observable behavior as the Lua builtins, but with slightly
farmbuyer@76 250 -- different hardcoded strings and, more importantly, implicit logging.
farmbuyer@76 251 function error(txt,lvl)
farmbuyer@76 252 pprint('ERROR()', txt)
farmbuyer@76 253 pprint('DEBUGSTACK()', _G.debugstack())
farmbuyer@76 254 _G.error(txt,lvl)
farmbuyer@76 255 end
farmbuyer@76 256 function assert(cond,msg,...)
farmbuyer@76 257 if cond then
farmbuyer@76 258 return cond,msg,...
farmbuyer@76 259 else
farmbuyer@76 260 error('ASSERT() FAILED: '..tostring(msg or 'nil'))
farmbuyer@76 261 end
farmbuyer@76 262 end
farmbuyer@76 263
farmbuyer@1 264 enabled = false
farmbuyer@1 265 rebroadcast = false
farmbuyer@76 266 display = nil -- reference to display frame iff visible
farmbuyer@1 267 loot_clean = nil -- index of last GUI entry with known-current visual data
farmbuyer@1 268 threshold = debug.loot and 0 or 3 -- rare by default
farmbuyer@1 269 sharder = nil -- name of person whose loot is marked as shards
farmbuyer@1 270
farmbuyer@1 271 -- The rest is also used in the GUI:
farmbuyer@1 272
farmbuyer@76 273 sender_list = {active={},names={}} -- this should be reworked
farmbuyer@1 274 popped = nil -- non-nil when reminder has been shown, actual value unimportant
farmbuyer@1 275
farmbuyer@2 276 bossmod_registered = nil
farmbuyer@76 277 bossmods = {}
farmbuyer@2 278
farmbuyer@76 279 requesting = nil -- prompting for additional rebroadcasters
farmbuyer@1 280
farmbuyer@76 281 -- don't use NUM_ITEM_QUALITIES as the upper loop bound unless we expect
farmbuyer@76 282 -- heirlooms to show up
farmbuyer@76 283 thresholds = {}
farmbuyer@1 284 for i = 0,6 do
farmbuyer@11 285 thresholds[i] = _G.ITEM_QUALITY_COLORS[i].hex .. _G["ITEM_QUALITY"..i.."_DESC"] .. "|r"
farmbuyer@1 286 end
farmbuyer@1 287
farmbuyer@1 288 _G.setfenv (1, _G)
farmbuyer@1 289 end
farmbuyer@1 290
farmbuyer@1 291 addon = LibStub("AceAddon-3.0"):NewAddon(addon, "Ouro Loot",
farmbuyer@1 292 "AceTimer-3.0", "AceComm-3.0", "AceConsole-3.0", "AceEvent-3.0")
farmbuyer@1 293
farmbuyer@67 294 -- if given, MSG should be a complete-ish sentence
farmbuyer@67 295 function addon:load_assert (cond, msg, ...)
farmbuyer@67 296 if cond then
farmbuyer@67 297 return cond, msg, ...
farmbuyer@67 298 end
farmbuyer@67 299 msg = msg or "load-time assertion failed!"
farmbuyer@67 300 self.NOLOAD = msg
farmbuyer@67 301 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 302 SLASH_ACECONSOLE_OUROLOOT1 = nil
farmbuyer@67 303 SLASH_ACECONSOLE_OUROLOOT2 = nil
farmbuyer@76 304 self.error (msg, --[[level=]]2)
farmbuyer@67 305 end
farmbuyer@67 306
farmbuyer@67 307 -- Seriously? ORLY?
farmbuyer@67 308 -- YARLY. Go ahead and guess what was involved in tracking this down. If
farmbuyer@67 309 -- more such effects are added in the future, the "id==xxxxx" will need to
farmbuyer@67 310 -- change into a probe of a table of known-problematic IDs.
farmbuyer@67 311 for i = 1, 40 do -- BUFF_MAX_DISPLAY==32, enh
farmbuyer@67 312 local id = select(11,UnitAura('player', i, 'HELPFUL'))
farmbuyer@67 313 if id == 88715 then
farmbuyer@67 314 -- What I really want to do is pause until the thing is clicked off,
farmbuyer@67 315 -- then continue with the rest of the file. No can do. Could also
farmbuyer@67 316 -- just set some hooks and then re-OnInit/OnEnable after the aura
farmbuyer@67 317 -- expires, but that's a hassle. GAH. Punt.
farmbuyer@67 318 local text = UnitAura('player', i, 'HELPFUL')
farmbuyer@67 319 text = ([[Cannot initialize while |cff71d5ff|Hspell:88715|h[%s]|h|cff00ff00 is active!]]):
farmbuyer@67 320 format(text)
farmbuyer@67 321 addon:load_assert(nil,text)
farmbuyer@67 322 return -- were this C code running through lint, I'd put NOTREACHED
farmbuyer@67 323 end
farmbuyer@67 324 end
farmbuyer@67 325
farmbuyer@86 326 -- Class color support. Do the expensive string.format calls up front, and
farmbuyer@86 327 -- the cheap all-string-all-at-once single-op concatenation as needed.
farmbuyer@86 328 do
farmbuyer@86 329 local cc = {}
farmbuyer@86 330 local function extract (color_info)
farmbuyer@93 331 local hex
farmbuyer@93 332 if color_info.colorStr then -- MoP
farmbuyer@93 333 hex = "|c" .. color_info.colorStr
farmbuyer@93 334 else -- pre-MoP
farmbuyer@93 335 hex = ("|cff%.2x%.2x%.2x"):format(255*color_info.r,
farmbuyer@93 336 255*color_info.g, 255*color_info.b)
farmbuyer@93 337 end
farmbuyer@92 338 return { r=color_info.r, g=color_info.g, b=color_info.b, a=1, hex=hex }
farmbuyer@86 339 end
farmbuyer@86 340 local function fill_out_class_colors()
farmbuyer@86 341 for class,color in pairs(CUSTOM_CLASS_COLORS or RAID_CLASS_COLORS) do
farmbuyer@86 342 cc[class] = extract(color)
farmbuyer@86 343 end
farmbuyer@86 344 cc.DEFAULT = extract(_G.NORMAL_FONT_COLOR)
farmbuyer@86 345 end
farmbuyer@86 346 if CUSTOM_CLASS_COLORS and CUSTOM_CLASS_COLORS.RegisterCallback then
farmbuyer@86 347 CUSTOM_CLASS_COLORS:RegisterCallback(fill_out_class_colors)
farmbuyer@86 348 end
farmbuyer@93 349 addon.class_colors = cc -- this stays around
farmbuyer@93 350 addon.fill_out_class_colors = fill_out_class_colors -- this doesn't
farmbuyer@93 351
farmbuyer@86 352 -- What I really want is to have the hooked :Print understand a special
farmbuyer@86 353 -- format specifier like "%Cs" and do the colorizing automatically.
farmbuyer@86 354 function addon:colorize (text, class)
farmbuyer@86 355 return ((class and cc[class]) or cc.DEFAULT).hex .. text .. "|r"
farmbuyer@86 356 end
farmbuyer@86 357 end
farmbuyer@86 358
farmbuyer@1 359
farmbuyer@6 360 ------ Globals
farmbuyer@1 361 local g_loot = nil
farmbuyer@1 362 local g_restore_p = nil
farmbuyer@76 363 local g_wafer_thin = nil -- prompting for additional rebroadcasters
farmbuyer@1 364 local g_today = nil -- "today" entry in g_loot
farmbuyer@16 365 local g_boss_signpost = nil
farmbuyer@73 366 local g_seeing_oldsigs = nil
farmbuyer@73 367 local g_uniques = nil -- memoization of unique loot events
farmbuyer@76 368 local g_unique_replace = nil
farmbuyer@1 369 local opts = nil
farmbuyer@1 370
farmbuyer@76 371 local error = addon.error
farmbuyer@76 372 local assert = addon.assert
farmbuyer@76 373
farmbuyer@73 374 -- for speeding up local loads, not because I think _G will change
farmbuyer@73 375 local _G = _G
farmbuyer@73 376 local type = _G.type
farmbuyer@73 377 local select = _G.select
farmbuyer@73 378 local pairs = _G.pairs
farmbuyer@73 379 local ipairs = _G.ipairs
farmbuyer@73 380 local tinsert = _G.table.insert
farmbuyer@73 381 local tremove = _G.table.remove
farmbuyer@73 382 local tostring = _G.tostring
farmbuyer@73 383 local tonumber = _G.tonumber
farmbuyer@73 384 local wipe = _G.table.wipe
farmbuyer@73 385
farmbuyer@1 386 local pprint, tabledump = addon.pprint, flib.tabledump
farmbuyer@90 387 local CopyTable = _G.CopyTable
farmbuyer@90 388 local GetNumRaidMembers = _G.GetNumGroupMembers or _G.GetNumRaidMembers
farmbuyer@93 389 local IsInRaid = _G.IsInRaid or (function() return GetNumRaidMembers() > 0 end)
farmbuyer@1 390 -- En masse forward decls of symbols defined inside local blocks
farmbuyer@41 391 local _register_bossmod, makedate, create_new_cache, _init, _log
farmbuyer@81 392 local _history_by_loot_id, _setup_unique_replace, _unavoidable_collision
farmbuyer@81 393 local _notify_about_change, _notify_about_remote
farmbuyer@1 394
farmbuyer@27 395 -- Try to extract numbers from the .toc "Version" and munge them into an
farmbuyer@27 396 -- integral form for comparison. The result doesn't need to be meaningful as
farmbuyer@38 397 -- long as we can reliably feed two of them to "<" and get useful answers.
farmbuyer@29 398 --
farmbuyer@89 399 -- This makes/reinforces an assumption that version_large of release packages
farmbuyer@29 400 -- (e.g., 2016001) will always be higher than those of development packages
farmbuyer@29 401 -- (e.g., 87), due to the tagging system versus subversion file revs. This
farmbuyer@29 402 -- is good, as local dev code will never trigger a false positive update
farmbuyer@62 403 -- warning for other users.
farmbuyer@27 404 do
farmbuyer@27 405 local r = 0
farmbuyer@89 406 for d in addon.version:gmatch("%d+") do
farmbuyer@27 407 r = 1000*r + d
farmbuyer@27 408 end
farmbuyer@62 409 -- If it's a big enough number to obviously be a release, then make
farmbuyer@65 410 -- sure it's big enough to overcome many small previous point releases.
farmbuyer@62 411 while r > 2000 and r < 2000000 do
farmbuyer@62 412 r = 1000*r
farmbuyer@62 413 end
farmbuyer@89 414 version_large = math.max(r,1)
farmbuyer@27 415 end
farmbuyer@27 416
farmbuyer@1 417 -- Hypertext support, inspired by DBM broadcast pizza timers
farmbuyer@1 418 do
farmbuyer@76 419 local hypertext_format_str = "|HOuroLoot:%d|h%s[%s]|r|h"
farmbuyer@76 420 local func_map = {} --_G.setmetatable({}, {__mode = 'k'})
farmbuyer@76 421 local text_map = {} --_G.setmetatable({}, {__mode = 'kv'})
farmbuyer@76 422 local base = _G.newproxy(true)
farmbuyer@76 423 _G.getmetatable(base).__tostring = function(ud) return text_map[ud] end
farmbuyer@76 424 --@debug@
farmbuyer@76 425 -- collecting these tokens is an interesting micro-optimization but not yet
farmbuyer@76 426 _G.getmetatable(base).__gc = function(ud)
farmbuyer@76 427 print("Collecting hyperlink object <",tostring(ud),">")
farmbuyer@76 428 end
farmbuyer@76 429 --@end-debug@
farmbuyer@1 430
farmbuyer@38 431 -- TEXT will automatically be surrounded by brackets
farmbuyer@76 432 -- COLOR can be ITEM_QUALITY_* or a formatting string ("|cff...")
farmbuyer@76 433 -- FUNC can be "MethodName", "tab_title", or a function
farmbuyer@76 434 --
farmbuyer@78 435 -- Returns an opaque token and a matching number. Calling tostring() on
farmbuyer@78 436 -- the token will yield a formatted clickable string that can be displayed
farmbuyer@78 437 -- in chat. The MethodName and raw function callbacks will both be
farmbuyer@78 438 -- passed the addon table and the same matching number.
farmbuyer@78 439 --
farmbuyer@78 440 -- This is largely an excuse to fool around with Lua data constructs.
farmbuyer@76 441 function addon.format_hypertext (text, color, func)
farmbuyer@76 442 local ret = _G.newproxy(base)
farmbuyer@76 443 local num = #text_map + 1
farmbuyer@76 444 text_map[ret] = hypertext_format_str:format (num,
farmbuyer@76 445 type(color)=='number' and ITEM_QUALITY_COLORS[color].hex or color,
farmbuyer@76 446 text)
farmbuyer@76 447 text_map[num] = ret
farmbuyer@76 448 func_map[ret] = func
farmbuyer@78 449 return ret, num
farmbuyer@1 450 end
farmbuyer@1 451
farmbuyer@76 452 --[[
farmbuyer@76 453 link: OuroLoot:n
farmbuyer@76 454 fullstring: |HOuroLoot:n|h|cff.....[foo]|r|h
farmbuyer@76 455 mousebutton: "LeftButton", "MiddleButton", "RightButton"
farmbuyer@76 456
farmbuyer@76 457 amusingly, print()'ing the fullstring below as a debugging aid yields
farmbuyer@76 458 another clickable link, yay data reproducability
farmbuyer@76 459 ]]
farmbuyer@76 460 local strsplit = _G.strsplit
farmbuyer@76 461 DEFAULT_CHAT_FRAME:HookScript("OnHyperlinkClick", function(self, link, fullstring, mousebutton)
farmbuyer@1 462 local ltype, arg = strsplit(":",link)
farmbuyer@76 463 if ltype ~= "OuroLoot" then return end
farmbuyer@78 464 arg = tonumber(arg)
farmbuyer@78 465 local f = func_map[text_map[arg]]
farmbuyer@76 466 if type(f) == 'function' then
farmbuyer@78 467 f (addon, arg)
farmbuyer@76 468 elseif type(f) == 'string' then
farmbuyer@76 469 if type(addon[f]) == 'function' then
farmbuyer@78 470 addon[f](addon,arg) -- method name
farmbuyer@76 471 else
farmbuyer@76 472 addon:BuildMainDisplay(f) -- tab title fragment
farmbuyer@1 473 end
farmbuyer@1 474 end
farmbuyer@1 475 end)
farmbuyer@1 476
farmbuyer@1 477 local old = ItemRefTooltip.SetHyperlink
farmbuyer@1 478 function ItemRefTooltip:SetHyperlink (link, ...)
farmbuyer@76 479 if link:match("^OuroLoot") then return end
farmbuyer@1 480 return old (self, link, ...)
farmbuyer@1 481 end
farmbuyer@1 482 end
farmbuyer@1 483
farmbuyer@1 484 do
farmbuyer@1 485 -- copied here because it's declared local to the calendar ui, thanks blizz ><
farmbuyer@1 486 local CALENDAR_FULLDATE_MONTH_NAMES = {
farmbuyer@1 487 FULLDATE_MONTH_JANUARY, FULLDATE_MONTH_FEBRUARY, FULLDATE_MONTH_MARCH,
farmbuyer@1 488 FULLDATE_MONTH_APRIL, FULLDATE_MONTH_MAY, FULLDATE_MONTH_JUNE,
farmbuyer@1 489 FULLDATE_MONTH_JULY, FULLDATE_MONTH_AUGUST, FULLDATE_MONTH_SEPTEMBER,
farmbuyer@1 490 FULLDATE_MONTH_OCTOBER, FULLDATE_MONTH_NOVEMBER, FULLDATE_MONTH_DECEMBER,
farmbuyer@1 491 }
farmbuyer@1 492 -- returns "dd Month yyyy", mm, dd, yyyy
farmbuyer@1 493 function makedate()
farmbuyer@1 494 Calendar_LoadUI()
farmbuyer@1 495 local _, M, D, Y = CalendarGetDate()
farmbuyer@1 496 local text = ("%d %s %d"):format(D, CALENDAR_FULLDATE_MONTH_NAMES[M], Y)
farmbuyer@1 497 return text, M, D, Y
farmbuyer@1 498 end
farmbuyer@1 499 end
farmbuyer@1 500
farmbuyer@56 501 -- Returns an instance name or abbreviation, followed by the raid size
farmbuyer@1 502 local function instance_tag()
farmbuyer@61 503 -- possibly redo this with the new GetRaidDifficulty function
farmbuyer@1 504 local name, typeof, diffcode, diffstr, _, perbossheroic, isdynamic = GetInstanceInfo()
farmbuyer@56 505 local t, r
farmbuyer@1 506 name = addon.instance_abbrev[name] or name
farmbuyer@56 507 if typeof == "none" then return name, MAX_RAID_MEMBERS end
farmbuyer@1 508 -- diffstr is "5 Player", "10 Player (Heroic)", etc. ugh.
farmbuyer@35 509 if (GetLFGMode()) and (GetLFGModeType() == 'raid') then
farmbuyer@56 510 t,r = 'LFR', 25
farmbuyer@35 511 elseif diffcode == 1 then
farmbuyer@93 512 if IsInRaid() then
farmbuyer@76 513 t,r = "10",10
farmbuyer@76 514 else
farmbuyer@76 515 t,r = "5",5
farmbuyer@76 516 end
farmbuyer@1 517 elseif diffcode == 2 then
farmbuyer@93 518 if IsInRaid() then
farmbuyer@76 519 t,r = "25",25
farmbuyer@76 520 else
farmbuyer@76 521 t,r = "5h",5
farmbuyer@76 522 end
farmbuyer@1 523 elseif diffcode == 3 then
farmbuyer@56 524 t,r = "10h", 10
farmbuyer@1 525 elseif diffcode == 4 then
farmbuyer@56 526 t,r = "25h", 25
farmbuyer@1 527 end
farmbuyer@1 528 -- dynamic difficulties always return normal "codes"
farmbuyer@1 529 if isdynamic and perbossheroic == 1 then
farmbuyer@1 530 t = t .. "h"
farmbuyer@1 531 end
farmbuyer@56 532 return name .. "(" .. t .. ")", r
farmbuyer@1 533 end
farmbuyer@1 534 addon.instance_tag = instance_tag -- grumble
farmbuyer@42 535 addon.latest_instance = nil -- spelling reminder, assigned elsewhere
farmbuyer@1 536
farmbuyer@73 537 -- Memoizing cache of unique IDs as we generate or search for them. Keys are
farmbuyer@73 538 -- the uniques, values are the following:
farmbuyer@87 539 -- 'history' active player name in self.history
farmbuyer@73 540 -- 'history_may' index into player's uniques list, CAN QUICKLY BE OUTDATED
farmbuyer@73 541 -- and will instantly be wrong after manual insertion
farmbuyer@73 542 -- 'loot' active index into g_loot
farmbuyer@73 543 -- with all but the history entry optional. Values of g_uniqes.NOTFOUND
farmbuyer@73 544 -- indicate a known missing status. Use g_uniques:RESET() to wipe the cache
farmbuyer@73 545 -- and return to searching mode.
farmbuyer@73 546 do
farmbuyer@73 547 local notfound = -1
farmbuyer@73 548 local notfound_ret = { history = notfound }
farmbuyer@73 549 local mt
farmbuyer@73 550
farmbuyer@73 551 -- This can either be its own function or a slightly redundant __index.
farmbuyer@73 552 local function m_probe_only (t, k)
farmbuyer@73 553 return rawget(t,k) or notfound_ret
farmbuyer@73 554 end
farmbuyer@73 555
farmbuyer@73 556 -- Expensive search.
farmbuyer@73 557 local function m_full_search (t, k)
farmbuyer@73 558 local L, H, HU, loot
farmbuyer@73 559 -- Try active loot entries first
farmbuyer@73 560 for i,e in addon:filtered_loot_iter('loot') do
farmbuyer@73 561 if k == e.unique then
farmbuyer@73 562 L,loot = i,e
farmbuyer@73 563 break
farmbuyer@73 564 end
farmbuyer@73 565 end
farmbuyer@73 566 -- If it's active, try looking through that player's history first.
farmbuyer@73 567 if L then
farmbuyer@73 568 local hi,h = addon:get_loot_history (loot.person)
farmbuyer@73 569 for ui,u in ipairs(h.unique) do
farmbuyer@73 570 if k == u then
farmbuyer@87 571 H, HU = h.name, ui
farmbuyer@73 572 break
farmbuyer@73 573 end
farmbuyer@73 574 end
farmbuyer@73 575 else
farmbuyer@73 576 -- No luck? Ugh, may have been reassigned and we're probing from
farmbuyer@73 577 -- older data. Search the rest of current realm's history.
farmbuyer@73 578 for hi,h in ipairs(addon.history) do
farmbuyer@73 579 for ui,u in ipairs(h.unique) do
farmbuyer@73 580 if k == u then
farmbuyer@87 581 H, HU = h.name, ui
farmbuyer@73 582 break
farmbuyer@73 583 end
farmbuyer@73 584 end
farmbuyer@73 585 end
farmbuyer@73 586 end
farmbuyer@73 587 local ret = { loot = L, history = H or notfound, history_may = HU }
farmbuyer@73 588 t[k] = ret
farmbuyer@73 589 return ret
farmbuyer@73 590 end
farmbuyer@73 591
farmbuyer@73 592 local function m_setmode (self, mode)
farmbuyer@73 593 mt.__index = (mode == 'probe') and m_probe_only or
farmbuyer@73 594 (mode == 'search') and m_full_search or
farmbuyer@73 595 nil -- maybe error() here?
farmbuyer@73 596 end
farmbuyer@73 597
farmbuyer@73 598 local function m_reset (self)
farmbuyer@73 599 wipe(self)
farmbuyer@73 600 self[''] = notfound_ret -- special case for receiving older broadcast
farmbuyer@73 601 self.NOTFOUND = notfound
farmbuyer@73 602 self.RESET = m_reset
farmbuyer@73 603 self.SEARCH = m_full_search
farmbuyer@73 604 self.TEST = m_probe_only
farmbuyer@73 605 self.SETMODE = m_setmode
farmbuyer@73 606 mt.__index = m_full_search
farmbuyer@73 607 return self
farmbuyer@73 608 end
farmbuyer@73 609
farmbuyer@73 610 -- If unique keys ever change into objects instead of strings, change
farmbuyer@73 611 -- this into a weakly-keyed table.
farmbuyer@73 612 mt = { __metatable = 'Should be using setmode.' }
farmbuyer@73 613
farmbuyer@73 614 g_uniques = setmetatable (m_reset{}, mt)
farmbuyer@73 615 end
farmbuyer@73 616
farmbuyer@1 617
farmbuyer@1 618 ------ Expiring caches
farmbuyer@1 619 --[[
farmbuyer@73 620 cache = create_new_cache ("mycache", 15 [,cleanup])
farmbuyer@73 621 cache:add(foo)
farmbuyer@73 622 cache:test(foo) -- returns true
farmbuyer@73 623 ....5 seconds pass
farmbuyer@73 624 cache:add(bar)
farmbuyer@73 625 ....10 seconds pass
farmbuyer@73 626 cache:test(foo) -- returns false
farmbuyer@73 627 cache:test(bar) -- returns true
farmbuyer@73 628 ....5 seconds pass
farmbuyer@73 629 ....bar also gone, cleanup() called
farmbuyer@1 630 ]]
farmbuyer@1 631 do
farmbuyer@1 632 local caches = {}
farmbuyer@25 633 local cleanup_group = _G.AnimTimerFrame:CreateAnimationGroup()
farmbuyer@10 634 local time = _G.time
farmbuyer@1 635 cleanup_group:SetLooping("REPEAT")
farmbuyer@1 636 cleanup_group:SetScript("OnLoop", function(cg)
farmbuyer@1 637 addon.dprint('cache',"OnLoop firing")
farmbuyer@10 638 local now = time()
farmbuyer@1 639 local alldone = true
farmbuyer@1 640 -- this is ass-ugly
farmbuyer@25 641 for name,c in pairs(caches) do
farmbuyer@25 642 local fifo = c.fifo
farmbuyer@25 643 local active = #fifo > 0
farmbuyer@73 644 while (#fifo > 0) and (now > fifo[1].t) do
farmbuyer@40 645 addon.dprint('cache', name, "cache removing", fifo[1].t, "<", fifo[1].m, ">")
farmbuyer@25 646 tremove(fifo,1)
farmbuyer@1 647 end
farmbuyer@25 648 if active and #fifo == 0 and c.func then
farmbuyer@25 649 addon.dprint('cache', name, "empty, firing cleanup")
farmbuyer@25 650 c:func()
farmbuyer@25 651 end
farmbuyer@25 652 alldone = alldone and (#fifo == 0)
farmbuyer@1 653 end
farmbuyer@1 654 if alldone then
farmbuyer@40 655 addon.dprint('cache',"OnLoop FINISHING animation group")
farmbuyer@1 656 cleanup_group:Finish()
farmbuyer@73 657 _G.collectgarbage()
farmbuyer@40 658 else
farmbuyer@40 659 addon.dprint('cache',"OnLoop done, not yet finished")
farmbuyer@1 660 end
farmbuyer@1 661 end)
farmbuyer@1 662
farmbuyer@1 663 local function _add (cache, x)
farmbuyer@73 664 local datum = { t=time()+cache.ttl, m=x }
farmbuyer@25 665 cache.hash[x] = datum
farmbuyer@25 666 tinsert (cache.fifo, datum)
farmbuyer@1 667 if not cleanup_group:IsPlaying() then
farmbuyer@41 668 addon.dprint('cache', cache.name, "with entry", datum.t, "<", datum.m, "> STARTING animation group")
farmbuyer@40 669 cache.cleanup:SetDuration(1) -- hmmm
farmbuyer@1 670 cleanup_group:Play()
farmbuyer@1 671 end
farmbuyer@1 672 end
farmbuyer@1 673 local function _test (cache, x)
farmbuyer@40 674 -- FIXME This can return false positives, if called after the onloop
farmbuyer@40 675 -- fifo has been removed but before the GC has removed the weak entry.
farmbuyer@76 676 -- What to do, what to do... try forcing a GC during alldone.
farmbuyer@25 677 return cache.hash[x] ~= nil
farmbuyer@1 678 end
farmbuyer@25 679
farmbuyer@1 680 function create_new_cache (name, ttl, on_alldone)
farmbuyer@25 681 -- setting OnFinished for cleanup fires at the end of each inner loop,
farmbuyer@25 682 -- with no 'requested' argument to distinguish cases. thus, on_alldone.
farmbuyer@76 683 -- FWIW, on_alldone is passed this table as its sole argument:
farmbuyer@1 684 local c = {
farmbuyer@1 685 ttl = ttl,
farmbuyer@1 686 name = name,
farmbuyer@1 687 add = _add,
farmbuyer@1 688 test = _test,
farmbuyer@1 689 cleanup = cleanup_group:CreateAnimation("Animation"),
farmbuyer@1 690 func = on_alldone,
farmbuyer@25 691 fifo = {},
farmbuyer@25 692 hash = setmetatable({}, {__mode='kv'}),
farmbuyer@1 693 }
farmbuyer@1 694 c.cleanup:SetOrder(1)
farmbuyer@25 695 caches[name] = c
farmbuyer@1 696 return c
farmbuyer@1 697 end
farmbuyer@1 698 end
farmbuyer@1 699
farmbuyer@1 700
farmbuyer@1 701 ------ Ace3 framework stuff
farmbuyer@1 702 function addon:OnInitialize()
farmbuyer@67 703 if self.author_debug then
farmbuyer@67 704 _G.OL = self
farmbuyer@73 705 _G.g_uniques = g_uniques
farmbuyer@67 706 end
farmbuyer@73 707 _log = _G.OuroLootSV_log
farmbuyer@41 708
farmbuyer@1 709 -- VARIABLES_LOADED has fired by this point; test if we're doing something like
farmbuyer@1 710 -- relogging during a raid and already have collected loot data
farmbuyer@73 711 local OuroLootSV = _G.OuroLootSV
farmbuyer@1 712 g_restore_p = OuroLootSV ~= nil
farmbuyer@1 713 self.dprint('flow', "oninit sets restore as", g_restore_p)
farmbuyer@1 714
farmbuyer@73 715 if _G.OuroLootSV_opts == nil then
farmbuyer@73 716 _G.OuroLootSV_opts = {}
farmbuyer@76 717 local vclick = self.format_hypertext ([[click here]], ITEM_QUALITY_UNCOMMON, 'help')
farmbuyer@1 718 self:ScheduleTimer(function(s)
farmbuyer@76 719 s:Print(virgin, tostring(vclick))
farmbuyer@1 720 virgin = nil
farmbuyer@1 721 end,10,self)
farmbuyer@65 722 else
farmbuyer@65 723 virgin = nil
farmbuyer@1 724 end
farmbuyer@73 725 opts = _G.OuroLootSV_opts
farmbuyer@66 726 local stored_datarev = opts.datarev or 14
farmbuyer@1 727 for opt,default in pairs(option_defaults) do
farmbuyer@1 728 if opts[opt] == nil then
farmbuyer@1 729 opts[opt] = default
farmbuyer@1 730 end
farmbuyer@1 731 end
farmbuyer@56 732 opts.datarev = option_defaults.datarev
farmbuyer@38 733
farmbuyer@25 734 -- transition&remove old options
farmbuyer@25 735 opts['forum_use_itemid'] = nil
farmbuyer@25 736 if opts['forum_format'] then
farmbuyer@25 737 opts.forum['Custom...'] = opts['forum_format']
farmbuyer@25 738 opts['forum_format'] = nil
farmbuyer@25 739 end
farmbuyer@25 740 if opts.forum['[url]'] then
farmbuyer@25 741 opts.forum['[url] Wowhead'] = opts.forum['[url]']
farmbuyer@25 742 opts.forum['[url]'] = nil
farmbuyer@25 743 opts.forum['[url] MMO/Wowstead'] = option_defaults.forum['[url] MMO/Wowstead']
farmbuyer@25 744 if opts['forum_current'] == '[url]' then
farmbuyer@25 745 opts['forum_current'] = '[url] Wowhead'
farmbuyer@25 746 end
farmbuyer@25 747 end
farmbuyer@94 748 if opts['register_slashloot'] then
farmbuyer@94 749 self:Print[[The "/loot" command is no longer available, because it is used by Blizzard in MoP. You can toggle on other synonyms for "/ouroloot" in the Options tab.]]
farmbuyer@94 750 end
farmbuyer@94 751 opts['register_slashloot'] = nil
farmbuyer@1 752 option_defaults = nil
farmbuyer@16 753 if OuroLootSV then -- may not be the same as testing g_restore_p soon
farmbuyer@16 754 if OuroLootSV.saved then
farmbuyer@16 755 OuroLootSV_saved = OuroLootSV.saved; OuroLootSV.saved = nil
farmbuyer@16 756 end
farmbuyer@16 757 if OuroLootSV.threshold then
farmbuyer@16 758 opts.threshold = OuroLootSV.threshold; OuroLootSV.threshold = nil
farmbuyer@16 759 end
farmbuyer@16 760 if OuroLootSV.autoshard then
farmbuyer@16 761 opts.autoshard = OuroLootSV.autoshard; OuroLootSV.autoshard = nil
farmbuyer@16 762 end
farmbuyer@16 763 end
farmbuyer@38 764
farmbuyer@1 765 -- get item filter table if needed
farmbuyer@1 766 if opts.itemfilter == nil then
farmbuyer@67 767 opts.itemfilter = self.default_itemfilter
farmbuyer@95 768 else
farmbuyer@95 769 -- remove old stuff that doesn't "drop" anymore
farmbuyer@95 770 -- find some way of only doing this once
farmbuyer@95 771 opts.itemfilter[29434] = nil -- Badge of Justice
farmbuyer@95 772 opts.itemfilter[40752] = nil -- Emblem of Heroism
farmbuyer@95 773 opts.itemfilter[40753] = nil -- Emblem of Valor
farmbuyer@95 774 opts.itemfilter[45624] = nil -- Emblem of Conquest
farmbuyer@95 775 opts.itemfilter[43228] = nil -- Stone Keeper's Shard
farmbuyer@95 776 opts.itemfilter[47241] = nil -- Emblem of Triumph
farmbuyer@95 777 opts.itemfilter[49426] = nil -- Emblem of Frost
farmbuyer@1 778 end
farmbuyer@67 779 self.default_itemfilter = nil
farmbuyer@70 780 if opts.itemvault == nil then
farmbuyer@70 781 opts.itemvault = self.default_itemvault
farmbuyer@70 782 end
farmbuyer@70 783 self.default_itemvault = nil
farmbuyer@1 784
farmbuyer@1 785 self:RegisterChatCommand("ouroloot", "OnSlash")
farmbuyer@94 786 if opts.register_slash_synonyms then
farmbuyer@94 787 -- Maybe use %w here for non-English locales?
farmbuyer@94 788 local n = 2
farmbuyer@94 789 for s in opts.slash_synonyms:gmatch("/%a+") do
farmbuyer@94 790 _G['SLASH_ACECONSOLE_OUROLOOT'..n] = s
farmbuyer@94 791 n = n + 1
farmbuyer@94 792 end
farmbuyer@1 793 end
farmbuyer@1 794
farmbuyer@73 795 self.history_all = self.history_all or _G.OuroLootSV_hist or {}
farmbuyer@73 796 local r = self:load_assert (_G.GetRealmName(), "how the freak does GetRealmName() fail?")
farmbuyer@1 797 self.history_all[r] = self:_prep_new_history_category (self.history_all[r], r)
farmbuyer@1 798 self.history = self.history_all[r]
farmbuyer@71 799
farmbuyer@71 800 local histformat = self.history_all.HISTFORMAT
farmbuyer@71 801 self.history_all.HISTFORMAT = nil -- don't keep this in live data
farmbuyer@73 802 if _G.OuroLootSV_hist
farmbuyer@73 803 and (histformat == nil or histformat < 4)
farmbuyer@73 804 then -- some big honkin' loops
farmbuyer@38 805 for rname,realm in pairs(self.history_all) do
farmbuyer@38 806 for pk,player in ipairs(realm) do
farmbuyer@73 807 if histformat == nil or histformat < 3 then
farmbuyer@73 808 for lk,loot in ipairs(player) do
farmbuyer@73 809 if loot.count == "" then
farmbuyer@73 810 loot.count = nil
farmbuyer@73 811 end
farmbuyer@73 812 if not loot.unique then
farmbuyer@73 813 loot.unique = loot.id .. ' ' .. loot.when
farmbuyer@73 814 end
farmbuyer@71 815 end
farmbuyer@38 816 end
farmbuyer@73 817 -- format 3 to format 4 was a major revamp of per-player data
farmbuyer@76 818 self:_uplift_history_format(player)
farmbuyer@38 819 end
farmbuyer@38 820 end
farmbuyer@38 821 end
farmbuyer@73 822 self._uplift_history_format = nil
farmbuyer@6 823 --OuroLootSV_hist = nil
farmbuyer@1 824
farmbuyer@56 825 -- Handle changes to the stored data format in stages from oldest to newest.
farmbuyer@73 826 -- bumpers[X] is responsible for updating from X to X+1.
farmbuyer@73 827 -- (This is turning into a lot of loops over the same table. Consolidate?)
farmbuyer@56 828 if OuroLootSV then
farmbuyer@56 829 local dirty = false
farmbuyer@66 830 local bumpers = {}
farmbuyer@66 831 bumpers[14] = function()
farmbuyer@56 832 for i,e in ipairs(OuroLootSV) do
farmbuyer@56 833 if e.bosskill then
farmbuyer@56 834 e.bossname, e.bosskill = e.bosskill, nil
farmbuyer@56 835 end
farmbuyer@55 836 end
farmbuyer@55 837 end
farmbuyer@66 838
farmbuyer@66 839 bumpers[15] = function()
farmbuyer@56 840 for i,e in ipairs(OuroLootSV) do
farmbuyer@56 841 if e.kind == 'boss' then
farmbuyer@56 842 e.maxsize, e.raiderlist, e.raidersnap = 0, nil, {}
farmbuyer@56 843 end
farmbuyer@56 844 end
farmbuyer@56 845 OuroLootSV.raiders = OuroLootSV.raiders or {}
farmbuyer@56 846 for name,r in pairs(OuroLootSV.raiders) do
farmbuyer@56 847 r.subgroup = 0
farmbuyer@56 848 end
farmbuyer@56 849 end
farmbuyer@66 850
farmbuyer@66 851 bumpers[16] = function()
farmbuyer@61 852 for i,e in ipairs(OuroLootSV) do
farmbuyer@61 853 if e.kind == 'boss' then -- brown paper bag bugs
farmbuyer@61 854 e.raidersnap = e.raidersnap or {}
farmbuyer@61 855 e.maxsize = e.maxsize or 0
farmbuyer@61 856 end
farmbuyer@61 857 end
farmbuyer@66 858 end
farmbuyer@66 859
farmbuyer@66 860 bumpers[17] = function()
farmbuyer@66 861 for i,e in ipairs(OuroLootSV) do
farmbuyer@66 862 if e.kind == 'loot' and e.is_heroic then
farmbuyer@66 863 e.variant, e.is_heroic = 1, nil
farmbuyer@66 864 -- Could try detecting any previous LFR loot here, but... gah
farmbuyer@66 865 end
farmbuyer@66 866 end
farmbuyer@66 867 end
farmbuyer@66 868
farmbuyer@69 869 bumpers[18] = bumpers[16]
farmbuyer@69 870 -- In the not-very-many days between 16 and 19, I managed to break
farmbuyer@69 871 -- the exact same data in the exact same way. At least they're
farmbuyer@69 872 -- not actually running the same loop twice... probably... sigh.
farmbuyer@69 873
farmbuyer@71 874 bumpers[19] = function()
farmbuyer@73 875 local date = _G.date
farmbuyer@71 876 for i,e in ipairs(OuroLootSV) do
farmbuyer@73 877 if e.kind == 'loot' then
farmbuyer@73 878 if e.history_unique then
farmbuyer@73 879 e.unique, e.history_unique = e.history_unique, nil
farmbuyer@73 880 end
farmbuyer@73 881 if e.unique == nil or #e.unique == 0 then
farmbuyer@85 882 e.unique = e.id .. e.person .. date("%Y/%m/%d %H:%M",e.stamp)
farmbuyer@73 883 end
farmbuyer@71 884 end
farmbuyer@71 885 end
farmbuyer@71 886 end
farmbuyer@71 887
farmbuyer@66 888 --[===[
farmbuyer@66 889 local real = bumpers
farmbuyer@66 890 bumpers = newproxy(true)
farmbuyer@66 891 local mt = getmetatable(bumpers)
farmbuyer@66 892 mt.__index = real
farmbuyer@66 893 mt.__gc = function() print"whadda ya know, garbage collection works" end ]===]
farmbuyer@66 894
farmbuyer@66 895 while stored_datarev < opts.datarev do
farmbuyer@66 896 self:Printf("Transitioning saved data format to %d...", stored_datarev+1)
farmbuyer@66 897 dirty = true
farmbuyer@66 898 bumpers[stored_datarev]()
farmbuyer@66 899 stored_datarev = stored_datarev + 1
farmbuyer@61 900 end
farmbuyer@56 901 if dirty then self:Print("Saved data has been massaged into shape.") end
farmbuyer@55 902 end
farmbuyer@55 903
farmbuyer@95 904 self:FINISH_SPECIAL_TABS()
farmbuyer@1 905 _init(self)
farmbuyer@89 906 self.dprint('flow', "version strings:", version_large, self.revision, self.status_text)
farmbuyer@95 907 self.gui_state_pointer = nil
farmbuyer@67 908 self.load_assert = nil
farmbuyer@66 909 self.OnInitialize = nil -- free up ALL the things!
farmbuyer@1 910 end
farmbuyer@1 911
farmbuyer@1 912 function addon:OnEnable()
farmbuyer@10 913 self:RegisterEvent("PLAYER_LOGOUT")
farmbuyer@90 914 self:RegisterEvent(RAID_ROSTER_UPDATE_EVENT,"RAID_ROSTER_UPDATE")
farmbuyer@1 915
farmbuyer@73 916 -- Cribbed from Talented. I like the way jerry thinks: the first string
farmbuyer@73 917 -- argument can be a format spec for the remainder of the arguments.
farmbuyer@1 918 -- AceConsole:Printf isn't used because we can't specify a prefix without
farmbuyer@73 919 -- jumping through ridonkulous hoops. The part about overriding :Print
farmbuyer@1 920 -- with a version using prefix hyperlinks is my fault.
farmbuyer@37 921 --
farmbuyer@73 922 -- CFPrint added instead of the usual Print testing of the first arg for
farmbuyer@73 923 -- frame-ness, which would slow down all printing and only rarely be useful.
farmbuyer@73 924 --
farmbuyer@37 925 -- There is no ITEM_QUALITY_LEGENDARY constant. Sigh.
farmbuyer@1 926 do
farmbuyer@1 927 local AC = LibStub("AceConsole-3.0")
farmbuyer@76 928 local chat_prefix = self.format_hypertext ("Ouro Loot", --[[legendary]]5,
farmbuyer@76 929 --[[empty -> nil -> main tab]]'')
farmbuyer@76 930 local chat_prefix_s = tostring(chat_prefix)
farmbuyer@1 931 function addon:Print (str, ...)
farmbuyer@1 932 if type(str) == 'string' and str:find("%", nil, --[[plainmatch=]]true) then
farmbuyer@76 933 return AC:Print (chat_prefix_s, str:format(...))
farmbuyer@1 934 else
farmbuyer@76 935 return AC:Print (chat_prefix_s, str, ...)
farmbuyer@1 936 end
farmbuyer@1 937 end
farmbuyer@73 938 function addon:CFPrint (frame, str, ...)
farmbuyer@73 939 assert(type(frame)=='table' and frame.AddMessage)
farmbuyer@73 940 if type(str) == 'string' and str:find("%", nil, --[[plainmatch=]]true) then
farmbuyer@76 941 return AC:Print (frame, chat_prefix_s, str:format(...))
farmbuyer@73 942 else
farmbuyer@76 943 return AC:Print (frame, chat_prefix_s, str, ...)
farmbuyer@73 944 end
farmbuyer@73 945 end
farmbuyer@1 946 end
farmbuyer@1 947
farmbuyer@93 948 -- Copy these over once, now that other addons have mostly loaded. Any
farmbuyer@93 949 -- future tweaks via CUSTOM_CLASS_COLORS will trigger the same callback.
farmbuyer@93 950 if addon.fill_out_class_colors then
farmbuyer@93 951 addon.fill_out_class_colors()
farmbuyer@93 952 addon.fill_out_class_colors = nil
farmbuyer@93 953 end
farmbuyer@93 954
farmbuyer@51 955 while opts.keybinding do
farmbuyer@51 956 if InCombatLockdown() then
farmbuyer@76 957 local reload = self.format_hypertext ([[the options tab]],
farmbuyer@76 958 ITEM_QUALITY_UNCOMMON, 'opt')
farmbuyer@51 959 self:Print("Cannot create '%s' as a keybinding while in combat!",
farmbuyer@51 960 opts.keybinding_text)
farmbuyer@76 961 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.",
farmbuyer@76 962 tostring(reload))
farmbuyer@51 963 break
farmbuyer@51 964 end
farmbuyer@51 965
farmbuyer@15 966 KeyBindingFrame_LoadUI()
farmbuyer@1 967 local btn = CreateFrame("Button", "OuroLootBindingOpen", nil, "SecureActionButtonTemplate")
farmbuyer@1 968 btn:SetAttribute("type", "macro")
farmbuyer@1 969 btn:SetAttribute("macrotext", "/ouroloot toggle")
farmbuyer@1 970 if SetBindingClick(opts.keybinding_text, "OuroLootBindingOpen") then
farmbuyer@73 971 -- a simple SaveBindings(GetCurrentBindingSet()) occasionally fails when
farmbuyer@73 972 -- GCBS() decides to return neither 1 nor 2 during load, for reasons nobody
farmbuyer@73 973 -- has ever learned
farmbuyer@15 974 local c = GetCurrentBindingSet()
farmbuyer@15 975 if c == ACCOUNT_BINDINGS or c == CHARACTER_BINDINGS then
farmbuyer@15 976 SaveBindings(c)
farmbuyer@15 977 end
farmbuyer@1 978 else
farmbuyer@51 979 self:Print("Error registering '%s' as a keybinding, check spelling!",
farmbuyer@51 980 opts.keybinding_text)
farmbuyer@1 981 end
farmbuyer@51 982 break
farmbuyer@1 983 end
farmbuyer@1 984
farmbuyer@20 985 --[[
farmbuyer@20 986 The four loot format patterns of interest, changed into relatively tight
farmbuyer@20 987 string match patterns. Done at enable-time rather than load-time against
farmbuyer@20 988 the slim chance that one of the non-US "delocalizers" needs to mess with
farmbuyer@20 989 the global patterns before we transform them.
farmbuyer@20 990
farmbuyer@20 991 The SELF variants can be replaced with LOOT_ITEM_PUSHED_SELF[_MULTIPLE] to
farmbuyer@20 992 trigger on 'receive item' instead, which would detect extracting stuff
farmbuyer@20 993 from mail, or s/PUSHED/CREATED/ for things like healthstones and guild
farmbuyer@20 994 cauldron flasks.
farmbuyer@76 995
farmbuyer@76 996 ??? do something with LOOT_ITEM_WHILE_PLAYER_INELIGIBLE for locked LFRs?
farmbuyer@20 997 ]]
farmbuyer@20 998
farmbuyer@20 999 -- LOOT_ITEM = "%s receives loot: %s." --> (.+) receives loot: (.+)%.
farmbuyer@20 1000 g_LOOT_ITEM_ss = _G.LOOT_ITEM:gsub('%.$','%%.'):gsub('%%s','(.+)')
farmbuyer@20 1001
farmbuyer@20 1002 -- LOOT_ITEM_MULTIPLE = "%s receives loot: %sx%d." --> (.+) receives loot: (.+)(x%d+)%.
farmbuyer@20 1003 g_LOOT_ITEM_MULTIPLE_sss = _G.LOOT_ITEM_MULTIPLE:gsub('%.$','%%.'):gsub('%%s','(.+)'):gsub('x%%d','(x%%d+)')
farmbuyer@20 1004
farmbuyer@20 1005 -- LOOT_ITEM_SELF = "You receive loot: %s." --> You receive loot: (.+)%.
farmbuyer@20 1006 g_LOOT_ITEM_SELF_s = _G.LOOT_ITEM_SELF:gsub('%.$','%%.'):gsub('%%s','(.+)')
farmbuyer@20 1007
farmbuyer@20 1008 -- LOOT_ITEM_SELF_MULTIPLE = "You receive loot: %sx%d." --> You receive loot: (.+)(x%d+)%.
farmbuyer@20 1009 g_LOOT_ITEM_SELF_MULTIPLE_ss = _G.LOOT_ITEM_SELF_MULTIPLE:gsub('%.$','%%.'):gsub('%%s','(.+)'):gsub('x%%d','(x%%d+)')
farmbuyer@20 1010
farmbuyer@44 1011 --[[
farmbuyer@44 1012 Stick something in the Blizzard addons options list, where most users
farmbuyer@44 1013 will probably look these days. Try to be conservative about needless
farmbuyer@44 1014 frame creation.
farmbuyer@44 1015 ]]
farmbuyer@44 1016 local bliz = CreateFrame("Frame")
farmbuyer@44 1017 bliz.name = "Ouro Loot"
farmbuyer@44 1018 bliz:SetScript("OnShow", function(_b)
farmbuyer@44 1019 local button = CreateFrame("Button",nil,_b,"UIPanelButtonTemplate")
farmbuyer@44 1020 button:SetWidth(150)
farmbuyer@44 1021 button:SetHeight(22)
farmbuyer@44 1022 button:SetScript("OnClick", function()
farmbuyer@44 1023 _G.InterfaceOptionsFrameCancel:Click()
farmbuyer@44 1024 _G.HideUIPanel(GameMenuFrame)
farmbuyer@44 1025 addon:OpenMainDisplayToTab"Options"
farmbuyer@44 1026 end)
farmbuyer@44 1027 button:SetText('"/ouroloot opt"')
farmbuyer@44 1028 button:SetPoint("TOPLEFT",20,-20)
farmbuyer@44 1029 _b:SetScript("OnShow",nil)
farmbuyer@44 1030 end)
farmbuyer@44 1031 _G.InterfaceOptions_AddCategory(bliz)
farmbuyer@44 1032
farmbuyer@73 1033 -- Maybe load up g_uniques now?
farmbuyer@73 1034 if opts.precache_history_uniques then
farmbuyer@73 1035 self:_cache_history_uniques()
farmbuyer@73 1036 end
farmbuyer@73 1037
farmbuyer@49 1038 self:_scan_LOD_modules()
farmbuyer@49 1039
farmbuyer@73 1040 self:_set_remote_change_chatframe (opts.chatty_on_remote_changes_frame, --[[silent_p=]]true)
farmbuyer@73 1041
farmbuyer@1 1042 if self.debug.flow then self:Print"is in control-flow debug mode." end
farmbuyer@1 1043 end
farmbuyer@1 1044 --function addon:OnDisable() end
farmbuyer@1 1045
farmbuyer@58 1046 do
farmbuyer@58 1047 local prototype = {}
farmbuyer@95 1048 local registry
farmbuyer@95 1049
farmbuyer@95 1050 -- By default, no plugins. First one in sets up code for any after.
farmbuyer@95 1051 addon.get_plugin = flib.nullfunc
farmbuyer@95 1052
farmbuyer@95 1053 -- Fires before the plugin's own OnEnable (inherited or otherwise).
farmbuyer@95 1054 function addon:OnModuleCreated (plugin)
farmbuyer@95 1055 if not registry then
farmbuyer@95 1056 registry = {}
farmbuyer@95 1057 addon.get_plugin = function(a,t) return registry[t] end
farmbuyer@95 1058 prototype.register_text_generator = function(p,t,...)
farmbuyer@95 1059 registry[t] = p
farmbuyer@95 1060 return addon:register_text_generator(t,...)
farmbuyer@95 1061 end
farmbuyer@95 1062 prototype.register_tab_control = function(p,t,...)
farmbuyer@95 1063 registry[t] = p
farmbuyer@95 1064 return addon:register_tab_control(t,...)
farmbuyer@95 1065 end
farmbuyer@95 1066 end
farmbuyer@95 1067 end
farmbuyer@95 1068
farmbuyer@58 1069 local function module_OnEnable (plugin)
farmbuyer@58 1070 if plugin.option_defaults then
farmbuyer@58 1071 local SVname = 'OuroLoot'..plugin:GetName()..'_opts'
farmbuyer@58 1072 if not _G[SVname] then
farmbuyer@58 1073 _G[SVname] = {}
farmbuyer@58 1074 if type(plugin.OnFirstTime) == 'function' then
farmbuyer@58 1075 plugin:OnFirstTime()
farmbuyer@58 1076 end
farmbuyer@58 1077 end
farmbuyer@58 1078 plugin.opts = _G[SVname]
farmbuyer@58 1079 for option,default in pairs(plugin.option_defaults) do
farmbuyer@58 1080 if plugin.opts[option] == nil then
farmbuyer@58 1081 plugin.opts[option] = default
farmbuyer@58 1082 end
farmbuyer@58 1083 end
farmbuyer@58 1084 plugin.option_defaults = nil
farmbuyer@58 1085 end
farmbuyer@58 1086 end
farmbuyer@58 1087
farmbuyer@58 1088 prototype.OnEnable = module_OnEnable
farmbuyer@58 1089 prototype.default_OnEnable = module_OnEnable
farmbuyer@58 1090
farmbuyer@58 1091 addon:SetDefaultModuleLibraries("AceConsole-3.0")
farmbuyer@58 1092 addon:SetDefaultModulePrototype(prototype)
farmbuyer@63 1093
farmbuyer@64 1094 local err = [[Module '%s' cannot register itself because it failed a required condition: '%s']]
farmbuyer@63 1095 function addon:ConstrainedNewModule (modname, minrev, mincomm, mindata)
farmbuyer@63 1096 if not addon.author_debug then
farmbuyer@89 1097 if minrev and tonumber(minrev) > (tonumber(self.revision) or math.huge) then
farmbuyer@63 1098 self:Print(err,modname,
farmbuyer@89 1099 "revision "..self.revision.." older than minimum "..minrev)
farmbuyer@63 1100 return false
farmbuyer@63 1101 end
farmbuyer@89 1102 if mincomm and tonumber(mincomm) > tonumber(self.commrev) then
farmbuyer@63 1103 self:Print(err,modname,
farmbuyer@63 1104 "commrev "..self.commrev.." older than minimum "..mincomm)
farmbuyer@63 1105 return false
farmbuyer@63 1106 end
farmbuyer@89 1107 if mindata and tonumber(mindata) > opts.datarev then
farmbuyer@63 1108 self:Print(err,modname,
farmbuyer@63 1109 "datarev "..opts.datarev.." older than minimum "..mindata)
farmbuyer@63 1110 return false
farmbuyer@63 1111 end
farmbuyer@63 1112 end
farmbuyer@63 1113 return self:NewModule(modname)
farmbuyer@63 1114 end
farmbuyer@58 1115 end
farmbuyer@58 1116
farmbuyer@1 1117
farmbuyer@1 1118 ------ Event handlers
farmbuyer@1 1119 function addon:_clear_SVs()
farmbuyer@1 1120 g_loot = {} -- not saved, just fooling PLAYER_LOGOUT tests
farmbuyer@73 1121 _G.OuroLootSV = nil
farmbuyer@73 1122 _G.OuroLootSV_saved = nil
farmbuyer@73 1123 _G.OuroLootSV_opts = nil
farmbuyer@73 1124 _G.OuroLootSV_hist = nil
farmbuyer@73 1125 _G.OuroLootSV_log = nil
farmbuyer@73 1126 _G.ReloadUI()
farmbuyer@1 1127 end
farmbuyer@1 1128 function addon:PLAYER_LOGOUT()
farmbuyer@90 1129 self:UnregisterEvent(RAID_ROSTER_UPDATE_EVENT)
farmbuyer@16 1130 self:UnregisterEvent("PLAYER_ENTERING_WORLD")
farmbuyer@16 1131
farmbuyer@73 1132 local worth_saving = #g_loot > 0 or _G.next(g_loot.raiders)
farmbuyer@16 1133 if not worth_saving then for text in self:registered_textgen_iter() do
farmbuyer@16 1134 worth_saving = worth_saving or g_loot.printed[text] > 0
farmbuyer@16 1135 end end
farmbuyer@16 1136 if worth_saving then
farmbuyer@16 1137 opts.autoshard = self.sharder
farmbuyer@16 1138 opts.threshold = self.threshold
farmbuyer@1 1139 for i,e in ipairs(g_loot) do
farmbuyer@1 1140 e.cols = nil
farmbuyer@1 1141 end
farmbuyer@73 1142 _G.OuroLootSV = g_loot
farmbuyer@16 1143 else
farmbuyer@73 1144 _G.OuroLootSV = nil
farmbuyer@1 1145 end
farmbuyer@16 1146
farmbuyer@38 1147 worth_saving = false
farmbuyer@6 1148 for r,t in pairs(self.history_all) do if type(t) == 'table' then
farmbuyer@8 1149 if #t == 0 then
farmbuyer@8 1150 self.history_all[r] = nil
farmbuyer@8 1151 else
farmbuyer@38 1152 worth_saving = true
farmbuyer@8 1153 t.realm = nil
farmbuyer@8 1154 t.st = nil
farmbuyer@8 1155 t.byname = nil
farmbuyer@8 1156 end
farmbuyer@6 1157 end end
farmbuyer@38 1158 if worth_saving then
farmbuyer@73 1159 _G.OuroLootSV_hist = self.history_all
farmbuyer@73 1160 _G.OuroLootSV_hist.HISTFORMAT = 4
farmbuyer@38 1161 else
farmbuyer@73 1162 _G.OuroLootSV_hist = nil
farmbuyer@38 1163 end
farmbuyer@73 1164 _G.OuroLootSV_log = #_G.OuroLootSV_log > 0 and _G.OuroLootSV_log or nil
farmbuyer@1 1165 end
farmbuyer@1 1166
farmbuyer@10 1167 do
farmbuyer@67 1168 local IsInInstance, UnitIsConnected, UnitClass, UnitRace, UnitSex,
farmbuyer@56 1169 UnitLevel, UnitInRaid, UnitIsVisible, GetGuildInfo, GetRaidRosterInfo =
farmbuyer@67 1170 IsInInstance, UnitIsConnected, UnitClass, UnitRace, UnitSex,
farmbuyer@56 1171 UnitLevel, UnitInRaid, UnitIsVisible, GetGuildInfo, GetRaidRosterInfo
farmbuyer@10 1172 local time, difftime = time, difftime
farmbuyer@10 1173 local R_ACTIVE, R_OFFLINE, R_LEFT = 1, 2, 3
farmbuyer@10 1174
farmbuyer@10 1175 local lastevent, now = 0, 0
farmbuyer@16 1176 local redo_count = 0
farmbuyer@16 1177 local redo, timer_handle
farmbuyer@10 1178
farmbuyer@10 1179 function addon:CheckRoster (leaving_p, now_a)
farmbuyer@10 1180 if not g_loot.raiders then return end -- bad transition
farmbuyer@10 1181
farmbuyer@10 1182 now = now_a or time()
farmbuyer@10 1183
farmbuyer@10 1184 if leaving_p then
farmbuyer@16 1185 if timer_handle then
farmbuyer@16 1186 self:CancelTimer(timer_handle)
farmbuyer@16 1187 timer_handle = nil
farmbuyer@16 1188 end
farmbuyer@10 1189 for name,r in pairs(g_loot.raiders) do
farmbuyer@10 1190 r.leave = r.leave or now
farmbuyer@10 1191 end
farmbuyer@10 1192 return
farmbuyer@10 1193 end
farmbuyer@10 1194
farmbuyer@10 1195 for name,r in pairs(g_loot.raiders) do
farmbuyer@10 1196 if r.online ~= R_LEFT and not UnitInRaid(name) then
farmbuyer@10 1197 r.online = R_LEFT
farmbuyer@10 1198 r.leave = now
farmbuyer@10 1199 end
farmbuyer@10 1200 end
farmbuyer@10 1201
farmbuyer@16 1202 if redo then
farmbuyer@16 1203 redo_count = redo_count + 1
farmbuyer@16 1204 end
farmbuyer@16 1205 redo = false
farmbuyer@10 1206 for i = 1, GetNumRaidMembers() do
farmbuyer@10 1207 local unit = 'raid'..i
farmbuyer@67 1208 -- We grab a bunch of return values here, but only pay attention to
farmbuyer@67 1209 -- them under specific circumstances.
farmbuyer@67 1210 local name, connected, subgroup, level, class, _
farmbuyer@67 1211 name, _, subgroup, level, _, class, connected = GetRaidRosterInfo(i)
farmbuyer@10 1212 -- No, that's not my typo, it really is "uknownbeing" in Blizzard's code.
farmbuyer@10 1213 if name and name ~= UNKNOWN and name ~= UNKNOWNOBJECT and name ~= UKNOWNBEING then
farmbuyer@10 1214 if not g_loot.raiders[name] then
farmbuyer@10 1215 g_loot.raiders[name] = { needinfo=true }
farmbuyer@10 1216 end
farmbuyer@10 1217 local r = g_loot.raiders[name]
farmbuyer@68 1218 r.subgroup = subgroup or (NUM_RAID_GROUPS+1)
farmbuyer@10 1219 if r.needinfo and UnitIsVisible(unit) then
farmbuyer@10 1220 r.needinfo = nil
farmbuyer@56 1221 r.class = class --select(2,UnitClass(unit))
farmbuyer@10 1222 r.race = select(2,UnitRace(unit))
farmbuyer@10 1223 r.sex = UnitSex(unit)
farmbuyer@56 1224 r.level = level --UnitLevel(unit)
farmbuyer@10 1225 r.guild = GetGuildInfo(unit)
farmbuyer@10 1226 end
farmbuyer@56 1227 --local connected = UnitIsConnected(unit)
farmbuyer@10 1228 if connected and r.online ~= R_ACTIVE then
farmbuyer@10 1229 r.join = r.join or now
farmbuyer@10 1230 r.online = R_ACTIVE
farmbuyer@10 1231 elseif (not connected) and r.online ~= R_OFFLINE then
farmbuyer@10 1232 r.leave = now
farmbuyer@10 1233 r.online = R_OFFLINE
farmbuyer@10 1234 end
farmbuyer@10 1235 redo = redo or r.needinfo
farmbuyer@10 1236 end
farmbuyer@10 1237 end
farmbuyer@16 1238 if redo then -- XXX test redo_count here and eventually give up?
farmbuyer@16 1239 if not timer_handle then
farmbuyer@16 1240 timer_handle = self:ScheduleRepeatingTimer("RAID_ROSTER_UPDATE", 60)
farmbuyer@16 1241 end
farmbuyer@16 1242 else
farmbuyer@16 1243 redo_count = 0
farmbuyer@16 1244 if timer_handle then
farmbuyer@16 1245 self:CancelTimer(timer_handle)
farmbuyer@16 1246 timer_handle = nil
farmbuyer@16 1247 end
farmbuyer@10 1248 end
farmbuyer@10 1249 end
farmbuyer@10 1250
farmbuyer@10 1251 function addon:RAID_ROSTER_UPDATE (event)
farmbuyer@10 1252 if GetNumRaidMembers() == 0 then
farmbuyer@16 1253 -- Leaving a raid group.
farmbuyer@10 1254 -- Because of PLAYER_ENTERING_WORLD, this code also executes on load
farmbuyer@10 1255 -- screens while soloing and in regular groups. Take care.
farmbuyer@16 1256 self.dprint('flow', "GetNumRaidMembers == 0")
farmbuyer@16 1257 if self.enabled and not self.debug.notraid then
farmbuyer@16 1258 self.dprint('flow', "enabled, leaving raid")
farmbuyer@10 1259 self.popped = nil
farmbuyer@80 1260 self:Deactivate()
farmbuyer@10 1261 self:CheckRoster(--[[leaving raid]]true)
farmbuyer@10 1262 end
farmbuyer@10 1263 return
farmbuyer@10 1264 end
farmbuyer@10 1265
farmbuyer@1 1266 local inside,whatkind = IsInInstance()
farmbuyer@1 1267 if inside and (whatkind == "pvp" or whatkind == "arena") then
farmbuyer@41 1268 self.dprint('flow', "got RRU event but in pvp zone, bailing")
farmbuyer@41 1269 return
farmbuyer@1 1270 end
farmbuyer@10 1271
farmbuyer@10 1272 local docheck = self.enabled
farmbuyer@1 1273 if event == "Activate" then
farmbuyer@1 1274 -- dispatched manually from Activate
farmbuyer@10 1275 self:RegisterEvent("CHAT_MSG_LOOT")
farmbuyer@2 1276 _register_bossmod(self)
farmbuyer@10 1277 docheck = true
farmbuyer@90 1278 elseif event == RAID_ROSTER_UPDATE_EVENT then
farmbuyer@10 1279 -- hot code path, be careful
farmbuyer@10 1280
farmbuyer@1 1281 -- event registration from onload, joined a raid, maybe show popup
farmbuyer@16 1282 self.dprint('flow', "RRU check:", self.popped, opts.popup_on_join)
farmbuyer@10 1283 if (not self.popped) and opts.popup_on_join then
farmbuyer@1 1284 self.popped = StaticPopup_Show "OUROL_REMIND"
farmbuyer@1 1285 self.popped.data = self
farmbuyer@10 1286 return
farmbuyer@1 1287 end
farmbuyer@1 1288 end
farmbuyer@11 1289 -- Throttle the checks fired by common events.
farmbuyer@10 1290 if docheck and not InCombatLockdown() then
farmbuyer@10 1291 now = time()
farmbuyer@10 1292 if difftime(now,lastevent) > 45 then
farmbuyer@10 1293 lastevent = now
farmbuyer@10 1294 self:CheckRoster(false,now)
farmbuyer@10 1295 end
farmbuyer@10 1296 end
farmbuyer@1 1297 end
farmbuyer@1 1298 end
farmbuyer@1 1299
farmbuyer@73 1300 --[=[ CHAT_MSG_LOOT handler and its helpers.
farmbuyer@73 1301 Situations for "unique tag" generation, given N people seeing local loot
farmbuyer@73 1302 events, M people seeing remote rebroadcasts, and player Z adding manually:
farmbuyer@73 1303
farmbuyer@73 1304 + Local tracking: All LOCALs should see the same itemstring, thus the same
farmbuyer@73 1305 unique ID stripped out of field #9. LOCALn includes this in the broadcast
farmbuyer@73 1306 to REMOTEm. Tag is a large number, meaningless for clients and players.
farmbuyer@73 1307
farmbuyer@73 1308 + Local broadcasting, remote tracking: same as local tracking. Possibly
farmbuyer@73 1309 some weirdness if all local versions are significantly older than the remote
farmbuyer@73 1310 versions; in this case each REMOTEn will generate their own tags of the form
farmbuyer@73 1311 itemID+formatted_date, which will not be "unique" for the next 60 seconds.
farmbuyer@73 1312 As long as at least one LOCALn is recent enough to strip and broadcast a
farmbuyer@73 1313 proper ID, multiple items of the same visible name will not be "lost".
farmbuyer@73 1314
farmbuyer@73 1315 + Z manually inserts a loot entry: Z generates a tag, preserved locally.
farmbuyer@73 1316 If Z rebrodcasts that entry, all REMOTEs will see it. Tag is of the form
farmbuyer@73 1317 "n" followed by a random number.
farmbuyer@73 1318 ]=]
farmbuyer@1 1319 do
farmbuyer@73 1320 local counter, _do_loot
farmbuyer@73 1321 do
farmbuyer@73 1322 local count = 0
farmbuyer@73 1323 function counter() count = count + 1; return count; end
farmbuyer@73 1324 end
farmbuyer@73 1325
farmbuyer@42 1326 local function maybe_trash_kill_entry()
farmbuyer@80 1327 -- This field is set on various boss interactions, so we've got a
farmbuyer@80 1328 -- kill/wipe entry already.
farmbuyer@42 1329 if addon.latest_instance then return end
farmbuyer@69 1330 local ss, max, inst = addon:snapshot_raid()
farmbuyer@69 1331 addon.latest_instance = inst
farmbuyer@69 1332 addon:_mark_boss_kill (addon._addBossEntry{
farmbuyer@61 1333 kind='boss', reason='kill', bossname=[[trash]],
farmbuyer@61 1334 instance=addon.latest_instance, duration=0,
farmbuyer@61 1335 raidersnap=ss, maxsize=max
farmbuyer@42 1336 })
farmbuyer@42 1337 end
farmbuyer@42 1338
farmbuyer@76 1339 -- Alert other trackers that unique tag EXISTING in subsequent 'casts
farmbuyer@76 1340 -- should be replaced by REPLACE instead. If multiple players all saw
farmbuyer@76 1341 -- the same loot event, this will cause a flurry of cross-improvs.
farmbuyer@76 1342 local function _announce_unique_improvisation (existing, replace)
farmbuyer@76 1343 if not g_unique_replace then _setup_unique_replace() end
farmbuyer@76 1344 g_unique_replace.new_entry (g_unique_replace.me, existing, replace, 'improv')
farmbuyer@76 1345 addon:vbroadcast('improv', g_unique_replace.me, existing, replace)
farmbuyer@76 1346 end
farmbuyer@76 1347
farmbuyer@73 1348 local random = _G.math.random
farmbuyer@76 1349 local function _many_uniques_handle_it (u, prefix)
farmbuyer@76 1350 if u then
farmbuyer@73 1351 -- Check and alert for an existing value.
farmbuyer@73 1352 u = tostring(u)
farmbuyer@73 1353 if g_uniques[u].history ~= g_uniques.NOTFOUND then
farmbuyer@76 1354 if not g_unique_replace then _setup_unique_replace() end
farmbuyer@76 1355 local maybe = g_unique_replace.get_previous_replacement (u)
farmbuyer@76 1356 if maybe then
farmbuyer@76 1357 addon.dprint('loot',"previous replaced tag ("..u
farmbuyer@76 1358 ..") with ("..maybe.."), using that instead")
farmbuyer@76 1359 return false, u, maybe
farmbuyer@76 1360 end
farmbuyer@76 1361 local can_replace_p,improv = _many_uniques_handle_it (nil, 'c')
farmbuyer@76 1362 if can_replace_p then
farmbuyer@76 1363 _announce_unique_improvisation (u, improv)
farmbuyer@76 1364 return false, u, improv
farmbuyer@76 1365 end
farmbuyer@76 1366 return false, u
farmbuyer@73 1367 end
farmbuyer@73 1368 addon.dprint('loot',"verified unique tag ("..u..")")
farmbuyer@73 1369 else
farmbuyer@87 1370 -- Need to *find* an unused value. For now use a range of J*10^4
farmbuyer@87 1371 -- where J is Jenny's Constant. Thank you, <xkcd.com/1047>.
farmbuyer@76 1372 prefix = prefix or 'n'
farmbuyer@73 1373 repeat
farmbuyer@76 1374 u = prefix .. random(8675309)
farmbuyer@76 1375 until g_uniques:TEST(u).history == g_uniques.NOTFOUND
farmbuyer@76 1376 addon.dprint('loot',"created unique tag ("..u..")")
farmbuyer@73 1377 end
farmbuyer@73 1378 return true, u
farmbuyer@73 1379 end
farmbuyer@73 1380
farmbuyer@1 1381 -- Recent loot cache
farmbuyer@25 1382 local candidates = {}
farmbuyer@73 1383 local sigmap = {}
farmbuyer@73 1384 local function preempt_older_signature (oldersig, newersig)
farmbuyer@76 1385 local origin = candidates[oldersig] and candidates[oldersig].bcast_from
farmbuyer@73 1386 if origin and g_seeing_oldsigs[origin] then
farmbuyer@73 1387 -- replace entry from older client with this newer one
farmbuyer@73 1388 candidates[oldersig] = nil
farmbuyer@76 1389 addon.dprint('loot', "preempting signature <", oldersig, "> from", origin)
farmbuyer@73 1390 end
farmbuyer@73 1391 return false
farmbuyer@73 1392 end
farmbuyer@73 1393
farmbuyer@25 1394 local function prefer_local_loots (cache)
farmbuyer@25 1395 -- The function name is a bit of a misnomer, as local entries overwrite
farmbuyer@25 1396 -- remote entries as the candidate table is populated. This routine is
farmbuyer@39 1397 -- here to extract the final results once the cache timers have expired.
farmbuyer@38 1398 --
farmbuyer@34 1399 -- Keep this sync'd with the local_override branch below.
farmbuyer@25 1400 for i,sig in ipairs(candidates) do
farmbuyer@25 1401 addon.dprint('loot', "processing candidate entry", i, sig)
farmbuyer@25 1402 local loot = candidates[sig]
farmbuyer@25 1403 if loot then
farmbuyer@42 1404 maybe_trash_kill_entry() -- Generate *some* kind of boss/location entry
farmbuyer@25 1405 candidates[sig] = nil
farmbuyer@25 1406 local looti = addon._addLootEntry(loot)
farmbuyer@73 1407 addon.dprint('loot', "entry", i, "was found, added at index", looti)
farmbuyer@25 1408 if (loot.disposition ~= 'shard')
farmbuyer@25 1409 and (loot.disposition ~= 'gvault')
farmbuyer@25 1410 and (not addon.history_suppress)
farmbuyer@25 1411 then
farmbuyer@25 1412 addon:_addHistoryEntry(looti)
farmbuyer@76 1413 elseif #loot.unique > 0 then
farmbuyer@76 1414 g_uniques[loot.unique] = -- stub entry
farmbuyer@76 1415 { loot = looti, history = g_uniques.NOTFOUND }
farmbuyer@25 1416 end
farmbuyer@25 1417 end
farmbuyer@25 1418 end
farmbuyer@25 1419
farmbuyer@25 1420 if addon.display then
farmbuyer@25 1421 addon:redisplay()
farmbuyer@25 1422 end
farmbuyer@47 1423 wipe(candidates)
farmbuyer@73 1424 wipe(sigmap)
farmbuyer@25 1425 end
farmbuyer@73 1426 local recent_loot = create_new_cache ('loot', comm_cleanup_ttl+3, prefer_local_loots)
farmbuyer@1 1427
farmbuyer@73 1428 local strsplit, GetItemInfo, GetItemIcon, UnitClass =
farmbuyer@73 1429 _G.strsplit, _G.GetItemInfo, _G.GetItemIcon, _G.UnitClass
farmbuyer@1 1430
farmbuyer@71 1431 -- 'from' only present if this is triggered by a broadcast
farmbuyer@73 1432 function _do_loot (self, local_override, recipient, unique, itemid, count, from, extratext)
farmbuyer@73 1433 local prefix = "_do_loot[" .. counter() .. "]"
farmbuyer@6 1434 local itexture = GetItemIcon(itemid)
farmbuyer@6 1435 local iname, ilink, iquality = GetItemInfo(itemid)
farmbuyer@73 1436 local cache_miss
farmbuyer@6 1437 if (not iname) or (not itexture) then
farmbuyer@73 1438 cache_miss = true
farmbuyer@6 1439 iname, ilink, iquality, itexture =
farmbuyer@6 1440 UNKNOWN..': '..itemid, 'item:6948', ITEM_QUALITY_COMMON, [[ICONS\INV_Misc_QuestionMark]]
farmbuyer@6 1441 end
farmbuyer@73 1442 self.dprint('loot',">>"..prefix, "R:", recipient, "U:", unique, "I:",
farmbuyer@71 1443 itemid, "C:", count, "frm:", from, "ex:", extratext, "q:", iquality)
farmbuyer@1 1444
farmbuyer@19 1445 itemid = tonumber(ilink:match("item:(%d+)") or 0)
farmbuyer@73 1446
farmbuyer@73 1447 -- This is only a 'while' to make jumping out of it easy.
farmbuyer@76 1448 local i, unique_okay, replacement, ret1, ret2
farmbuyer@73 1449 while local_override
farmbuyer@73 1450 or ((iquality >= self.threshold) and not opts.itemfilter[itemid])
farmbuyer@73 1451 do
farmbuyer@76 1452 unique_okay, unique, replacement =
farmbuyer@76 1453 _many_uniques_handle_it ((not local_override) and unique)
farmbuyer@73 1454 if not unique_okay then
farmbuyer@76 1455 if replacement then
farmbuyer@76 1456 -- collision, but we've generated a placeholder for now
farmbuyer@76 1457 -- and broadcast the fact
farmbuyer@76 1458 self.dprint('loot', "substituting", unique, "with", replacement)
farmbuyer@76 1459 else
farmbuyer@76 1460 i = g_uniques[unique]
farmbuyer@81 1461 local err = unique_collision:format (iname, unique,
farmbuyer@76 1462 tostring(from), tostring(i.loot), tostring(i.history))
farmbuyer@77 1463 _unavoidable_collision (err)
farmbuyer@76 1464 -- Make sure this is logged one way or another
farmbuyer@76 1465 ;(self.debug.loot and self.dprint or pprint)('loot', "COLLISION", prefix, err);
farmbuyer@76 1466 ret1, ret2 = nil, err
farmbuyer@76 1467 break
farmbuyer@76 1468 end
farmbuyer@73 1469 end
farmbuyer@73 1470
farmbuyer@1 1471 if (self.rebroadcast and (not from)) and not local_override then
farmbuyer@71 1472 self:vbroadcast('loot', recipient, unique, itemid, count)
farmbuyer@1 1473 end
farmbuyer@25 1474 if (not self.enabled) and (not local_override) then break end
farmbuyer@73 1475
farmbuyer@73 1476 local oldersig = recipient .. iname .. (count or "")
farmbuyer@73 1477 local signature, seenit
farmbuyer@73 1478 if #unique > 0 then
farmbuyer@73 1479 -- newer case
farmbuyer@73 1480 signature = unique .. oldersig
farmbuyer@73 1481 sigmap[oldersig] = signature
farmbuyer@76 1482 seenit = (from and recent_loot:test(signature))
farmbuyer@73 1483 -- The following clause is what handles older 'casts arriving
farmbuyer@73 1484 -- earlier. All this is tested inside-out to maximize short
farmbuyer@76 1485 -- circuit evaluation; the preempt function always returns
farmbuyer@76 1486 -- false to force seenit off.
farmbuyer@76 1487 or (g_seeing_oldsigs and preempt_older_signature(oldersig,signature))
farmbuyer@25 1488 else
farmbuyer@73 1489 -- older case, only remote
farmbuyer@73 1490 assert(from)
farmbuyer@73 1491 signature = sigmap[oldersig] or oldersig
farmbuyer@73 1492 seenit = recent_loot:test(signature)
farmbuyer@73 1493 end
farmbuyer@73 1494
farmbuyer@73 1495 if seenit then
farmbuyer@80 1496 self.dprint('loot', "remote", prefix, "<", signature,
farmbuyer@73 1497 "> already in cache, skipping from", from)
farmbuyer@73 1498 break
farmbuyer@73 1499 end
farmbuyer@73 1500
farmbuyer@73 1501 -- There is some redundancy in all this, in the interests of ease-of-coding
farmbuyer@73 1502 i = {
farmbuyer@73 1503 kind = 'loot',
farmbuyer@73 1504 person = recipient,
farmbuyer@73 1505 person_class= select(2,UnitClass(recipient)),
farmbuyer@73 1506 cache_miss = cache_miss,
farmbuyer@73 1507 quality = iquality,
farmbuyer@73 1508 itemname = iname,
farmbuyer@73 1509 id = itemid,
farmbuyer@73 1510 itemlink = ilink,
farmbuyer@73 1511 itexture = itexture,
farmbuyer@76 1512 unique = replacement or unique,
farmbuyer@73 1513 count = (count and count ~= "") and count or nil,
farmbuyer@73 1514 bcast_from = from,
farmbuyer@73 1515 extratext = extratext,
farmbuyer@73 1516 variant = self:is_variant_item(ilink),
farmbuyer@73 1517 }
farmbuyer@73 1518 if opts.itemvault[itemid] then
farmbuyer@73 1519 i.disposition = 'gvault'
farmbuyer@73 1520 elseif recipient == self.sharder then
farmbuyer@73 1521 i.disposition = 'shard'
farmbuyer@73 1522 end
farmbuyer@73 1523 if local_override then
farmbuyer@73 1524 -- player is adding loot by hand, don't wait for network cache timeouts
farmbuyer@73 1525 -- keep this sync'd with prefer_local_loots above
farmbuyer@73 1526 if i.extratext == 'shard'
farmbuyer@73 1527 or i.extratext == 'gvault'
farmbuyer@73 1528 or i.extratext == 'offspec'
farmbuyer@73 1529 then
farmbuyer@73 1530 i.disposition = i.extratext
farmbuyer@70 1531 end
farmbuyer@73 1532 local looti = self._addLootEntry(i)
farmbuyer@73 1533 if (i.disposition ~= 'shard')
farmbuyer@73 1534 and (i.disposition ~= 'gvault')
farmbuyer@73 1535 and (not self.history_suppress)
farmbuyer@73 1536 then
farmbuyer@73 1537 self:_addHistoryEntry(looti)
farmbuyer@76 1538 else
farmbuyer@76 1539 g_uniques[i.unique] = -- stub entry
farmbuyer@76 1540 { loot = looti, history = g_uniques.NOTFOUND }
farmbuyer@34 1541 end
farmbuyer@73 1542 ret1 = looti -- return value mostly for gui's manual entry
farmbuyer@76 1543 self.dprint('loot', "manual", looti)
farmbuyer@73 1544 else
farmbuyer@73 1545 recent_loot:add(signature)
farmbuyer@73 1546 candidates[signature] = i
farmbuyer@73 1547 tinsert (candidates, signature)
farmbuyer@80 1548 self.dprint('loot', prefix, "<", signature,
farmbuyer@73 1549 "> added to cache as candidate", #candidates)
farmbuyer@1 1550 end
farmbuyer@25 1551 break
farmbuyer@1 1552 end
farmbuyer@73 1553 self.dprint('loot',"<<"..prefix, "out")
farmbuyer@73 1554 return ret1, ret2
farmbuyer@1 1555 end
farmbuyer@1 1556
farmbuyer@73 1557 -- Returns the index of the resulting new loot entry, or nil after
farmbuyer@73 1558 -- displaying any errors.
farmbuyer@1 1559 function addon:CHAT_MSG_LOOT (event, ...)
farmbuyer@1 1560 if (not self.rebroadcast) and (not self.enabled) and (event ~= "manual") then return end
farmbuyer@1 1561
farmbuyer@1 1562 --[[
farmbuyer@1 1563 iname: Hearthstone
farmbuyer@1 1564 iquality: integer
farmbuyer@1 1565 ilink: clickable formatted link
farmbuyer@1 1566 itemstring: item:6948:....
farmbuyer@1 1567 itexture: inventory icon texture
farmbuyer@1 1568 ]]
farmbuyer@1 1569
farmbuyer@1 1570 if event == "CHAT_MSG_LOOT" then
farmbuyer@1 1571 local msg = ...
farmbuyer@20 1572 local person, itemstring, count
farmbuyer@21 1573 --ChatFrame2:AddMessage("original string: >"..(msg:gsub("\124","\124\124")).."<")
farmbuyer@20 1574
farmbuyer@20 1575 -- test in most likely order: other people get more loot than "you" do
farmbuyer@20 1576 person, itemstring, count = msg:match(g_LOOT_ITEM_MULTIPLE_sss)
farmbuyer@20 1577 if not person then
farmbuyer@20 1578 person, itemstring = msg:match(g_LOOT_ITEM_ss)
farmbuyer@20 1579 end
farmbuyer@20 1580 if not person then
farmbuyer@20 1581 itemstring, count = msg:match(g_LOOT_ITEM_SELF_MULTIPLE_ss)
farmbuyer@20 1582 if not itemstring then
farmbuyer@20 1583 itemstring = msg:match(g_LOOT_ITEM_SELF_s)
farmbuyer@20 1584 end
farmbuyer@20 1585 end
farmbuyer@20 1586
farmbuyer@89 1587 if not itemstring then return end -- "PlayerX selected Greed", etc, not looting
farmbuyer@65 1588 self.dprint('loot', "CHAT_MSG_LOOT, person is", person,
farmbuyer@65 1589 ", itemstring is", itemstring, ", count is", count)
farmbuyer@1 1590
farmbuyer@1 1591 -- Name might be colorized, remove the highlighting
farmbuyer@20 1592 if person then
farmbuyer@20 1593 person = person:match("|c%x%x%x%x%x%x%x%x(%S+)") or person
farmbuyer@20 1594 else
farmbuyer@38 1595 person = my_name -- UNIT_YOU / You
farmbuyer@20 1596 end
farmbuyer@1 1597
farmbuyer@71 1598 --local id = tonumber(itemstring:match('|Hitem:(%d+):'))
farmbuyer@71 1599 local id,unique,_
farmbuyer@71 1600 _,id,_,_,_,_,_,_,unique = strsplit (":", itemstring)
farmbuyer@71 1601 if unique == 0 then unique = nil end
farmbuyer@1 1602
farmbuyer@73 1603 return _do_loot (self, false, person, unique, id, count)
farmbuyer@1 1604
farmbuyer@1 1605 elseif event == "broadcast" then
farmbuyer@73 1606 return _do_loot(self, false, ...)
farmbuyer@1 1607
farmbuyer@1 1608 elseif event == "manual" then
farmbuyer@1 1609 local r,i,n = ...
farmbuyer@73 1610 return _do_loot(self, true, r, --[[unique=]]nil, i,
farmbuyer@71 1611 --[[count=]]nil, --[[from=]]nil, n)
farmbuyer@1 1612 end
farmbuyer@1 1613 end
farmbuyer@1 1614 end
farmbuyer@1 1615
farmbuyer@80 1616 -- This only triggers on entering combat after a registered boss kill.
farmbuyer@80 1617 -- Clearing this field forces subsequent trash kills to generate an entry
farmbuyer@80 1618 -- via maybe_trash_kill_entry.
farmbuyer@80 1619 -- (Possibly what is wanted is to start a 3 or 5 minute timer, and *then*
farmbuyer@80 1620 -- look for the next combat?)
farmbuyer@80 1621 function addon:PLAYER_REGEN_DISABLED()
farmbuyer@80 1622 self:UnregisterEvent ("PLAYER_REGEN_DISABLED")
farmbuyer@80 1623 self.latest_instance = nil
farmbuyer@80 1624 end
farmbuyer@80 1625
farmbuyer@1 1626
farmbuyer@1 1627 ------ Slash command handler
farmbuyer@1 1628 -- Thought about breaking this up into a table-driven dispatcher. But
farmbuyer@1 1629 -- that would result in a pile of teensy functions, most of which would
farmbuyer@1 1630 -- never be called. Too much overhead. (2.0: Most of these removed now
farmbuyer@1 1631 -- that GUI is in place.)
farmbuyer@76 1632 do
farmbuyer@76 1633 local green_help_link = addon.format_hypertext ([[Click here]],
farmbuyer@76 1634 ITEM_QUALITY_UNCOMMON, 'help')
farmbuyer@76 1635 function addon:OnSlash (txt) --, editbox)
farmbuyer@76 1636 txt = strtrim(txt:lower())
farmbuyer@76 1637 local cmd, arg = ""
farmbuyer@76 1638 do
farmbuyer@76 1639 local s,e = txt:find("^%a+")
farmbuyer@76 1640 if s then
farmbuyer@76 1641 cmd = txt:sub(s,e)
farmbuyer@76 1642 s = txt:find("%S", e+2)
farmbuyer@76 1643 if s then arg = txt:sub(s,-1) end
farmbuyer@76 1644 end
farmbuyer@1 1645 end
farmbuyer@1 1646
farmbuyer@76 1647 if cmd == "" then
farmbuyer@76 1648 if InCombatLockdown() then
farmbuyer@76 1649 return self:Print("Shouldn't display window in combat.")
farmbuyer@76 1650 else
farmbuyer@76 1651 return self:BuildMainDisplay()
farmbuyer@76 1652 end
farmbuyer@1 1653
farmbuyer@76 1654 elseif cmd:find("^thre") then
farmbuyer@76 1655 self:SetThreshold(arg)
farmbuyer@1 1656
farmbuyer@76 1657 elseif cmd == "on" then self:Activate(arg)
farmbuyer@76 1658 elseif cmd == "off" then self:Deactivate()
farmbuyer@76 1659 elseif cmd == "broadcast" or cmd == "bcast" then self:Activate(nil,true)
farmbuyer@76 1660
farmbuyer@76 1661 elseif cmd == "toggle" then
farmbuyer@76 1662 if self.display then
farmbuyer@76 1663 self.display:Hide()
farmbuyer@76 1664 else
farmbuyer@76 1665 return self:BuildMainDisplay()
farmbuyer@76 1666 end
farmbuyer@76 1667
farmbuyer@76 1668 elseif cmd == "fake" then -- maybe comment this out for real users
farmbuyer@76 1669 self:_mark_boss_kill (self._addBossEntry{
farmbuyer@76 1670 kind='boss',reason='kill',bossname="Baron Steamroller",duration=0
farmbuyer@76 1671 })
farmbuyer@76 1672 self:CHAT_MSG_LOOT ('manual', my_name, 54797)
farmbuyer@76 1673 if self.display then
farmbuyer@76 1674 self:redisplay()
farmbuyer@76 1675 end
farmbuyer@76 1676 self:Print "Baron Steamroller has been slain. Congratulations on your rug."
farmbuyer@76 1677
farmbuyer@76 1678 elseif cmd == "debug" then
farmbuyer@76 1679 if arg then
farmbuyer@76 1680 self.debug[arg] = not self.debug[arg]
farmbuyer@76 1681 _G.print(arg,self.debug[arg])
farmbuyer@76 1682 if self.debug[arg] then self.DEBUG_PRINT = true end
farmbuyer@76 1683 else
farmbuyer@76 1684 self.DEBUG_PRINT = not self.DEBUG_PRINT
farmbuyer@76 1685 end
farmbuyer@76 1686
farmbuyer@76 1687 elseif cmd == "save" and arg and arg:len() > 0 then
farmbuyer@76 1688 self:save_saveas(arg)
farmbuyer@76 1689 elseif cmd == "list" then
farmbuyer@76 1690 self:save_list()
farmbuyer@76 1691 elseif cmd == "restore" and arg and arg:len() > 0 then
farmbuyer@76 1692 self:save_restore(tonumber(arg))
farmbuyer@76 1693 elseif cmd == "delete" and arg and arg:len() > 0 then
farmbuyer@76 1694 self:save_delete(tonumber(arg))
farmbuyer@76 1695
farmbuyer@76 1696 elseif cmd == "help" then
farmbuyer@76 1697 self:BuildMainDisplay('help')
farmbuyer@76 1698 elseif cmd == "ping" then
farmbuyer@76 1699 self:DoPing()
farmbuyer@76 1700
farmbuyer@81 1701 elseif cmd == "fix" then
farmbuyer@81 1702 if arg == "?" then
farmbuyer@95 1703 self:Print[['/ouroloot fix cache' updates loot that wasn't in the cache]]
farmbuyer@95 1704 self:Print[['/ouroloot fix history' repairs inconsistent data on the History tab]]
farmbuyer@95 1705 self:Print[['/ouroloot fix' changes no stored data, only allows the window to be displayed again (this is built into all fixes above)]]
farmbuyer@81 1706 return
farmbuyer@81 1707 elseif arg == "cache" then
farmbuyer@81 1708 self:do_item_cache_fixup()
farmbuyer@81 1709 elseif arg == "history" then
farmbuyer@81 1710 self:repair_history_integrity()
farmbuyer@81 1711 end
farmbuyer@81 1712 self.NOLOAD = nil
farmbuyer@81 1713 self:Print("Window unlocked, best of luck.")
farmbuyer@76 1714
farmbuyer@23 1715 else
farmbuyer@88 1716 if self:OpenMainDisplayToTab(cmd,arg) then
farmbuyer@76 1717 return
farmbuyer@76 1718 end
farmbuyer@76 1719 self:Print("Unknown command '%s'. %s to see the help window.",
farmbuyer@76 1720 cmd, tostring(green_help_link))
farmbuyer@23 1721 end
farmbuyer@1 1722 end
farmbuyer@1 1723 end
farmbuyer@1 1724
farmbuyer@1 1725 function addon:SetThreshold (arg, quiet_p)
farmbuyer@1 1726 local q = tonumber(arg)
farmbuyer@1 1727 if q then
farmbuyer@76 1728 q = math.floor(q+0.1)
farmbuyer@1 1729 if q<0 or q>6 then
farmbuyer@1 1730 return self:Print("Threshold must be 0-6.")
farmbuyer@1 1731 end
farmbuyer@1 1732 else
farmbuyer@1 1733 q = qualnames[arg]
farmbuyer@1 1734 if not q then
farmbuyer@1 1735 return self:Print("Unrecognized item quality argument.")
farmbuyer@1 1736 end
farmbuyer@1 1737 end
farmbuyer@1 1738 self.threshold = q
farmbuyer@1 1739 if not quiet_p then self:Print("Threshold now set to %s.", self.thresholds[q]) end
farmbuyer@1 1740 end
farmbuyer@1 1741
farmbuyer@1 1742
farmbuyer@1 1743 ------ On/off
farmbuyer@80 1744 -- Both of these need to be (effectively) idempotent.
farmbuyer@1 1745 function addon:Activate (opt_threshold, opt_bcast_only)
farmbuyer@16 1746 self.dprint('flow', ":Activate is running")
farmbuyer@90 1747 self:RegisterEvent(RAID_ROSTER_UPDATE_EVENT,"RAID_ROSTER_UPDATE")
farmbuyer@16 1748 self:RegisterEvent("PLAYER_ENTERING_WORLD",
farmbuyer@16 1749 function() self:ScheduleTimer("RAID_ROSTER_UPDATE", 5, "PLAYER_ENTERING_WORLD") end)
farmbuyer@1 1750 self.popped = true
farmbuyer@93 1751 if IsInRaid() then
farmbuyer@16 1752 self.dprint('flow', ">:Activate calling RRU")
farmbuyer@1 1753 self:RAID_ROSTER_UPDATE("Activate")
farmbuyer@1 1754 elseif self.debug.notraid then
farmbuyer@76 1755 self.dprint('flow', ">:(notraid) Activate registering loot and bossmods")
farmbuyer@10 1756 self:RegisterEvent("CHAT_MSG_LOOT")
farmbuyer@2 1757 _register_bossmod(self)
farmbuyer@1 1758 elseif g_restore_p then
farmbuyer@1 1759 g_restore_p = nil
farmbuyer@16 1760 self.popped = nil -- get the reminder if later joining a raid
farmbuyer@16 1761 if #g_loot == 0 then
farmbuyer@16 1762 -- only generated text and raider join/leave data, not worth verbage
farmbuyer@16 1763 self.dprint('flow', ">:Activate restored generated texts, un-popping")
farmbuyer@16 1764 return
farmbuyer@16 1765 end
farmbuyer@78 1766 self:Print("Restored previous data, but not in a raid",
farmbuyer@48 1767 "and 5-player mode not active. |cffff0505NOT tracking loot|r;",
farmbuyer@1 1768 "use 'enable' to activate loot tracking, or 'clear' to erase",
farmbuyer@1 1769 "previous data, or 'help' to read about saved-texts commands.")
farmbuyer@1 1770 return
farmbuyer@1 1771 end
farmbuyer@1 1772 self.rebroadcast = true -- hardcode to true; this used to be more complicated
farmbuyer@1 1773 self.enabled = not opt_bcast_only
farmbuyer@73 1774 g_seeing_oldsigs = nil
farmbuyer@1 1775 if opt_threshold then
farmbuyer@1 1776 self:SetThreshold (opt_threshold, --[[quiet_p=]]true)
farmbuyer@1 1777 end
farmbuyer@80 1778 self:Print("Now %s; threshold currently %s.",
farmbuyer@1 1779 self.enabled and "tracking" or "only broadcasting",
farmbuyer@1 1780 self.thresholds[self.threshold])
farmbuyer@89 1781 self:broadcast('revcheck',version_large)
farmbuyer@1 1782 end
farmbuyer@1 1783
farmbuyer@95 1784 -- Note: running '/ouroloot off' will also avoid the popup reminder when
farmbuyer@1 1785 -- joining a raid, but will not change the saved option setting.
farmbuyer@1 1786 function addon:Deactivate()
farmbuyer@1 1787 self.enabled = false
farmbuyer@1 1788 self.rebroadcast = false
farmbuyer@90 1789 self:UnregisterEvent(RAID_ROSTER_UPDATE_EVENT)
farmbuyer@10 1790 self:UnregisterEvent("PLAYER_ENTERING_WORLD")
farmbuyer@10 1791 self:UnregisterEvent("CHAT_MSG_LOOT")
farmbuyer@80 1792 self:Print("Deactivated.")
farmbuyer@1 1793 end
farmbuyer@1 1794
farmbuyer@1 1795 function addon:Clear(verbose_p)
farmbuyer@1 1796 local repopup, st
farmbuyer@1 1797 if self.display then
farmbuyer@1 1798 -- in the new version, this is likely to always be the case
farmbuyer@1 1799 repopup = true
farmbuyer@1 1800 st = self.display:GetUserData("eoiST")
farmbuyer@1 1801 if not st then
farmbuyer@1 1802 self.dprint('flow', "Clear: display visible but eoiST not set??")
farmbuyer@1 1803 end
farmbuyer@1 1804 self.display:Hide()
farmbuyer@1 1805 end
farmbuyer@1 1806 g_restore_p = nil
farmbuyer@73 1807 _G.OuroLootSV = nil
farmbuyer@1 1808 self:_reset_timestamps()
farmbuyer@1 1809 if verbose_p then
farmbuyer@73 1810 if (_G.OuroLootSV_saved and #_G.OuroLootSV_saved>0) then
farmbuyer@73 1811 self:Print("Current loot data cleared, %d saved sets remaining.", #_G.OuroLootSV_saved)
farmbuyer@1 1812 else
farmbuyer@1 1813 self:Print("Current loot data cleared.")
farmbuyer@1 1814 end
farmbuyer@1 1815 end
farmbuyer@1 1816 _init(self,st)
farmbuyer@1 1817 if repopup then
farmbuyer@1 1818 addon:BuildMainDisplay()
farmbuyer@1 1819 end
farmbuyer@1 1820 end
farmbuyer@1 1821
farmbuyer@1 1822
farmbuyer@1 1823 ------ Behind the scenes routines
farmbuyer@19 1824 -- Semi-experimental debugging aid.
farmbuyer@19 1825 do
farmbuyer@76 1826 -- Declaring _log as local to here can result in this sequence:
farmbuyer@41 1827 -- 1) logging happens, followed by reload or logout/login
farmbuyer@41 1828 -- 2) _log points to SV_log
farmbuyer@41 1829 -- 3) VARIABLES_LOADED replaces SV_log pointer with restored version
farmbuyer@41 1830 -- 4) logging happens to _log table (now with no other references)
farmbuyer@41 1831 -- 5) at logout, nothing new has been entered in the table being saved
farmbuyer@19 1832 local date = _G.date
farmbuyer@19 1833 function addon:log_with_timestamp (msg)
farmbuyer@73 1834 tinsert (_log, date('%m/%d %H:%M:%S ')..msg)
farmbuyer@19 1835 end
farmbuyer@19 1836 end
farmbuyer@19 1837
farmbuyer@49 1838 -- Check for plugins which haven't already been loaded, and add hooks for
farmbuyer@49 1839 -- them. Credit to DBM for the approach here.
farmbuyer@49 1840 function addon:_scan_LOD_modules()
farmbuyer@49 1841 for i = 1, GetNumAddOns() do
farmbuyer@49 1842 if GetAddOnMetadata (i, "X-OuroLoot-Plugin")
farmbuyer@49 1843 and IsAddOnLoadOnDemand(i)
farmbuyer@49 1844 and not IsAddOnLoaded(i)
farmbuyer@49 1845 then
farmbuyer@49 1846 local folder, _, _, enabled, _, reason = GetAddOnInfo(i)
farmbuyer@57 1847 if enabled or opts.display_disabled_LODs then
farmbuyer@57 1848 local tabtitle = GetAddOnMetadata (i, "X-OuroLoot-Plugin")
farmbuyer@57 1849 self:_gui_add_LOD_tab (tabtitle, folder, i, enabled, reason)
farmbuyer@57 1850 end
farmbuyer@49 1851 end
farmbuyer@49 1852 end
farmbuyer@49 1853 end
farmbuyer@49 1854
farmbuyer@73 1855 -- Routines for printing changes made by remote users.
farmbuyer@73 1856 do
farmbuyer@73 1857 local remote_change_chatframe
farmbuyer@73 1858
farmbuyer@73 1859 function addon:_set_remote_change_chatframe (arg, silent_p)
farmbuyer@73 1860 local frame
farmbuyer@73 1861 if type(arg) == 'number' then
farmbuyer@73 1862 arg = _G.math.min (arg, _G.NUM_CHAT_WINDOWS)
farmbuyer@73 1863 frame = _G['ChatFrame'..arg]
farmbuyer@73 1864 elseif type(arg) == 'string' then
farmbuyer@73 1865 frame = _G[arg]
farmbuyer@73 1866 end
farmbuyer@73 1867 if type(frame) == 'table' and type(frame.AddMessage) == 'function' then
farmbuyer@73 1868 remote_change_chatframe = frame
farmbuyer@73 1869 if not silent_p then
farmbuyer@73 1870 local msg = "Now printing to chat frame " .. arg
farmbuyer@73 1871 if frame.GetName then
farmbuyer@73 1872 msg = msg .. " (" .. tostring(frame:GetName()) .. ")"
farmbuyer@73 1873 end
farmbuyer@73 1874 self:Print(msg)
farmbuyer@73 1875 if frame ~= _G.DEFAULT_CHAT_FRAME then
farmbuyer@73 1876 self:CFPrint (frame, msg)
farmbuyer@73 1877 end
farmbuyer@73 1878 end
farmbuyer@73 1879 return frame
farmbuyer@73 1880 else
farmbuyer@73 1881 self:Print("'%s' was not a valid chat frame number/name, no change has been made.", arg)
farmbuyer@73 1882 end
farmbuyer@73 1883 end
farmbuyer@73 1884
farmbuyer@81 1885 function _notify_about_change (chatframe, source, index, olddisp, from_whom, from_class)
farmbuyer@73 1886 local e = g_loot[index]
farmbuyer@80 1887 if not e then
farmbuyer@80 1888 -- how did this happen?
farmbuyer@80 1889 return
farmbuyer@80 1890 end
farmbuyer@89 1891 if source == my_name then
farmbuyer@89 1892 source = _G.UNIT_YOU
farmbuyer@89 1893 end
farmbuyer@86 1894 local from_text, to_text
farmbuyer@73 1895 if from_whom then
farmbuyer@89 1896 if from_whom == my_name then
farmbuyer@89 1897 from_whom = _G.UNIT_YOU
farmbuyer@89 1898 end
farmbuyer@86 1899 from_text = addon:colorize (from_whom, from_class)
farmbuyer@89 1900 to_text = e.person == my_name and _G.UNIT_YOU or e.person
farmbuyer@89 1901 to_text = addon:colorize (to_text, e.person_class)
farmbuyer@73 1902 else
farmbuyer@73 1903 if olddisp then
farmbuyer@73 1904 from_text = addon.disposition_colors[olddisp].text
farmbuyer@73 1905 else
farmbuyer@73 1906 olddisp = "normal"
farmbuyer@73 1907 from_text = "normal"
farmbuyer@73 1908 end
farmbuyer@86 1909 from_text = addon.disposition_colors[olddisp].hex
farmbuyer@86 1910 .. from_text .. "|r"
farmbuyer@86 1911
farmbuyer@73 1912 if e.disposition then
farmbuyer@73 1913 to_text = addon.disposition_colors[e.disposition].text
farmbuyer@73 1914 else
farmbuyer@73 1915 to_text = "normal"
farmbuyer@73 1916 end
farmbuyer@86 1917 to_text = addon.disposition_colors[e.disposition or "normal"].hex
farmbuyer@86 1918 .. to_text .. "|r"
farmbuyer@73 1919 end
farmbuyer@73 1920
farmbuyer@81 1921 addon.dprint ('loot', "notification:", source, index,
farmbuyer@86 1922 e.itemlink, from_text, to_text)
farmbuyer@81 1923 addon:CFPrint (chatframe, remote_chatty, source, index,
farmbuyer@86 1924 e.itemlink, from_text, to_text)
farmbuyer@73 1925 end
farmbuyer@81 1926
farmbuyer@81 1927 function _notify_about_remote (sender, index, olddisp, from_whom, from_class)
farmbuyer@81 1928 _notify_about_change (remote_change_chatframe, sender, index, olddisp, from_whom, from_class)
farmbuyer@81 1929 end
farmbuyer@73 1930 end
farmbuyer@73 1931
farmbuyer@1 1932 -- Adds indices to traverse the tables in a nice sorted order.
farmbuyer@1 1933 do
farmbuyer@1 1934 local byindex, temp = {}, {}
farmbuyer@1 1935 local function sort (src, dest)
farmbuyer@1 1936 for k in pairs(src) do
farmbuyer@1 1937 temp[#temp+1] = k
farmbuyer@1 1938 end
farmbuyer@73 1939 _G.table.sort(temp)
farmbuyer@47 1940 wipe(dest)
farmbuyer@1 1941 for i = 1, #temp do
farmbuyer@1 1942 dest[i] = src[temp[i]]
farmbuyer@1 1943 end
farmbuyer@1 1944 end
farmbuyer@1 1945
farmbuyer@1 1946 function addon.sender_list.sort()
farmbuyer@1 1947 sort (addon.sender_list.active, byindex)
farmbuyer@47 1948 wipe(temp)
farmbuyer@1 1949 addon.sender_list.activeI = #byindex
farmbuyer@1 1950 sort (addon.sender_list.names, byindex)
farmbuyer@47 1951 wipe(temp)
farmbuyer@1 1952 end
farmbuyer@1 1953 addon.sender_list.namesI = byindex
farmbuyer@1 1954 end
farmbuyer@1 1955
farmbuyer@23 1956 function addon:DoPing()
farmbuyer@23 1957 self:Print("Give me a ping, Vasili. One ping only, please.")
farmbuyer@23 1958 self.sender_list.active = {}
farmbuyer@23 1959 self.sender_list.names = {}
farmbuyer@23 1960 self:broadcast('ping')
farmbuyer@89 1961 self:broadcast('revcheck',version_large)
farmbuyer@27 1962 end
farmbuyer@27 1963
farmbuyer@89 1964 function addon:_check_version (otherrev)
farmbuyer@93 1965 self.dprint('comm', version_large, "revchecking against", otherrev)
farmbuyer@27 1966 otherrev = tonumber(otherrev)
farmbuyer@89 1967 if otherrev == version_large then
farmbuyer@27 1968 -- normal case
farmbuyer@27 1969
farmbuyer@89 1970 elseif otherrev < version_large then
farmbuyer@27 1971 self.dprint('comm', "ours is newer, notifying")
farmbuyer@89 1972 self:broadcast('revcheck',version_large)
farmbuyer@27 1973
farmbuyer@27 1974 else
farmbuyer@76 1975 self.dprint('comm', "ours is older, (possibly) yammering")
farmbuyer@27 1976 if newer_warning then
farmbuyer@76 1977 local pop = addon.format_hypertext ([[click here]], ITEM_QUALITY_UNCOMMON,
farmbuyer@76 1978 function()
farmbuyer@92 1979 -- Sadly, this is not generated by the packager, so hardcode it
farmbuyer@92 1980 -- for now. The 'data' field is handled differently for onshow
farmbuyer@92 1981 -- than for other callbacks.
farmbuyer@92 1982 StaticPopup_Show("OUROL_URL",
farmbuyer@92 1983 --[[text_arg1=]]nil, --[[text_arg2=]]nil,
farmbuyer@76 1984 --[[data=]][[http://www.curse.com/addons/wow/ouroloot]])
farmbuyer@76 1985 end)
farmbuyer@76 1986 local ping = addon.format_hypertext ([[click here]], ITEM_QUALITY_UNCOMMON, 'DoPing')
farmbuyer@76 1987 self:Print(newer_warning, tostring(pop), tostring(ping))
farmbuyer@27 1988 newer_warning = nil
farmbuyer@27 1989 end
farmbuyer@27 1990 end
farmbuyer@23 1991 end
farmbuyer@23 1992
farmbuyer@1 1993 -- Generic helpers
farmbuyer@28 1994 -- Returns index and entry at that index, or nil if not found.
farmbuyer@1 1995 function addon._find_next_after (kind, index)
farmbuyer@1 1996 index = index + 1
farmbuyer@1 1997 while index <= #g_loot do
farmbuyer@1 1998 if g_loot[index].kind == kind then
farmbuyer@1 1999 return index, g_loot[index]
farmbuyer@1 2000 end
farmbuyer@1 2001 index = index + 1
farmbuyer@1 2002 end
farmbuyer@1 2003 end
farmbuyer@28 2004 -- Essentially a _find_next_after('time'-or-'boss'), but if KIND is
farmbuyer@28 2005 -- 'boss', will also stop upon finding a timestamp. Returns nil if
farmbuyer@28 2006 -- appropriate fencepost is not found.
farmbuyer@28 2007 function addon._find_timeboss_fencepost (kind, index)
farmbuyer@28 2008 local fencepost
farmbuyer@28 2009 local closest_time = addon._find_next_after('time',index)
farmbuyer@28 2010 if kind == 'time' then
farmbuyer@28 2011 fencepost = closest_time
farmbuyer@28 2012 elseif kind == 'boss' then
farmbuyer@28 2013 local closest_boss = addon._find_next_after('boss',index)
farmbuyer@28 2014 if not closest_boss then
farmbuyer@28 2015 fencepost = closest_time
farmbuyer@28 2016 elseif not closest_time then
farmbuyer@28 2017 fencepost = closest_boss
farmbuyer@28 2018 else
farmbuyer@73 2019 fencepost = _G.math.min(closest_time,closest_boss)
farmbuyer@28 2020 end
farmbuyer@28 2021 end
farmbuyer@28 2022 return fencepost
farmbuyer@28 2023 end
farmbuyer@1 2024
farmbuyer@1 2025 -- Iterate through g_loot entries according to the KIND field. Loop variables
farmbuyer@1 2026 -- are g_loot indices and the corresponding entries (essentially ipairs + some
farmbuyer@1 2027 -- conditionals).
farmbuyer@1 2028 function addon:filtered_loot_iter (filter_kind)
farmbuyer@1 2029 return self._find_next_after, filter_kind, 0
farmbuyer@1 2030 end
farmbuyer@1 2031
farmbuyer@1 2032 do
farmbuyer@1 2033 local itt
farmbuyer@1 2034 local function create()
farmbuyer@1 2035 local tip, lefts = CreateFrame("GameTooltip"), {}
farmbuyer@1 2036 for i = 1, 2 do -- scanning idea here also snagged from Talented
farmbuyer@1 2037 local L,R = tip:CreateFontString(), tip:CreateFontString()
farmbuyer@1 2038 L:SetFontObject(GameFontNormal)
farmbuyer@1 2039 R:SetFontObject(GameFontNormal)
farmbuyer@1 2040 tip:AddFontStrings(L,R)
farmbuyer@1 2041 lefts[i] = L
farmbuyer@1 2042 end
farmbuyer@1 2043 tip.lefts = lefts
farmbuyer@1 2044 return tip
farmbuyer@1 2045 end
farmbuyer@66 2046 function addon:is_variant_item(item) -- returns number or *nil*
farmbuyer@1 2047 itt = itt or create()
farmbuyer@1 2048 itt:SetOwner(UIParent,"ANCHOR_NONE")
farmbuyer@1 2049 itt:ClearLines()
farmbuyer@1 2050 itt:SetHyperlink(item)
farmbuyer@1 2051 local t = itt.lefts[2]:GetText()
farmbuyer@1 2052 itt:Hide()
farmbuyer@66 2053 return (t == ITEM_HEROIC and 1)
farmbuyer@66 2054 or (t == RAID_FINDER and 2) -- no ITEM_ for this, apparently
farmbuyer@66 2055 or nil
farmbuyer@1 2056 end
farmbuyer@1 2057 end
farmbuyer@1 2058
farmbuyer@79 2059 -- Called at the end of OnInit, and then also when a 'clear' is being
farmbuyer@16 2060 -- performed. If SV's are present then g_restore_p will be true.
farmbuyer@1 2061 function _init (self, possible_st)
farmbuyer@1 2062 self.dprint('flow',"_init running")
farmbuyer@1 2063 self.loot_clean = nil
farmbuyer@1 2064 self.hist_clean = nil
farmbuyer@1 2065 if g_restore_p then
farmbuyer@73 2066 g_loot = _G.OuroLootSV
farmbuyer@16 2067 self.popped = #g_loot > 0
farmbuyer@1 2068 self.dprint('flow', "restoring", #g_loot, "entries")
farmbuyer@16 2069 self:ScheduleTimer("Activate", 12, opts.threshold)
farmbuyer@1 2070 -- FIXME printed could be too large if entries were deleted, how much do we care?
farmbuyer@16 2071 self.sharder = opts.autoshard
farmbuyer@1 2072 else
farmbuyer@80 2073 g_loot = {}
farmbuyer@80 2074 end
farmbuyer@80 2075 if type(g_loot.raiders) ~= 'table' then
farmbuyer@80 2076 g_loot.raiders = {}
farmbuyer@80 2077 end
farmbuyer@80 2078 if type(g_loot.printed) ~= 'table' then
farmbuyer@80 2079 g_loot.printed = {}
farmbuyer@1 2080 end
farmbuyer@1 2081
farmbuyer@16 2082 self.threshold = opts.threshold or self.threshold -- in the case of restoring but not tracking
farmbuyer@79 2083 self:gui_init (g_loot, g_uniques)
farmbuyer@16 2084 opts.autoshard = nil
farmbuyer@16 2085 opts.threshold = nil
farmbuyer@1 2086
farmbuyer@1 2087 if g_restore_p then
farmbuyer@73 2088 self:zero_printed_fenceposts() -- g_loot.printed.* = previous/safe values
farmbuyer@1 2089 else
farmbuyer@73 2090 self:zero_printed_fenceposts(0) -- g_loot.printed.* = 0
farmbuyer@1 2091 end
farmbuyer@1 2092 if possible_st then
farmbuyer@1 2093 possible_st:SetData(g_loot)
farmbuyer@1 2094 end
farmbuyer@1 2095
farmbuyer@89 2096 self.status_text = ("%s(r%s) communicating as ident %s commrev %s"):
farmbuyer@89 2097 format (self.version, self.revision, self.ident, self.commrev)
farmbuyer@1 2098 self:RegisterComm(self.ident)
farmbuyer@1 2099 self:RegisterComm(self.identTg, "OnCommReceivedNocache")
farmbuyer@1 2100
farmbuyer@1 2101 if self.author_debug then
farmbuyer@1 2102 _G.Oloot = g_loot
farmbuyer@1 2103 end
farmbuyer@1 2104 end
farmbuyer@1 2105
farmbuyer@61 2106 -- Raid roster snapshots
farmbuyer@61 2107 do
farmbuyer@61 2108 function addon:snapshot_raid (only_inraid_p)
farmbuyer@61 2109 local ss = CopyTable(g_loot.raiders)
farmbuyer@61 2110 local instance,maxsize = instance_tag()
farmbuyer@61 2111 if only_inraid_p then
farmbuyer@73 2112 for name,info in _G.next, ss do
farmbuyer@61 2113 if info.online == 3 then
farmbuyer@61 2114 ss[name] = nil
farmbuyer@61 2115 end
farmbuyer@61 2116 end
farmbuyer@61 2117 end
farmbuyer@73 2118 return ss, maxsize, instance, _G.time()
farmbuyer@61 2119 end
farmbuyer@61 2120 end
farmbuyer@61 2121
farmbuyer@25 2122 -- Tie-in with Deadly Boss Mods (or other such addons)
farmbuyer@1 2123 do
farmbuyer@25 2124 local candidates = {}
farmbuyer@25 2125 local location
farmbuyer@1 2126 local function fixup_durations (cache)
farmbuyer@1 2127 local boss, bossi
farmbuyer@1 2128 boss = candidates[1]
farmbuyer@1 2129 if #candidates == 1 then
farmbuyer@1 2130 -- (1) or (2)
farmbuyer@1 2131 boss.duration = boss.duration or 0
farmbuyer@19 2132 addon.dprint('loot', "only one boss candidate")
farmbuyer@1 2133 else
farmbuyer@1 2134 -- (3), should only be one 'cast entry and our local entry
farmbuyer@1 2135 if #candidates ~= 2 then
farmbuyer@1 2136 -- could get a bunch of 'cast entries on the heels of one another
farmbuyer@1 2137 -- before the local one ever fires, apparently... sigh
farmbuyer@1 2138 --addon:Print("<warning> s3 cache has %d entries, does that seem right to you?", #candidates)
farmbuyer@1 2139 end
farmbuyer@1 2140 if candidates[2].duration == nil then
farmbuyer@1 2141 --addon:Print("<warning> s3's second entry is not the local trigger, does that seem right to you?")
farmbuyer@1 2142 end
farmbuyer@1 2143 -- try and be generic anyhow
farmbuyer@1 2144 for i,c in ipairs(candidates) do
farmbuyer@1 2145 if c.duration then
farmbuyer@1 2146 boss = c
farmbuyer@19 2147 addon.dprint('loot', "fixup found boss candidate", i, "duration", c.duration)
farmbuyer@1 2148 break
farmbuyer@1 2149 end
farmbuyer@1 2150 end
farmbuyer@1 2151 end
farmbuyer@69 2152 bossi = addon._addBossEntry(boss)
farmbuyer@19 2153 -- addon.
farmbuyer@19 2154 bossi = addon._adjustBossOrder (bossi, g_boss_signpost) or bossi
farmbuyer@16 2155 g_boss_signpost = nil
farmbuyer@42 2156 addon.latest_instance = boss.instance
farmbuyer@19 2157 addon.dprint('loot', "added boss entry", bossi)
farmbuyer@1 2158 if boss.reason == 'kill' then
farmbuyer@80 2159 addon:RegisterEvent ("PLAYER_REGEN_DISABLED")
farmbuyer@1 2160 addon:_mark_boss_kill (bossi)
farmbuyer@1 2161 if opts.chatty_on_kill then
farmbuyer@55 2162 addon:Print("Registered kill for '%s' in %s!", boss.bossname, boss.instance)
farmbuyer@1 2163 end
farmbuyer@1 2164 end
farmbuyer@47 2165 wipe(candidates)
farmbuyer@1 2166 end
farmbuyer@73 2167 local recent_boss = create_new_cache ('boss', 10, fixup_durations)
farmbuyer@1 2168
farmbuyer@1 2169 -- Similar to _do_loot, but duration+ parms only present when locally generated.
farmbuyer@56 2170 local function _do_boss (self, reason, bossname, intag, maxsize, duration)
farmbuyer@56 2171 self.dprint('loot',">>_do_boss, R:", reason, "B:", bossname,
farmbuyer@56 2172 "T:", intag, "MS:", maxsize, "D:", duration)
farmbuyer@1 2173 if self.rebroadcast and duration then
farmbuyer@56 2174 self:vbroadcast('boss', reason, bossname, intag, maxsize)
farmbuyer@1 2175 end
farmbuyer@1 2176 -- This is only a loop to make jumping out of it easy, and still do cleanup below.
farmbuyer@1 2177 while self.enabled do
farmbuyer@1 2178 if reason == 'wipe' and opts.no_tracking_wipes then break end
farmbuyer@1 2179 bossname = (opts.snarky_boss and self.boss_abbrev[bossname] or bossname) or bossname
farmbuyer@1 2180 local not_from_local = duration == nil
farmbuyer@1 2181 local signature = bossname .. reason
farmbuyer@73 2182 if not_from_local and recent_boss:test(signature) then
farmbuyer@39 2183 self.dprint('cache', "remote boss <",signature,"> already in cache, skipping")
farmbuyer@1 2184 else
farmbuyer@73 2185 recent_boss:add(signature)
farmbuyer@16 2186 g_boss_signpost = #g_loot + 1
farmbuyer@19 2187 self.dprint('loot', "added boss signpost", g_boss_signpost)
farmbuyer@1 2188 -- Possible scenarios: (1) we don't see a boss event at all (e.g., we're
farmbuyer@1 2189 -- outside the instance) and so this only happens once as a non-local event,
farmbuyer@1 2190 -- (2) we see a local event first and all non-local events are filtered
farmbuyer@1 2191 -- by the cache, (3) we happen to get some non-local events before doing
farmbuyer@1 2192 -- our local event (not because of network weirdness but because our local
farmbuyer@1 2193 -- DBM might not trigger for a while).
farmbuyer@1 2194 local c = {
farmbuyer@1 2195 kind = 'boss',
farmbuyer@55 2196 bossname = bossname,
farmbuyer@1 2197 reason = reason,
farmbuyer@1 2198 instance = intag,
farmbuyer@56 2199 duration = duration, -- deliberately may be nil
farmbuyer@61 2200 raidersnap = self:snapshot_raid(),
farmbuyer@56 2201 maxsize = maxsize,
farmbuyer@1 2202 }
farmbuyer@1 2203 tinsert(candidates,c)
farmbuyer@1 2204 end
farmbuyer@17 2205 break
farmbuyer@1 2206 end
farmbuyer@1 2207 self.dprint('loot',"<<_do_boss out")
farmbuyer@1 2208 end
farmbuyer@56 2209 -- This exposes the function to OCR, and can be a wrapper layer later.
farmbuyer@1 2210 addon.on_boss_broadcast = _do_boss
farmbuyer@1 2211
farmbuyer@1 2212 function addon:_mark_boss_kill (index)
farmbuyer@1 2213 local e = g_loot[index]
farmbuyer@17 2214 if not e then
farmbuyer@17 2215 self:Print("Something horribly wrong;", index, "is not a valid entry!")
farmbuyer@17 2216 return
farmbuyer@17 2217 end
farmbuyer@55 2218 if not e.bossname then
farmbuyer@16 2219 self:Print("Something horribly wrong;", index, "is not a boss entry!")
farmbuyer@16 2220 return
farmbuyer@1 2221 end
farmbuyer@1 2222 if e.reason ~= 'wipe' then
farmbuyer@1 2223 -- enh, bail
farmbuyer@1 2224 self.loot_clean = index-1
farmbuyer@1 2225 end
farmbuyer@1 2226 local attempts = 1
farmbuyer@1 2227 local first
farmbuyer@1 2228
farmbuyer@69 2229 -- Maybe redo this to only collapse *contiguous* wipes...?
farmbuyer@1 2230 local i,d = 1,g_loot[1]
farmbuyer@1 2231 while d ~= e do
farmbuyer@55 2232 if d.bossname and
farmbuyer@55 2233 d.bossname == e.bossname and
farmbuyer@53 2234 d.instance == e.instance and
farmbuyer@1 2235 d.reason == 'wipe'
farmbuyer@1 2236 then
farmbuyer@1 2237 first = first or i
farmbuyer@1 2238 attempts = attempts + 1
farmbuyer@1 2239 assert(tremove(g_loot,i)==d,"_mark_boss_kill screwed up data badly")
farmbuyer@1 2240 else
farmbuyer@1 2241 i = i + 1
farmbuyer@1 2242 end
farmbuyer@1 2243 d = g_loot[i]
farmbuyer@1 2244 end
farmbuyer@1 2245 e.reason = 'kill'
farmbuyer@1 2246 e.attempts = attempts
farmbuyer@1 2247 self.loot_clean = first or index-1
farmbuyer@1 2248 end
farmbuyer@1 2249
farmbuyer@2 2250 function addon:register_boss_mod (name, registration_func, deregistration_func)
farmbuyer@2 2251 assert(type(name)=='string')
farmbuyer@2 2252 assert(type(registration_func)=='function')
farmbuyer@76 2253 if deregistration_func ~= nil then
farmbuyer@76 2254 assert(type(deregistration_func)=='function')
farmbuyer@76 2255 end
farmbuyer@2 2256 self.bossmods[#self.bossmods+1] = {
farmbuyer@2 2257 n = name,
farmbuyer@2 2258 r = registration_func,
farmbuyer@2 2259 d = deregistration_func,
farmbuyer@2 2260 }
farmbuyer@5 2261 self.bossmods[name] = self.bossmods[#self.bossmods]
farmbuyer@5 2262 assert(self.bossmods[name].n == self.bossmods[#self.bossmods].n)
farmbuyer@2 2263 end
farmbuyer@1 2264
farmbuyer@2 2265 function _register_bossmod (self, force_p)
farmbuyer@2 2266 local x = self.bossmod_registered and self.bossmods[self.bossmod_registered]
farmbuyer@2 2267 if x then
farmbuyer@2 2268 if x.n == opts.bossmod and not force_p then
farmbuyer@2 2269 -- trying to register with already-registered boss mod
farmbuyer@2 2270 return
farmbuyer@2 2271 else
farmbuyer@2 2272 -- deregister
farmbuyer@2 2273 if x.d then x.d(self) end
farmbuyer@2 2274 end
farmbuyer@1 2275 end
farmbuyer@1 2276
farmbuyer@2 2277 x = nil
farmbuyer@2 2278 for k,v in ipairs(self.bossmods) do
farmbuyer@2 2279 if v.n == opts.bossmod then
farmbuyer@2 2280 x = k
farmbuyer@2 2281 break
farmbuyer@2 2282 end
farmbuyer@1 2283 end
farmbuyer@1 2284
farmbuyer@2 2285 if not x then
farmbuyer@2 2286 self.status_text = "|cffff1010No boss-mod found!|r"
farmbuyer@2 2287 self:Print(self.status_text)
farmbuyer@2 2288 return
farmbuyer@1 2289 end
farmbuyer@1 2290
farmbuyer@2 2291 if self.bossmods[x].r (self, _do_boss) then
farmbuyer@5 2292 self.bossmod_registered = self.bossmods[x].n
farmbuyer@1 2293 else
farmbuyer@2 2294 self:Print("|cffff1010Boss mod registration failed|r")
farmbuyer@1 2295 end
farmbuyer@1 2296 end
farmbuyer@2 2297 end
farmbuyer@1 2298
farmbuyer@1 2299 -- Adding entries to the loot record, and tracking the corresponding timestamp.
farmbuyer@1 2300 do
farmbuyer@73 2301 local rawget, setmetatable = _G.rawget, _G.setmetatable
farmbuyer@73 2302
farmbuyer@1 2303 -- This shouldn't be required. /sadface
farmbuyer@1 2304 local loot_entry_mt = {
farmbuyer@1 2305 __index = function (e,key)
farmbuyer@1 2306 if key == 'cols' then
farmbuyer@15 2307 pprint('mt', e.kind, "key is", key)
farmbuyer@1 2308 --tabledump(e) -- not actually that useful
farmbuyer@1 2309 addon:_fill_out_eoi_data(1)
farmbuyer@1 2310 end
farmbuyer@1 2311 return rawget(e,key)
farmbuyer@1 2312 end
farmbuyer@1 2313 }
farmbuyer@1 2314
farmbuyer@1 2315 -- Given a loot index, searches backwards for a timestamp. Returns that
farmbuyer@1 2316 -- index and the time entry, or nil if it falls off the beginning. Pass an
farmbuyer@65 2317 -- optional second index to search no earlier than that.
farmbuyer@1 2318 -- May also be able to make good use of this in forum-generation routine.
farmbuyer@1 2319 function addon:find_previous_time_entry(i,stop)
farmbuyer@17 2320 stop = stop or 0
farmbuyer@1 2321 while i > stop do
farmbuyer@1 2322 if g_loot[i].kind == 'time' then
farmbuyer@1 2323 return i, g_loot[i]
farmbuyer@1 2324 end
farmbuyer@1 2325 i = i - 1
farmbuyer@1 2326 end
farmbuyer@1 2327 end
farmbuyer@1 2328
farmbuyer@1 2329 -- format_timestamp (["format_string"], Day, [Loot])
farmbuyer@1 2330 -- DAY is a loot entry with kind=='time', and controls the date printed.
farmbuyer@1 2331 -- LOOT may be any kind of entry in the g_loot table. If present, it
farmbuyer@1 2332 -- overrides the hour and minute printed; if absent, those values are
farmbuyer@1 2333 -- taken from the DAY entry.
farmbuyer@1 2334 -- FORMAT_STRING may contain $x (x in Y/M/D/h/m) tokens.
farmbuyer@84 2335 -- FIXME this is horribabble
farmbuyer@1 2336 local format_timestamp_values, point2dee = {}, "%.2d"
farmbuyer@1 2337 function addon:format_timestamp (fmt_opt, day_entry, time_entry_opt)
farmbuyer@1 2338 if not time_entry_opt then
farmbuyer@1 2339 if type(fmt_opt) == 'table' then -- Two entries, default format
farmbuyer@1 2340 time_entry_opt, day_entry = day_entry, fmt_opt
farmbuyer@1 2341 fmt_opt = "$Y/$M/$D $h:$m"
farmbuyer@41 2342 --elseif type(fmt_opt) == "string" then -- Day entry only, caller-specified format
farmbuyer@1 2343 end
farmbuyer@1 2344 end
farmbuyer@1 2345 --format_timestamp_values.Y = point2dee:format (day_entry.startday.year % 100)
farmbuyer@1 2346 format_timestamp_values.Y = ("%.4d"):format (day_entry.startday.year)
farmbuyer@1 2347 format_timestamp_values.M = point2dee:format (day_entry.startday.month)
farmbuyer@1 2348 format_timestamp_values.D = point2dee:format (day_entry.startday.day)
farmbuyer@1 2349 format_timestamp_values.h = point2dee:format ((time_entry_opt or day_entry).hour)
farmbuyer@1 2350 format_timestamp_values.m = point2dee:format ((time_entry_opt or day_entry).minute)
farmbuyer@1 2351 return fmt_opt:gsub ('%$([YMDhm])', format_timestamp_values)
farmbuyer@1 2352 end
farmbuyer@1 2353
farmbuyer@1 2354 local done_todays_date
farmbuyer@1 2355 function addon:_reset_timestamps()
farmbuyer@1 2356 done_todays_date = nil
farmbuyer@1 2357 end
farmbuyer@1 2358 local function do_todays_date()
farmbuyer@1 2359 local text, M, D, Y = makedate()
farmbuyer@1 2360 local found,ts = #g_loot+1
farmbuyer@1 2361 repeat
farmbuyer@1 2362 found,ts = addon:find_previous_time_entry(found-1)
farmbuyer@1 2363 if found and ts.startday.text == text then
farmbuyer@1 2364 done_todays_date = true
farmbuyer@1 2365 end
farmbuyer@1 2366 until done_todays_date or (not found)
farmbuyer@1 2367 if done_todays_date then
farmbuyer@1 2368 g_today = ts
farmbuyer@1 2369 else
farmbuyer@1 2370 done_todays_date = true
farmbuyer@1 2371 g_today = g_loot[addon._addLootEntry{
farmbuyer@1 2372 kind = 'time',
farmbuyer@1 2373 startday = {
farmbuyer@1 2374 text = text, month = M, day = D, year = Y
farmbuyer@1 2375 }
farmbuyer@1 2376 }]
farmbuyer@1 2377 end
farmbuyer@1 2378 addon:_fill_out_eoi_data(1)
farmbuyer@1 2379 end
farmbuyer@1 2380
farmbuyer@1 2381 -- Adding anything original to g_loot goes through this routine.
farmbuyer@89 2382 -- More precisely, anything new on the EOI tab hits this; it does not
farmbuyer@89 2383 -- necessarily need to be a looted item.
farmbuyer@1 2384 function addon._addLootEntry (e)
farmbuyer@1 2385 setmetatable(e,loot_entry_mt)
farmbuyer@1 2386
farmbuyer@1 2387 if not done_todays_date then do_todays_date() end
farmbuyer@1 2388
farmbuyer@73 2389 local h, m = _G.GetGameTime()
farmbuyer@10 2390 --local localuptime = math.floor(GetTime())
farmbuyer@73 2391 local time_t = _G.time()
farmbuyer@1 2392 e.hour = h
farmbuyer@1 2393 e.minute = m
farmbuyer@10 2394 e.stamp = time_t --localuptime
farmbuyer@89 2395 if e.kind == 'loot' then
farmbuyer@89 2396 if (not e.unique) or (#e.unique==0) then
farmbuyer@89 2397 e.unique = e.id .. (e.disposition or e.person) .. _G.date("%Y/%m/%d %H:%M",e.stamp)
farmbuyer@89 2398 end
farmbuyer@89 2399 end
farmbuyer@1 2400 local index = #g_loot + 1
farmbuyer@1 2401 g_loot[index] = e
farmbuyer@1 2402 return index
farmbuyer@1 2403 end
farmbuyer@16 2404
farmbuyer@69 2405 -- Safety wrapper only.
farmbuyer@69 2406 -- XXX Maybe pprint something here.
farmbuyer@69 2407 function addon._addBossEntry (e)
farmbuyer@69 2408 local ret = addon._addLootEntry(e)
farmbuyer@69 2409 assert(e.kind=='boss')
farmbuyer@69 2410 local needSize = e.maxsize == nil
farmbuyer@69 2411 local needSnap = e.raidersnap == nil
farmbuyer@69 2412 local needInst = e.instance == nil
farmbuyer@69 2413 if needSize or needSnap then
farmbuyer@69 2414 local ss, max, inst = addon:snapshot_raid()
farmbuyer@69 2415 if needSize then e.maxsize = max end
farmbuyer@69 2416 if needSnap then e.raidersnap = ss end
farmbuyer@69 2417 if needInst then e.instance = inst end
farmbuyer@69 2418 end
farmbuyer@69 2419 return ret
farmbuyer@69 2420 end
farmbuyer@69 2421
farmbuyer@16 2422 -- Problem: (1) boss kill happens, (2) fast looting happens, (3) boss
farmbuyer@16 2423 -- cache cleanup fires. Result: loot shows up before boss kill entry.
farmbuyer@16 2424 -- Solution: We need to shuffle the boss entry above any of the loot
farmbuyer@16 2425 -- from that boss.
farmbuyer@16 2426 function addon._adjustBossOrder (is, should_be)
farmbuyer@82 2427 if is == should_be then
farmbuyer@16 2428 return
farmbuyer@16 2429 end
farmbuyer@17 2430 if (type(is)~='number') or (type(should_be)~='number') or (is < should_be) then
farmbuyer@19 2431 pprint('loot', is, should_be, "...the hell? bailing")
farmbuyer@16 2432 return
farmbuyer@16 2433 end
farmbuyer@16 2434 if g_loot[should_be].kind == 'time' then
farmbuyer@16 2435 should_be = should_be + 1
farmbuyer@16 2436 if is == should_be then
farmbuyer@16 2437 return
farmbuyer@16 2438 end
farmbuyer@16 2439 end
farmbuyer@16 2440
farmbuyer@16 2441 assert(g_loot[is].kind == 'boss')
farmbuyer@16 2442 local boss = tremove (g_loot, is)
farmbuyer@16 2443 tinsert (g_loot, should_be, boss)
farmbuyer@16 2444 return should_be
farmbuyer@16 2445 end
farmbuyer@1 2446 end
farmbuyer@1 2447
farmbuyer@19 2448 -- In the rare case of items getting put into the loot table without current
farmbuyer@19 2449 -- item cache data (which will have arrived by now).
farmbuyer@19 2450 function addon:do_item_cache_fixup()
farmbuyer@19 2451 self:Print("Fixing up missing item cache data...")
farmbuyer@19 2452
farmbuyer@19 2453 local numfound = 0
farmbuyer@19 2454 local borkedpat = '^'..UNKNOWN..': (%S+)'
farmbuyer@19 2455
farmbuyer@73 2456 -- 'while true' so that we can use (inner) break as (outer) continue
farmbuyer@73 2457 for i,e in self:filtered_loot_iter('loot') do while true do
farmbuyer@73 2458 if not e.cache_miss then break end
farmbuyer@73 2459 local borked_id = e.itemname:match(borkedpat)
farmbuyer@73 2460 if not borked_id then break end
farmbuyer@73 2461 numfound = numfound + 1
farmbuyer@73 2462 -- Best to use the safest and most flexible API here, which is GII and
farmbuyer@73 2463 -- its assload of return values.
farmbuyer@73 2464 local iname, ilink, iquality, _,_,_,_,_,_, itexture = GetItemInfo(borked_id)
farmbuyer@73 2465 if iname then
farmbuyer@73 2466 local msg = [[ Entry %d patched up with %s.]]
farmbuyer@73 2467 e.quality = iquality
farmbuyer@73 2468 e.itemname = iname
farmbuyer@73 2469 e.id = tonumber(ilink:match("item:(%d+)"))
farmbuyer@73 2470 e.itemlink = ilink
farmbuyer@73 2471 e.itexture = itexture
farmbuyer@73 2472 e.cache_miss = nil
farmbuyer@73 2473 if e.unique then
farmbuyer@73 2474 local gu = g_uniques[e.unique]
farmbuyer@73 2475 local player_i, player_h, hist_i = _history_by_loot_id (e.unique, "fixcache")
farmbuyer@73 2476 if gu.loot ~= i then -- is this an actual problem?
farmbuyer@76 2477 pprint ('loot',
farmbuyer@76 2478 ("Unique value '%s' had iterator value %d but g_uniques index %s."):
farmbuyer@76 2479 format(e.unique,i,tostring(gu.loot)))
farmbuyer@73 2480 end
farmbuyer@73 2481 if player_i then
farmbuyer@73 2482 player_h.id[e.unique] = e.id
farmbuyer@73 2483 msg = [[ Entry %d (and history) patched up with %s.]]
farmbuyer@19 2484 end
farmbuyer@19 2485 end
farmbuyer@73 2486 self:Print(msg, i, ilink)
farmbuyer@19 2487 end
farmbuyer@73 2488 break
farmbuyer@73 2489 end end
farmbuyer@19 2490
farmbuyer@19 2491 self:Print("...finished. Found %d |4entry:entries; with weird data.", numfound)
farmbuyer@19 2492 end
farmbuyer@19 2493
farmbuyer@76 2494 do
farmbuyer@76 2495 local gur
farmbuyer@76 2496
farmbuyer@76 2497 -- Strictly speaking, we'd want to handle individual 'exist' entries
farmbuyer@76 2498 -- as they expire, rather then waiting for all of them to expire and then
farmbuyer@76 2499 -- doing them as a group. But, if there're more than one of these per
farmbuyer@76 2500 -- boss, something else is funky anyhow and certainly not a hurry.
farmbuyer@76 2501 local function fixup_unique_replacements()
farmbuyer@76 2502 for exist,info in pairs(gur.replacements) do
farmbuyer@76 2503 local winning_index = 1
farmbuyer@76 2504 local winner = info[1]
farmbuyer@76 2505 info[1] = nil
farmbuyer@82 2506 pprint('improv', "fixup for", exist, "starting with guid",
farmbuyer@82 2507 winner[1], "with tag", winner[2], "out of", #info,
farmbuyer@82 2508 "total entries")
farmbuyer@76 2509 -- Lowest player GUID wins. Seniority gotta count for something.
farmbuyer@76 2510 for i = 2, #info do
farmbuyer@76 2511 if winner[1] <= info[i][1] then
farmbuyer@76 2512 pprint('improv', "champ wins against", i, info[i][1])
farmbuyer@76 2513 flib.del(info[i])
farmbuyer@76 2514 else
farmbuyer@76 2515 pprint('improv', "challenger wins with", i, info[i][1])
farmbuyer@76 2516 flib.del(winner)
farmbuyer@76 2517 winner = info[i]
farmbuyer@76 2518 winning_index = i
farmbuyer@76 2519 end
farmbuyer@76 2520 info[i] = nil
farmbuyer@76 2521 end
farmbuyer@76 2522 pprint('improv', "final:", winner[1], winner[2])
farmbuyer@76 2523 --[[
farmbuyer@76 2524 A: winner was generated locally
farmbuyer@82 2525 >winning_index == 1
farmbuyer@76 2526 >g_loot and history already has the replacement value
farmbuyer@76 2527 B: winner was generated remotely
farmbuyer@76 2528 >need to scan and replace
farmbuyer@82 2529 Detecting A is strictly an optimization. We should be able to do
farmbuyer@89 2530 this code safely in all cases. Important to note: a local winner
farmbuyer@92 2531 will always be at index 1, but a winner at index 1 does not
farmbuyer@92 2532 necessarily mean it was locally generated (e.g., if the local
farmbuyer@92 2533 itemfilter drops it but a remote player does an improv). Just
farmbuyer@92 2534 do the general case until/unless this becomes a problem.
farmbuyer@76 2535 ]]
farmbuyer@89 2536 local cache = g_uniques:SEARCH(exist)
farmbuyer@89 2537 local looti,hi,ui = cache.loot, cache.history, cache.history_may
farmbuyer@89 2538
farmbuyer@89 2539 -- Active loot
farmbuyer@89 2540 if looti and g_loot[looti].unique == exist then
farmbuyer@89 2541 pprint('improv', "found and replaced loot entry", looti)
farmbuyer@89 2542 g_loot[looti].unique = winner[2]
farmbuyer@89 2543 else
farmbuyer@89 2544 -- If sharded, filtered, or the improv was done by the local
farmbuyer@89 2545 -- player, then the "previous" unique would not have made it
farmbuyer@89 2546 -- into the tables to begin with. So don't flag an error.
farmbuyer@89 2547 pprint('improv', "No active loot found", looti,
farmbuyer@89 2548 looti and g_loot[looti].unique, winning_index)
farmbuyer@89 2549 end
farmbuyer@89 2550
farmbuyer@89 2551 -- History
farmbuyer@89 2552 if hi ~= g_uniques.NOTFOUND then
farmbuyer@89 2553 hi = addon.history.byname[hi]
farmbuyer@89 2554 local hist = addon.history[hi]
farmbuyer@89 2555 if ui and hist.unique[ui] == exist then
farmbuyer@89 2556 -- ui is valid
farmbuyer@82 2557 else
farmbuyer@89 2558 ui = nil
farmbuyer@89 2559 for i,ui2 in ipairs(hist.unique) do
farmbuyer@89 2560 if ui2 == exist then
farmbuyer@89 2561 ui = i
farmbuyer@89 2562 break
farmbuyer@76 2563 end
farmbuyer@76 2564 end
farmbuyer@89 2565 end
farmbuyer@89 2566 if ui then
farmbuyer@89 2567 pprint('improv', "found and replacing history entry", hi,
farmbuyer@89 2568 ui, hist.name)
farmbuyer@89 2569 assert(exist ~= winner[2])
farmbuyer@89 2570 hist.when[winner[2]] = hist.when[exist]
farmbuyer@89 2571 hist.id[winner[2]] = hist.id[exist]
farmbuyer@89 2572 hist.count[winner[2]] = hist.count[exist]
farmbuyer@89 2573 hist.unique[ui] = winner[2]
farmbuyer@89 2574 hist.when[exist] = nil
farmbuyer@89 2575 hist.id[exist] = nil
farmbuyer@89 2576 hist.count[exist] = nil
farmbuyer@76 2577 end
farmbuyer@76 2578 end
farmbuyer@89 2579
farmbuyer@76 2580 pprint('improv', "finished with", exist, "into", winner[2])
farmbuyer@76 2581 flib.del(winner)
farmbuyer@76 2582 flib.del(info)
farmbuyer@76 2583 gur.replacements[exist] = nil
farmbuyer@76 2584 end
farmbuyer@76 2585 end
farmbuyer@76 2586
farmbuyer@76 2587 local function new_entry (id, exist, repl, is_local)
farmbuyer@76 2588 pprint('improv', "new_entry", id, exist, repl, is_local)
farmbuyer@76 2589 gur.replacements[exist] = gur.replacements[exist] or flib.new()
farmbuyer@76 2590 tinsert (gur.replacements[exist], flib.new (tonumber(id), repl))
farmbuyer@76 2591 if is_local then
farmbuyer@76 2592 gur.replacements[exist].LOCAL = repl
farmbuyer@76 2593 end
farmbuyer@76 2594 gur.cache:add (exist)
farmbuyer@76 2595 end
farmbuyer@76 2596
farmbuyer@76 2597 local function get_previous_replacement (exist)
farmbuyer@76 2598 local l = gur.replacements[exist]
farmbuyer@76 2599 if l and l.LOCAL then
farmbuyer@76 2600 pprint('improv', "check for previous", exist, "returns valid",
farmbuyer@76 2601 l.LOCAL)
farmbuyer@76 2602 return l.LOCAL
farmbuyer@76 2603 end
farmbuyer@76 2604 pprint('improv', "check for previous", exist, "returns nil")
farmbuyer@76 2605 end
farmbuyer@76 2606
farmbuyer@76 2607 function _setup_unique_replace ()
farmbuyer@76 2608 gur = {}
farmbuyer@76 2609 gur.cache = create_new_cache ('improv', 10, fixup_unique_replacements)
farmbuyer@76 2610 gur.me = tonumber(_G.UnitGUID('player'):sub(-7),16)
farmbuyer@76 2611 gur.replacements = {}
farmbuyer@76 2612 gur.new_entry = new_entry
farmbuyer@76 2613 gur.get_previous_replacement = get_previous_replacement
farmbuyer@76 2614 g_unique_replace = gur
farmbuyer@76 2615 _setup_unique_replace = nil
farmbuyer@76 2616 end
farmbuyer@76 2617 end
farmbuyer@76 2618
farmbuyer@77 2619 do
farmbuyer@77 2620 local clicky
farmbuyer@81 2621 function addon:horrible_horrible_error (err_msg)
farmbuyer@81 2622 if self.display then
farmbuyer@81 2623 local d = self.display
farmbuyer@81 2624 if d then
farmbuyer@81 2625 local eoist = d:GetUserData("eoiST")
farmbuyer@81 2626 if eoist then eoist:Hide() end
farmbuyer@81 2627 local histst = d:GetUserData("histST")
farmbuyer@81 2628 if histst then histst:Hide() end
farmbuyer@81 2629 d:Hide()
farmbuyer@81 2630 end
farmbuyer@81 2631 end
farmbuyer@81 2632 self.NOLOAD = err_msg
farmbuyer@77 2633 -- This should happen so rarely that it's not worth moving into gui.lua
farmbuyer@81 2634 if not StaticPopupDialogs["OUROL_ARGH"] then
farmbuyer@81 2635 StaticPopupDialogs["OUROL_ARGH"] = flib.StaticPopup{
farmbuyer@77 2636 button1 = OKAY,
farmbuyer@77 2637 }
farmbuyer@77 2638 clicky = addon.format_hypertext(
farmbuyer@77 2639 [[ SYSTEM FAILURE -- RELEASE RINZLER ]], "|cffff0000",
farmbuyer@81 2640 function() StaticPopup_Show "OUROL_ARGH" end)
farmbuyer@77 2641 end
farmbuyer@81 2642 StaticPopupDialogs["OUROL_ARGH"].text = horrible_error_text:format(err_msg)
farmbuyer@77 2643 _G.PlaySoundFile ([[Interface\AddOns\Ouro_Loot\sfrr.ogg]], "Master")
farmbuyer@77 2644 addon:Print (" ")
farmbuyer@77 2645 addon:Print (" ", clicky)
farmbuyer@77 2646 addon:Print (" ")
farmbuyer@77 2647 end
farmbuyer@81 2648
farmbuyer@81 2649 function _unavoidable_collision (err)
farmbuyer@81 2650 addon:horrible_horrible_error (err)
farmbuyer@81 2651 -- we don't actually need to kill the GUI in this case
farmbuyer@81 2652 addon.NOLOAD = nil
farmbuyer@81 2653 end
farmbuyer@77 2654 end
farmbuyer@77 2655
farmbuyer@1 2656
farmbuyer@1 2657 ------ Saved texts
farmbuyer@1 2658 function addon:check_saved_table(silent_p)
farmbuyer@73 2659 local s = _G.OuroLootSV_saved
farmbuyer@1 2660 if s and (#s > 0) then return s end
farmbuyer@73 2661 _G.OuroLootSV_saved = nil
farmbuyer@1 2662 if not silent_p then self:Print("There are no saved loot texts.") end
farmbuyer@1 2663 end
farmbuyer@1 2664
farmbuyer@1 2665 function addon:save_list()
farmbuyer@81 2666 local s = self:check_saved_table(); if not s then return end
farmbuyer@1 2667 for i,t in ipairs(s) do
farmbuyer@1 2668 self:Print("#%d %s %d entries %s", i, t.date, t.count, t.name)
farmbuyer@1 2669 end
farmbuyer@1 2670 end
farmbuyer@1 2671
farmbuyer@1 2672 function addon:save_saveas(name)
farmbuyer@73 2673 _G.OuroLootSV_saved = _G.OuroLootSV_saved or {}
farmbuyer@73 2674 local SV = _G.OuroLootSV_saved
farmbuyer@16 2675 local n = #SV + 1
farmbuyer@1 2676 local save = {
farmbuyer@1 2677 name = name,
farmbuyer@1 2678 date = makedate(),
farmbuyer@1 2679 count = #g_loot,
farmbuyer@1 2680 }
farmbuyer@10 2681 for text in self:registered_textgen_iter() do
farmbuyer@10 2682 save[text] = g_loot[text]
farmbuyer@10 2683 end
farmbuyer@1 2684 self:Print("Saving current loot texts to #%d '%s'", n, name)
farmbuyer@16 2685 SV[n] = save
farmbuyer@1 2686 return self:save_list()
farmbuyer@1 2687 end
farmbuyer@1 2688
farmbuyer@1 2689 function addon:save_restore(num)
farmbuyer@81 2690 local s = self:check_saved_table(); if not s then return end
farmbuyer@1 2691 if (not num) or (num > #s) then
farmbuyer@1 2692 return self:Print("Saved text number must be 1 - "..#s)
farmbuyer@1 2693 end
farmbuyer@1 2694 local save = s[num]
farmbuyer@92 2695 self:Print("Overwriting current loot data with saved text #%d '%s'",num,save.name)
farmbuyer@1 2696 self:Clear(--[[verbose_p=]]false)
farmbuyer@1 2697 -- Clear will already have displayed the window, and re-selected the first
farmbuyer@1 2698 -- tab. Set these up for when the text tabs are clicked.
farmbuyer@10 2699 for text in self:registered_textgen_iter() do
farmbuyer@10 2700 g_loot[text] = save[text]
farmbuyer@10 2701 end
farmbuyer@1 2702 end
farmbuyer@1 2703
farmbuyer@1 2704 function addon:save_delete(num)
farmbuyer@81 2705 local s = self:check_saved_table(); if not s then return end
farmbuyer@1 2706 if (not num) or (num > #s) then
farmbuyer@1 2707 return self:Print("Saved text number must be 1 - "..#s)
farmbuyer@1 2708 end
farmbuyer@1 2709 self:Print("Deleting saved text #"..num)
farmbuyer@1 2710 tremove(s,num)
farmbuyer@1 2711 return self:save_list()
farmbuyer@1 2712 end
farmbuyer@1 2713
farmbuyer@1 2714
farmbuyer@1 2715 ------ Loot histories
farmbuyer@1 2716 -- history_all = {
farmbuyer@1 2717 -- ["Kilrogg"] = {
farmbuyer@1 2718 -- ["realm"] = "Kilrogg", -- not saved
farmbuyer@1 2719 -- ["st"] = { lib-st display table }, -- not saved
farmbuyer@1 2720 -- ["byname"] = { -- not saved
farmbuyer@1 2721 -- ["OtherPlayer"] = 2,
farmbuyer@1 2722 -- ["Farmbuyer"] = 1,
farmbuyer@1 2723 -- }
farmbuyer@1 2724 -- [1] = {
farmbuyer@1 2725 -- ["name"] = "Farmbuyer",
farmbuyer@73 2726 -- ["person_class"] = "PRIEST", -- may be missing, used in display only
farmbuyer@73 2727 -- -- sorted array:
farmbuyer@73 2728 -- ["unique"] = { most_recent_tag, previous_tag, .... },
farmbuyer@73 2729 -- -- these are indexed by unique tags, and 'count' may be missing:
farmbuyer@73 2730 -- ["when"] = { ["tag"] = "formatted timestamp for displaying loot" },
farmbuyer@73 2731 -- ["id"] = { ["tag"] = 11111 },
farmbuyer@73 2732 -- ["count"] = { ["tag"] = "x3", .... },
farmbuyer@1 2733 -- },
farmbuyer@1 2734 -- [2] = {
farmbuyer@1 2735 -- ["name"] = "OtherPlayer",
farmbuyer@1 2736 -- ......
farmbuyer@1 2737 -- }, ......
farmbuyer@1 2738 -- },
farmbuyer@1 2739 -- ["OtherRealm"] = ......
farmbuyer@1 2740 -- }
farmbuyer@73 2741 --
farmbuyer@73 2742 -- Up through 2.81.4 (specifically through rev 95), an individual player's
farmbuyer@73 2743 -- table looked like this:
farmbuyer@73 2744 -- ["name"] = "Farmbuyer",
farmbuyer@73 2745 -- [1] = { id = nnnnn, when = "formatted timestamp for displaying" } -- most recent loot
farmbuyer@73 2746 -- [2] = { ......., [count = "x3"] } -- previous loot
farmbuyer@73 2747 -- which was much easier to manipulate, but had a ton of memory overhead.
farmbuyer@1 2748 do
farmbuyer@73 2749 -- Sorts a player's history from newest to oldest, according to the
farmbuyer@73 2750 -- formatted timestamp. This is expensive, and destructive for P.unique.
farmbuyer@76 2751 local function compare_timestamps (L, R)
farmbuyer@76 2752 return L > R -- reverse of normal order, newest first
farmbuyer@76 2753 end
farmbuyer@73 2754 local function sort_player (p)
farmbuyer@73 2755 local new_uniques, uniques_bywhen, when_array = {}, {}, {}
farmbuyer@73 2756 for u,tstamp in pairs(p.when) do
farmbuyer@73 2757 uniques_bywhen[tstamp] = u
farmbuyer@73 2758 when_array[#when_array+1] = tstamp
farmbuyer@73 2759 end
farmbuyer@76 2760 _G.table.sort (when_array, compare_timestamps)
farmbuyer@73 2761 for i,tstamp in ipairs(when_array) do
farmbuyer@73 2762 new_uniques[i] = uniques_bywhen[tstamp]
farmbuyer@73 2763 end
farmbuyer@73 2764 p.unique = new_uniques
farmbuyer@73 2765 end
farmbuyer@73 2766
farmbuyer@81 2767 function addon:repair_history_integrity()
farmbuyer@81 2768 local rcount, pcount, hcount, errors = 0, 0, 0, 0
farmbuyer@81 2769 local empties_to_delete = {}
farmbuyer@81 2770
farmbuyer@81 2771 for rname,realm in pairs(self.history_all) do
farmbuyer@81 2772 for pk,player in ipairs(realm) do
farmbuyer@81 2773 local id, when, unique, count = {}, {}, {}, {}
farmbuyer@81 2774 for i,h in ipairs(player.unique) do
farmbuyer@81 2775 h = tostring(h)
farmbuyer@81 2776 if player.when[h] and player.id[h] then
farmbuyer@81 2777 unique[#unique+1] = h
farmbuyer@81 2778 id[h] = player.id[h]
farmbuyer@81 2779 when[h] = player.when[h]
farmbuyer@81 2780 count[h] = player.count[h]
farmbuyer@81 2781 else
farmbuyer@81 2782 self:Print("Realm %s, player %s, entry %d: tag <%s>, id <%s>, time <%s>, count <%s>",
farmbuyer@81 2783 rname, player.name, i, h, tostring(player.id[h]),
farmbuyer@81 2784 tostring(player.when[h]), tostring(player.count[h]))
farmbuyer@81 2785 errors = errors + 1
farmbuyer@81 2786 end
farmbuyer@81 2787 hcount = hcount + 1
farmbuyer@81 2788 end
farmbuyer@81 2789 player.id, player.when, player.unique, player.count =
farmbuyer@81 2790 id, when, unique, count
farmbuyer@81 2791 player.person_class = g_loot.raiders[player.name]
farmbuyer@81 2792 and g_loot.raiders[player.name].class
farmbuyer@81 2793 if #player.unique > 1 then
farmbuyer@81 2794 sort_player(player)
farmbuyer@81 2795 elseif #player.unique == 0 then
farmbuyer@81 2796 tinsert (empties_to_delete, 1, pk)
farmbuyer@81 2797 end
farmbuyer@81 2798 pcount = pcount + 1
farmbuyer@81 2799 end
farmbuyer@81 2800 if #empties_to_delete > 0 then
farmbuyer@81 2801 for _,pk in ipairs(empties_to_delete) do
farmbuyer@81 2802 local player = tremove (realm, pk)
farmbuyer@81 2803 self:Print("Realm %s, player %s, is empty", rname, player.name)
farmbuyer@81 2804 end
farmbuyer@81 2805 wipe(empties_to_delete)
farmbuyer@81 2806 end
farmbuyer@81 2807 if #realm == 0 then
farmbuyer@81 2808 self.history_all[rname] = nil
farmbuyer@81 2809 self:Print("Realm %s is empty", rname)
farmbuyer@81 2810 end
farmbuyer@81 2811 rcount = rcount + 1
farmbuyer@81 2812 end
farmbuyer@81 2813 self:_build_history_names()
farmbuyer@81 2814 if errors > 0 then
farmbuyer@81 2815 self:Print("The listed entries have been erased from history.")
farmbuyer@81 2816 end
farmbuyer@81 2817 end
farmbuyer@81 2818
farmbuyer@73 2819 -- Possibly called during login. Cleared when no longer needed.
farmbuyer@73 2820 -- Rewrites a PLAYER table from format 3 to format 4.
farmbuyer@76 2821 function addon:_uplift_history_format (player)
farmbuyer@73 2822 local unique, when, id, count = {}, {}, {}, {}
farmbuyer@73 2823 local name = player.name
farmbuyer@73 2824
farmbuyer@73 2825 for i,h in ipairs(player) do
farmbuyer@73 2826 local U = h.unique
farmbuyer@73 2827 unique[i] = U
farmbuyer@73 2828 when[U] = h.when
farmbuyer@73 2829 id[U] = h.id
farmbuyer@73 2830 count[U] = h.count
farmbuyer@73 2831 end
farmbuyer@73 2832
farmbuyer@73 2833 wipe(player)
farmbuyer@73 2834 player.name = name
farmbuyer@73 2835 player.id, player.when, player.unique, player.count =
farmbuyer@73 2836 id, when, unique, count
farmbuyer@73 2837 end
farmbuyer@76 2838
farmbuyer@73 2839 function addon:_cache_history_uniques()
farmbuyer@73 2840 UpdateAddOnMemoryUsage()
farmbuyer@73 2841 local before = GetAddOnMemoryUsage(nametag)
farmbuyer@73 2842 local trouble
farmbuyer@73 2843 local count = 0
farmbuyer@73 2844 for hi,player in ipairs(self.history) do
farmbuyer@73 2845 for ui,u in ipairs(player.unique) do
farmbuyer@87 2846 g_uniques[u] = { history = player.name, history_may = ui }
farmbuyer@73 2847 count = count + 1
farmbuyer@73 2848 end
farmbuyer@73 2849 end
farmbuyer@73 2850 for i,e in self:filtered_loot_iter('loot') do
farmbuyer@73 2851 if e.unique and e.unique ~= "" then
farmbuyer@73 2852 local hmmm = _G.rawget(g_uniques,e.unique)
farmbuyer@73 2853 if hmmm then
farmbuyer@82 2854 hmmm.loot = i
farmbuyer@73 2855 elseif e.disposition == 'shard' or e.disposition == 'gvault' then
farmbuyer@73 2856 g_uniques[e.unique] = { loot = i, history = g_uniques.NOTFOUND }
farmbuyer@73 2857 count = count + 1
farmbuyer@73 2858 else
farmbuyer@76 2859 hmmm = "active data not found in history ("..i.."/"..tostring(e.unique)
farmbuyer@76 2860 ..") in precache loop! trying to fixup for this session"
farmbuyer@76 2861 pprint(hmmm) -- more?
farmbuyer@73 2862 -- try to simply fix up errors as we go
farmbuyer@73 2863 g_uniques[e.unique] = { loot = i, history = g_uniques.NOTFOUND }
farmbuyer@73 2864 end
farmbuyer@73 2865 else
farmbuyer@89 2866 -- The usual cause: when only source is from an older client
farmbuyer@89 2867 -- and the disposition did not trigger addhistory, then not
farmbuyer@89 2868 -- even a stub history entry happens. Code has now been added
farmbuyer@89 2869 -- to try harder to prevent this, but it's still best to not
farmbuyer@89 2870 -- simple ignore it.
farmbuyer@73 2871 trouble = true
farmbuyer@89 2872 pprint('loot', "ERROR precache loop found missing/outdated unique tag!",
farmbuyer@89 2873 i, "tag <"..tostring(e.unique).."> from?", tostring(e.bcast_from))
farmbuyer@73 2874 end
farmbuyer@73 2875 end
farmbuyer@73 2876 UpdateAddOnMemoryUsage()
farmbuyer@73 2877 local after = GetAddOnMemoryUsage(nametag)
farmbuyer@73 2878 self:Print("Pre-scanning history for faster loot handling on %s used %.2f MB of memory across %d entries.",
farmbuyer@73 2879 self.history.realm, (after-before)/1024, count)
farmbuyer@73 2880 if trouble then
farmbuyer@73 2881 self:Print("Note that there were inconsistencies in the data;",
farmbuyer@73 2882 "you should consider submitting a bug report (including your",
farmbuyer@73 2883 "SavedVariables file), and regenerating or preening this",
farmbuyer@89 2884 "realm's loot history. If you keep seeing this message, type",
farmbuyer@89 2885 "'/ouroloot fix ?' and try some of those actions.")
farmbuyer@73 2886 end
farmbuyer@73 2887 g_uniques:SETMODE('probe')
farmbuyer@73 2888 self._cache_history_uniques = nil
farmbuyer@73 2889 end
farmbuyer@6 2890
farmbuyer@4 2891 -- Builds the map of names to array indices, using passed table or
farmbuyer@4 2892 -- self.history, and stores the result into its 'byname' field. Also
farmbuyer@4 2893 -- called from the GUI code at least once.
farmbuyer@1 2894 function addon:_build_history_names (opt_hist)
farmbuyer@1 2895 local hist = opt_hist or self.history
farmbuyer@1 2896 local m = {}
farmbuyer@1 2897 for i = 1, #hist do
farmbuyer@1 2898 m[hist[i].name] = i
farmbuyer@1 2899 end
farmbuyer@73 2900 -- why yes, I *did* spend many years as a YP/NIS admin, how did you know?
farmbuyer@1 2901 hist.byname = m
farmbuyer@1 2902 end
farmbuyer@1 2903
farmbuyer@1 2904 -- Prepares and returns table to be used as self.history.
farmbuyer@1 2905 function addon:_prep_new_history_category (prev_table, realmname)
farmbuyer@1 2906 local t = prev_table or {
farmbuyer@1 2907 --kind = 'realm',
farmbuyer@6 2908 --realm = realmname,
farmbuyer@1 2909 }
farmbuyer@6 2910 t.realm = realmname
farmbuyer@1 2911
farmbuyer@1 2912 --[[
farmbuyer@1 2913 t.cols = setmetatable({
farmbuyer@1 2914 { value = realmname },
farmbuyer@1 2915 }, self.time_column1_used_mt)
farmbuyer@1 2916 ]]
farmbuyer@1 2917
farmbuyer@1 2918 if not t.byname then
farmbuyer@1 2919 self:_build_history_names (t)
farmbuyer@1 2920 end
farmbuyer@1 2921
farmbuyer@1 2922 return t
farmbuyer@1 2923 end
farmbuyer@1 2924
farmbuyer@4 2925 -- Maps a name to an array index, creating new tables if needed. Returns
farmbuyer@6 2926 -- the index and the table at that index.
farmbuyer@4 2927 function addon:get_loot_history (name)
farmbuyer@4 2928 local i
farmbuyer@4 2929 i = self.history.byname[name]
farmbuyer@4 2930 if not i then
farmbuyer@4 2931 i = #self.history + 1
farmbuyer@73 2932 self.history[i] = { name=name, id={}, when={}, unique={}, count={} }
farmbuyer@4 2933 self.history.byname[name] = i
farmbuyer@4 2934 end
farmbuyer@6 2935 return i, self.history[i]
farmbuyer@4 2936 end
farmbuyer@4 2937
farmbuyer@73 2938 -- Prepends data from the loot entry at LOOTINDEX to be the new most
farmbuyer@73 2939 -- recent history entry for that player.
farmbuyer@1 2940 function addon:_addHistoryEntry (lootindex)
farmbuyer@1 2941 local e = g_loot[lootindex]
farmbuyer@6 2942 if e.kind ~= 'loot' then return end
farmbuyer@6 2943
farmbuyer@6 2944 local i,h = self:get_loot_history(e.person)
farmbuyer@73 2945 local when = self:format_timestamp (g_today, e)
farmbuyer@87 2946 assert(h.name==e.person)
farmbuyer@73 2947
farmbuyer@25 2948 -- If any of these change, update the end of history_handle_disposition.
farmbuyer@71 2949 if (not e.unique) or (#e.unique==0) then
farmbuyer@84 2950 e.unique = e.id .. e.person .. when
farmbuyer@71 2951 end
farmbuyer@73 2952 local U = e.unique
farmbuyer@73 2953 tinsert (h.unique, 1, U)
farmbuyer@73 2954 h.when[U] = when
farmbuyer@73 2955 h.id[U] = e.id
farmbuyer@73 2956 h.count[U] = e.count
farmbuyer@73 2957
farmbuyer@87 2958 g_uniques[U] = { loot = lootindex, history = e.person }
farmbuyer@6 2959 end
farmbuyer@6 2960
farmbuyer@24 2961 -- Create new history table based on current loot.
farmbuyer@6 2962 function addon:rewrite_history (realmname)
farmbuyer@6 2963 local r = assert(realmname)
farmbuyer@6 2964 self.history_all[r] = self:_prep_new_history_category (nil, r)
farmbuyer@6 2965 self.history = self.history_all[r]
farmbuyer@73 2966 g_uniques:RESET()
farmbuyer@6 2967
farmbuyer@6 2968 local g_today_real = g_today
farmbuyer@6 2969 for i,e in ipairs(g_loot) do
farmbuyer@6 2970 if e.kind == 'time' then
farmbuyer@6 2971 g_today = e
farmbuyer@6 2972 elseif e.kind == 'loot' then
farmbuyer@6 2973 self:_addHistoryEntry(i)
farmbuyer@6 2974 end
farmbuyer@6 2975 end
farmbuyer@6 2976 g_today = g_today_real
farmbuyer@6 2977 self.hist_clean = nil
farmbuyer@6 2978
farmbuyer@6 2979 -- safety measure: resort players' tables based on formatted timestamp
farmbuyer@6 2980 for i,h in ipairs(self.history) do
farmbuyer@73 2981 sort_player(h)
farmbuyer@6 2982 end
farmbuyer@6 2983 end
farmbuyer@6 2984
farmbuyer@76 2985 -- Clears all but the most recent HOWMANY (optional, default 1) entries
farmbuyer@76 2986 -- for each player on REALMNAME.
farmbuyer@76 2987 -- This function's name is the legacy of the orignal fsck(8) "-p" option,
farmbuyer@76 2988 -- which has a similar feel.
farmbuyer@76 2989 function addon:preen_history (realmname, howmany)
farmbuyer@6 2990 local r = assert(realmname)
farmbuyer@76 2991 howmany = tonumber(howmany) or 1
farmbuyer@76 2992 if type(self.history_all[r]) ~= 'table' then
farmbuyer@76 2993 return
farmbuyer@76 2994 end
farmbuyer@73 2995 g_uniques:RESET()
farmbuyer@76 2996 for i,h in ipairs(self.history_all[r]) do
farmbuyer@73 2997 -- This is going to do horrible things to memory. The subtables
farmbuyer@73 2998 -- after this step would be large and sparse, with no good way
farmbuyer@73 2999 -- of shrinking the allocation...
farmbuyer@73 3000 sort_player(h)
farmbuyer@73 3001 -- ...so it's better in the long run to discard them.
farmbuyer@76 3002 local new_unique, new_id, new_when, new_count = {}, {}, {}, {}
farmbuyer@76 3003 for ui = 1, howmany do
farmbuyer@76 3004 local U = h.unique[ui]
farmbuyer@76 3005 if not U then break end
farmbuyer@76 3006 new_unique[ui] = U
farmbuyer@76 3007 new_id[U] = h.id[U]
farmbuyer@76 3008 new_when[U] = h.when[U]
farmbuyer@76 3009 new_count[U] = h.count[U]
farmbuyer@76 3010 end
farmbuyer@76 3011 h.unique, h.id, h.when, h.count =
farmbuyer@76 3012 new_unique, new_id, new_when, new_count
farmbuyer@6 3013 end
farmbuyer@1 3014 end
farmbuyer@24 3015
farmbuyer@73 3016 -- Given a unique tag OR an entry in a g_loot table, looks up the
farmbuyer@73 3017 -- corresponding history entry. Returns the player's index and history
farmbuyer@73 3018 -- table (as in get_loot_history) and the index into that table of the
farmbuyer@73 3019 -- loot entry. On failure, returns nil and an error message ready to be
farmbuyer@73 3020 -- formatted with the loot's name/itemlink.
farmbuyer@73 3021 function _history_by_loot_id (needle, operation_text)
farmbuyer@73 3022 local errtxt
farmbuyer@73 3023 if type(needle) == 'string' then
farmbuyer@73 3024 -- unique tag
farmbuyer@73 3025 elseif type(needle) == 'table' then
farmbuyer@73 3026 if needle.kind ~= 'loot' then
farmbuyer@73 3027 error("trying to "..operation_text.." something that isn't loot")
farmbuyer@73 3028 end
farmbuyer@73 3029 needle = needle.unique
farmbuyer@73 3030 if not needle then
farmbuyer@73 3031 return nil, --[[errtxt=]]"Entry for %s is missing a history tag!"
farmbuyer@73 3032 end
farmbuyer@73 3033 else
farmbuyer@73 3034 error("'"..tostring(needle).."' is neither unique string nor loot entry!")
farmbuyer@25 3035 end
farmbuyer@24 3036
farmbuyer@73 3037 local player_i, player_h
farmbuyer@73 3038 local cache = g_uniques[needle]
farmbuyer@24 3039
farmbuyer@73 3040 if cache.history == g_uniques.NOTFOUND then
farmbuyer@73 3041 -- 1) loot an item, 2) clear old history, 3) reassign from current loot
farmbuyer@73 3042 -- Bah. Anybody that tricky is already recoding the tables directly anyhow.
farmbuyer@73 3043 errtxt = "There is no record of %s ever having been assigned!"
farmbuyer@24 3044 else
farmbuyer@87 3045 player_i = addon.history.byname[cache.history]
farmbuyer@73 3046 player_h = addon.history[player_i]
farmbuyer@73 3047 if cache.history_may
farmbuyer@73 3048 and needle == player_h.unique[cache.history_may]
farmbuyer@73 3049 then
farmbuyer@73 3050 return player_i, player_h, cache.history_may
farmbuyer@73 3051 end
farmbuyer@73 3052 for i,u in ipairs(player_h.unique) do
farmbuyer@73 3053 if needle == u then
farmbuyer@73 3054 cache.history_may = i -- might help, might not
farmbuyer@73 3055 return player_i, player_h, i
farmbuyer@24 3056 end
farmbuyer@24 3057 end
farmbuyer@24 3058 end
farmbuyer@24 3059
farmbuyer@73 3060 if not errtxt then
farmbuyer@73 3061 -- The cache finder got a hit, but now it's gone? WTF?
farmbuyer@73 3062 errtxt = "ZOMG! %s was in history but now is gone. Possibly your history tables have been corrupted and should be recreated. This is likely a bug. Tell Farmbuyer what steps you took to cause this."
farmbuyer@24 3063 end
farmbuyer@73 3064 return nil, errtxt
farmbuyer@25 3065 end
farmbuyer@24 3066
farmbuyer@81 3067 -- Handles reassigning loot between players. Arguments depend on who's
farmbuyer@81 3068 -- calling it:
farmbuyer@81 3069 -- "local", row_index, new_recipient
farmbuyer@81 3070 -- "remote", sender, unique_id, item_id, old_recipient, new_recipient
farmbuyer@81 3071 -- In the local case, must also broadcast a trigger. In the remote case,
farmbuyer@81 3072 -- must figure out the corresponding loot entry (if it exists). In both
farmbuyer@81 3073 -- cases, must update history appropriately. Returns nil if anything odd
farmbuyer@81 3074 -- happens; returns the affected loot index on success.
farmbuyer@81 3075 --function addon:reassign_loot (index, to_name)
farmbuyer@81 3076 function addon:reassign_loot (how, ...)
farmbuyer@81 3077 -- This must all be filled out in all cases:
farmbuyer@81 3078 local e, index, from_name, to_name, unique, id
farmbuyer@81 3079 -- Only set in remote case:
farmbuyer@81 3080 local sender
farmbuyer@81 3081
farmbuyer@81 3082 if how == "local" then
farmbuyer@81 3083 -- GUI doesn't allow reassignment unless the item is not-shard,
farmbuyer@81 3084 -- so we can assume the presence of a unique tag in this function.
farmbuyer@81 3085 index, to_name = ...
farmbuyer@81 3086 assert(type(to_name)=='string' and to_name:len()>0)
farmbuyer@81 3087 index = assert(tonumber(index))
farmbuyer@81 3088 e = g_loot[index]
farmbuyer@81 3089 id = e.id
farmbuyer@81 3090 unique = assert(e.unique)
farmbuyer@81 3091 from_name = e.person
farmbuyer@81 3092
farmbuyer@81 3093 elseif how == "remote" then
farmbuyer@81 3094 sender, unique, id, from_name, to_name = ...
farmbuyer@81 3095 id = tonumber(id)
farmbuyer@81 3096 local cache
farmbuyer@81 3097 local loop = 0
farmbuyer@81 3098 repeat -- wtb continue statement pst
farmbuyer@81 3099 if loop > 1 then break end
farmbuyer@81 3100 e = nil
farmbuyer@81 3101 cache = cache and g_uniques:SEARCH(unique) or g_uniques[unique]
farmbuyer@81 3102 index = tonumber(cache.loot)
farmbuyer@81 3103 if index then
farmbuyer@81 3104 e = g_loot[index]
farmbuyer@81 3105 else
farmbuyer@81 3106 end
farmbuyer@81 3107 loop = loop + 1
farmbuyer@81 3108 until e and (e.id == id)
farmbuyer@81 3109
farmbuyer@81 3110 else
farmbuyer@81 3111 return -- silently ignore future cases from future clients
farmbuyer@81 3112 end
farmbuyer@81 3113
farmbuyer@81 3114 if self.debug.loot then
farmbuyer@81 3115 local m = ("Reassign index %d (pre-unique %s) with id %d from '%s' to '%s'."):
farmbuyer@81 3116 format(index, unique, id, tostring(from_name), tostring(to_name))
farmbuyer@81 3117 self.dprint('loot', m)
farmbuyer@81 3118 if sender == my_name then
farmbuyer@81 3119 self.dprint('loot',"(Returning early from debug mode's double self-reassign.)")
farmbuyer@81 3120 return index
farmbuyer@81 3121 end
farmbuyer@81 3122 end
farmbuyer@81 3123
farmbuyer@81 3124 if not e then
farmbuyer@81 3125 -- say something?
farmbuyer@81 3126 return
farmbuyer@81 3127 end
farmbuyer@81 3128
farmbuyer@73 3129 local from_i, from_h, hist_i = _history_by_loot_id (e, "reassign")
farmbuyer@25 3130 local to_i,to_h = self:get_loot_history(to_name)
farmbuyer@25 3131
farmbuyer@81 3132 if not from_i then
farmbuyer@81 3133 if how == "local" then
farmbuyer@81 3134 -- from_h here is the formatted error text
farmbuyer@81 3135 self:Print(from_h .. " Loot will be reassigned, but history will NOT be updated.", e.itemlink)
farmbuyer@81 3136 end
farmbuyer@25 3137 else
farmbuyer@81 3138 -- XXX do some sanity checks here? from_name == from_h.name, etc?
farmbuyer@87 3139 -- If something were wrong at this point, what could we do?
farmbuyer@81 3140
farmbuyer@73 3141 local U = tremove (from_h.unique, hist_i)
farmbuyer@73 3142 -- The loot master giveth...
farmbuyer@73 3143 to_h.unique[#to_h.unique+1] = U
farmbuyer@73 3144 to_h.when[U] = from_h.when[U]
farmbuyer@73 3145 to_h.id[U] = from_h.id[U]
farmbuyer@73 3146 to_h.count[U] = from_h.count[U]
farmbuyer@73 3147 sort_player(to_h)
farmbuyer@73 3148 -- ...and the loot master taketh away.
farmbuyer@73 3149 from_h.when[U] = nil
farmbuyer@73 3150 from_h.id[U] = nil
farmbuyer@73 3151 from_h.count[U] = nil
farmbuyer@73 3152 -- Blessed be the lookup cache of the loot master.
farmbuyer@87 3153 g_uniques[U] = { loot = index, history = to_name }
farmbuyer@25 3154 end
farmbuyer@81 3155 local from_person_class = e.person_class or from_h.person_class
farmbuyer@81 3156 or (g_loot.raiders[from_name] and g_loot.raiders[from_name].class)
farmbuyer@81 3157 or select(2,_G.UnitClass(from_name))
farmbuyer@25 3158 e.person = to_name
farmbuyer@81 3159 e.person_class = to_h.person_class
farmbuyer@81 3160 or (g_loot.raiders[to_name] and g_loot.raiders[to_name].class)
farmbuyer@81 3161 or select(2,_G.UnitClass(to_name))
farmbuyer@25 3162 self.hist_clean = nil
farmbuyer@81 3163 self.loot_clean = nil
farmbuyer@81 3164
farmbuyer@81 3165 if how == "local" then
farmbuyer@81 3166 _notify_about_change (_G.DEFAULT_CHAT_FRAME, _G.UNIT_YOU, index,
farmbuyer@81 3167 nil, from_name, from_person_class)
farmbuyer@81 3168 self:vbroadcast('reassign', unique, id, from_name, to_name)
farmbuyer@81 3169 elseif opts.chatty_on_remote_changes then
farmbuyer@81 3170 _notify_about_remote (sender, index, nil, from_name, from_person_class)
farmbuyer@81 3171 end
farmbuyer@81 3172 if self.display then
farmbuyer@81 3173 self.display:GetUserData("eoiST"):OuroLoot_Refresh(index)
farmbuyer@81 3174 self:redisplay()
farmbuyer@81 3175 end
farmbuyer@81 3176 return index
farmbuyer@25 3177 end
farmbuyer@25 3178
farmbuyer@84 3179 local function expunge (player, index_or_unique)
farmbuyer@84 3180 local i,u
farmbuyer@84 3181 if type(index_or_unique) == 'string' then
farmbuyer@84 3182 for u = 1, #player.unique do
farmbuyer@84 3183 if player.unique[u] == index_or_unique then
farmbuyer@84 3184 i = u
farmbuyer@84 3185 break
farmbuyer@84 3186 end
farmbuyer@84 3187 end
farmbuyer@84 3188 elseif type(index_or_unique) == 'number' then
farmbuyer@84 3189 i = index_or_unique
farmbuyer@84 3190 end
farmbuyer@84 3191 if not i then
farmbuyer@84 3192 return -- error here?
farmbuyer@84 3193 end
farmbuyer@84 3194 u = player.unique[i]
farmbuyer@84 3195 assert(#u>0)
farmbuyer@84 3196 tremove (player.unique, i)
farmbuyer@84 3197 player.when[u], player.id[u], player.count[u] = nil, nil, nil
farmbuyer@84 3198 g_uniques[u] = nil
farmbuyer@84 3199 addon.hist_clean = nil
farmbuyer@87 3200 return #player.unique
farmbuyer@84 3201 end
farmbuyer@84 3202
farmbuyer@84 3203 -- Mirror of _addHistoryEntry. Arguments are either:
farmbuyer@84 3204 -- E - loot entry
farmbuyer@84 3205 -- U,ITEM - unique tag, and a name/itemlink for errors
farmbuyer@87 3206 -- If this entry was the only one for that player, will also remove that
farmbuyer@87 3207 -- player's tables from the history array.
farmbuyer@87 3208 --
farmbuyer@87 3209 -- On success, returns the number of remaining history entries for that
farmbuyer@87 3210 -- player (potentially zero). On failure, returns nil+error.
farmbuyer@87 3211 function addon:_delHistoryEntry (first, item)
farmbuyer@84 3212 if type(first) == 'table' then
farmbuyer@87 3213 item = first.itemlink or item
farmbuyer@84 3214 --elseif type(first) == 'string' then
farmbuyer@84 3215 end
farmbuyer@84 3216
farmbuyer@84 3217 local from_i, from_h, hist_i = _history_by_loot_id (first, "delete")
farmbuyer@84 3218
farmbuyer@36 3219 if not from_i then
farmbuyer@36 3220 -- from_h is the formatted error text
farmbuyer@84 3221 return nil, (from_h
farmbuyer@84 3222 .." Loot will be deleted, but history will NOT be updated."
farmbuyer@87 3223 ):format(item)
farmbuyer@36 3224 end
farmbuyer@36 3225
farmbuyer@87 3226 local remaining = expunge (from_h, hist_i)
farmbuyer@87 3227 if not remaining then
farmbuyer@87 3228 return nil, "Something bizarre happening trying to delete "..item
farmbuyer@87 3229 elseif remaining > 0 then
farmbuyer@87 3230 return remaining
farmbuyer@87 3231 end
farmbuyer@87 3232 tremove (self.history, from_i)
farmbuyer@87 3233 self:_build_history_names()
farmbuyer@87 3234 return 0
farmbuyer@36 3235 end
farmbuyer@36 3236
farmbuyer@25 3237 -- Any extra work for the "Mark as <x>" dropdown actions. The
farmbuyer@25 3238 -- corresponding <x> will already have been assigned in the loot entry.
farmbuyer@73 3239 local deleted_cache = {}
farmbuyer@25 3240 function addon:history_handle_disposition (index, olddisp)
farmbuyer@25 3241 local e = g_loot[index]
farmbuyer@25 3242 -- Standard disposition has a nil entry, but that's tedious in debug
farmbuyer@25 3243 -- output, so force to a string instead.
farmbuyer@25 3244 olddisp = olddisp or 'normal'
farmbuyer@25 3245 local newdisp = e.disposition or 'normal'
farmbuyer@25 3246 -- Ignore misclicks and the like
farmbuyer@25 3247 if olddisp == newdisp then return end
farmbuyer@25 3248
farmbuyer@25 3249 local name = e.person
farmbuyer@25 3250
farmbuyer@25 3251 if (newdisp == 'shard' or newdisp == 'gvault') then
farmbuyer@73 3252 local name_i, name_h, hist_i = _history_by_loot_id (e, "mark")
farmbuyer@73 3253 -- remove history entry if it exists
farmbuyer@84 3254 -- FIXME revist this and use expunge
farmbuyer@25 3255 if hist_i then
farmbuyer@73 3256 local c = flib.new()
farmbuyer@73 3257 local hist_u = tremove (name_h.unique, hist_i)
farmbuyer@73 3258 c.when = name_h.when[hist_u]
farmbuyer@73 3259 c.id = name_h.id[hist_u]
farmbuyer@73 3260 c.count = name_h.count[hist_u]
farmbuyer@73 3261 deleted_cache[hist_u] = c
farmbuyer@73 3262 name_h.when[hist_u] = nil
farmbuyer@73 3263 name_h.id[hist_u] = nil
farmbuyer@73 3264 name_h.count[hist_u] = nil
farmbuyer@25 3265 self.hist_clean = nil
farmbuyer@25 3266 elseif (olddisp == 'shard' or olddisp == 'gvault') then
farmbuyer@25 3267 -- Sharding a vault item, or giving the auto-sharder something to bank,
farmbuyer@25 3268 -- etc, wouldn't necessarily have had a history entry to begin with.
farmbuyer@73 3269 -- So this isn't treated as an error.
farmbuyer@25 3270 else
farmbuyer@25 3271 self:Print(name_h .. " Loot has been marked, but history will NOT be updated.", e.itemlink)
farmbuyer@25 3272 end
farmbuyer@25 3273 return
farmbuyer@25 3274 end
farmbuyer@25 3275
farmbuyer@25 3276 if (olddisp == 'shard' or olddisp == 'gvault')
farmbuyer@25 3277 and (newdisp == 'normal' or newdisp == 'offspec')
farmbuyer@25 3278 then
farmbuyer@25 3279 local name_i, name_h = self:get_loot_history(name)
farmbuyer@25 3280
farmbuyer@25 3281 -- Must create a new history entry. Could call '_addHistoryEntry(index)'
farmbuyer@25 3282 -- but that would duplicate a lot of effort. To start with, check the
farmbuyer@25 3283 -- cache of stuff we've already deleted; if it's not there then just do
farmbuyer@25 3284 -- the same steps as _addHistoryEntry.
farmbuyer@73 3285 -- FIXME The deleted cache isn't nearly as useful now with the new data structures.
farmbuyer@73 3286 local when
farmbuyer@73 3287 if (not e.unique) or (#e.unique==0) then
farmbuyer@85 3288 when = g_today and self:format_timestamp (g_today, e) or date("%Y/%m/%d %H:%M",e.stamp)
farmbuyer@84 3289 e.unique = e.id .. name .. when
farmbuyer@25 3290 end
farmbuyer@73 3291 local U = e.unique
farmbuyer@73 3292 local c = deleted_cache[U]
farmbuyer@73 3293 deleted_cache[U] = nil
farmbuyer@73 3294 name_h.unique[#name_h.unique+1] = U
farmbuyer@85 3295 name_h.when[U] = c and c.when or when or date("%Y/%m/%d %H:%M",e.stamp)
farmbuyer@73 3296 name_h.id[U] = e.id -- c.id
farmbuyer@73 3297 name_h.count[U] = c and c.count or e.count
farmbuyer@73 3298 sort_player(name_h)
farmbuyer@87 3299 g_uniques[U] = { loot = index, history = name }
farmbuyer@25 3300 self.hist_clean = nil
farmbuyer@73 3301
farmbuyer@73 3302 if c then flib.del(c) end
farmbuyer@73 3303
farmbuyer@25 3304 return
farmbuyer@25 3305 end
farmbuyer@24 3306 end
farmbuyer@73 3307
farmbuyer@73 3308 -- This is not entirely "history" but not completely anything else either.
farmbuyer@73 3309 -- Handles the primary "Mark as <x>" action. Arguments depend on who's
farmbuyer@73 3310 -- calling it:
farmbuyer@73 3311 -- "local", row_index, new_disposition
farmbuyer@73 3312 -- "remote", sender, unique_id, item_id, old_disposition, new_disposition
farmbuyer@73 3313 -- In the local case, must also broadcast a trigger. In the remote case,
farmbuyer@73 3314 -- must figure out the corresponding loot entry (if it exists). In both
farmbuyer@73 3315 -- cases, must update history appropriately. Returns nil if anything odd
farmbuyer@73 3316 -- happens (not necessarily an error!); returns the affected loot index
farmbuyer@73 3317 -- on success.
farmbuyer@73 3318 function addon:loot_mark_disposition (how, ...)
farmbuyer@73 3319 -- This must all be filled out in all cases:
farmbuyer@73 3320 local e, index, olddisp, newdisp, unique, id
farmbuyer@73 3321 -- Only set in remote case:
farmbuyer@73 3322 local sender
farmbuyer@73 3323
farmbuyer@73 3324 if how == "local" then
farmbuyer@73 3325 index, newdisp = ...
farmbuyer@73 3326 index = assert(tonumber(index))
farmbuyer@73 3327 e = g_loot[index]
farmbuyer@73 3328 id = e.id
farmbuyer@73 3329 unique = e.unique -- can potentially still be nil at this step
farmbuyer@73 3330 olddisp = e.disposition
farmbuyer@73 3331
farmbuyer@73 3332 elseif how == "remote" then
farmbuyer@73 3333 sender, unique, id, olddisp, newdisp = ...
farmbuyer@81 3334 id = tonumber(id)
farmbuyer@81 3335 local cache
farmbuyer@81 3336 local loop = 0
farmbuyer@81 3337 repeat -- wtb continue statement pst
farmbuyer@81 3338 if loop > 1 then break end
farmbuyer@81 3339 e = nil
farmbuyer@81 3340 cache = cache and g_uniques:SEARCH(unique) or g_uniques[unique]
farmbuyer@73 3341 index = tonumber(cache.loot)
farmbuyer@81 3342 if index then
farmbuyer@81 3343 e = g_loot[index]
farmbuyer@81 3344 else
farmbuyer@81 3345 end
farmbuyer@81 3346 loop = loop + 1
farmbuyer@81 3347 until e and (e.id == id)
farmbuyer@73 3348
farmbuyer@73 3349 else
farmbuyer@76 3350 return -- silently ignore future cases from future clients
farmbuyer@73 3351 end
farmbuyer@73 3352
farmbuyer@73 3353 if self.debug.loot then
farmbuyer@80 3354 local m = ("Re-mark index %d (pre-unique %s) with id %d from '%s' to '%s'."):
farmbuyer@73 3355 format(index, unique, id, tostring(olddisp), tostring(newdisp))
farmbuyer@73 3356 self.dprint('loot', m)
farmbuyer@73 3357 if sender == my_name then
farmbuyer@76 3358 self.dprint('loot',"(Returning early from debug mode's double self-mark.)")
farmbuyer@73 3359 return index
farmbuyer@73 3360 end
farmbuyer@73 3361 end
farmbuyer@73 3362
farmbuyer@73 3363 if not e then
farmbuyer@73 3364 -- say something?
farmbuyer@73 3365 return
farmbuyer@73 3366 end
farmbuyer@73 3367
farmbuyer@73 3368 e.disposition = newdisp
farmbuyer@73 3369 e.bcast_from = nil -- I actually don't remember now why this gets cleared...
farmbuyer@73 3370 e.extratext = nil
farmbuyer@73 3371 self:history_handle_disposition (index, olddisp)
farmbuyer@81 3372 self.hist_clean = nil
farmbuyer@81 3373 self.loot_clean = nil
farmbuyer@73 3374 -- A unique tag has been set by this point.
farmbuyer@73 3375 if how == "local" then
farmbuyer@73 3376 unique = assert(e.unique)
farmbuyer@73 3377 self:vbroadcast('mark', unique, id, olddisp, newdisp)
farmbuyer@73 3378 end
farmbuyer@73 3379 return index
farmbuyer@73 3380 end
farmbuyer@1 3381 end
farmbuyer@1 3382
farmbuyer@1 3383
farmbuyer@1 3384 ------ Player communication
farmbuyer@1 3385 do
farmbuyer@73 3386 local select, tconcat, strsplit, unpack = select, table.concat, strsplit, unpack
farmbuyer@56 3387 --[[ old way: repeated string concatenations, BAD
farmbuyer@76 3388 new way: new table on every broadcast, BAD
farmbuyer@56 3389 local msg = ...
farmbuyer@56 3390 for i = 2, select('#',...) do
farmbuyer@56 3391 msg = msg .. '\a' .. (select(i,...) or "")
farmbuyer@56 3392 end
farmbuyer@56 3393 return msg
farmbuyer@56 3394 ]]
farmbuyer@56 3395 local function assemble(t,...)
farmbuyer@73 3396 local n = select('#',...)
farmbuyer@73 3397 if n > 0 then
farmbuyer@56 3398 local msg = {t,...}
farmbuyer@56 3399 -- tconcat requires strings, but T is known to be one already
farmbuyer@89 3400 -- can't use #msg since there might be nil holes
farmbuyer@73 3401 for i = 2, n+1 do
farmbuyer@71 3402 msg[i] = tostring(msg[i] or "")
farmbuyer@56 3403 end
farmbuyer@56 3404 return tconcat (msg, '\a')
farmbuyer@56 3405 end
farmbuyer@56 3406 return t
farmbuyer@56 3407 end
farmbuyer@56 3408
farmbuyer@56 3409 -- broadcast('tag', <stuff>)
farmbuyer@56 3410 -- vbroadcast('tag', <stuff>)
farmbuyer@56 3411 function addon:vbroadcast(tag,...)
farmbuyer@56 3412 return self:broadcast(self.commrev..tag,...)
farmbuyer@56 3413 end
farmbuyer@56 3414 function addon:broadcast(tag,...)
farmbuyer@56 3415 local msg = assemble(tag,...)
farmbuyer@56 3416 self.dprint('comm', "<broadcast>:", msg)
farmbuyer@89 3417 self:SendCommMessage(self.ident, msg, "RAID")
farmbuyer@89 3418 -- this is what lets us debug our own message traffic:
farmbuyer@89 3419 if self.debug.comm then
farmbuyer@89 3420 self:SendCommMessage(self.ident, msg, "GUILD")
farmbuyer@89 3421 end
farmbuyer@56 3422 end
farmbuyer@56 3423 -- whispercast(<to>, 'tag', <stuff>)
farmbuyer@56 3424 function addon:whispercast(to,...)
farmbuyer@56 3425 local msg = assemble(...)
farmbuyer@56 3426 self.dprint('comm', "<whispercast>@", to, ":", msg)
farmbuyer@56 3427 self:SendCommMessage(self.identTg, msg, "WHISPER", to)
farmbuyer@56 3428 end
farmbuyer@56 3429
farmbuyer@1 3430 local function adduser (name, status, active)
farmbuyer@1 3431 if status then addon.sender_list.names[name] = status end
farmbuyer@1 3432 if active then addon.sender_list.active[name] = active end
farmbuyer@1 3433 end
farmbuyer@1 3434
farmbuyer@1 3435 -- Incoming handler functions. All take the sender name and the incoming
farmbuyer@1 3436 -- tag as the first two arguments. All of these are active even when the
farmbuyer@1 3437 -- player is not tracking loot, so test for that when appropriate.
farmbuyer@1 3438 local OCR_funcs = {}
farmbuyer@1 3439
farmbuyer@1 3440 OCR_funcs.ping = function (sender)
farmbuyer@1 3441 pprint('comm', "incoming ping from", sender)
farmbuyer@89 3442 local what = addon.enabled and "tracking" or
farmbuyer@89 3443 (addon.rebroadcast and "broadcasting" or "disabled")
farmbuyer@89 3444 addon:whispercast (sender, 'pong', addon.version, what, addon.revision)
farmbuyer@1 3445 end
farmbuyer@89 3446 OCR_funcs.pong = function (sender, _, ver, status, opt_rev)
farmbuyer@89 3447 local s = ("|cff00ff00%s|r %s(r%s) is |cff00ffff%s|r"):
farmbuyer@89 3448 format (sender, ver, opt_rev or "?", status)
farmbuyer@1 3449 addon:Print("Echo: ", s)
farmbuyer@1 3450 adduser (sender, s, status=="tracking" or status=="broadcasting" or nil)
farmbuyer@1 3451 end
farmbuyer@27 3452 OCR_funcs.revcheck = function (sender, _, revlarge)
farmbuyer@27 3453 addon.dprint('comm', "revcheck, sender", sender)
farmbuyer@89 3454 addon:_check_version (revlarge)
farmbuyer@27 3455 end
farmbuyer@1 3456
farmbuyer@76 3457 OCR_funcs['17improv'] = function (sender, _, senderid, existing, replace)
farmbuyer@76 3458 addon.dprint('comm', "DOTimprov/17, sender", sender, "id", senderid,
farmbuyer@76 3459 "existing", existing, "replace", replace)
farmbuyer@76 3460 if not g_unique_replace then _setup_unique_replace() end
farmbuyer@76 3461 g_unique_replace.new_entry (senderid, existing, replace)
farmbuyer@76 3462 end
farmbuyer@76 3463
farmbuyer@73 3464 OCR_funcs['17mark'] = function (sender, _, unique, item, old, new)
farmbuyer@73 3465 addon.dprint('comm', "DOTmark/17, sender", sender, "unique", unique,
farmbuyer@73 3466 "item", item, "from old", old, "to new", new)
farmbuyer@73 3467 local index = addon:loot_mark_disposition ("remote", sender, unique, item, old, new)
farmbuyer@73 3468 --if not addon.enabled then return end -- hmm
farmbuyer@73 3469 if index and opts.chatty_on_remote_changes then
farmbuyer@81 3470 _notify_about_remote (sender, index, old)
farmbuyer@73 3471 end
farmbuyer@73 3472 end
farmbuyer@73 3473
farmbuyer@81 3474 OCR_funcs['17reassign'] = function (sender, _, unique, item, from, to)
farmbuyer@81 3475 addon.dprint('comm', "DOTreassign/17, sender", sender, "unique", unique,
farmbuyer@81 3476 "item", item, "from", from, "to", to)
farmbuyer@81 3477 --[[local index =]] addon:reassign_loot ("remote", sender, unique, item, from, to)
farmbuyer@81 3478 -- Notification handled inside reassign_loot.
farmbuyer@81 3479 end
farmbuyer@81 3480
farmbuyer@71 3481 OCR_funcs['16loot'] = function (sender, _, recip, item, count, extratext)
farmbuyer@71 3482 addon.dprint('comm', "DOTloot/16, sender", sender, "recip", recip, "item", item, "count", count)
farmbuyer@1 3483 if not addon.enabled then return end
farmbuyer@1 3484 adduser (sender, nil, true)
farmbuyer@73 3485 -- Empty unique string will pass through all of the loot handling,
farmbuyer@73 3486 -- and then be rewritten by the history routine (into older string
farmbuyer@73 3487 -- of ID+date).
farmbuyer@73 3488 g_seeing_oldsigs = g_seeing_oldsigs or {}
farmbuyer@73 3489 g_seeing_oldsigs[sender] = true
farmbuyer@71 3490 addon:CHAT_MSG_LOOT ("broadcast", recip, --[[unique=]]"", item, count, sender, extratext)
farmbuyer@1 3491 end
farmbuyer@71 3492 OCR_funcs.loot = OCR_funcs['16loot'] -- old unversioned stuff goes to 16
farmbuyer@71 3493 OCR_funcs['17loot'] = function (sender, _, recip, unique, item, count, extratext)
farmbuyer@73 3494 addon.dprint('comm', "DOTloot/17, sender", sender, "recip", recip,
farmbuyer@73 3495 "unique", unique, "item", item, "count", count, "extratext", extratext)
farmbuyer@71 3496 if not addon.enabled then return end
farmbuyer@71 3497 adduser (sender, nil, true)
farmbuyer@71 3498 addon:CHAT_MSG_LOOT ("broadcast", recip, unique, item, count, sender, extratext)
farmbuyer@71 3499 end
farmbuyer@1 3500
farmbuyer@1 3501 OCR_funcs.boss = function (sender, _, reason, bossname, instancetag)
farmbuyer@56 3502 addon.dprint('comm', "DOTboss, sender", sender, "reason", reason,
farmbuyer@56 3503 "name", bossname, "it", instancetag)
farmbuyer@1 3504 if not addon.enabled then return end
farmbuyer@1 3505 adduser (sender, nil, true)
farmbuyer@56 3506 addon:on_boss_broadcast (reason, bossname, instancetag, --[[maxsize=]]0)
farmbuyer@56 3507 end
farmbuyer@56 3508 OCR_funcs['16boss'] = function (sender, _, reason, bossname, instancetag, maxsize)
farmbuyer@73 3509 addon.dprint('comm', "DOTboss/16,17, sender", sender, "reason", reason,
farmbuyer@56 3510 "name", bossname, "it", instancetag, "size", maxsize)
farmbuyer@56 3511 if not addon.enabled then return end
farmbuyer@56 3512 adduser (sender, nil, true)
farmbuyer@56 3513 addon:on_boss_broadcast (reason, bossname, instancetag, maxsize)
farmbuyer@1 3514 end
farmbuyer@71 3515 OCR_funcs['17boss'] = OCR_funcs['16boss']
farmbuyer@1 3516
farmbuyer@76 3517 local bcast_on = addon.format_hypertext ([[the red pill]], '|cffff4040',
farmbuyer@78 3518 function (self)
farmbuyer@78 3519 if not self.rebroadcast then
farmbuyer@78 3520 self:Activate(nil,true)
farmbuyer@76 3521 end
farmbuyer@78 3522 self:broadcast('bcast_responder')
farmbuyer@76 3523 end)
farmbuyer@76 3524 local waferthin = addon.format_hypertext ([[the blue pill]], '|cff0070dd',
farmbuyer@78 3525 function (self)
farmbuyer@76 3526 g_wafer_thin = true -- mint? it's wafer thin!
farmbuyer@78 3527 self:broadcast('bcast_denied') -- fuck off, I'm full
farmbuyer@76 3528 end)
farmbuyer@1 3529 OCR_funcs.bcast_req = function (sender)
farmbuyer@1 3530 if addon.debug.comm or ((not g_wafer_thin) and (not addon.rebroadcast))
farmbuyer@1 3531 then
farmbuyer@95 3532 addon:Print("%s has requested additional broadcasters! Click %s to enable rebroadcasting, or %s to remain off and also ignore rebroadcast requests for as long as you're logged in.",
farmbuyer@1 3533 sender,
farmbuyer@76 3534 tostring(bcast_on),
farmbuyer@76 3535 tostring(waferthin))
farmbuyer@1 3536 end
farmbuyer@18 3537 addon.popped = true
farmbuyer@1 3538 end
farmbuyer@1 3539
farmbuyer@1 3540 OCR_funcs.bcast_responder = function (sender)
farmbuyer@1 3541 if addon.debug.comm or addon.requesting or
farmbuyer@1 3542 ((not g_wafer_thin) and (not addon.rebroadcast))
farmbuyer@1 3543 then
farmbuyer@1 3544 addon:Print(sender, "has answered the call and is now broadcasting loot.")
farmbuyer@1 3545 end
farmbuyer@1 3546 end
farmbuyer@1 3547 -- remove this tag once it's all tested
farmbuyer@1 3548 OCR_funcs.bcast_denied = function (sender)
farmbuyer@1 3549 if addon.requesting then addon:Print(sender, "declines futher broadcast requests.") end
farmbuyer@1 3550 end
farmbuyer@1 3551
farmbuyer@73 3552 -- Incoming message disassembler and dispatcher. The static weak table
farmbuyer@73 3553 -- is not my favorite approach to handling ellipses, but it lets me loop
farmbuyer@73 3554 -- through potential nils easily without creating a ton of garbage.
farmbuyer@73 3555 local OCR_data = setmetatable({}, {__mode='v'})
farmbuyer@1 3556 local function dotdotdot (sender, tag, ...)
farmbuyer@1 3557 local f = OCR_funcs[tag]
farmbuyer@73 3558 if f then
farmbuyer@73 3559 --wipe(OCR_data) costs more than its worth here
farmbuyer@73 3560 local n = select('#',...)
farmbuyer@73 3561 for i = 1, n do
farmbuyer@73 3562 local d = select(i,...)
farmbuyer@73 3563 OCR_data[i] = (d ~= "") and d or nil
farmbuyer@73 3564 end
farmbuyer@89 3565 addon.dprint('comm', ":...processing", tag, "from", sender, "with arg count", n)
farmbuyer@73 3566 return f(sender,tag,unpack(OCR_data,1,n))
farmbuyer@73 3567 end
farmbuyer@73 3568 addon.dprint('comm', "unknown comm message", tag, "from", sender)
farmbuyer@1 3569 end
farmbuyer@73 3570 -- Recent message cache (this can be accessed via advanced options panel)
farmbuyer@1 3571 addon.recent_messages = create_new_cache ('comm', comm_cleanup_ttl)
farmbuyer@1 3572
farmbuyer@1 3573 function addon:OnCommReceived (prefix, msg, distribution, sender)
farmbuyer@1 3574 if prefix ~= self.ident then return end
farmbuyer@1 3575 if not self.debug.comm then
farmbuyer@1 3576 if distribution ~= "RAID" and distribution ~= "WHISPER" then return end
farmbuyer@1 3577 if sender == my_name then return end
farmbuyer@1 3578 end
farmbuyer@1 3579 self.dprint('comm', ":OCR from", sender, "message is", msg)
farmbuyer@1 3580
farmbuyer@1 3581 if self.recent_messages:test(msg) then
farmbuyer@40 3582 self.dprint('cache', "OCR message <",msg,"> already in cache, skipping")
farmbuyer@40 3583 return
farmbuyer@1 3584 end
farmbuyer@1 3585 self.recent_messages:add(msg)
farmbuyer@1 3586
farmbuyer@1 3587 -- Nothing is actually returned, just (ab)using tail calls.
farmbuyer@1 3588 return dotdotdot(sender,strsplit('\a',msg))
farmbuyer@1 3589 end
farmbuyer@1 3590
farmbuyer@1 3591 function addon:OnCommReceivedNocache (prefix, msg, distribution, sender)
farmbuyer@1 3592 if prefix ~= self.identTg then return end
farmbuyer@1 3593 if not self.debug.comm then
farmbuyer@1 3594 if distribution ~= "WHISPER" then return end
farmbuyer@1 3595 if sender == my_name then return end
farmbuyer@1 3596 end
farmbuyer@1 3597 self.dprint('comm', ":OCRN from", sender, "message is", msg)
farmbuyer@1 3598 return dotdotdot(sender,strsplit('\a',msg))
farmbuyer@1 3599 end
farmbuyer@1 3600 end
farmbuyer@1 3601
farmbuyer@1 3602 -- vim:noet