annotate core.lua @ 51:857aea8ae33d

Also detect LFR happening and load then, before they zone in and potentially get into combat. Detect combat and avoid secure button creation in that case anyhow.
author Farmbuyer of US-Kilrogg <farmbuyer@gmail.com>
date Wed, 01 Feb 2012 02:34:10 +0000
parents 973d7396e0bf
children 7af58a7dce7d
rev   line source
farmbuyer@17 1 local nametag, addon = ...
farmbuyer@1 2
farmbuyer@1 3 --[==[
farmbuyer@1 4 g_loot's numeric indices are loot entries (including titles, separators,
farmbuyer@1 5 etc); its named indices are:
farmbuyer@50 6 - forum saved text from forum markup window, default nil
farmbuyer@50 7 - attend saved text from raid attendence window, default nil
farmbuyer@50 8 - printed.FOO last loot index formatted into text window FOO, default 0
farmbuyer@50 9
farmbuyer@50 10 Common g_loot indices:
farmbuyer@50 11 - kind time/boss/loot
farmbuyer@50 12 - hour 0-23, on the *physical instance server*, not the realm server
farmbuyer@50 13 - minute 0-59, ditto
farmbuyer@50 14 - stamp date+timestamp, on the local computer
farmbuyer@50 15 - cols graphical display data; cleared when logging out
farmbuyer@50 16
farmbuyer@50 17 Time specific g_loot indices:
farmbuyer@50 18 - startday table with month/day/year/text fields from makedate()
farmbuyer@50 19
farmbuyer@50 20 Boss specific g_loot indices:
farmbuyer@50 21 - bosskill name of boss/encounter; not necessarily a kill;
farmbuyer@50 22 - may be changed if "snarky boss names" option is enabled
farmbuyer@50 23 - reason wipe/kill ("pull" does not generate an entry)
farmbuyer@50 24 - instance name of instance, including size and difficulty
farmbuyer@50 25 - duration in seconds; may be missing
farmbuyer@50 26 - raiderlist "Able, Baker, Charlie"; may be missing
farmbuyer@50 27
farmbuyer@50 28 Loot specific g_loot indices:
farmbuyer@50 29 - person recipient
farmbuyer@50 30 - person_class class of recipient if available; may be missing;
farmbuyer@50 31 - will be classID-style (e.g., DEATHKNIGHT)
farmbuyer@50 32 - itemname not including square brackets
farmbuyer@50 33 - id itemID as number
farmbuyer@50 34 - itemlink full clickable link
farmbuyer@50 35 - itexture icon path (e.g., Interface\Icons\INV_Misc_Rune_01)
farmbuyer@50 36 - quality ITEM_QUALITY_* number
farmbuyer@50 37 - disposition offspec/gvault/shard; missing otherwise; can be set from extratext
farmbuyer@50 38 - count e.g., "x3"; missing otherwise; can be set/removed from extratext;
farmbuyer@50 39 - triggers only for a stack of items, not "the boss dropped double axes today"
farmbuyer@50 40 - is_heroic true if item is heroic; missing otherwise
farmbuyer@50 41 - cache_miss if GetItemInfo failed; SHOULD be missing (changes other fields)
farmbuyer@50 42 - bcast_from if rebroadcast from another player; missing otherwise
farmbuyer@50 43 - extratext text in Note column, including disposition and rebroadcasting
farmbuyer@50 44 - extratext_byhand true if text was edited by player directly; missing otherwise
farmbuyer@50 45
farmbuyer@1 46
farmbuyer@1 47 Functions arranged like this, with these lables (for jumping to). As a
farmbuyer@1 48 rule, member functions with UpperCamelCase names are called directly by
farmbuyer@1 49 user-facing code, ones with lowercase names are "one step removed", and
farmbuyer@1 50 names with leading underscores are strictly internal helper functions.
farmbuyer@1 51 ------ Saved variables
farmbuyer@1 52 ------ Constants
farmbuyer@1 53 ------ Addon member data
farmbuyer@6 54 ------ Globals
farmbuyer@1 55 ------ Expiring caches
farmbuyer@1 56 ------ Ace3 framework stuff
farmbuyer@1 57 ------ Event handlers
farmbuyer@1 58 ------ Slash command handler
farmbuyer@1 59 ------ On/off
farmbuyer@1 60 ------ Behind the scenes routines
farmbuyer@1 61 ------ Saved texts
farmbuyer@1 62 ------ Loot histories
farmbuyer@1 63 ------ Player communication
farmbuyer@1 64
farmbuyer@1 65 This started off as part of a raid addon package written by somebody else.
farmbuyer@1 66 After he retired, I began modifying the code. Eventually I set aside the
farmbuyer@1 67 entire package and rewrote the loot tracker module from scratch. Many of the
farmbuyer@1 68 variable/function naming conventions (sv_*, g_*, and family) stayed across the
farmbuyer@16 69 rewrite.
farmbuyer@16 70
farmbuyer@16 71 Some variables are needlessly initialized to nil just to look uniform.
farmbuyer@1 72
farmbuyer@1 73 ]==]
farmbuyer@1 74
farmbuyer@1 75 ------ Saved variables
farmbuyer@16 76 OuroLootSV = nil -- possible copy of g_loot
farmbuyer@16 77 OuroLootSV_saved = nil -- table of copies of saved texts, default nil; keys
farmbuyer@16 78 -- are numeric indices of tables, subkeys of those
farmbuyer@16 79 -- are name/forum/attend/date
farmbuyer@16 80 OuroLootSV_opts = nil -- same as option_defaults until changed
farmbuyer@16 81 -- autoshard: optional name of disenchanting player, default nil
farmbuyer@16 82 -- threshold: optional loot threshold, default nil
farmbuyer@16 83 OuroLootSV_hist = nil
farmbuyer@19 84 OuroLootSV_log = {}
farmbuyer@1 85
farmbuyer@1 86
farmbuyer@1 87 ------ Constants
farmbuyer@1 88 local option_defaults = {
farmbuyer@1 89 ['popup_on_join'] = true,
farmbuyer@1 90 ['register_slashloot'] = true,
farmbuyer@1 91 ['scroll_to_bottom'] = true,
farmbuyer@1 92 ['chatty_on_kill'] = false,
farmbuyer@1 93 ['no_tracking_wipes'] = false,
farmbuyer@1 94 ['snarky_boss'] = true,
farmbuyer@1 95 ['keybinding'] = false,
farmbuyer@2 96 ['bossmod'] = "DBM",
farmbuyer@1 97 ['keybinding_text'] = 'CTRL-SHIFT-O',
farmbuyer@1 98 ['forum'] = {
farmbuyer@25 99 ['[url] Wowhead'] = '[url=http://www.wowhead.com/?item=$I]$N[/url]$X - $T',
farmbuyer@25 100 ['[url] MMO/Wowstead'] = '[http://db.mmo-champion.com/i/$I]$X - $T',
farmbuyer@1 101 ['[item] by name'] = '[item]$N[/item]$X - $T',
farmbuyer@1 102 ['[item] by ID'] = '[item]$I[/item]$X - $T',
farmbuyer@1 103 ['Custom...'] = '',
farmbuyer@1 104 },
farmbuyer@1 105 ['forum_current'] = '[item] by name',
farmbuyer@1 106 }
farmbuyer@1 107 local virgin = "First time loaded? Hi! Use the /ouroloot or /loot command"
farmbuyer@1 108 .." to show the main display. You should probably browse the instructions"
farmbuyer@1 109 .." if you've never used this before; %s to display the help window. This"
farmbuyer@1 110 .." welcome message will not intrude again."
farmbuyer@27 111 local newer_warning = "A newer version has been released. You can %s to display"
farmbuyer@27 112 .." a download URL for copy-and-pasting. You can %s to ping other raiders"
farmbuyer@27 113 .." for their installed versions (same as '/ouroloot ping' or clicking the"
farmbuyer@27 114 .." 'Ping!' button on the options panel)."
farmbuyer@1 115 local qualnames = {
farmbuyer@1 116 ['gray'] = 0, ['grey'] = 0, ['poor'] = 0, ['trash'] = 0,
farmbuyer@1 117 ['white'] = 1, ['common'] = 1,
farmbuyer@1 118 ['green'] = 2, ['uncommon'] = 2,
farmbuyer@1 119 ['blue'] = 3, ['rare'] = 3,
farmbuyer@1 120 ['epic'] = 4, ['purple'] = 4,
farmbuyer@1 121 ['legendary'] = 5, ['orange'] = 5,
farmbuyer@1 122 ['artifact'] = 6,
farmbuyer@1 123 --['heirloom'] = 7,
farmbuyer@1 124 }
farmbuyer@1 125 local my_name = UnitName('player')
farmbuyer@40 126 local comm_cleanup_ttl = 4 -- seconds in the cache
farmbuyer@27 127 local revision_large = nil -- defaults to 1, possibly changed by revision
farmbuyer@20 128 local g_LOOT_ITEM_ss, g_LOOT_ITEM_MULTIPLE_sss, g_LOOT_ITEM_SELF_s, g_LOOT_ITEM_SELF_MULTIPLE_ss
farmbuyer@1 129
farmbuyer@1 130
farmbuyer@1 131 ------ Addon member data
farmbuyer@1 132 local flib = LibStub("LibFarmbuyer")
farmbuyer@1 133 addon.author_debug = flib.author_debug
farmbuyer@1 134
farmbuyer@6 135 -- Play cute games with namespaces here just to save typing. WTB Lua 5.2 PST.
farmbuyer@1 136 do local _G = _G setfenv (1, addon)
farmbuyer@1 137
farmbuyer@17 138 commrev = 15 -- number
farmbuyer@27 139 revision = _G.GetAddOnMetadata(nametag,"Version") or "?" -- "x.yy.z", etc
farmbuyer@1 140 ident = "OuroLoot2"
farmbuyer@1 141 identTg = "OuroLoot2Tg"
farmbuyer@1 142 status_text = nil
farmbuyer@1 143
farmbuyer@45 144 tekdebug = nil
farmbuyer@45 145 if _G.tekDebug then
farmbuyer@45 146 local tdframe = _G.tekDebug:GetFrame("Ouro Loot")
farmbuyer@45 147 function tekdebug (txt)
farmbuyer@45 148 -- tekDebug notices "<name passed to getframe>|r:"
farmbuyer@45 149 tdframe:AddMessage('|cff17ff0dOuro Loot|r:'..txt,1,1,1)
farmbuyer@45 150 end
farmbuyer@45 151 end
farmbuyer@45 152
farmbuyer@1 153 DEBUG_PRINT = false
farmbuyer@1 154 debug = {
farmbuyer@1 155 comm = false,
farmbuyer@1 156 loot = false,
farmbuyer@1 157 flow = false,
farmbuyer@1 158 notraid = false,
farmbuyer@1 159 cache = false,
farmbuyer@19 160 alsolog = false,
farmbuyer@1 161 }
farmbuyer@45 162 -- This looks ugly, but it factors out the load-time decisions from
farmbuyer@45 163 -- the run-time ones.
farmbuyer@45 164 if tekdebug then
farmbuyer@45 165 function dprint (t,...)
farmbuyer@45 166 if DEBUG_PRINT and debug[t] then
farmbuyer@45 167 local text = flib.safefprint(tekdebug,"<"..t.."> ",...)
farmbuyer@45 168 if debug.alsolog then
farmbuyer@45 169 addon:log_with_timestamp(text)
farmbuyer@45 170 end
farmbuyer@45 171 end
farmbuyer@45 172 end
farmbuyer@45 173 else
farmbuyer@45 174 function dprint (t,...)
farmbuyer@45 175 if DEBUG_PRINT and debug[t] then
farmbuyer@45 176 local text = flib.safeprint("<"..t.."> ",...)
farmbuyer@45 177 if debug.alsolog then
farmbuyer@45 178 addon:log_with_timestamp(text)
farmbuyer@45 179 end
farmbuyer@19 180 end
farmbuyer@19 181 end
farmbuyer@1 182 end
farmbuyer@1 183
farmbuyer@45 184 if author_debug and tekdebug then
farmbuyer@45 185 function pprint (t,...)
farmbuyer@45 186 local text = flib.safefprint(tekdebug,"<<"..t..">> ",...)
farmbuyer@19 187 if debug.alsolog then
farmbuyer@19 188 addon:log_with_timestamp(text)
farmbuyer@19 189 end
farmbuyer@1 190 end
farmbuyer@1 191 else
farmbuyer@1 192 pprint = flib.nullfunc
farmbuyer@1 193 end
farmbuyer@1 194
farmbuyer@1 195 enabled = false
farmbuyer@1 196 rebroadcast = false
farmbuyer@1 197 display = nil -- display frame, when visible
farmbuyer@1 198 loot_clean = nil -- index of last GUI entry with known-current visual data
farmbuyer@1 199 sender_list = {active={},names={}} -- this should be reworked
farmbuyer@1 200 threshold = debug.loot and 0 or 3 -- rare by default
farmbuyer@1 201 sharder = nil -- name of person whose loot is marked as shards
farmbuyer@1 202
farmbuyer@1 203 -- The rest is also used in the GUI:
farmbuyer@1 204
farmbuyer@1 205 popped = nil -- non-nil when reminder has been shown, actual value unimportant
farmbuyer@1 206
farmbuyer@2 207 bossmod_registered = nil
farmbuyer@2 208 bossmods = {}
farmbuyer@2 209
farmbuyer@1 210 requesting = nil -- for prompting for additional rebroadcasters
farmbuyer@1 211
farmbuyer@38 212 -- don't use NUM_ITEM_QUALITIES as the upper bound unless we expect heirlooms to show up
farmbuyer@11 213 thresholds = {}
farmbuyer@1 214 for i = 0,6 do
farmbuyer@11 215 thresholds[i] = _G.ITEM_QUALITY_COLORS[i].hex .. _G["ITEM_QUALITY"..i.."_DESC"] .. "|r"
farmbuyer@1 216 end
farmbuyer@1 217
farmbuyer@1 218 _G.setfenv (1, _G)
farmbuyer@1 219 end
farmbuyer@1 220
farmbuyer@1 221 addon = LibStub("AceAddon-3.0"):NewAddon(addon, "Ouro Loot",
farmbuyer@1 222 "AceTimer-3.0", "AceComm-3.0", "AceConsole-3.0", "AceEvent-3.0")
farmbuyer@1 223
farmbuyer@1 224
farmbuyer@6 225 ------ Globals
farmbuyer@1 226 local g_loot = nil
farmbuyer@1 227 local g_restore_p = nil
farmbuyer@1 228 local g_wafer_thin = nil -- for prompting for additional rebroadcasters
farmbuyer@1 229 local g_today = nil -- "today" entry in g_loot
farmbuyer@16 230 local g_boss_signpost = nil
farmbuyer@1 231 local opts = nil
farmbuyer@1 232
farmbuyer@47 233 local pairs, ipairs, tinsert, tremove, tonumber, wipe =
farmbuyer@47 234 pairs, ipairs, table.insert, table.remove, tonumber, table.wipe
farmbuyer@1 235 local pprint, tabledump = addon.pprint, flib.tabledump
farmbuyer@10 236 local GetNumRaidMembers = GetNumRaidMembers
farmbuyer@1 237 -- En masse forward decls of symbols defined inside local blocks
farmbuyer@41 238 local _register_bossmod, makedate, create_new_cache, _init, _log
farmbuyer@1 239
farmbuyer@27 240 -- Try to extract numbers from the .toc "Version" and munge them into an
farmbuyer@27 241 -- integral form for comparison. The result doesn't need to be meaningful as
farmbuyer@38 242 -- long as we can reliably feed two of them to "<" and get useful answers.
farmbuyer@29 243 --
farmbuyer@29 244 -- This makes/reinforces an assumption that revision_large of release packages
farmbuyer@29 245 -- (e.g., 2016001) will always be higher than those of development packages
farmbuyer@29 246 -- (e.g., 87), due to the tagging system versus subversion file revs. This
farmbuyer@29 247 -- is good, as local dev code will never trigger a false positive update
farmbuyer@38 248 -- warning for other users. The downside is that additional decimal places
farmbuyer@38 249 -- in the Version field for bugfixes (e.g., "2.16.4.1") imposes a high-water
farmbuyer@38 250 -- mark, as subsequent shorter strings ("2.16.5", "2.17") will never be larger.
farmbuyer@27 251 do
farmbuyer@27 252 local r = 0
farmbuyer@27 253 for d in addon.revision:gmatch("%d+") do
farmbuyer@27 254 r = 1000*r + d
farmbuyer@27 255 end
farmbuyer@27 256 revision_large = math.max(r,1)
farmbuyer@27 257 end
farmbuyer@27 258
farmbuyer@1 259 -- Hypertext support, inspired by DBM broadcast pizza timers
farmbuyer@1 260 do
farmbuyer@1 261 local hypertext_format_str = "|HOuroRaid:%s|h%s[%s]|r|h"
farmbuyer@1 262
farmbuyer@38 263 -- TEXT will automatically be surrounded by brackets
farmbuyer@38 264 -- COLOR can be item quality code or a hex string
farmbuyer@1 265 function addon.format_hypertext (code, text, color)
farmbuyer@1 266 return hypertext_format_str:format (code,
farmbuyer@11 267 type(color)=='number' and ITEM_QUALITY_COLORS[color].hex or color,
farmbuyer@1 268 text)
farmbuyer@1 269 end
farmbuyer@1 270
farmbuyer@1 271 DEFAULT_CHAT_FRAME:HookScript("OnHyperlinkClick", function(self, link, string, mousebutton)
farmbuyer@1 272 local ltype, arg = strsplit(":",link)
farmbuyer@1 273 if ltype ~= "OuroRaid" then return end
farmbuyer@51 274 -- XXX this is crap, redo this as a dispatch table with code at the call site
farmbuyer@1 275 if arg == 'openloot' then
farmbuyer@1 276 addon:BuildMainDisplay()
farmbuyer@27 277 elseif arg == 'popupurl' then
farmbuyer@30 278 -- Sadly, this is not generated by the packager, so hardcode it for now.
farmbuyer@30 279 -- The 'data' field is handled differently for onshow than for other callbacks.
farmbuyer@30 280 StaticPopup_Show("OUROL_URL", --[[text_arg1=]]nil, --[[text_arg2=]]nil,
farmbuyer@30 281 --[[data=]][[http://www.curse.com/addons/wow/ouroloot]])
farmbuyer@27 282 elseif arg == 'doping' then
farmbuyer@27 283 addon:DoPing()
farmbuyer@1 284 elseif arg == 'help' then
farmbuyer@1 285 addon:BuildMainDisplay('help')
farmbuyer@1 286 elseif arg == 'bcaston' then
farmbuyer@1 287 if not addon.rebroadcast then
farmbuyer@1 288 addon:Activate(nil,true)
farmbuyer@1 289 end
farmbuyer@1 290 addon:broadcast('bcast_responder')
farmbuyer@1 291 elseif arg == 'waferthin' then -- mint? it's wafer thin!
farmbuyer@1 292 g_wafer_thin = true -- fuck off, I'm full
farmbuyer@1 293 addon:broadcast('bcast_denied') -- remove once tested
farmbuyer@51 294 elseif arg == 'reload' then
farmbuyer@51 295 addon:BuildMainDisplay('opt')
farmbuyer@1 296 end
farmbuyer@1 297 end)
farmbuyer@1 298
farmbuyer@1 299 local old = ItemRefTooltip.SetHyperlink
farmbuyer@1 300 function ItemRefTooltip:SetHyperlink (link, ...)
farmbuyer@1 301 if link:match("^OuroRaid") then return end
farmbuyer@1 302 return old (self, link, ...)
farmbuyer@1 303 end
farmbuyer@1 304 end
farmbuyer@1 305
farmbuyer@1 306 do
farmbuyer@1 307 -- copied here because it's declared local to the calendar ui, thanks blizz ><
farmbuyer@1 308 local CALENDAR_FULLDATE_MONTH_NAMES = {
farmbuyer@1 309 FULLDATE_MONTH_JANUARY, FULLDATE_MONTH_FEBRUARY, FULLDATE_MONTH_MARCH,
farmbuyer@1 310 FULLDATE_MONTH_APRIL, FULLDATE_MONTH_MAY, FULLDATE_MONTH_JUNE,
farmbuyer@1 311 FULLDATE_MONTH_JULY, FULLDATE_MONTH_AUGUST, FULLDATE_MONTH_SEPTEMBER,
farmbuyer@1 312 FULLDATE_MONTH_OCTOBER, FULLDATE_MONTH_NOVEMBER, FULLDATE_MONTH_DECEMBER,
farmbuyer@1 313 }
farmbuyer@1 314 -- returns "dd Month yyyy", mm, dd, yyyy
farmbuyer@1 315 function makedate()
farmbuyer@1 316 Calendar_LoadUI()
farmbuyer@1 317 local _, M, D, Y = CalendarGetDate()
farmbuyer@1 318 local text = ("%d %s %d"):format(D, CALENDAR_FULLDATE_MONTH_NAMES[M], Y)
farmbuyer@1 319 return text, M, D, Y
farmbuyer@1 320 end
farmbuyer@1 321 end
farmbuyer@1 322
farmbuyer@1 323 -- Returns an instance name or abbreviation
farmbuyer@1 324 local function instance_tag()
farmbuyer@1 325 local name, typeof, diffcode, diffstr, _, perbossheroic, isdynamic = GetInstanceInfo()
farmbuyer@1 326 local t
farmbuyer@1 327 name = addon.instance_abbrev[name] or name
farmbuyer@1 328 if typeof == "none" then return name end
farmbuyer@1 329 -- diffstr is "5 Player", "10 Player (Heroic)", etc. ugh.
farmbuyer@35 330 if (GetLFGMode()) and (GetLFGModeType() == 'raid') then
farmbuyer@35 331 t = 'LFR'
farmbuyer@35 332 elseif diffcode == 1 then
farmbuyer@1 333 t = ((GetNumRaidMembers()>0) and "10" or "5")
farmbuyer@1 334 elseif diffcode == 2 then
farmbuyer@1 335 t = ((GetNumRaidMembers()>0) and "25" or "5h")
farmbuyer@1 336 elseif diffcode == 3 then
farmbuyer@1 337 t = "10h"
farmbuyer@1 338 elseif diffcode == 4 then
farmbuyer@1 339 t = "25h"
farmbuyer@1 340 end
farmbuyer@1 341 -- dynamic difficulties always return normal "codes"
farmbuyer@1 342 if isdynamic and perbossheroic == 1 then
farmbuyer@1 343 t = t .. "h"
farmbuyer@1 344 end
farmbuyer@1 345 return name .. "(" .. t .. ")"
farmbuyer@1 346 end
farmbuyer@1 347 addon.instance_tag = instance_tag -- grumble
farmbuyer@42 348 addon.latest_instance = nil -- spelling reminder, assigned elsewhere
farmbuyer@1 349
farmbuyer@1 350
farmbuyer@1 351 ------ Expiring caches
farmbuyer@1 352 --[[
farmbuyer@1 353 foo = create_new_cache("myfoo",15[,cleanup]) -- ttl
farmbuyer@1 354 foo:add("blah")
farmbuyer@1 355 foo:test("blah") -- returns true
farmbuyer@1 356 ]]
farmbuyer@1 357 do
farmbuyer@1 358 local caches = {}
farmbuyer@25 359 local cleanup_group = _G.AnimTimerFrame:CreateAnimationGroup()
farmbuyer@10 360 local time = _G.time
farmbuyer@1 361 cleanup_group:SetLooping("REPEAT")
farmbuyer@1 362 cleanup_group:SetScript("OnLoop", function(cg)
farmbuyer@1 363 addon.dprint('cache',"OnLoop firing")
farmbuyer@10 364 local now = time()
farmbuyer@1 365 local alldone = true
farmbuyer@1 366 -- this is ass-ugly
farmbuyer@25 367 for name,c in pairs(caches) do
farmbuyer@25 368 local fifo = c.fifo
farmbuyer@25 369 local active = #fifo > 0
farmbuyer@25 370 while (#fifo > 0) and (now - fifo[1].t > c.ttl) do
farmbuyer@40 371 addon.dprint('cache', name, "cache removing", fifo[1].t, "<", fifo[1].m, ">")
farmbuyer@25 372 tremove(fifo,1)
farmbuyer@1 373 end
farmbuyer@25 374 if active and #fifo == 0 and c.func then
farmbuyer@25 375 addon.dprint('cache', name, "empty, firing cleanup")
farmbuyer@25 376 c:func()
farmbuyer@25 377 end
farmbuyer@25 378 alldone = alldone and (#fifo == 0)
farmbuyer@1 379 end
farmbuyer@1 380 if alldone then
farmbuyer@40 381 addon.dprint('cache',"OnLoop FINISHING animation group")
farmbuyer@1 382 cleanup_group:Finish()
farmbuyer@40 383 else
farmbuyer@40 384 addon.dprint('cache',"OnLoop done, not yet finished")
farmbuyer@1 385 end
farmbuyer@1 386 end)
farmbuyer@1 387
farmbuyer@1 388 local function _add (cache, x)
farmbuyer@25 389 local datum = { t=time(), m=x }
farmbuyer@25 390 cache.hash[x] = datum
farmbuyer@25 391 tinsert (cache.fifo, datum)
farmbuyer@1 392 if not cleanup_group:IsPlaying() then
farmbuyer@41 393 addon.dprint('cache', cache.name, "with entry", datum.t, "<", datum.m, "> STARTING animation group")
farmbuyer@40 394 cache.cleanup:SetDuration(1) -- hmmm
farmbuyer@1 395 cleanup_group:Play()
farmbuyer@1 396 end
farmbuyer@1 397 end
farmbuyer@1 398 local function _test (cache, x)
farmbuyer@40 399 -- FIXME This can return false positives, if called after the onloop
farmbuyer@40 400 -- fifo has been removed but before the GC has removed the weak entry.
farmbuyer@40 401 -- What to do, what to do...
farmbuyer@25 402 return cache.hash[x] ~= nil
farmbuyer@1 403 end
farmbuyer@25 404
farmbuyer@1 405 function create_new_cache (name, ttl, on_alldone)
farmbuyer@25 406 -- setting OnFinished for cleanup fires at the end of each inner loop,
farmbuyer@25 407 -- with no 'requested' argument to distinguish cases. thus, on_alldone.
farmbuyer@1 408 local c = {
farmbuyer@1 409 ttl = ttl,
farmbuyer@1 410 name = name,
farmbuyer@1 411 add = _add,
farmbuyer@1 412 test = _test,
farmbuyer@1 413 cleanup = cleanup_group:CreateAnimation("Animation"),
farmbuyer@1 414 func = on_alldone,
farmbuyer@25 415 fifo = {},
farmbuyer@25 416 hash = setmetatable({}, {__mode='kv'}),
farmbuyer@1 417 }
farmbuyer@1 418 c.cleanup:SetOrder(1)
farmbuyer@25 419 caches[name] = c
farmbuyer@1 420 return c
farmbuyer@1 421 end
farmbuyer@1 422 end
farmbuyer@1 423
farmbuyer@1 424
farmbuyer@1 425 ------ Ace3 framework stuff
farmbuyer@1 426 function addon:OnInitialize()
farmbuyer@41 427 _log = OuroLootSV_log
farmbuyer@41 428
farmbuyer@1 429 -- VARIABLES_LOADED has fired by this point; test if we're doing something like
farmbuyer@1 430 -- relogging during a raid and already have collected loot data
farmbuyer@1 431 g_restore_p = OuroLootSV ~= nil
farmbuyer@1 432 self.dprint('flow', "oninit sets restore as", g_restore_p)
farmbuyer@1 433
farmbuyer@1 434 if OuroLootSV_opts == nil then
farmbuyer@1 435 OuroLootSV_opts = {}
farmbuyer@1 436 self:ScheduleTimer(function(s)
farmbuyer@1 437 s:Print(virgin, s.format_hypertext('help',"click here",ITEM_QUALITY_UNCOMMON))
farmbuyer@1 438 virgin = nil
farmbuyer@1 439 end,10,self)
farmbuyer@1 440 end
farmbuyer@1 441 opts = OuroLootSV_opts
farmbuyer@1 442 for opt,default in pairs(option_defaults) do
farmbuyer@1 443 if opts[opt] == nil then
farmbuyer@1 444 opts[opt] = default
farmbuyer@1 445 end
farmbuyer@1 446 end
farmbuyer@38 447
farmbuyer@25 448 -- transition&remove old options
farmbuyer@25 449 opts['forum_use_itemid'] = nil
farmbuyer@25 450 if opts['forum_format'] then
farmbuyer@25 451 opts.forum['Custom...'] = opts['forum_format']
farmbuyer@25 452 opts['forum_format'] = nil
farmbuyer@25 453 end
farmbuyer@25 454 if opts.forum['[url]'] then
farmbuyer@25 455 opts.forum['[url] Wowhead'] = opts.forum['[url]']
farmbuyer@25 456 opts.forum['[url]'] = nil
farmbuyer@25 457 opts.forum['[url] MMO/Wowstead'] = option_defaults.forum['[url] MMO/Wowstead']
farmbuyer@25 458 if opts['forum_current'] == '[url]' then
farmbuyer@25 459 opts['forum_current'] = '[url] Wowhead'
farmbuyer@25 460 end
farmbuyer@25 461 end
farmbuyer@1 462 option_defaults = nil
farmbuyer@16 463 if OuroLootSV then -- may not be the same as testing g_restore_p soon
farmbuyer@16 464 if OuroLootSV.saved then
farmbuyer@16 465 OuroLootSV_saved = OuroLootSV.saved; OuroLootSV.saved = nil
farmbuyer@16 466 end
farmbuyer@16 467 if OuroLootSV.threshold then
farmbuyer@16 468 opts.threshold = OuroLootSV.threshold; OuroLootSV.threshold = nil
farmbuyer@16 469 end
farmbuyer@16 470 if OuroLootSV.autoshard then
farmbuyer@16 471 opts.autoshard = OuroLootSV.autoshard; OuroLootSV.autoshard = nil
farmbuyer@16 472 end
farmbuyer@16 473 end
farmbuyer@38 474
farmbuyer@1 475 -- get item filter table if needed
farmbuyer@1 476 if opts.itemfilter == nil then
farmbuyer@1 477 opts.itemfilter = addon.default_itemfilter
farmbuyer@1 478 end
farmbuyer@1 479 addon.default_itemfilter = nil
farmbuyer@1 480
farmbuyer@1 481 self:RegisterChatCommand("ouroloot", "OnSlash")
farmbuyer@1 482 if opts.register_slashloot then
farmbuyer@50 483 -- NOTA BENE: do not use /loot in the LoadOn list, ChatTypeInfo gets confused
farmbuyer@50 484 -- maybe try to detect if this command is already in use...
farmbuyer@1 485 SLASH_ACECONSOLE_OUROLOOT2 = "/loot"
farmbuyer@1 486 end
farmbuyer@1 487
farmbuyer@1 488 self.history_all = self.history_all or OuroLootSV_hist or {}
farmbuyer@4 489 local r = assert(GetRealmName())
farmbuyer@1 490 self.history_all[r] = self:_prep_new_history_category (self.history_all[r], r)
farmbuyer@1 491 self.history = self.history_all[r]
farmbuyer@38 492 if (not InCombatLockdown()) and OuroLootSV_hist and
farmbuyer@38 493 (OuroLootSV_hist.HISTFORMAT == nil) -- restored data but it's older
farmbuyer@38 494 then
farmbuyer@38 495 -- Big honkin' loop
farmbuyer@38 496 for rname,realm in pairs(self.history_all) do
farmbuyer@38 497 for pk,player in ipairs(realm) do
farmbuyer@38 498 for lk,loot in ipairs(player) do
farmbuyer@38 499 if loot.count == "" then
farmbuyer@38 500 loot.count = nil
farmbuyer@38 501 end
farmbuyer@38 502 end
farmbuyer@38 503 end
farmbuyer@38 504 end
farmbuyer@38 505 end
farmbuyer@38 506 self.history_all.HISTFORMAT = nil -- don't keep this in live data
farmbuyer@6 507 --OuroLootSV_hist = nil
farmbuyer@1 508
farmbuyer@1 509 _init(self)
farmbuyer@27 510 self.dprint('flow', "version strings:", revision_large, self.status_text)
farmbuyer@1 511 self.OnInitialize = nil
farmbuyer@1 512 end
farmbuyer@1 513
farmbuyer@1 514 function addon:OnEnable()
farmbuyer@10 515 self:RegisterEvent("PLAYER_LOGOUT")
farmbuyer@10 516 self:RegisterEvent("RAID_ROSTER_UPDATE")
farmbuyer@1 517
farmbuyer@1 518 -- Cribbed from Talented. I like the way jerry thinks: the first argument
farmbuyer@1 519 -- can be a format spec for the remainder of the arguments. (The new
farmbuyer@1 520 -- AceConsole:Printf isn't used because we can't specify a prefix without
farmbuyer@1 521 -- jumping through ridonkulous hoops.) The part about overriding :Print
farmbuyer@1 522 -- with a version using prefix hyperlinks is my fault.
farmbuyer@37 523 --
farmbuyer@37 524 -- There is no ITEM_QUALITY_LEGENDARY constant. Sigh.
farmbuyer@1 525 do
farmbuyer@1 526 local AC = LibStub("AceConsole-3.0")
farmbuyer@1 527 local chat_prefix = self.format_hypertext('openloot',"Ouro Loot",--[[legendary]]5)
farmbuyer@1 528 function addon:Print (str, ...)
farmbuyer@1 529 if type(str) == 'string' and str:find("%", nil, --[[plainmatch=]]true) then
farmbuyer@1 530 return AC:Print (chat_prefix, str:format(...))
farmbuyer@1 531 else
farmbuyer@1 532 return AC:Print (chat_prefix, str, ...)
farmbuyer@1 533 end
farmbuyer@1 534 end
farmbuyer@1 535 end
farmbuyer@1 536
farmbuyer@51 537 while opts.keybinding do
farmbuyer@51 538 if InCombatLockdown() then
farmbuyer@51 539 self:Print("Cannot create '%s' as a keybinding while in combat!",
farmbuyer@51 540 opts.keybinding_text)
farmbuyer@51 541 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.", self.format_hypertext('reload',"the options tab",ITEM_QUALITY_UNCOMMON))
farmbuyer@51 542 break
farmbuyer@51 543 end
farmbuyer@51 544
farmbuyer@15 545 KeyBindingFrame_LoadUI()
farmbuyer@1 546 local btn = CreateFrame("Button", "OuroLootBindingOpen", nil, "SecureActionButtonTemplate")
farmbuyer@1 547 btn:SetAttribute("type", "macro")
farmbuyer@1 548 btn:SetAttribute("macrotext", "/ouroloot toggle")
farmbuyer@1 549 if SetBindingClick(opts.keybinding_text, "OuroLootBindingOpen") then
farmbuyer@15 550 -- a simple SaveBindings(GetCurrentBindingSet()) occasionally fails when GCBS
farmbuyer@38 551 -- decides to return neither 1 nor 2 during load, for reasons nobody has ever learned
farmbuyer@15 552 local c = GetCurrentBindingSet()
farmbuyer@15 553 if c == ACCOUNT_BINDINGS or c == CHARACTER_BINDINGS then
farmbuyer@15 554 SaveBindings(c)
farmbuyer@15 555 end
farmbuyer@1 556 else
farmbuyer@51 557 self:Print("Error registering '%s' as a keybinding, check spelling!",
farmbuyer@51 558 opts.keybinding_text)
farmbuyer@1 559 end
farmbuyer@51 560 break
farmbuyer@1 561 end
farmbuyer@1 562
farmbuyer@20 563 --[[
farmbuyer@20 564 The four loot format patterns of interest, changed into relatively tight
farmbuyer@20 565 string match patterns. Done at enable-time rather than load-time against
farmbuyer@20 566 the slim chance that one of the non-US "delocalizers" needs to mess with
farmbuyer@20 567 the global patterns before we transform them.
farmbuyer@20 568
farmbuyer@20 569 The SELF variants can be replaced with LOOT_ITEM_PUSHED_SELF[_MULTIPLE] to
farmbuyer@20 570 trigger on 'receive item' instead, which would detect extracting stuff
farmbuyer@20 571 from mail, or s/PUSHED/CREATED/ for things like healthstones and guild
farmbuyer@20 572 cauldron flasks.
farmbuyer@20 573 ]]
farmbuyer@20 574
farmbuyer@20 575 -- LOOT_ITEM = "%s receives loot: %s." --> (.+) receives loot: (.+)%.
farmbuyer@20 576 g_LOOT_ITEM_ss = _G.LOOT_ITEM:gsub('%.$','%%.'):gsub('%%s','(.+)')
farmbuyer@20 577
farmbuyer@20 578 -- LOOT_ITEM_MULTIPLE = "%s receives loot: %sx%d." --> (.+) receives loot: (.+)(x%d+)%.
farmbuyer@20 579 g_LOOT_ITEM_MULTIPLE_sss = _G.LOOT_ITEM_MULTIPLE:gsub('%.$','%%.'):gsub('%%s','(.+)'):gsub('x%%d','(x%%d+)')
farmbuyer@20 580
farmbuyer@20 581 -- LOOT_ITEM_SELF = "You receive loot: %s." --> You receive loot: (.+)%.
farmbuyer@20 582 g_LOOT_ITEM_SELF_s = _G.LOOT_ITEM_SELF:gsub('%.$','%%.'):gsub('%%s','(.+)')
farmbuyer@20 583
farmbuyer@20 584 -- LOOT_ITEM_SELF_MULTIPLE = "You receive loot: %sx%d." --> You receive loot: (.+)(x%d+)%.
farmbuyer@20 585 g_LOOT_ITEM_SELF_MULTIPLE_ss = _G.LOOT_ITEM_SELF_MULTIPLE:gsub('%.$','%%.'):gsub('%%s','(.+)'):gsub('x%%d','(x%%d+)')
farmbuyer@20 586
farmbuyer@44 587 --[[
farmbuyer@44 588 Stick something in the Blizzard addons options list, where most users
farmbuyer@44 589 will probably look these days. Try to be conservative about needless
farmbuyer@44 590 frame creation.
farmbuyer@44 591 ]]
farmbuyer@44 592 local bliz = CreateFrame("Frame")
farmbuyer@44 593 bliz.name = "Ouro Loot"
farmbuyer@44 594 bliz:SetScript("OnShow", function(_b)
farmbuyer@44 595 local button = CreateFrame("Button",nil,_b,"UIPanelButtonTemplate")
farmbuyer@44 596 button:SetWidth(150)
farmbuyer@44 597 button:SetHeight(22)
farmbuyer@44 598 button:SetScript("OnClick", function()
farmbuyer@44 599 _G.InterfaceOptionsFrameCancel:Click()
farmbuyer@44 600 _G.HideUIPanel(GameMenuFrame)
farmbuyer@44 601 addon:OpenMainDisplayToTab"Options"
farmbuyer@44 602 end)
farmbuyer@44 603 button:SetText('"/ouroloot opt"')
farmbuyer@44 604 button:SetPoint("TOPLEFT",20,-20)
farmbuyer@44 605 _b:SetScript("OnShow",nil)
farmbuyer@44 606 end)
farmbuyer@44 607 _G.InterfaceOptions_AddCategory(bliz)
farmbuyer@44 608
farmbuyer@49 609 self:_scan_LOD_modules()
farmbuyer@49 610
farmbuyer@1 611 if self.debug.flow then self:Print"is in control-flow debug mode." end
farmbuyer@1 612 end
farmbuyer@1 613 --function addon:OnDisable() end
farmbuyer@1 614
farmbuyer@1 615
farmbuyer@1 616 ------ Event handlers
farmbuyer@1 617 function addon:_clear_SVs()
farmbuyer@1 618 g_loot = {} -- not saved, just fooling PLAYER_LOGOUT tests
farmbuyer@1 619 OuroLootSV = nil
farmbuyer@16 620 OuroLootSV_saved = nil
farmbuyer@1 621 OuroLootSV_opts = nil
farmbuyer@1 622 OuroLootSV_hist = nil
farmbuyer@22 623 OuroLootSV_log = nil
farmbuyer@8 624 ReloadUI()
farmbuyer@1 625 end
farmbuyer@1 626 function addon:PLAYER_LOGOUT()
farmbuyer@16 627 self:UnregisterEvent("RAID_ROSTER_UPDATE")
farmbuyer@16 628 self:UnregisterEvent("PLAYER_ENTERING_WORLD")
farmbuyer@16 629
farmbuyer@16 630 local worth_saving = #g_loot > 0 or next(g_loot.raiders)
farmbuyer@16 631 if not worth_saving then for text in self:registered_textgen_iter() do
farmbuyer@16 632 worth_saving = worth_saving or g_loot.printed[text] > 0
farmbuyer@16 633 end end
farmbuyer@16 634 if worth_saving then
farmbuyer@16 635 opts.autoshard = self.sharder
farmbuyer@16 636 opts.threshold = self.threshold
farmbuyer@1 637 for i,e in ipairs(g_loot) do
farmbuyer@1 638 e.cols = nil
farmbuyer@1 639 end
farmbuyer@1 640 OuroLootSV = g_loot
farmbuyer@16 641 else
farmbuyer@16 642 OuroLootSV = nil
farmbuyer@1 643 end
farmbuyer@16 644
farmbuyer@38 645 worth_saving = false
farmbuyer@6 646 for r,t in pairs(self.history_all) do if type(t) == 'table' then
farmbuyer@8 647 if #t == 0 then
farmbuyer@8 648 self.history_all[r] = nil
farmbuyer@8 649 else
farmbuyer@38 650 worth_saving = true
farmbuyer@8 651 t.realm = nil
farmbuyer@8 652 t.st = nil
farmbuyer@8 653 t.byname = nil
farmbuyer@8 654 end
farmbuyer@6 655 end end
farmbuyer@38 656 if worth_saving then
farmbuyer@38 657 OuroLootSV_hist = self.history_all
farmbuyer@38 658 OuroLootSV_hist.HISTFORMAT = 2
farmbuyer@38 659 else
farmbuyer@38 660 OuroLootSV_hist = nil
farmbuyer@38 661 end
farmbuyer@19 662 OuroLootSV_log = #OuroLootSV_log > 0 and OuroLootSV_log or nil
farmbuyer@1 663 end
farmbuyer@1 664
farmbuyer@10 665 do
farmbuyer@10 666 local IsInInstance, UnitName, UnitIsConnected, UnitClass, UnitRace, UnitSex,
farmbuyer@10 667 UnitLevel, UnitInRaid, UnitIsVisible, GetGuildInfo =
farmbuyer@10 668 IsInInstance, UnitName, UnitIsConnected, UnitClass, UnitRace, UnitSex,
farmbuyer@10 669 UnitLevel, UnitInRaid, UnitIsVisible, GetGuildInfo
farmbuyer@10 670 local time, difftime = time, difftime
farmbuyer@10 671 local R_ACTIVE, R_OFFLINE, R_LEFT = 1, 2, 3
farmbuyer@10 672
farmbuyer@10 673 local lastevent, now = 0, 0
farmbuyer@16 674 local redo_count = 0
farmbuyer@16 675 local redo, timer_handle
farmbuyer@10 676
farmbuyer@10 677 function addon:CheckRoster (leaving_p, now_a)
farmbuyer@10 678 if not g_loot.raiders then return end -- bad transition
farmbuyer@10 679
farmbuyer@10 680 now = now_a or time()
farmbuyer@10 681
farmbuyer@10 682 if leaving_p then
farmbuyer@16 683 if timer_handle then
farmbuyer@16 684 self:CancelTimer(timer_handle)
farmbuyer@16 685 timer_handle = nil
farmbuyer@16 686 end
farmbuyer@10 687 for name,r in pairs(g_loot.raiders) do
farmbuyer@10 688 r.leave = r.leave or now
farmbuyer@10 689 end
farmbuyer@10 690 return
farmbuyer@10 691 end
farmbuyer@10 692
farmbuyer@10 693 for name,r in pairs(g_loot.raiders) do
farmbuyer@10 694 if r.online ~= R_LEFT and not UnitInRaid(name) then
farmbuyer@10 695 r.online = R_LEFT
farmbuyer@10 696 r.leave = now
farmbuyer@10 697 end
farmbuyer@10 698 end
farmbuyer@10 699
farmbuyer@16 700 if redo then
farmbuyer@16 701 redo_count = redo_count + 1
farmbuyer@16 702 end
farmbuyer@16 703 redo = false
farmbuyer@10 704 for i = 1, GetNumRaidMembers() do
farmbuyer@10 705 local unit = 'raid'..i
farmbuyer@10 706 local name = UnitName(unit)
farmbuyer@10 707 -- No, that's not my typo, it really is "uknownbeing" in Blizzard's code.
farmbuyer@10 708 if name and name ~= UNKNOWN and name ~= UNKNOWNOBJECT and name ~= UKNOWNBEING then
farmbuyer@10 709 if not g_loot.raiders[name] then
farmbuyer@10 710 g_loot.raiders[name] = { needinfo=true }
farmbuyer@10 711 end
farmbuyer@10 712 local r = g_loot.raiders[name]
farmbuyer@10 713 if r.needinfo and UnitIsVisible(unit) then
farmbuyer@10 714 r.needinfo = nil
farmbuyer@10 715 r.class = select(2,UnitClass(unit))
farmbuyer@10 716 r.race = select(2,UnitRace(unit))
farmbuyer@10 717 r.sex = UnitSex(unit)
farmbuyer@10 718 r.level = UnitLevel(unit)
farmbuyer@10 719 r.guild = GetGuildInfo(unit)
farmbuyer@10 720 end
farmbuyer@10 721 local connected = UnitIsConnected(unit)
farmbuyer@10 722 if connected and r.online ~= R_ACTIVE then
farmbuyer@10 723 r.join = r.join or now
farmbuyer@10 724 r.online = R_ACTIVE
farmbuyer@10 725 elseif (not connected) and r.online ~= R_OFFLINE then
farmbuyer@10 726 r.leave = now
farmbuyer@10 727 r.online = R_OFFLINE
farmbuyer@10 728 end
farmbuyer@10 729 redo = redo or r.needinfo
farmbuyer@10 730 end
farmbuyer@10 731 end
farmbuyer@16 732 if redo then -- XXX test redo_count here and eventually give up?
farmbuyer@16 733 if not timer_handle then
farmbuyer@16 734 timer_handle = self:ScheduleRepeatingTimer("RAID_ROSTER_UPDATE", 60)
farmbuyer@16 735 end
farmbuyer@16 736 else
farmbuyer@16 737 redo_count = 0
farmbuyer@16 738 if timer_handle then
farmbuyer@16 739 self:CancelTimer(timer_handle)
farmbuyer@16 740 timer_handle = nil
farmbuyer@16 741 end
farmbuyer@10 742 end
farmbuyer@10 743 end
farmbuyer@10 744
farmbuyer@10 745 function addon:RAID_ROSTER_UPDATE (event)
farmbuyer@10 746 if GetNumRaidMembers() == 0 then
farmbuyer@16 747 -- Leaving a raid group.
farmbuyer@10 748 -- Because of PLAYER_ENTERING_WORLD, this code also executes on load
farmbuyer@10 749 -- screens while soloing and in regular groups. Take care.
farmbuyer@16 750 self.dprint('flow', "GetNumRaidMembers == 0")
farmbuyer@16 751 if self.enabled and not self.debug.notraid then
farmbuyer@16 752 self.dprint('flow', "enabled, leaving raid")
farmbuyer@10 753 self.popped = nil
farmbuyer@16 754 self:Deactivate() -- self:UnregisterEvent("CHAT_MSG_LOOT")
farmbuyer@10 755 self:CheckRoster(--[[leaving raid]]true)
farmbuyer@10 756 end
farmbuyer@10 757 return
farmbuyer@10 758 end
farmbuyer@10 759
farmbuyer@1 760 local inside,whatkind = IsInInstance()
farmbuyer@1 761 if inside and (whatkind == "pvp" or whatkind == "arena") then
farmbuyer@41 762 self.dprint('flow', "got RRU event but in pvp zone, bailing")
farmbuyer@41 763 return
farmbuyer@1 764 end
farmbuyer@10 765
farmbuyer@10 766 local docheck = self.enabled
farmbuyer@1 767 if event == "Activate" then
farmbuyer@1 768 -- dispatched manually from Activate
farmbuyer@10 769 self:RegisterEvent("CHAT_MSG_LOOT")
farmbuyer@2 770 _register_bossmod(self)
farmbuyer@10 771 docheck = true
farmbuyer@1 772 elseif event == "RAID_ROSTER_UPDATE" then
farmbuyer@10 773 -- hot code path, be careful
farmbuyer@10 774
farmbuyer@1 775 -- event registration from onload, joined a raid, maybe show popup
farmbuyer@16 776 self.dprint('flow', "RRU check:", self.popped, opts.popup_on_join)
farmbuyer@10 777 if (not self.popped) and opts.popup_on_join then
farmbuyer@1 778 self.popped = StaticPopup_Show "OUROL_REMIND"
farmbuyer@1 779 self.popped.data = self
farmbuyer@10 780 return
farmbuyer@1 781 end
farmbuyer@1 782 end
farmbuyer@11 783 -- Throttle the checks fired by common events.
farmbuyer@10 784 if docheck and not InCombatLockdown() then
farmbuyer@10 785 now = time()
farmbuyer@10 786 if difftime(now,lastevent) > 45 then
farmbuyer@10 787 lastevent = now
farmbuyer@10 788 self:CheckRoster(false,now)
farmbuyer@10 789 end
farmbuyer@10 790 end
farmbuyer@1 791 end
farmbuyer@1 792 end
farmbuyer@1 793
farmbuyer@1 794 -- helper for CHAT_MSG_LOOT handler
farmbuyer@1 795 do
farmbuyer@42 796 local function maybe_trash_kill_entry()
farmbuyer@42 797 -- this is set on various boss interactions, so we've got a kill/wipe
farmbuyer@42 798 -- entry already
farmbuyer@42 799 if addon.latest_instance then return end
farmbuyer@42 800 addon.latest_instance = instance_tag()
farmbuyer@42 801 addon:_mark_boss_kill (addon._addLootEntry{
farmbuyer@42 802 kind='boss',reason='kill',bosskill=[[trash]],instance=addon.latest_instance,duration=0
farmbuyer@42 803 })
farmbuyer@42 804 end
farmbuyer@42 805
farmbuyer@1 806 -- Recent loot cache
farmbuyer@25 807 local candidates = {}
farmbuyer@25 808 local function prefer_local_loots (cache)
farmbuyer@25 809 -- The function name is a bit of a misnomer, as local entries overwrite
farmbuyer@25 810 -- remote entries as the candidate table is populated. This routine is
farmbuyer@39 811 -- here to extract the final results once the cache timers have expired.
farmbuyer@38 812 --
farmbuyer@34 813 -- Keep this sync'd with the local_override branch below.
farmbuyer@25 814 for i,sig in ipairs(candidates) do
farmbuyer@25 815 addon.dprint('loot', "processing candidate entry", i, sig)
farmbuyer@25 816 local loot = candidates[sig]
farmbuyer@25 817 if loot then
farmbuyer@25 818 addon.dprint('loot', i, "was found")
farmbuyer@42 819 maybe_trash_kill_entry() -- Generate *some* kind of boss/location entry
farmbuyer@25 820 candidates[sig] = nil
farmbuyer@25 821 local looti = addon._addLootEntry(loot)
farmbuyer@25 822 if (loot.disposition ~= 'shard')
farmbuyer@25 823 and (loot.disposition ~= 'gvault')
farmbuyer@25 824 and (not addon.history_suppress)
farmbuyer@25 825 then
farmbuyer@25 826 addon:_addHistoryEntry(looti)
farmbuyer@25 827 end
farmbuyer@25 828 end
farmbuyer@25 829 end
farmbuyer@25 830
farmbuyer@25 831 if addon.display then
farmbuyer@25 832 addon:redisplay()
farmbuyer@25 833 end
farmbuyer@47 834 wipe(candidates)
farmbuyer@25 835 end
farmbuyer@40 836 addon.recent_loot = create_new_cache ('loot', comm_cleanup_ttl+3, prefer_local_loots)
farmbuyer@1 837
farmbuyer@6 838 local GetItemInfo, GetItemIcon = GetItemInfo, GetItemIcon
farmbuyer@1 839
farmbuyer@1 840 -- 'from' and onwards only present if this is triggered by a broadcast
farmbuyer@1 841 function addon:_do_loot (local_override, recipient, itemid, count, from, extratext)
farmbuyer@6 842 local itexture = GetItemIcon(itemid)
farmbuyer@6 843 local iname, ilink, iquality = GetItemInfo(itemid)
farmbuyer@19 844 local i
farmbuyer@6 845 if (not iname) or (not itexture) then
farmbuyer@19 846 i = true
farmbuyer@6 847 iname, ilink, iquality, itexture =
farmbuyer@6 848 UNKNOWN..': '..itemid, 'item:6948', ITEM_QUALITY_COMMON, [[ICONS\INV_Misc_QuestionMark]]
farmbuyer@6 849 end
farmbuyer@19 850 self.dprint('loot',">>_do_loot, R:", recipient, "I:", itemid, "C:", count, "frm:", from, "ex:", extratext, "q:", iquality)
farmbuyer@1 851
farmbuyer@19 852 itemid = tonumber(ilink:match("item:(%d+)") or 0)
farmbuyer@39 853 -- This is only a 'while' to make jumping out of it easy and still do cleanup below.
farmbuyer@25 854 while local_override or ((iquality >= self.threshold) and not opts.itemfilter[itemid]) do
farmbuyer@1 855 if (self.rebroadcast and (not from)) and not local_override then
farmbuyer@1 856 self:broadcast('loot', recipient, itemid, count)
farmbuyer@1 857 end
farmbuyer@25 858 if (not self.enabled) and (not local_override) then break end
farmbuyer@25 859 local signature = recipient .. iname .. (count or "")
farmbuyer@25 860 if from and self.recent_loot:test(signature) then
farmbuyer@39 861 self.dprint('cache', "remote loot <",signature,"> already in cache, skipping")
farmbuyer@25 862 else
farmbuyer@25 863 -- There is some redundancy in all this, in the interests of ease-of-coding
farmbuyer@25 864 i = {
farmbuyer@25 865 kind = 'loot',
farmbuyer@25 866 person = recipient,
farmbuyer@25 867 person_class= select(2,UnitClass(recipient)),
farmbuyer@25 868 cache_miss = i and true or nil,
farmbuyer@25 869 quality = iquality,
farmbuyer@25 870 itemname = iname,
farmbuyer@25 871 id = itemid,
farmbuyer@25 872 itemlink = ilink,
farmbuyer@25 873 itexture = itexture,
farmbuyer@25 874 disposition = (recipient == self.sharder) and 'shard' or nil,
farmbuyer@38 875 count = (count and count ~= "") and count or nil,
farmbuyer@25 876 bcast_from = from,
farmbuyer@25 877 extratext = extratext,
farmbuyer@25 878 is_heroic = self:is_heroic_item(ilink),
farmbuyer@25 879 }
farmbuyer@34 880 if local_override then
farmbuyer@39 881 -- player is adding loot by hand, don't wait for network cache timeouts
farmbuyer@39 882 -- keep this sync'd with prefer_local_loots above
farmbuyer@34 883 if i.extratext == 'shard'
farmbuyer@34 884 or i.extratext == 'gvault'
farmbuyer@34 885 or i.extratext == 'offspec'
farmbuyer@34 886 then
farmbuyer@34 887 i.disposition = i.extratext
farmbuyer@34 888 end
farmbuyer@34 889 local looti = self._addLootEntry(i)
farmbuyer@34 890 if (i.disposition ~= 'shard')
farmbuyer@34 891 and (i.disposition ~= 'gvault')
farmbuyer@34 892 and (not self.history_suppress)
farmbuyer@34 893 then
farmbuyer@34 894 self:_addHistoryEntry(looti)
farmbuyer@34 895 end
farmbuyer@39 896 i = looti -- return value mostly for gui's manual entry
farmbuyer@34 897 else
farmbuyer@34 898 self.recent_loot:add(signature)
farmbuyer@34 899 candidates[signature] = i
farmbuyer@34 900 tinsert (candidates, signature)
farmbuyer@39 901 self.dprint('cache', "loot <",signature,"> added to cache as candidate", #candidates)
farmbuyer@34 902 end
farmbuyer@1 903 end
farmbuyer@25 904 break
farmbuyer@1 905 end
farmbuyer@1 906 self.dprint('loot',"<<_do_loot out")
farmbuyer@1 907 return i
farmbuyer@1 908 end
farmbuyer@1 909
farmbuyer@1 910 function addon:CHAT_MSG_LOOT (event, ...)
farmbuyer@1 911 if (not self.rebroadcast) and (not self.enabled) and (event ~= "manual") then return end
farmbuyer@1 912
farmbuyer@1 913 --[[
farmbuyer@1 914 iname: Hearthstone
farmbuyer@1 915 iquality: integer
farmbuyer@1 916 ilink: clickable formatted link
farmbuyer@1 917 itemstring: item:6948:....
farmbuyer@1 918 itexture: inventory icon texture
farmbuyer@1 919 ]]
farmbuyer@1 920
farmbuyer@1 921 if event == "CHAT_MSG_LOOT" then
farmbuyer@1 922 local msg = ...
farmbuyer@20 923 local person, itemstring, count
farmbuyer@21 924 --ChatFrame2:AddMessage("original string: >"..(msg:gsub("\124","\124\124")).."<")
farmbuyer@20 925
farmbuyer@20 926 -- test in most likely order: other people get more loot than "you" do
farmbuyer@20 927 person, itemstring, count = msg:match(g_LOOT_ITEM_MULTIPLE_sss)
farmbuyer@20 928 if not person then
farmbuyer@20 929 person, itemstring = msg:match(g_LOOT_ITEM_ss)
farmbuyer@20 930 end
farmbuyer@20 931 if not person then
farmbuyer@20 932 itemstring, count = msg:match(g_LOOT_ITEM_SELF_MULTIPLE_ss)
farmbuyer@20 933 if not itemstring then
farmbuyer@20 934 itemstring = msg:match(g_LOOT_ITEM_SELF_s)
farmbuyer@20 935 end
farmbuyer@20 936 end
farmbuyer@20 937
farmbuyer@20 938 self.dprint('loot', "CHAT_MSG_LOOT, person is", person, ", itemstring is", itemstring, ", count is", count)
farmbuyer@20 939 if not itemstring then return end -- "So-and-So selected Greed", etc, not actual looting
farmbuyer@1 940
farmbuyer@1 941 -- Name might be colorized, remove the highlighting
farmbuyer@20 942 if person then
farmbuyer@20 943 person = person:match("|c%x%x%x%x%x%x%x%x(%S+)") or person
farmbuyer@20 944 else
farmbuyer@38 945 person = my_name -- UNIT_YOU / You
farmbuyer@20 946 end
farmbuyer@1 947
farmbuyer@20 948 --local id = tonumber((select(2, strsplit(":", itemstring))))
farmbuyer@20 949 local id = tonumber(itemstring:match('|Hitem:(%d+):'))
farmbuyer@1 950
farmbuyer@1 951 return self:_do_loot (false, person, id, count)
farmbuyer@1 952
farmbuyer@1 953 elseif event == "broadcast" then
farmbuyer@1 954 return self:_do_loot(false, ...)
farmbuyer@1 955
farmbuyer@1 956 elseif event == "manual" then
farmbuyer@1 957 local r,i,n = ...
farmbuyer@1 958 return self:_do_loot(true, r,i,nil,nil,n)
farmbuyer@1 959 end
farmbuyer@1 960 end
farmbuyer@1 961 end
farmbuyer@1 962
farmbuyer@1 963
farmbuyer@1 964 ------ Slash command handler
farmbuyer@1 965 -- Thought about breaking this up into a table-driven dispatcher. But
farmbuyer@1 966 -- that would result in a pile of teensy functions, most of which would
farmbuyer@1 967 -- never be called. Too much overhead. (2.0: Most of these removed now
farmbuyer@1 968 -- that GUI is in place.)
farmbuyer@1 969 function addon:OnSlash (txt) --, editbox)
farmbuyer@1 970 txt = strtrim(txt:lower())
farmbuyer@1 971 local cmd, arg = ""
farmbuyer@1 972 do
farmbuyer@1 973 local s,e = txt:find("^%a+")
farmbuyer@1 974 if s then
farmbuyer@1 975 cmd = txt:sub(s,e)
farmbuyer@1 976 s = txt:find("%S", e+2)
farmbuyer@1 977 if s then arg = txt:sub(s,-1) end
farmbuyer@1 978 end
farmbuyer@1 979 end
farmbuyer@1 980
farmbuyer@1 981 if cmd == "" then
farmbuyer@1 982 if InCombatLockdown() then
farmbuyer@1 983 return self:Print("Can't display window in combat.")
farmbuyer@1 984 else
farmbuyer@1 985 return self:BuildMainDisplay()
farmbuyer@1 986 end
farmbuyer@1 987
farmbuyer@1 988 elseif cmd:find("^thre") then
farmbuyer@1 989 self:SetThreshold(arg)
farmbuyer@1 990
farmbuyer@1 991 elseif cmd == "on" then self:Activate(arg)
farmbuyer@1 992 elseif cmd == "off" then self:Deactivate()
farmbuyer@1 993 elseif cmd == "broadcast" or cmd == "bcast" then self:Activate(nil,true)
farmbuyer@1 994
farmbuyer@23 995 elseif cmd == "toggle" then
farmbuyer@23 996 if self.display then
farmbuyer@23 997 self.display:Hide()
farmbuyer@23 998 else
farmbuyer@23 999 return self:BuildMainDisplay()
farmbuyer@23 1000 end
farmbuyer@23 1001
farmbuyer@1 1002 elseif cmd == "fake" then -- maybe comment this out for real users
farmbuyer@1 1003 self:_mark_boss_kill (self._addLootEntry{
farmbuyer@1 1004 kind='boss',reason='kill',bosskill="Baron Steamroller",instance=instance_tag(),duration=0
farmbuyer@1 1005 })
farmbuyer@1 1006 self:CHAT_MSG_LOOT ('manual', my_name, 54797)
farmbuyer@1 1007 if self.display then
farmbuyer@1 1008 self:redisplay()
farmbuyer@1 1009 end
farmbuyer@1 1010 self:Print "Baron Steamroller has been slain. Congratulations on your rug."
farmbuyer@1 1011
farmbuyer@1 1012 elseif cmd == "debug" then
farmbuyer@1 1013 if arg then
farmbuyer@1 1014 self.debug[arg] = not self.debug[arg]
farmbuyer@1 1015 _G.print(arg,self.debug[arg])
farmbuyer@1 1016 if self.debug[arg] then self.DEBUG_PRINT = true end
farmbuyer@1 1017 else
farmbuyer@1 1018 self.DEBUG_PRINT = not self.DEBUG_PRINT
farmbuyer@1 1019 end
farmbuyer@1 1020
farmbuyer@1 1021 elseif cmd == "save" and arg and arg:len() > 0 then
farmbuyer@1 1022 self:save_saveas(arg)
farmbuyer@1 1023 elseif cmd == "list" then
farmbuyer@1 1024 self:save_list()
farmbuyer@1 1025 elseif cmd == "restore" and arg and arg:len() > 0 then
farmbuyer@1 1026 self:save_restore(tonumber(arg))
farmbuyer@1 1027 elseif cmd == "delete" and arg and arg:len() > 0 then
farmbuyer@1 1028 self:save_delete(tonumber(arg))
farmbuyer@1 1029
farmbuyer@1 1030 elseif cmd == "help" then
farmbuyer@1 1031 self:BuildMainDisplay('help')
farmbuyer@23 1032 elseif cmd == "ping" then
farmbuyer@23 1033 self:DoPing()
farmbuyer@1 1034
farmbuyer@19 1035 elseif cmd == "fixcache" then
farmbuyer@19 1036 self:do_item_cache_fixup()
farmbuyer@19 1037
farmbuyer@1 1038 else
farmbuyer@1 1039 if self:OpenMainDisplayToTab(cmd) then
farmbuyer@1 1040 return
farmbuyer@1 1041 end
farmbuyer@1 1042 self:Print("Unknown command '%s'. %s to see the help window.",
farmbuyer@1 1043 cmd, self.format_hypertext('help',"Click here",ITEM_QUALITY_UNCOMMON))
farmbuyer@1 1044 end
farmbuyer@1 1045 end
farmbuyer@1 1046
farmbuyer@1 1047 function addon:SetThreshold (arg, quiet_p)
farmbuyer@1 1048 local q = tonumber(arg)
farmbuyer@1 1049 if q then
farmbuyer@1 1050 q = math.floor(q+0.001)
farmbuyer@1 1051 if q<0 or q>6 then
farmbuyer@1 1052 return self:Print("Threshold must be 0-6.")
farmbuyer@1 1053 end
farmbuyer@1 1054 else
farmbuyer@1 1055 q = qualnames[arg]
farmbuyer@1 1056 if not q then
farmbuyer@1 1057 return self:Print("Unrecognized item quality argument.")
farmbuyer@1 1058 end
farmbuyer@1 1059 end
farmbuyer@1 1060 self.threshold = q
farmbuyer@1 1061 if not quiet_p then self:Print("Threshold now set to %s.", self.thresholds[q]) end
farmbuyer@1 1062 end
farmbuyer@1 1063
farmbuyer@1 1064
farmbuyer@1 1065 ------ On/off
farmbuyer@1 1066 function addon:Activate (opt_threshold, opt_bcast_only)
farmbuyer@16 1067 self.dprint('flow', ":Activate is running")
farmbuyer@10 1068 self:RegisterEvent("RAID_ROSTER_UPDATE")
farmbuyer@16 1069 self:RegisterEvent("PLAYER_ENTERING_WORLD",
farmbuyer@16 1070 function() self:ScheduleTimer("RAID_ROSTER_UPDATE", 5, "PLAYER_ENTERING_WORLD") end)
farmbuyer@1 1071 self.popped = true
farmbuyer@1 1072 if GetNumRaidMembers() > 0 then
farmbuyer@16 1073 self.dprint('flow', ">:Activate calling RRU")
farmbuyer@1 1074 self:RAID_ROSTER_UPDATE("Activate")
farmbuyer@1 1075 elseif self.debug.notraid then
farmbuyer@16 1076 self.dprint('flow', ">:Activate registering loot and bossmods")
farmbuyer@10 1077 self:RegisterEvent("CHAT_MSG_LOOT")
farmbuyer@2 1078 _register_bossmod(self)
farmbuyer@1 1079 elseif g_restore_p then
farmbuyer@1 1080 g_restore_p = nil
farmbuyer@16 1081 self.popped = nil -- get the reminder if later joining a raid
farmbuyer@16 1082 if #g_loot == 0 then
farmbuyer@16 1083 -- only generated text and raider join/leave data, not worth verbage
farmbuyer@16 1084 self.dprint('flow', ">:Activate restored generated texts, un-popping")
farmbuyer@16 1085 return
farmbuyer@16 1086 end
farmbuyer@1 1087 self:Print("Ouro Raid Loot restored previous data, but not in a raid",
farmbuyer@48 1088 "and 5-player mode not active. |cffff0505NOT tracking loot|r;",
farmbuyer@1 1089 "use 'enable' to activate loot tracking, or 'clear' to erase",
farmbuyer@1 1090 "previous data, or 'help' to read about saved-texts commands.")
farmbuyer@1 1091 return
farmbuyer@1 1092 end
farmbuyer@1 1093 self.rebroadcast = true -- hardcode to true; this used to be more complicated
farmbuyer@1 1094 self.enabled = not opt_bcast_only
farmbuyer@1 1095 if opt_threshold then
farmbuyer@1 1096 self:SetThreshold (opt_threshold, --[[quiet_p=]]true)
farmbuyer@1 1097 end
farmbuyer@1 1098 self:Print("Ouro Raid Loot is %s. Threshold currently %s.",
farmbuyer@1 1099 self.enabled and "tracking" or "only broadcasting",
farmbuyer@1 1100 self.thresholds[self.threshold])
farmbuyer@27 1101 self:broadcast('revcheck',revision_large)
farmbuyer@1 1102 end
farmbuyer@1 1103
farmbuyer@1 1104 -- Note: running '/loot off' will also avoid the popup reminder when
farmbuyer@1 1105 -- joining a raid, but will not change the saved option setting.
farmbuyer@1 1106 function addon:Deactivate()
farmbuyer@1 1107 self.enabled = false
farmbuyer@1 1108 self.rebroadcast = false
farmbuyer@10 1109 self:UnregisterEvent("RAID_ROSTER_UPDATE")
farmbuyer@10 1110 self:UnregisterEvent("PLAYER_ENTERING_WORLD")
farmbuyer@10 1111 self:UnregisterEvent("CHAT_MSG_LOOT")
farmbuyer@1 1112 self:Print("Ouro Raid Loot deactivated.")
farmbuyer@1 1113 end
farmbuyer@1 1114
farmbuyer@1 1115 function addon:Clear(verbose_p)
farmbuyer@1 1116 local repopup, st
farmbuyer@1 1117 if self.display then
farmbuyer@1 1118 -- in the new version, this is likely to always be the case
farmbuyer@1 1119 repopup = true
farmbuyer@1 1120 st = self.display:GetUserData("eoiST")
farmbuyer@1 1121 if not st then
farmbuyer@1 1122 self.dprint('flow', "Clear: display visible but eoiST not set??")
farmbuyer@1 1123 end
farmbuyer@1 1124 self.display:Hide()
farmbuyer@1 1125 end
farmbuyer@1 1126 g_restore_p = nil
farmbuyer@1 1127 OuroLootSV = nil
farmbuyer@1 1128 self:_reset_timestamps()
farmbuyer@1 1129 if verbose_p then
farmbuyer@16 1130 if (OuroLootSV_saved and #OuroLootSV_saved>0) then
farmbuyer@16 1131 self:Print("Current loot data cleared, %d saved sets remaining.", #OuroLootSV_saved)
farmbuyer@1 1132 else
farmbuyer@1 1133 self:Print("Current loot data cleared.")
farmbuyer@1 1134 end
farmbuyer@1 1135 end
farmbuyer@1 1136 _init(self,st)
farmbuyer@1 1137 if repopup then
farmbuyer@1 1138 addon:BuildMainDisplay()
farmbuyer@1 1139 end
farmbuyer@1 1140 end
farmbuyer@1 1141
farmbuyer@1 1142
farmbuyer@1 1143 ------ Behind the scenes routines
farmbuyer@19 1144 -- Semi-experimental debugging aid.
farmbuyer@19 1145 do
farmbuyer@41 1146 -- Putting _log local to here can result in this sequence:
farmbuyer@41 1147 -- 1) logging happens, followed by reload or logout/login
farmbuyer@41 1148 -- 2) _log points to SV_log
farmbuyer@41 1149 -- 3) VARIABLES_LOADED replaces SV_log pointer with restored version
farmbuyer@41 1150 -- 4) logging happens to _log table (now with no other references)
farmbuyer@41 1151 -- 5) at logout, nothing new has been entered in the table being saved
farmbuyer@19 1152 local date = _G.date
farmbuyer@19 1153 function addon:log_with_timestamp (msg)
farmbuyer@41 1154 tinsert (_log, date('%m:%d %H:%M:%S ')..msg)
farmbuyer@19 1155 end
farmbuyer@19 1156 end
farmbuyer@19 1157
farmbuyer@49 1158 -- Check for plugins which haven't already been loaded, and add hooks for
farmbuyer@49 1159 -- them. Credit to DBM for the approach here.
farmbuyer@49 1160 function addon:_scan_LOD_modules()
farmbuyer@49 1161 for i = 1, GetNumAddOns() do
farmbuyer@49 1162 if GetAddOnMetadata (i, "X-OuroLoot-Plugin")
farmbuyer@49 1163 and IsAddOnLoadOnDemand(i)
farmbuyer@49 1164 and not IsAddOnLoaded(i)
farmbuyer@49 1165 then
farmbuyer@49 1166 local folder, _, _, enabled, _, reason = GetAddOnInfo(i)
farmbuyer@49 1167 local tabtitle = GetAddOnMetadata (i, "X-OuroLoot-Plugin")
farmbuyer@49 1168 self:_gui_add_LOD_tab (tabtitle, folder, i, enabled, reason)
farmbuyer@49 1169 end
farmbuyer@49 1170 end
farmbuyer@49 1171 end
farmbuyer@49 1172
farmbuyer@1 1173 -- Adds indices to traverse the tables in a nice sorted order.
farmbuyer@1 1174 do
farmbuyer@1 1175 local byindex, temp = {}, {}
farmbuyer@1 1176 local function sort (src, dest)
farmbuyer@1 1177 for k in pairs(src) do
farmbuyer@1 1178 temp[#temp+1] = k
farmbuyer@1 1179 end
farmbuyer@1 1180 table.sort(temp)
farmbuyer@47 1181 wipe(dest)
farmbuyer@1 1182 for i = 1, #temp do
farmbuyer@1 1183 dest[i] = src[temp[i]]
farmbuyer@1 1184 end
farmbuyer@1 1185 end
farmbuyer@1 1186
farmbuyer@1 1187 function addon.sender_list.sort()
farmbuyer@1 1188 sort (addon.sender_list.active, byindex)
farmbuyer@47 1189 wipe(temp)
farmbuyer@1 1190 addon.sender_list.activeI = #byindex
farmbuyer@1 1191 sort (addon.sender_list.names, byindex)
farmbuyer@47 1192 wipe(temp)
farmbuyer@1 1193 end
farmbuyer@1 1194 addon.sender_list.namesI = byindex
farmbuyer@1 1195 end
farmbuyer@1 1196
farmbuyer@1 1197 -- Message sending.
farmbuyer@1 1198 -- See OCR_funcs.tag at the end of this file for incoming message treatment.
farmbuyer@1 1199 do
farmbuyer@1 1200 local function assemble(...)
farmbuyer@1 1201 local msg = ...
farmbuyer@1 1202 for i = 2, select('#',...) do
farmbuyer@1 1203 msg = msg .. '\a' .. (select(i,...) or "")
farmbuyer@1 1204 end
farmbuyer@1 1205 return msg
farmbuyer@1 1206 end
farmbuyer@1 1207
farmbuyer@1 1208 -- broadcast('tag', <stuff>)
farmbuyer@1 1209 function addon:broadcast(...)
farmbuyer@1 1210 local msg = assemble(...)
farmbuyer@1 1211 self.dprint('comm', "<broadcast>:", msg)
farmbuyer@1 1212 -- the "GUILD" here is just so that we can also pick up on it
farmbuyer@1 1213 self:SendCommMessage(self.ident, msg, self.debug.comm and "GUILD" or "RAID")
farmbuyer@1 1214 end
farmbuyer@1 1215 -- whispercast(<to>, 'tag', <stuff>)
farmbuyer@1 1216 function addon:whispercast(to,...)
farmbuyer@1 1217 local msg = assemble(...)
farmbuyer@1 1218 self.dprint('comm', "<whispercast>@", to, ":", msg)
farmbuyer@1 1219 self:SendCommMessage(self.identTg, msg, "WHISPER", to)
farmbuyer@1 1220 end
farmbuyer@1 1221 end
farmbuyer@1 1222
farmbuyer@23 1223 function addon:DoPing()
farmbuyer@23 1224 self:Print("Give me a ping, Vasili. One ping only, please.")
farmbuyer@23 1225 self.sender_list.active = {}
farmbuyer@23 1226 self.sender_list.names = {}
farmbuyer@23 1227 self:broadcast('ping')
farmbuyer@27 1228 self:broadcast('revcheck',revision_large)
farmbuyer@27 1229 end
farmbuyer@27 1230
farmbuyer@27 1231 function addon:_check_revision (otherrev)
farmbuyer@27 1232 self.dprint('comm', "revchecking against", otherrev)
farmbuyer@27 1233 otherrev = tonumber(otherrev)
farmbuyer@27 1234 if otherrev == revision_large then
farmbuyer@27 1235 -- normal case
farmbuyer@27 1236
farmbuyer@27 1237 elseif otherrev < revision_large then
farmbuyer@27 1238 self.dprint('comm', "ours is newer, notifying")
farmbuyer@27 1239 self:broadcast('revcheck',revision_large)
farmbuyer@27 1240
farmbuyer@27 1241 else
farmbuyer@27 1242 self.dprint('comm', "ours is older, yammering")
farmbuyer@27 1243 if newer_warning then
farmbuyer@27 1244 self:Print(newer_warning,
farmbuyer@29 1245 self.format_hypertext('popupurl',"click here",ITEM_QUALITY_UNCOMMON),
farmbuyer@27 1246 self.format_hypertext('doping',"click here",ITEM_QUALITY_UNCOMMON))
farmbuyer@27 1247 newer_warning = nil
farmbuyer@27 1248 end
farmbuyer@27 1249 end
farmbuyer@23 1250 end
farmbuyer@23 1251
farmbuyer@1 1252 -- Generic helpers
farmbuyer@28 1253 -- Returns index and entry at that index, or nil if not found.
farmbuyer@1 1254 function addon._find_next_after (kind, index)
farmbuyer@1 1255 index = index + 1
farmbuyer@1 1256 while index <= #g_loot do
farmbuyer@1 1257 if g_loot[index].kind == kind then
farmbuyer@1 1258 return index, g_loot[index]
farmbuyer@1 1259 end
farmbuyer@1 1260 index = index + 1
farmbuyer@1 1261 end
farmbuyer@1 1262 end
farmbuyer@28 1263 -- Essentially a _find_next_after('time'-or-'boss'), but if KIND is
farmbuyer@28 1264 -- 'boss', will also stop upon finding a timestamp. Returns nil if
farmbuyer@28 1265 -- appropriate fencepost is not found.
farmbuyer@28 1266 function addon._find_timeboss_fencepost (kind, index)
farmbuyer@28 1267 local fencepost
farmbuyer@28 1268 local closest_time = addon._find_next_after('time',index)
farmbuyer@28 1269 if kind == 'time' then
farmbuyer@28 1270 fencepost = closest_time
farmbuyer@28 1271 elseif kind == 'boss' then
farmbuyer@28 1272 local closest_boss = addon._find_next_after('boss',index)
farmbuyer@28 1273 if not closest_boss then
farmbuyer@28 1274 fencepost = closest_time
farmbuyer@28 1275 elseif not closest_time then
farmbuyer@28 1276 fencepost = closest_boss
farmbuyer@28 1277 else
farmbuyer@28 1278 fencepost = math.min(closest_time,closest_boss)
farmbuyer@28 1279 end
farmbuyer@28 1280 end
farmbuyer@28 1281 return fencepost
farmbuyer@28 1282 end
farmbuyer@1 1283
farmbuyer@1 1284 -- Iterate through g_loot entries according to the KIND field. Loop variables
farmbuyer@1 1285 -- are g_loot indices and the corresponding entries (essentially ipairs + some
farmbuyer@1 1286 -- conditionals).
farmbuyer@1 1287 function addon:filtered_loot_iter (filter_kind)
farmbuyer@1 1288 return self._find_next_after, filter_kind, 0
farmbuyer@1 1289 end
farmbuyer@1 1290
farmbuyer@1 1291 do
farmbuyer@1 1292 local itt
farmbuyer@1 1293 local function create()
farmbuyer@1 1294 local tip, lefts = CreateFrame("GameTooltip"), {}
farmbuyer@1 1295 for i = 1, 2 do -- scanning idea here also snagged from Talented
farmbuyer@1 1296 local L,R = tip:CreateFontString(), tip:CreateFontString()
farmbuyer@1 1297 L:SetFontObject(GameFontNormal)
farmbuyer@1 1298 R:SetFontObject(GameFontNormal)
farmbuyer@1 1299 tip:AddFontStrings(L,R)
farmbuyer@1 1300 lefts[i] = L
farmbuyer@1 1301 end
farmbuyer@1 1302 tip.lefts = lefts
farmbuyer@1 1303 return tip
farmbuyer@1 1304 end
farmbuyer@1 1305 function addon:is_heroic_item(item) -- returns true or *nil*
farmbuyer@1 1306 itt = itt or create()
farmbuyer@1 1307 itt:SetOwner(UIParent,"ANCHOR_NONE")
farmbuyer@1 1308 itt:ClearLines()
farmbuyer@1 1309 itt:SetHyperlink(item)
farmbuyer@1 1310 local t = itt.lefts[2]:GetText()
farmbuyer@1 1311 itt:Hide()
farmbuyer@1 1312 return (t == ITEM_HEROIC) or nil
farmbuyer@1 1313 end
farmbuyer@1 1314 end
farmbuyer@1 1315
farmbuyer@1 1316 -- Called when first loading up, and then also when a 'clear' is being
farmbuyer@16 1317 -- performed. If SV's are present then g_restore_p will be true.
farmbuyer@1 1318 function _init (self, possible_st)
farmbuyer@1 1319 self.dprint('flow',"_init running")
farmbuyer@1 1320 self.loot_clean = nil
farmbuyer@1 1321 self.hist_clean = nil
farmbuyer@1 1322 if g_restore_p then
farmbuyer@1 1323 g_loot = OuroLootSV
farmbuyer@16 1324 self.popped = #g_loot > 0
farmbuyer@1 1325 self.dprint('flow', "restoring", #g_loot, "entries")
farmbuyer@16 1326 self:ScheduleTimer("Activate", 12, opts.threshold)
farmbuyer@1 1327 -- FIXME printed could be too large if entries were deleted, how much do we care?
farmbuyer@16 1328 self.sharder = opts.autoshard
farmbuyer@1 1329 else
farmbuyer@10 1330 g_loot = { printed = {}, raiders = {} }
farmbuyer@1 1331 end
farmbuyer@1 1332
farmbuyer@16 1333 self.threshold = opts.threshold or self.threshold -- in the case of restoring but not tracking
farmbuyer@1 1334 self:gui_init(g_loot)
farmbuyer@16 1335 opts.autoshard = nil
farmbuyer@16 1336 opts.threshold = nil
farmbuyer@1 1337
farmbuyer@1 1338 if g_restore_p then
farmbuyer@1 1339 self:zero_printed_fenceposts() -- g_loot.printed.* = previous/safe values
farmbuyer@1 1340 else
farmbuyer@1 1341 self:zero_printed_fenceposts(0) -- g_loot.printed.* = 0
farmbuyer@1 1342 end
farmbuyer@1 1343 if possible_st then
farmbuyer@1 1344 possible_st:SetData(g_loot)
farmbuyer@1 1345 end
farmbuyer@1 1346
farmbuyer@17 1347 self.status_text = ("%s communicating as ident %s commrev %d"):format(self.revision,self.ident,self.commrev)
farmbuyer@1 1348 self:RegisterComm(self.ident)
farmbuyer@1 1349 self:RegisterComm(self.identTg, "OnCommReceivedNocache")
farmbuyer@1 1350
farmbuyer@1 1351 if self.author_debug then
farmbuyer@1 1352 _G.OL = self
farmbuyer@1 1353 _G.Oloot = g_loot
farmbuyer@1 1354 end
farmbuyer@1 1355 end
farmbuyer@1 1356
farmbuyer@25 1357 -- Tie-in with Deadly Boss Mods (or other such addons)
farmbuyer@1 1358 do
farmbuyer@25 1359 local candidates = {}
farmbuyer@25 1360 local location
farmbuyer@1 1361 local function fixup_durations (cache)
farmbuyer@1 1362 local boss, bossi
farmbuyer@1 1363 boss = candidates[1]
farmbuyer@1 1364 if #candidates == 1 then
farmbuyer@1 1365 -- (1) or (2)
farmbuyer@1 1366 boss.duration = boss.duration or 0
farmbuyer@19 1367 addon.dprint('loot', "only one boss candidate")
farmbuyer@1 1368 else
farmbuyer@1 1369 -- (3), should only be one 'cast entry and our local entry
farmbuyer@1 1370 if #candidates ~= 2 then
farmbuyer@1 1371 -- could get a bunch of 'cast entries on the heels of one another
farmbuyer@1 1372 -- before the local one ever fires, apparently... sigh
farmbuyer@1 1373 --addon:Print("<warning> s3 cache has %d entries, does that seem right to you?", #candidates)
farmbuyer@1 1374 end
farmbuyer@1 1375 if candidates[2].duration == nil then
farmbuyer@1 1376 --addon:Print("<warning> s3's second entry is not the local trigger, does that seem right to you?")
farmbuyer@1 1377 end
farmbuyer@1 1378 -- try and be generic anyhow
farmbuyer@1 1379 for i,c in ipairs(candidates) do
farmbuyer@1 1380 if c.duration then
farmbuyer@1 1381 boss = c
farmbuyer@19 1382 addon.dprint('loot', "fixup found boss candidate", i, "duration", c.duration)
farmbuyer@1 1383 break
farmbuyer@1 1384 end
farmbuyer@1 1385 end
farmbuyer@1 1386 end
farmbuyer@1 1387 bossi = addon._addLootEntry(boss)
farmbuyer@19 1388 -- addon.
farmbuyer@19 1389 bossi = addon._adjustBossOrder (bossi, g_boss_signpost) or bossi
farmbuyer@16 1390 g_boss_signpost = nil
farmbuyer@42 1391 addon.latest_instance = boss.instance
farmbuyer@19 1392 addon.dprint('loot', "added boss entry", bossi)
farmbuyer@1 1393 if boss.reason == 'kill' then
farmbuyer@1 1394 addon:_mark_boss_kill (bossi)
farmbuyer@1 1395 if opts.chatty_on_kill then
farmbuyer@1 1396 addon:Print("Registered kill for '%s' in %s!", boss.bosskill, boss.instance)
farmbuyer@1 1397 end
farmbuyer@1 1398 end
farmbuyer@47 1399 wipe(candidates)
farmbuyer@1 1400 end
farmbuyer@1 1401 addon.recent_boss = create_new_cache ('boss', 10, fixup_durations)
farmbuyer@1 1402
farmbuyer@1 1403 -- Similar to _do_loot, but duration+ parms only present when locally generated.
farmbuyer@1 1404 local function _do_boss (self, reason, bossname, intag, duration, raiders)
farmbuyer@1 1405 self.dprint('loot',">>_do_boss, R:", reason, "B:", bossname, "T:", intag,
farmbuyer@1 1406 "D:", duration, "RL:", (raiders and #raiders or 'nil'))
farmbuyer@1 1407 if self.rebroadcast and duration then
farmbuyer@1 1408 self:broadcast('boss', reason, bossname, intag)
farmbuyer@1 1409 end
farmbuyer@1 1410 -- This is only a loop to make jumping out of it easy, and still do cleanup below.
farmbuyer@1 1411 while self.enabled do
farmbuyer@1 1412 if reason == 'wipe' and opts.no_tracking_wipes then break end
farmbuyer@1 1413 bossname = (opts.snarky_boss and self.boss_abbrev[bossname] or bossname) or bossname
farmbuyer@1 1414 local not_from_local = duration == nil
farmbuyer@1 1415 local signature = bossname .. reason
farmbuyer@1 1416 if not_from_local and self.recent_boss:test(signature) then
farmbuyer@39 1417 self.dprint('cache', "remote boss <",signature,"> already in cache, skipping")
farmbuyer@1 1418 else
farmbuyer@1 1419 self.recent_boss:add(signature)
farmbuyer@16 1420 g_boss_signpost = #g_loot + 1
farmbuyer@19 1421 self.dprint('loot', "added boss signpost", g_boss_signpost)
farmbuyer@1 1422 -- Possible scenarios: (1) we don't see a boss event at all (e.g., we're
farmbuyer@1 1423 -- outside the instance) and so this only happens once as a non-local event,
farmbuyer@1 1424 -- (2) we see a local event first and all non-local events are filtered
farmbuyer@1 1425 -- by the cache, (3) we happen to get some non-local events before doing
farmbuyer@1 1426 -- our local event (not because of network weirdness but because our local
farmbuyer@1 1427 -- DBM might not trigger for a while).
farmbuyer@1 1428 local c = {
farmbuyer@1 1429 kind = 'boss',
farmbuyer@1 1430 bosskill = bossname, -- minor misnomer, might not actually be a kill
farmbuyer@1 1431 reason = reason,
farmbuyer@1 1432 instance = intag,
farmbuyer@1 1433 duration = duration, -- these two deliberately may be nil
farmbuyer@1 1434 raiderlist = raiders and table.concat(raiders, ", ")
farmbuyer@1 1435 }
farmbuyer@1 1436 tinsert(candidates,c)
farmbuyer@1 1437 end
farmbuyer@17 1438 break
farmbuyer@1 1439 end
farmbuyer@1 1440 self.dprint('loot',"<<_do_boss out")
farmbuyer@1 1441 end
farmbuyer@1 1442 -- No wrapping layer for now
farmbuyer@1 1443 addon.on_boss_broadcast = _do_boss
farmbuyer@1 1444
farmbuyer@1 1445 function addon:_mark_boss_kill (index)
farmbuyer@1 1446 local e = g_loot[index]
farmbuyer@17 1447 if not e then
farmbuyer@17 1448 self:Print("Something horribly wrong;", index, "is not a valid entry!")
farmbuyer@17 1449 return
farmbuyer@17 1450 end
farmbuyer@1 1451 if not e.bosskill then
farmbuyer@16 1452 self:Print("Something horribly wrong;", index, "is not a boss entry!")
farmbuyer@16 1453 return
farmbuyer@1 1454 end
farmbuyer@1 1455 if e.reason ~= 'wipe' then
farmbuyer@1 1456 -- enh, bail
farmbuyer@1 1457 self.loot_clean = index-1
farmbuyer@1 1458 end
farmbuyer@1 1459 local attempts = 1
farmbuyer@1 1460 local first
farmbuyer@1 1461
farmbuyer@1 1462 local i,d = 1,g_loot[1]
farmbuyer@1 1463 while d ~= e do
farmbuyer@1 1464 if d.bosskill and
farmbuyer@1 1465 d.bosskill == e.bosskill and
farmbuyer@1 1466 d.reason == 'wipe'
farmbuyer@1 1467 then
farmbuyer@1 1468 first = first or i
farmbuyer@1 1469 attempts = attempts + 1
farmbuyer@1 1470 assert(tremove(g_loot,i)==d,"_mark_boss_kill screwed up data badly")
farmbuyer@1 1471 else
farmbuyer@1 1472 i = i + 1
farmbuyer@1 1473 end
farmbuyer@1 1474 d = g_loot[i]
farmbuyer@1 1475 end
farmbuyer@1 1476 e.reason = 'kill'
farmbuyer@1 1477 e.attempts = attempts
farmbuyer@1 1478 self.loot_clean = first or index-1
farmbuyer@1 1479 end
farmbuyer@1 1480
farmbuyer@2 1481 function addon:register_boss_mod (name, registration_func, deregistration_func)
farmbuyer@2 1482 assert(type(name)=='string')
farmbuyer@2 1483 assert(type(registration_func)=='function')
farmbuyer@2 1484 if deregistration_func ~= nil then assert(type(deregistration_func)=='function') end
farmbuyer@2 1485 self.bossmods[#self.bossmods+1] = {
farmbuyer@2 1486 n = name,
farmbuyer@2 1487 r = registration_func,
farmbuyer@2 1488 d = deregistration_func,
farmbuyer@2 1489 }
farmbuyer@5 1490 self.bossmods[name] = self.bossmods[#self.bossmods]
farmbuyer@5 1491 assert(self.bossmods[name].n == self.bossmods[#self.bossmods].n)
farmbuyer@2 1492 end
farmbuyer@1 1493
farmbuyer@2 1494 function _register_bossmod (self, force_p)
farmbuyer@2 1495 local x = self.bossmod_registered and self.bossmods[self.bossmod_registered]
farmbuyer@2 1496 if x then
farmbuyer@2 1497 if x.n == opts.bossmod and not force_p then
farmbuyer@2 1498 -- trying to register with already-registered boss mod
farmbuyer@2 1499 return
farmbuyer@2 1500 else
farmbuyer@2 1501 -- deregister
farmbuyer@2 1502 if x.d then x.d(self) end
farmbuyer@2 1503 end
farmbuyer@1 1504 end
farmbuyer@1 1505
farmbuyer@2 1506 x = nil
farmbuyer@2 1507 for k,v in ipairs(self.bossmods) do
farmbuyer@2 1508 if v.n == opts.bossmod then
farmbuyer@2 1509 x = k
farmbuyer@2 1510 break
farmbuyer@2 1511 end
farmbuyer@1 1512 end
farmbuyer@1 1513
farmbuyer@2 1514 if not x then
farmbuyer@2 1515 self.status_text = "|cffff1010No boss-mod found!|r"
farmbuyer@2 1516 self:Print(self.status_text)
farmbuyer@2 1517 return
farmbuyer@1 1518 end
farmbuyer@1 1519
farmbuyer@2 1520 if self.bossmods[x].r (self, _do_boss) then
farmbuyer@5 1521 --self.bossmod_registered = x
farmbuyer@5 1522 self.bossmod_registered = self.bossmods[x].n
farmbuyer@1 1523 else
farmbuyer@2 1524 self:Print("|cffff1010Boss mod registration failed|r")
farmbuyer@1 1525 end
farmbuyer@1 1526 end
farmbuyer@2 1527 end
farmbuyer@1 1528
farmbuyer@1 1529 -- Adding entries to the loot record, and tracking the corresponding timestamp.
farmbuyer@1 1530 do
farmbuyer@1 1531 -- This shouldn't be required. /sadface
farmbuyer@1 1532 local loot_entry_mt = {
farmbuyer@1 1533 __index = function (e,key)
farmbuyer@1 1534 if key == 'cols' then
farmbuyer@15 1535 pprint('mt', e.kind, "key is", key)
farmbuyer@1 1536 --tabledump(e) -- not actually that useful
farmbuyer@1 1537 addon:_fill_out_eoi_data(1)
farmbuyer@1 1538 end
farmbuyer@1 1539 return rawget(e,key)
farmbuyer@1 1540 end
farmbuyer@1 1541 }
farmbuyer@1 1542
farmbuyer@1 1543 -- Given a loot index, searches backwards for a timestamp. Returns that
farmbuyer@1 1544 -- index and the time entry, or nil if it falls off the beginning. Pass an
farmbuyer@1 1545 -- optional second index to search no earlier than it.
farmbuyer@1 1546 -- May also be able to make good use of this in forum-generation routine.
farmbuyer@1 1547 function addon:find_previous_time_entry(i,stop)
farmbuyer@17 1548 stop = stop or 0
farmbuyer@1 1549 while i > stop do
farmbuyer@1 1550 if g_loot[i].kind == 'time' then
farmbuyer@1 1551 return i, g_loot[i]
farmbuyer@1 1552 end
farmbuyer@1 1553 i = i - 1
farmbuyer@1 1554 end
farmbuyer@1 1555 end
farmbuyer@1 1556
farmbuyer@1 1557 -- format_timestamp (["format_string"], Day, [Loot])
farmbuyer@1 1558 -- DAY is a loot entry with kind=='time', and controls the date printed.
farmbuyer@1 1559 -- LOOT may be any kind of entry in the g_loot table. If present, it
farmbuyer@1 1560 -- overrides the hour and minute printed; if absent, those values are
farmbuyer@1 1561 -- taken from the DAY entry.
farmbuyer@1 1562 -- FORMAT_STRING may contain $x (x in Y/M/D/h/m) tokens.
farmbuyer@1 1563 local format_timestamp_values, point2dee = {}, "%.2d"
farmbuyer@1 1564 function addon:format_timestamp (fmt_opt, day_entry, time_entry_opt)
farmbuyer@1 1565 if not time_entry_opt then
farmbuyer@1 1566 if type(fmt_opt) == 'table' then -- Two entries, default format
farmbuyer@1 1567 time_entry_opt, day_entry = day_entry, fmt_opt
farmbuyer@1 1568 fmt_opt = "$Y/$M/$D $h:$m"
farmbuyer@41 1569 --elseif type(fmt_opt) == "string" then -- Day entry only, caller-specified format
farmbuyer@1 1570 end
farmbuyer@1 1571 end
farmbuyer@1 1572 --format_timestamp_values.Y = point2dee:format (day_entry.startday.year % 100)
farmbuyer@1 1573 format_timestamp_values.Y = ("%.4d"):format (day_entry.startday.year)
farmbuyer@1 1574 format_timestamp_values.M = point2dee:format (day_entry.startday.month)
farmbuyer@1 1575 format_timestamp_values.D = point2dee:format (day_entry.startday.day)
farmbuyer@1 1576 format_timestamp_values.h = point2dee:format ((time_entry_opt or day_entry).hour)
farmbuyer@1 1577 format_timestamp_values.m = point2dee:format ((time_entry_opt or day_entry).minute)
farmbuyer@1 1578 return fmt_opt:gsub ('%$([YMDhm])', format_timestamp_values)
farmbuyer@1 1579 end
farmbuyer@1 1580
farmbuyer@1 1581 local done_todays_date
farmbuyer@1 1582 function addon:_reset_timestamps()
farmbuyer@1 1583 done_todays_date = nil
farmbuyer@1 1584 end
farmbuyer@1 1585 local function do_todays_date()
farmbuyer@1 1586 local text, M, D, Y = makedate()
farmbuyer@1 1587 local found,ts = #g_loot+1
farmbuyer@1 1588 repeat
farmbuyer@1 1589 found,ts = addon:find_previous_time_entry(found-1)
farmbuyer@1 1590 if found and ts.startday.text == text then
farmbuyer@1 1591 done_todays_date = true
farmbuyer@1 1592 end
farmbuyer@1 1593 until done_todays_date or (not found)
farmbuyer@1 1594 if done_todays_date then
farmbuyer@1 1595 g_today = ts
farmbuyer@1 1596 else
farmbuyer@1 1597 done_todays_date = true
farmbuyer@1 1598 g_today = g_loot[addon._addLootEntry{
farmbuyer@1 1599 kind = 'time',
farmbuyer@1 1600 startday = {
farmbuyer@1 1601 text = text, month = M, day = D, year = Y
farmbuyer@1 1602 }
farmbuyer@1 1603 }]
farmbuyer@1 1604 end
farmbuyer@1 1605 addon:_fill_out_eoi_data(1)
farmbuyer@1 1606 end
farmbuyer@1 1607
farmbuyer@1 1608 -- Adding anything original to g_loot goes through this routine.
farmbuyer@1 1609 function addon._addLootEntry (e)
farmbuyer@1 1610 setmetatable(e,loot_entry_mt)
farmbuyer@1 1611
farmbuyer@1 1612 if not done_todays_date then do_todays_date() end
farmbuyer@1 1613
farmbuyer@1 1614 local h, m = GetGameTime()
farmbuyer@10 1615 --local localuptime = math.floor(GetTime())
farmbuyer@10 1616 local time_t = time()
farmbuyer@1 1617 e.hour = h
farmbuyer@1 1618 e.minute = m
farmbuyer@10 1619 e.stamp = time_t --localuptime
farmbuyer@1 1620 local index = #g_loot + 1
farmbuyer@1 1621 g_loot[index] = e
farmbuyer@1 1622 return index
farmbuyer@1 1623 end
farmbuyer@16 1624
farmbuyer@16 1625 -- Problem: (1) boss kill happens, (2) fast looting happens, (3) boss
farmbuyer@16 1626 -- cache cleanup fires. Result: loot shows up before boss kill entry.
farmbuyer@16 1627 -- Solution: We need to shuffle the boss entry above any of the loot
farmbuyer@16 1628 -- from that boss.
farmbuyer@16 1629 function addon._adjustBossOrder (is, should_be)
farmbuyer@16 1630 --pprint('loot', is, should_be)
farmbuyer@16 1631 if is == should_be then --pprint('loot', "equal, yay")
farmbuyer@16 1632 return
farmbuyer@16 1633 end
farmbuyer@17 1634 if (type(is)~='number') or (type(should_be)~='number') or (is < should_be) then
farmbuyer@19 1635 pprint('loot', is, should_be, "...the hell? bailing")
farmbuyer@16 1636 return
farmbuyer@16 1637 end
farmbuyer@16 1638 if g_loot[should_be].kind == 'time' then
farmbuyer@16 1639 should_be = should_be + 1
farmbuyer@16 1640 if is == should_be then
farmbuyer@16 1641 --pprint('loot', "needed to mark day entry, otherwise equal, yay")
farmbuyer@16 1642 return
farmbuyer@16 1643 end
farmbuyer@16 1644 end
farmbuyer@16 1645
farmbuyer@16 1646 assert(g_loot[is].kind == 'boss')
farmbuyer@16 1647 local boss = tremove (g_loot, is)
farmbuyer@16 1648 --pprint('loot', "MOVING", boss.bosskill)
farmbuyer@16 1649 tinsert (g_loot, should_be, boss)
farmbuyer@16 1650 return should_be
farmbuyer@16 1651 end
farmbuyer@1 1652 end
farmbuyer@1 1653
farmbuyer@19 1654 -- In the rare case of items getting put into the loot table without current
farmbuyer@19 1655 -- item cache data (which will have arrived by now).
farmbuyer@19 1656 function addon:do_item_cache_fixup()
farmbuyer@19 1657 self:Print("Fixing up missing item cache data...")
farmbuyer@19 1658
farmbuyer@19 1659 local numfound = 0
farmbuyer@19 1660 local borkedpat = '^'..UNKNOWN..': (%S+)'
farmbuyer@19 1661
farmbuyer@19 1662 for i,e in self:filtered_loot_iter('loot') do
farmbuyer@19 1663 if e.cache_miss then
farmbuyer@19 1664 local borked_id = e.itemname:match(borkedpat)
farmbuyer@19 1665 if borked_id then
farmbuyer@19 1666 numfound = numfound + 1
farmbuyer@19 1667 -- Best to use the safest and most flexible API here, which is GII and
farmbuyer@19 1668 -- its assload of return values.
farmbuyer@19 1669 local iname, ilink, iquality, _,_,_,_,_,_, itexture = GetItemInfo(borked_id)
farmbuyer@19 1670 if iname then
farmbuyer@19 1671 self:Print(" Entry %d patched up with %s.", i, ilink)
farmbuyer@19 1672 e.quality = iquality
farmbuyer@19 1673 e.itemname = iname
farmbuyer@19 1674 e.id = tonumber(ilink:match("item:(%d+)"))
farmbuyer@19 1675 e.itemlink = ilink
farmbuyer@19 1676 e.itexture = itexture
farmbuyer@19 1677 e.cache_miss = nil
farmbuyer@19 1678 end
farmbuyer@19 1679 end
farmbuyer@19 1680 end
farmbuyer@19 1681 end
farmbuyer@19 1682
farmbuyer@19 1683 self:Print("...finished. Found %d |4entry:entries; with weird data.", numfound)
farmbuyer@19 1684 end
farmbuyer@19 1685
farmbuyer@1 1686
farmbuyer@1 1687 ------ Saved texts
farmbuyer@1 1688 function addon:check_saved_table(silent_p)
farmbuyer@16 1689 local s = OuroLootSV_saved
farmbuyer@1 1690 if s and (#s > 0) then return s end
farmbuyer@16 1691 OuroLootSV_saved = nil
farmbuyer@1 1692 if not silent_p then self:Print("There are no saved loot texts.") end
farmbuyer@1 1693 end
farmbuyer@1 1694
farmbuyer@1 1695 function addon:save_list()
farmbuyer@1 1696 local s = self:check_saved_table(); if not s then return end;
farmbuyer@1 1697 for i,t in ipairs(s) do
farmbuyer@1 1698 self:Print("#%d %s %d entries %s", i, t.date, t.count, t.name)
farmbuyer@1 1699 end
farmbuyer@1 1700 end
farmbuyer@1 1701
farmbuyer@1 1702 function addon:save_saveas(name)
farmbuyer@16 1703 OuroLootSV_saved = OuroLootSV_saved or {}
farmbuyer@16 1704 local SV = OuroLootSV_saved
farmbuyer@16 1705 local n = #SV + 1
farmbuyer@1 1706 local save = {
farmbuyer@1 1707 name = name,
farmbuyer@1 1708 date = makedate(),
farmbuyer@1 1709 count = #g_loot,
farmbuyer@1 1710 }
farmbuyer@10 1711 for text in self:registered_textgen_iter() do
farmbuyer@10 1712 save[text] = g_loot[text]
farmbuyer@10 1713 end
farmbuyer@1 1714 self:Print("Saving current loot texts to #%d '%s'", n, name)
farmbuyer@16 1715 SV[n] = save
farmbuyer@1 1716 return self:save_list()
farmbuyer@1 1717 end
farmbuyer@1 1718
farmbuyer@1 1719 function addon:save_restore(num)
farmbuyer@1 1720 local s = self:check_saved_table(); if not s then return end;
farmbuyer@1 1721 if (not num) or (num > #s) then
farmbuyer@1 1722 return self:Print("Saved text number must be 1 - "..#s)
farmbuyer@1 1723 end
farmbuyer@1 1724 local save = s[num]
farmbuyer@1 1725 self:Print("Overwriting current loot data with saved text #%d '%s'", num, save.name)
farmbuyer@1 1726 self:Clear(--[[verbose_p=]]false)
farmbuyer@1 1727 -- Clear will already have displayed the window, and re-selected the first
farmbuyer@1 1728 -- tab. Set these up for when the text tabs are clicked.
farmbuyer@10 1729 for text in self:registered_textgen_iter() do
farmbuyer@10 1730 g_loot[text] = save[text]
farmbuyer@10 1731 end
farmbuyer@1 1732 end
farmbuyer@1 1733
farmbuyer@1 1734 function addon:save_delete(num)
farmbuyer@1 1735 local s = self:check_saved_table(); if not s then return end;
farmbuyer@1 1736 if (not num) or (num > #s) then
farmbuyer@1 1737 return self:Print("Saved text number must be 1 - "..#s)
farmbuyer@1 1738 end
farmbuyer@1 1739 self:Print("Deleting saved text #"..num)
farmbuyer@1 1740 tremove(s,num)
farmbuyer@1 1741 return self:save_list()
farmbuyer@1 1742 end
farmbuyer@1 1743
farmbuyer@1 1744
farmbuyer@1 1745 ------ Loot histories
farmbuyer@1 1746 -- history_all = {
farmbuyer@1 1747 -- ["Kilrogg"] = {
farmbuyer@1 1748 -- ["realm"] = "Kilrogg", -- not saved
farmbuyer@1 1749 -- ["st"] = { lib-st display table }, -- not saved
farmbuyer@1 1750 -- ["byname"] = { -- not saved
farmbuyer@1 1751 -- ["OtherPlayer"] = 2,
farmbuyer@1 1752 -- ["Farmbuyer"] = 1,
farmbuyer@1 1753 -- }
farmbuyer@1 1754 -- [1] = {
farmbuyer@1 1755 -- ["name"] = "Farmbuyer",
farmbuyer@1 1756 -- [1] = { id = nnnnn, when = "formatted timestamp for displaying" } -- most recent loot
farmbuyer@1 1757 -- [2] = { ......., [count = "x3"] } -- previous loot
farmbuyer@1 1758 -- },
farmbuyer@1 1759 -- [2] = {
farmbuyer@1 1760 -- ["name"] = "OtherPlayer",
farmbuyer@1 1761 -- ......
farmbuyer@1 1762 -- }, ......
farmbuyer@1 1763 -- },
farmbuyer@1 1764 -- ["OtherRealm"] = ......
farmbuyer@1 1765 -- }
farmbuyer@1 1766 do
farmbuyer@6 1767 local tsort = table.sort
farmbuyer@6 1768 local comp = function(L,R) return L.when > R.when end
farmbuyer@6 1769
farmbuyer@4 1770 -- Builds the map of names to array indices, using passed table or
farmbuyer@4 1771 -- self.history, and stores the result into its 'byname' field. Also
farmbuyer@4 1772 -- called from the GUI code at least once.
farmbuyer@1 1773 function addon:_build_history_names (opt_hist)
farmbuyer@1 1774 local hist = opt_hist or self.history
farmbuyer@1 1775 local m = {}
farmbuyer@1 1776 for i = 1, #hist do
farmbuyer@1 1777 m[hist[i].name] = i
farmbuyer@1 1778 end
farmbuyer@1 1779 hist.byname = m
farmbuyer@1 1780 end
farmbuyer@1 1781
farmbuyer@1 1782 -- Prepares and returns table to be used as self.history.
farmbuyer@1 1783 function addon:_prep_new_history_category (prev_table, realmname)
farmbuyer@1 1784 local t = prev_table or {
farmbuyer@1 1785 --kind = 'realm',
farmbuyer@6 1786 --realm = realmname,
farmbuyer@1 1787 }
farmbuyer@6 1788 t.realm = realmname
farmbuyer@1 1789
farmbuyer@1 1790 --[[
farmbuyer@1 1791 t.cols = setmetatable({
farmbuyer@1 1792 { value = realmname },
farmbuyer@1 1793 }, self.time_column1_used_mt)
farmbuyer@1 1794 ]]
farmbuyer@1 1795
farmbuyer@1 1796 if not t.byname then
farmbuyer@1 1797 self:_build_history_names (t)
farmbuyer@1 1798 end
farmbuyer@1 1799
farmbuyer@1 1800 return t
farmbuyer@1 1801 end
farmbuyer@1 1802
farmbuyer@4 1803 -- Maps a name to an array index, creating new tables if needed. Returns
farmbuyer@6 1804 -- the index and the table at that index.
farmbuyer@4 1805 function addon:get_loot_history (name)
farmbuyer@4 1806 local i
farmbuyer@4 1807 i = self.history.byname[name]
farmbuyer@4 1808 if not i then
farmbuyer@4 1809 i = #self.history + 1
farmbuyer@4 1810 self.history[i] = { name=name }
farmbuyer@4 1811 self.history.byname[name] = i
farmbuyer@4 1812 end
farmbuyer@6 1813 return i, self.history[i]
farmbuyer@4 1814 end
farmbuyer@4 1815
farmbuyer@1 1816 function addon:_addHistoryEntry (lootindex)
farmbuyer@1 1817 local e = g_loot[lootindex]
farmbuyer@6 1818 if e.kind ~= 'loot' then return end
farmbuyer@6 1819
farmbuyer@6 1820 local i,h = self:get_loot_history(e.person)
farmbuyer@25 1821 -- If any of these change, update the end of history_handle_disposition.
farmbuyer@1 1822 local n = {
farmbuyer@1 1823 id = e.id,
farmbuyer@1 1824 when = self:format_timestamp (g_today, e),
farmbuyer@1 1825 count = e.count,
farmbuyer@1 1826 }
farmbuyer@6 1827 tinsert (h, 1, n)
farmbuyer@24 1828 e.history_unique = n.id .. ' ' .. n.when
farmbuyer@6 1829 end
farmbuyer@6 1830
farmbuyer@24 1831 -- Create new history table based on current loot.
farmbuyer@6 1832 function addon:rewrite_history (realmname)
farmbuyer@6 1833 local r = assert(realmname)
farmbuyer@6 1834 self.history_all[r] = self:_prep_new_history_category (nil, r)
farmbuyer@6 1835 self.history = self.history_all[r]
farmbuyer@6 1836
farmbuyer@6 1837 local g_today_real = g_today
farmbuyer@6 1838 for i,e in ipairs(g_loot) do
farmbuyer@6 1839 if e.kind == 'time' then
farmbuyer@6 1840 g_today = e
farmbuyer@6 1841 elseif e.kind == 'loot' then
farmbuyer@6 1842 self:_addHistoryEntry(i)
farmbuyer@6 1843 end
farmbuyer@6 1844 end
farmbuyer@6 1845 g_today = g_today_real
farmbuyer@6 1846 self.hist_clean = nil
farmbuyer@6 1847
farmbuyer@6 1848 -- safety measure: resort players' tables based on formatted timestamp
farmbuyer@6 1849 for i,h in ipairs(self.history) do
farmbuyer@6 1850 tsort (h, comp)
farmbuyer@6 1851 end
farmbuyer@6 1852 end
farmbuyer@6 1853
farmbuyer@24 1854 -- Clears all but latest entry for each player.
farmbuyer@6 1855 function addon:preen_history (realmname)
farmbuyer@6 1856 local r = assert(realmname)
farmbuyer@6 1857 for i,h in ipairs(self.history) do
farmbuyer@6 1858 tsort (h, comp)
farmbuyer@6 1859 while #h > 1 do
farmbuyer@6 1860 tremove (h)
farmbuyer@6 1861 end
farmbuyer@6 1862 end
farmbuyer@1 1863 end
farmbuyer@24 1864
farmbuyer@25 1865 -- Given an entry in a g_loot table, looks up the corresponding history
farmbuyer@25 1866 -- entry. Returns the player's index and history table (as in get_loot_history)
farmbuyer@25 1867 -- and the index into that table of the loot entry. On failure, returns nil
farmbuyer@25 1868 -- and an error message ready to be formatted with the loot's name/itemlink.
farmbuyer@25 1869 function addon:_history_by_loot_id (loot, operation_text)
farmbuyer@25 1870 -- Using assert() here would be concatenating error strings that probably
farmbuyer@25 1871 -- wouldn't be used. Do more verbose testing instead.
farmbuyer@25 1872 if type(loot) ~= 'table' then
farmbuyer@25 1873 error("trying to "..operation_text.." nonexistant entry")
farmbuyer@25 1874 end
farmbuyer@25 1875 if loot.kind ~= 'loot' then
farmbuyer@25 1876 error("trying to "..operation_text.." something that isn't loot")
farmbuyer@25 1877 end
farmbuyer@24 1878
farmbuyer@25 1879 local player = loot.person
farmbuyer@25 1880 local tag = loot.history_unique
farmbuyer@24 1881 local errtxt
farmbuyer@25 1882 local player_i, player_h, hist_i
farmbuyer@24 1883
farmbuyer@24 1884 if not tag then
farmbuyer@24 1885 errtxt = "Entry for %s is missing a history tag!"
farmbuyer@24 1886 else
farmbuyer@25 1887 player_i,player_h = self:get_loot_history(player)
farmbuyer@25 1888 for i,h in ipairs(player_h) do
farmbuyer@24 1889 local unique = h.id .. ' ' .. h.when
farmbuyer@24 1890 if unique == tag then
farmbuyer@25 1891 hist_i = i
farmbuyer@24 1892 break
farmbuyer@24 1893 end
farmbuyer@24 1894 end
farmbuyer@25 1895 if not hist_i then
farmbuyer@24 1896 -- 1) loot an item, 2) clear old history, 3) reassign from current loot
farmbuyer@24 1897 -- Bah. Anybody that tricky is already recoding the tables directly anyhow.
farmbuyer@24 1898 errtxt = "There is no record of %s ever having been assigned!"
farmbuyer@24 1899 end
farmbuyer@24 1900 end
farmbuyer@24 1901
farmbuyer@24 1902 if errtxt then
farmbuyer@25 1903 return nil, errtxt
farmbuyer@24 1904 end
farmbuyer@25 1905 return player_i, player_h, hist_i
farmbuyer@25 1906 end
farmbuyer@24 1907
farmbuyer@25 1908 function addon:reassign_loot (index, to_name)
farmbuyer@25 1909 assert(type(to_name)=='string' and to_name:len()>0)
farmbuyer@25 1910 local e = g_loot[index]
farmbuyer@25 1911 local from_i, from_h, hist_i = self:_history_by_loot_id (e, "reassign")
farmbuyer@25 1912 local from_name = e.person
farmbuyer@25 1913 local to_i,to_h = self:get_loot_history(to_name)
farmbuyer@25 1914
farmbuyer@25 1915 if not from_i then
farmbuyer@25 1916 -- from_h is the formatted error text
farmbuyer@25 1917 self:Print(from_h .. " Loot will be reassigned, but history will NOT be updated.", e.itemlink)
farmbuyer@25 1918 else
farmbuyer@25 1919 local hist_h = tremove (from_h, hist_i)
farmbuyer@25 1920 tinsert (to_h, 1, hist_h)
farmbuyer@25 1921 tsort (from_h, comp)
farmbuyer@25 1922 tsort (to_h, comp)
farmbuyer@25 1923 end
farmbuyer@25 1924 e.person = to_name
farmbuyer@25 1925 e.person_class = select(2,UnitClass(to_name))
farmbuyer@25 1926 self.hist_clean = nil
farmbuyer@25 1927
farmbuyer@25 1928 self:Print("Reassigned entry %d/%s from '%s' to '%s'.", index, e.itemlink, from_name, to_name)
farmbuyer@25 1929 end
farmbuyer@25 1930
farmbuyer@36 1931 -- Similar to _addHistoryEntry. The second arg may be a loot entry
farmbuyer@36 1932 -- (which used to be at LOOTINDEX), or nil (and the loot entry will
farmbuyer@36 1933 -- be pulled from LOOTINDEX instead).
farmbuyer@36 1934 function addon:_delHistoryEntry (lootindex, opt_e)
farmbuyer@36 1935 local e = opt_e or g_loot[lootindex]
farmbuyer@36 1936 if e.kind ~= 'loot' then return end
farmbuyer@36 1937
farmbuyer@36 1938 local from_i, from_h, hist_i = self:_history_by_loot_id (e, "delete")
farmbuyer@36 1939 if not from_i then
farmbuyer@36 1940 -- from_h is the formatted error text
farmbuyer@36 1941 self:Print(from_h .. " Loot will be deleted, but history will NOT be updated.", e.itemlink)
farmbuyer@36 1942 return
farmbuyer@36 1943 end
farmbuyer@36 1944
farmbuyer@36 1945 --[[local hist_h = ]]tremove (from_h, hist_i)
farmbuyer@36 1946 tsort (from_h, comp)
farmbuyer@36 1947 self.hist_clean = nil
farmbuyer@36 1948
farmbuyer@36 1949 self:Print("Removed history entry %d/%s from '%s'.", lootindex, e.itemlink, e.person)
farmbuyer@36 1950 end
farmbuyer@36 1951
farmbuyer@25 1952 -- Any extra work for the "Mark as <x>" dropdown actions. The
farmbuyer@25 1953 -- corresponding <x> will already have been assigned in the loot entry.
farmbuyer@25 1954 local deleted_cache = {} --setmetatable({}, {__mode='k'})
farmbuyer@25 1955 function addon:history_handle_disposition (index, olddisp)
farmbuyer@25 1956 local e = g_loot[index]
farmbuyer@25 1957 -- Standard disposition has a nil entry, but that's tedious in debug
farmbuyer@25 1958 -- output, so force to a string instead.
farmbuyer@25 1959 olddisp = olddisp or 'normal'
farmbuyer@25 1960 local newdisp = e.disposition or 'normal'
farmbuyer@25 1961 -- Ignore misclicks and the like
farmbuyer@25 1962 if olddisp == newdisp then return end
farmbuyer@25 1963
farmbuyer@25 1964 local name = e.person
farmbuyer@25 1965
farmbuyer@25 1966 if (newdisp == 'shard' or newdisp == 'gvault') then
farmbuyer@25 1967 local name_i, name_h, hist_i = self:_history_by_loot_id (e, "mark")
farmbuyer@25 1968 -- remove history entry
farmbuyer@25 1969 if hist_i then
farmbuyer@25 1970 local hist_h = tremove (name_h, hist_i)
farmbuyer@25 1971 deleted_cache[e.history_unique] = hist_h
farmbuyer@25 1972 self.hist_clean = nil
farmbuyer@25 1973 elseif (olddisp == 'shard' or olddisp == 'gvault') then
farmbuyer@25 1974 -- Sharding a vault item, or giving the auto-sharder something to bank,
farmbuyer@25 1975 -- etc, wouldn't necessarily have had a history entry to begin with.
farmbuyer@25 1976 else
farmbuyer@25 1977 self:Print(name_h .. " Loot has been marked, but history will NOT be updated.", e.itemlink)
farmbuyer@25 1978 end
farmbuyer@25 1979 return
farmbuyer@25 1980 end
farmbuyer@25 1981
farmbuyer@25 1982 if (olddisp == 'shard' or olddisp == 'gvault')
farmbuyer@25 1983 and (newdisp == 'normal' or newdisp == 'offspec')
farmbuyer@25 1984 then
farmbuyer@25 1985 local name_i, name_h = self:get_loot_history(name)
farmbuyer@25 1986
farmbuyer@25 1987 -- Must create a new history entry. Could call '_addHistoryEntry(index)'
farmbuyer@25 1988 -- but that would duplicate a lot of effort. To start with, check the
farmbuyer@25 1989 -- cache of stuff we've already deleted; if it's not there then just do
farmbuyer@25 1990 -- the same steps as _addHistoryEntry.
farmbuyer@25 1991 local entry
farmbuyer@25 1992 if e.history_unique and deleted_cache[e.history_unique] then
farmbuyer@25 1993 entry = deleted_cache[e.history_unique]
farmbuyer@25 1994 deleted_cache[e.history_unique] = nil
farmbuyer@25 1995 end
farmbuyer@25 1996 local when = g_today and self:format_timestamp (g_today, e) or tostring(e.stamp)
farmbuyer@25 1997 entry = entry or {
farmbuyer@25 1998 id = e.id,
farmbuyer@25 1999 when = when,
farmbuyer@25 2000 count = e.count,
farmbuyer@25 2001 }
farmbuyer@25 2002 tinsert (name_h, 1, entry)
farmbuyer@25 2003 e.history_unique = e.history_unique or (entry.id .. ' ' .. entry.when)
farmbuyer@25 2004 self.hist_clean = nil
farmbuyer@25 2005 return
farmbuyer@25 2006 end
farmbuyer@24 2007 end
farmbuyer@1 2008 end
farmbuyer@1 2009
farmbuyer@1 2010
farmbuyer@1 2011 ------ Player communication
farmbuyer@1 2012 do
farmbuyer@1 2013 local function adduser (name, status, active)
farmbuyer@1 2014 if status then addon.sender_list.names[name] = status end
farmbuyer@1 2015 if active then addon.sender_list.active[name] = active end
farmbuyer@1 2016 end
farmbuyer@1 2017
farmbuyer@1 2018 -- Incoming handler functions. All take the sender name and the incoming
farmbuyer@1 2019 -- tag as the first two arguments. All of these are active even when the
farmbuyer@1 2020 -- player is not tracking loot, so test for that when appropriate.
farmbuyer@1 2021 local OCR_funcs = {}
farmbuyer@1 2022
farmbuyer@1 2023 OCR_funcs.ping = function (sender)
farmbuyer@1 2024 pprint('comm', "incoming ping from", sender)
farmbuyer@1 2025 addon:whispercast (sender, 'pong', addon.revision,
farmbuyer@1 2026 addon.enabled and "tracking" or (addon.rebroadcast and "broadcasting" or "disabled"))
farmbuyer@1 2027 end
farmbuyer@1 2028 OCR_funcs.pong = function (sender, _, rev, status)
farmbuyer@17 2029 local s = ("|cff00ff00%s|r %s is |cff00ffff%s|r"):format(sender,rev,status)
farmbuyer@1 2030 addon:Print("Echo: ", s)
farmbuyer@1 2031 adduser (sender, s, status=="tracking" or status=="broadcasting" or nil)
farmbuyer@1 2032 end
farmbuyer@27 2033 OCR_funcs.revcheck = function (sender, _, revlarge)
farmbuyer@27 2034 addon.dprint('comm', "revcheck, sender", sender)
farmbuyer@27 2035 addon:_check_revision (revlarge)
farmbuyer@27 2036 end
farmbuyer@1 2037
farmbuyer@1 2038 OCR_funcs.loot = function (sender, _, recip, item, count, extratext)
farmbuyer@1 2039 addon.dprint('comm', "DOTloot, sender", sender, "recip", recip, "item", item, "count", count)
farmbuyer@1 2040 if not addon.enabled then return end
farmbuyer@1 2041 adduser (sender, nil, true)
farmbuyer@1 2042 addon:CHAT_MSG_LOOT ("broadcast", recip, item, count, sender, extratext)
farmbuyer@1 2043 end
farmbuyer@1 2044
farmbuyer@1 2045 OCR_funcs.boss = function (sender, _, reason, bossname, instancetag)
farmbuyer@1 2046 addon.dprint('comm', "DOTboss, sender", sender, "reason", reason, "name", bossname, "it", instancetag)
farmbuyer@1 2047 if not addon.enabled then return end
farmbuyer@1 2048 adduser (sender, nil, true)
farmbuyer@1 2049 addon:on_boss_broadcast (reason, bossname, instancetag)
farmbuyer@1 2050 end
farmbuyer@1 2051
farmbuyer@1 2052 OCR_funcs.bcast_req = function (sender)
farmbuyer@1 2053 if addon.debug.comm or ((not g_wafer_thin) and (not addon.rebroadcast))
farmbuyer@1 2054 then
farmbuyer@38 2055 addon:Print("%s has requested additional broadcasters! Choose %s to enable rebroadcasting, or %s to remain off and also ignore rebroadcast requests for as long as you're logged in.",
farmbuyer@1 2056 sender,
farmbuyer@1 2057 addon.format_hypertext('bcaston',"the red pill",'|cffff4040'),
farmbuyer@1 2058 addon.format_hypertext('waferthin',"the blue pill",'|cff0070dd'))
farmbuyer@1 2059 end
farmbuyer@18 2060 addon.popped = true
farmbuyer@1 2061 end
farmbuyer@1 2062
farmbuyer@1 2063 OCR_funcs.bcast_responder = function (sender)
farmbuyer@1 2064 if addon.debug.comm or addon.requesting or
farmbuyer@1 2065 ((not g_wafer_thin) and (not addon.rebroadcast))
farmbuyer@1 2066 then
farmbuyer@1 2067 addon:Print(sender, "has answered the call and is now broadcasting loot.")
farmbuyer@1 2068 end
farmbuyer@1 2069 end
farmbuyer@1 2070 -- remove this tag once it's all tested
farmbuyer@1 2071 OCR_funcs.bcast_denied = function (sender)
farmbuyer@1 2072 if addon.requesting then addon:Print(sender, "declines futher broadcast requests.") end
farmbuyer@1 2073 end
farmbuyer@1 2074
farmbuyer@1 2075 -- Incoming message dispatcher
farmbuyer@1 2076 local function dotdotdot (sender, tag, ...)
farmbuyer@1 2077 local f = OCR_funcs[tag]
farmbuyer@1 2078 addon.dprint('comm', ":... processing",tag,"from",sender)
farmbuyer@1 2079 if f then return f(sender,tag,...) end
farmbuyer@1 2080 addon.dprint('comm', "unknown comm message",tag",from", sender)
farmbuyer@1 2081 end
farmbuyer@1 2082 -- Recent message cache
farmbuyer@1 2083 addon.recent_messages = create_new_cache ('comm', comm_cleanup_ttl)
farmbuyer@1 2084
farmbuyer@1 2085 function addon:OnCommReceived (prefix, msg, distribution, sender)
farmbuyer@1 2086 if prefix ~= self.ident then return end
farmbuyer@1 2087 if not self.debug.comm then
farmbuyer@1 2088 if distribution ~= "RAID" and distribution ~= "WHISPER" then return end
farmbuyer@1 2089 if sender == my_name then return end
farmbuyer@1 2090 end
farmbuyer@1 2091 self.dprint('comm', ":OCR from", sender, "message is", msg)
farmbuyer@1 2092
farmbuyer@1 2093 if self.recent_messages:test(msg) then
farmbuyer@40 2094 self.dprint('cache', "OCR message <",msg,"> already in cache, skipping")
farmbuyer@40 2095 return
farmbuyer@1 2096 end
farmbuyer@1 2097 self.recent_messages:add(msg)
farmbuyer@1 2098
farmbuyer@1 2099 -- Nothing is actually returned, just (ab)using tail calls.
farmbuyer@1 2100 return dotdotdot(sender,strsplit('\a',msg))
farmbuyer@1 2101 end
farmbuyer@1 2102
farmbuyer@1 2103 function addon:OnCommReceivedNocache (prefix, msg, distribution, sender)
farmbuyer@1 2104 if prefix ~= self.identTg then return end
farmbuyer@1 2105 if not self.debug.comm then
farmbuyer@1 2106 if distribution ~= "WHISPER" then return end
farmbuyer@1 2107 if sender == my_name then return end
farmbuyer@1 2108 end
farmbuyer@1 2109 self.dprint('comm', ":OCRN from", sender, "message is", msg)
farmbuyer@1 2110 return dotdotdot(sender,strsplit('\a',msg))
farmbuyer@1 2111 end
farmbuyer@1 2112 end
farmbuyer@1 2113
farmbuyer@1 2114 -- vim:noet