annotate core.lua @ 147:e1a90e398231

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