annotate core.lua @ 146:543fcf15add7

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