annotate core.lua @ 151:42dd3076baaf

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