annotate core.lua @ 115:289c7667adab

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