Mercurial > wow > ouroloot
comparison core.lua @ 1:822b6ca3ef89
Import of 2.15, moving to wowace svn.
| author | Farmbuyer of US-Kilrogg <farmbuyer@gmail.com> |
|---|---|
| date | Sat, 16 Apr 2011 06:03:29 +0000 |
| parents | |
| children | fe437e761ef8 |
comparison
equal
deleted
inserted
replaced
| 0:0f14a1e5364d | 1:822b6ca3ef89 |
|---|---|
| 1 local addon = select(2,...) | |
| 2 | |
| 3 --[==[ | |
| 4 g_loot's numeric indices are loot entries (including titles, separators, | |
| 5 etc); its named indices are: | |
| 6 - forum: saved text from forum markup window, default nil | |
| 7 - attend: saved text from raid attendence window, default nil | |
| 8 - printed.FOO: last index formatted into text window FOO, default 0 | |
| 9 - saved: table of copies of saved texts, default nil; keys are numeric | |
| 10 indices of tables, subkeys of those are name/forum/attend/date | |
| 11 - autoshard: optional name of disenchanting player, default nil | |
| 12 - threshold: optional loot threshold, default nil | |
| 13 | |
| 14 Functions arranged like this, with these lables (for jumping to). As a | |
| 15 rule, member functions with UpperCamelCase names are called directly by | |
| 16 user-facing code, ones with lowercase names are "one step removed", and | |
| 17 names with leading underscores are strictly internal helper functions. | |
| 18 ------ Saved variables | |
| 19 ------ Constants | |
| 20 ------ Addon member data | |
| 21 ------ Locals | |
| 22 ------ Expiring caches | |
| 23 ------ Ace3 framework stuff | |
| 24 ------ Event handlers | |
| 25 ------ Slash command handler | |
| 26 ------ On/off | |
| 27 ------ Behind the scenes routines | |
| 28 ------ Saved texts | |
| 29 ------ Loot histories | |
| 30 ------ Player communication | |
| 31 | |
| 32 This started off as part of a raid addon package written by somebody else. | |
| 33 After he retired, I began modifying the code. Eventually I set aside the | |
| 34 entire package and rewrote the loot tracker module from scratch. Many of the | |
| 35 variable/function naming conventions (sv_*, g_*, and family) stayed across the | |
| 36 rewrite. Some variables are needlessly initialized to nil just to look uniform. | |
| 37 | |
| 38 ]==] | |
| 39 | |
| 40 ------ Saved variables | |
| 41 OuroLootSV = nil -- possible copy of g_loot | |
| 42 OuroLootSV_opts = nil -- same as option_defaults until changed | |
| 43 OuroLootSV_hist = nil | |
| 44 | |
| 45 | |
| 46 ------ Constants | |
| 47 local option_defaults = { | |
| 48 ['popup_on_join'] = true, | |
| 49 ['register_slashloot'] = true, | |
| 50 ['scroll_to_bottom'] = true, | |
| 51 ['chatty_on_kill'] = false, | |
| 52 ['no_tracking_wipes'] = false, | |
| 53 ['snarky_boss'] = true, | |
| 54 ['keybinding'] = false, | |
| 55 ['keybinding_text'] = 'CTRL-SHIFT-O', | |
| 56 ['forum'] = { | |
| 57 ['[url]'] = '[url=http://www.wowhead.com/?item=$I]$N[/url]$X - $T', | |
| 58 ['[item] by name'] = '[item]$N[/item]$X - $T', | |
| 59 ['[item] by ID'] = '[item]$I[/item]$X - $T', | |
| 60 ['Custom...'] = '', | |
| 61 }, | |
| 62 ['forum_current'] = '[item] by name', | |
| 63 } | |
| 64 local virgin = "First time loaded? Hi! Use the /ouroloot or /loot command" | |
| 65 .." to show the main display. You should probably browse the instructions" | |
| 66 .." if you've never used this before; %s to display the help window. This" | |
| 67 .." welcome message will not intrude again." | |
| 68 local qualnames = { | |
| 69 ['gray'] = 0, ['grey'] = 0, ['poor'] = 0, ['trash'] = 0, | |
| 70 ['white'] = 1, ['common'] = 1, | |
| 71 ['green'] = 2, ['uncommon'] = 2, | |
| 72 ['blue'] = 3, ['rare'] = 3, | |
| 73 ['epic'] = 4, ['purple'] = 4, | |
| 74 ['legendary'] = 5, ['orange'] = 5, | |
| 75 ['artifact'] = 6, | |
| 76 --['heirloom'] = 7, | |
| 77 } | |
| 78 local my_name = UnitName('player') | |
| 79 local comm_cleanup_ttl = 5 -- seconds in the cache | |
| 80 | |
| 81 | |
| 82 ------ Addon member data | |
| 83 local flib = LibStub("LibFarmbuyer") | |
| 84 addon.author_debug = flib.author_debug | |
| 85 | |
| 86 -- Play cute games with namespaces here just to save typing. | |
| 87 do local _G = _G setfenv (1, addon) | |
| 88 | |
| 89 revision = 15 | |
| 90 ident = "OuroLoot2" | |
| 91 identTg = "OuroLoot2Tg" | |
| 92 status_text = nil | |
| 93 | |
| 94 DEBUG_PRINT = false | |
| 95 debug = { | |
| 96 comm = false, | |
| 97 loot = false, | |
| 98 flow = false, | |
| 99 notraid = false, | |
| 100 cache = false, | |
| 101 } | |
| 102 function dprint (t,...) | |
| 103 if DEBUG_PRINT and debug[t] then return _G.print("<"..t.."> ",...) end | |
| 104 end | |
| 105 | |
| 106 if author_debug then | |
| 107 function pprint(t,...) | |
| 108 return _G.print("<<"..t..">> ",...) | |
| 109 end | |
| 110 else | |
| 111 pprint = flib.nullfunc | |
| 112 end | |
| 113 | |
| 114 enabled = false | |
| 115 rebroadcast = false | |
| 116 display = nil -- display frame, when visible | |
| 117 loot_clean = nil -- index of last GUI entry with known-current visual data | |
| 118 sender_list = {active={},names={}} -- this should be reworked | |
| 119 threshold = debug.loot and 0 or 3 -- rare by default | |
| 120 sharder = nil -- name of person whose loot is marked as shards | |
| 121 | |
| 122 -- The rest is also used in the GUI: | |
| 123 | |
| 124 popped = nil -- non-nil when reminder has been shown, actual value unimportant | |
| 125 | |
| 126 -- This is an amalgamation of all four LOOT_ITEM_* patterns. | |
| 127 -- Captures: 1 person/You, 2 itemstring, 3 rest of string after final |r until '.' | |
| 128 -- Can change 'loot' to 'item' to trigger on, e.g., extracting stuff from mail. | |
| 129 loot_pattern = "(%S+) receives? loot:.*|cff%x+|H(.-)|h.*|r(.*)%.$" | |
| 130 | |
| 131 dbm_registered = nil | |
| 132 requesting = nil -- for prompting for additional rebroadcasters | |
| 133 | |
| 134 thresholds, quality_hexes = {}, {} | |
| 135 for i = 0,6 do | |
| 136 local hex = _G.select(4,_G.GetItemQualityColor(i)) | |
| 137 local desc = _G["ITEM_QUALITY"..i.."_DESC"] | |
| 138 quality_hexes[i] = hex | |
| 139 thresholds[i] = hex .. desc .. "|r" | |
| 140 end | |
| 141 | |
| 142 _G.setfenv (1, _G) | |
| 143 end | |
| 144 | |
| 145 addon = LibStub("AceAddon-3.0"):NewAddon(addon, "Ouro Loot", | |
| 146 "AceTimer-3.0", "AceComm-3.0", "AceConsole-3.0", "AceEvent-3.0") | |
| 147 | |
| 148 | |
| 149 ------ Locals | |
| 150 local g_loot = nil | |
| 151 local g_restore_p = nil | |
| 152 local g_saved_tmp = nil -- restoring across a clear | |
| 153 local g_wafer_thin = nil -- for prompting for additional rebroadcasters | |
| 154 local g_today = nil -- "today" entry in g_loot | |
| 155 local opts = nil | |
| 156 | |
| 157 local pairs, ipairs, tinsert, tremove, tonumber = pairs, ipairs, table.insert, table.remove, tonumber | |
| 158 | |
| 159 local pprint, tabledump = addon.pprint, flib.tabledump | |
| 160 | |
| 161 -- En masse forward decls of symbols defined inside local blocks | |
| 162 local _registerDBM -- break out into separate file | |
| 163 local makedate, create_new_cache, _init | |
| 164 | |
| 165 -- Hypertext support, inspired by DBM broadcast pizza timers | |
| 166 do | |
| 167 local hypertext_format_str = "|HOuroRaid:%s|h%s[%s]|r|h" | |
| 168 | |
| 169 function addon.format_hypertext (code, text, color) | |
| 170 return hypertext_format_str:format (code, | |
| 171 type(color)=='number' and addon.quality_hexes[color] or color, | |
| 172 text) | |
| 173 end | |
| 174 | |
| 175 DEFAULT_CHAT_FRAME:HookScript("OnHyperlinkClick", function(self, link, string, mousebutton) | |
| 176 local ltype, arg = strsplit(":",link) | |
| 177 if ltype ~= "OuroRaid" then return end | |
| 178 if arg == 'openloot' then | |
| 179 addon:BuildMainDisplay() | |
| 180 elseif arg == 'help' then | |
| 181 addon:BuildMainDisplay('help') | |
| 182 elseif arg == 'bcaston' then | |
| 183 if not addon.rebroadcast then | |
| 184 addon:Activate(nil,true) | |
| 185 end | |
| 186 addon:broadcast('bcast_responder') | |
| 187 elseif arg == 'waferthin' then -- mint? it's wafer thin! | |
| 188 g_wafer_thin = true -- fuck off, I'm full | |
| 189 addon:broadcast('bcast_denied') -- remove once tested | |
| 190 end | |
| 191 end) | |
| 192 | |
| 193 local old = ItemRefTooltip.SetHyperlink | |
| 194 function ItemRefTooltip:SetHyperlink (link, ...) | |
| 195 if link:match("^OuroRaid") then return end | |
| 196 return old (self, link, ...) | |
| 197 end | |
| 198 end | |
| 199 | |
| 200 do | |
| 201 -- copied here because it's declared local to the calendar ui, thanks blizz >< | |
| 202 local CALENDAR_FULLDATE_MONTH_NAMES = { | |
| 203 FULLDATE_MONTH_JANUARY, FULLDATE_MONTH_FEBRUARY, FULLDATE_MONTH_MARCH, | |
| 204 FULLDATE_MONTH_APRIL, FULLDATE_MONTH_MAY, FULLDATE_MONTH_JUNE, | |
| 205 FULLDATE_MONTH_JULY, FULLDATE_MONTH_AUGUST, FULLDATE_MONTH_SEPTEMBER, | |
| 206 FULLDATE_MONTH_OCTOBER, FULLDATE_MONTH_NOVEMBER, FULLDATE_MONTH_DECEMBER, | |
| 207 } | |
| 208 -- returns "dd Month yyyy", mm, dd, yyyy | |
| 209 function makedate() | |
| 210 Calendar_LoadUI() | |
| 211 local _, M, D, Y = CalendarGetDate() | |
| 212 local text = ("%d %s %d"):format(D, CALENDAR_FULLDATE_MONTH_NAMES[M], Y) | |
| 213 return text, M, D, Y | |
| 214 end | |
| 215 end | |
| 216 | |
| 217 -- Returns an instance name or abbreviation | |
| 218 local function instance_tag() | |
| 219 local name, typeof, diffcode, diffstr, _, perbossheroic, isdynamic = GetInstanceInfo() | |
| 220 local t | |
| 221 name = addon.instance_abbrev[name] or name | |
| 222 if typeof == "none" then return name end | |
| 223 -- diffstr is "5 Player", "10 Player (Heroic)", etc. ugh. | |
| 224 if diffcode == 1 then | |
| 225 t = ((GetNumRaidMembers()>0) and "10" or "5") | |
| 226 elseif diffcode == 2 then | |
| 227 t = ((GetNumRaidMembers()>0) and "25" or "5h") | |
| 228 elseif diffcode == 3 then | |
| 229 t = "10h" | |
| 230 elseif diffcode == 4 then | |
| 231 t = "25h" | |
| 232 end | |
| 233 -- dynamic difficulties always return normal "codes" | |
| 234 if isdynamic and perbossheroic == 1 then | |
| 235 t = t .. "h" | |
| 236 end | |
| 237 return name .. "(" .. t .. ")" | |
| 238 end | |
| 239 addon.instance_tag = instance_tag -- grumble | |
| 240 | |
| 241 | |
| 242 ------ Expiring caches | |
| 243 --[[ | |
| 244 foo = create_new_cache("myfoo",15[,cleanup]) -- ttl | |
| 245 foo:add("blah") | |
| 246 foo:test("blah") -- returns true | |
| 247 ]] | |
| 248 do | |
| 249 local caches = {} | |
| 250 local cleanup_group = AnimTimerFrame:CreateAnimationGroup() | |
| 251 cleanup_group:SetLooping("REPEAT") | |
| 252 cleanup_group:SetScript("OnLoop", function(cg) | |
| 253 addon.dprint('cache',"OnLoop firing") | |
| 254 local now = GetTime() | |
| 255 local alldone = true | |
| 256 -- this is ass-ugly | |
| 257 for _,c in ipairs(caches) do | |
| 258 while (#c > 0) and (now - c[1].t > c.ttl) do | |
| 259 addon.dprint('cache', c.name, "cache removing",c[1].t, c[1].m) | |
| 260 tremove(c,1) | |
| 261 end | |
| 262 alldone = alldone and (#c == 0) | |
| 263 end | |
| 264 if alldone then | |
| 265 addon.dprint('cache',"OnLoop finishing animation group") | |
| 266 cleanup_group:Finish() | |
| 267 for _,c in ipairs(caches) do | |
| 268 if c.func then c:func() end | |
| 269 end | |
| 270 end | |
| 271 addon.dprint('cache',"OnLoop done") | |
| 272 end) | |
| 273 | |
| 274 local function _add (cache, x) | |
| 275 tinsert(cache, {t=GetTime(),m=x}) | |
| 276 if not cleanup_group:IsPlaying() then | |
| 277 addon.dprint('cache', cache.name, "STARTING animation group") | |
| 278 cache.cleanup:SetDuration(2) -- hmmm | |
| 279 cleanup_group:Play() | |
| 280 end | |
| 281 end | |
| 282 local function _test (cache, x) | |
| 283 for _,v in ipairs(cache) do | |
| 284 if v.m == x then return true end | |
| 285 end | |
| 286 end | |
| 287 function create_new_cache (name, ttl, on_alldone) | |
| 288 local c = { | |
| 289 ttl = ttl, | |
| 290 name = name, | |
| 291 add = _add, | |
| 292 test = _test, | |
| 293 cleanup = cleanup_group:CreateAnimation("Animation"), | |
| 294 func = on_alldone, | |
| 295 } | |
| 296 c.cleanup:SetOrder(1) | |
| 297 -- setting OnFinished for cleanup fires at the end of each inner loop, | |
| 298 -- with no 'requested' argument to distinguish cases. thus, on_alldone. | |
| 299 tinsert (caches, c) | |
| 300 return c | |
| 301 end | |
| 302 end | |
| 303 | |
| 304 | |
| 305 ------ Ace3 framework stuff | |
| 306 function addon:OnInitialize() | |
| 307 -- VARIABLES_LOADED has fired by this point; test if we're doing something like | |
| 308 -- relogging during a raid and already have collected loot data | |
| 309 g_restore_p = OuroLootSV ~= nil | |
| 310 self.dprint('flow', "oninit sets restore as", g_restore_p) | |
| 311 | |
| 312 if OuroLootSV_opts == nil then | |
| 313 OuroLootSV_opts = {} | |
| 314 self:ScheduleTimer(function(s) | |
| 315 s:Print(virgin, s.format_hypertext('help',"click here",ITEM_QUALITY_UNCOMMON)) | |
| 316 virgin = nil | |
| 317 end,10,self) | |
| 318 end | |
| 319 opts = OuroLootSV_opts | |
| 320 for opt,default in pairs(option_defaults) do | |
| 321 if opts[opt] == nil then | |
| 322 opts[opt] = default | |
| 323 end | |
| 324 end | |
| 325 option_defaults = nil | |
| 326 -- transition/remove old options | |
| 327 opts["forum_use_itemid"] = nil | |
| 328 if opts["forum_format"] then | |
| 329 opts.forum["Custom..."] = opts["forum_format"] | |
| 330 opts["forum_format"] = nil | |
| 331 end | |
| 332 -- get item filter table if needed | |
| 333 if opts.itemfilter == nil then | |
| 334 opts.itemfilter = addon.default_itemfilter | |
| 335 end | |
| 336 addon.default_itemfilter = nil | |
| 337 | |
| 338 self:RegisterChatCommand("ouroloot", "OnSlash") | |
| 339 -- maybe try to detect if this command is already in use... | |
| 340 if opts.register_slashloot then | |
| 341 SLASH_ACECONSOLE_OUROLOOT2 = "/loot" | |
| 342 end | |
| 343 | |
| 344 self.history_all = self.history_all or OuroLootSV_hist or {} | |
| 345 local r = GetRealmName() | |
| 346 self.history_all[r] = self:_prep_new_history_category (self.history_all[r], r) | |
| 347 self.history = self.history_all[r] | |
| 348 | |
| 349 _init(self) | |
| 350 self.OnInitialize = nil | |
| 351 end | |
| 352 | |
| 353 function addon:OnEnable() | |
| 354 self:RegisterEvent "PLAYER_LOGOUT" | |
| 355 self:RegisterEvent "RAID_ROSTER_UPDATE" | |
| 356 | |
| 357 -- Cribbed from Talented. I like the way jerry thinks: the first argument | |
| 358 -- can be a format spec for the remainder of the arguments. (The new | |
| 359 -- AceConsole:Printf isn't used because we can't specify a prefix without | |
| 360 -- jumping through ridonkulous hoops.) The part about overriding :Print | |
| 361 -- with a version using prefix hyperlinks is my fault. | |
| 362 do | |
| 363 local AC = LibStub("AceConsole-3.0") | |
| 364 local chat_prefix = self.format_hypertext('openloot',"Ouro Loot",--[[legendary]]5) | |
| 365 function addon:Print (str, ...) | |
| 366 if type(str) == 'string' and str:find("%", nil, --[[plainmatch=]]true) then | |
| 367 return AC:Print (chat_prefix, str:format(...)) | |
| 368 else | |
| 369 return AC:Print (chat_prefix, str, ...) | |
| 370 end | |
| 371 end | |
| 372 end | |
| 373 | |
| 374 if opts.keybinding then | |
| 375 local btn = CreateFrame("Button", "OuroLootBindingOpen", nil, "SecureActionButtonTemplate") | |
| 376 btn:SetAttribute("type", "macro") | |
| 377 btn:SetAttribute("macrotext", "/ouroloot toggle") | |
| 378 if SetBindingClick(opts.keybinding_text, "OuroLootBindingOpen") then | |
| 379 SaveBindings(GetCurrentBindingSet()) | |
| 380 else | |
| 381 self:Print("Error registering '%s' as a keybinding, check spelling!", | |
| 382 opts.keybinding_text) | |
| 383 end | |
| 384 end | |
| 385 | |
| 386 if self.debug.flow then self:Print"is in control-flow debug mode." end | |
| 387 end | |
| 388 --function addon:OnDisable() end | |
| 389 | |
| 390 | |
| 391 ------ Event handlers | |
| 392 function addon:_clear_SVs() | |
| 393 g_loot = {} -- not saved, just fooling PLAYER_LOGOUT tests | |
| 394 OuroLootSV = nil | |
| 395 OuroLootSV_opts = nil | |
| 396 OuroLootSV_hist = nil | |
| 397 end | |
| 398 function addon:PLAYER_LOGOUT() | |
| 399 if (#g_loot > 0) or g_loot.saved | |
| 400 or (g_loot.forum and g_loot.forum ~= "") | |
| 401 or (g_loot.attend and g_loot.attend ~= "") | |
| 402 then | |
| 403 g_loot.autoshard = self.sharder | |
| 404 g_loot.threshold = self.threshold | |
| 405 --OuroLootSV = g_loot | |
| 406 --for i,e in ipairs(OuroLootSV) do | |
| 407 for i,e in ipairs(g_loot) do | |
| 408 e.cols = nil | |
| 409 end | |
| 410 OuroLootSV = g_loot | |
| 411 end | |
| 412 self.history.kind = nil | |
| 413 self.history.st = nil | |
| 414 self.history.byname = nil | |
| 415 OuroLootSV_hist = self.history_all | |
| 416 end | |
| 417 | |
| 418 function addon:RAID_ROSTER_UPDATE (event) | |
| 419 if GetNumRaidMembers() > 0 then | |
| 420 local inside,whatkind = IsInInstance() | |
| 421 if inside and (whatkind == "pvp" or whatkind == "arena") then | |
| 422 return self.dprint('flow', "got RRU event but in pvp zone, bailing") | |
| 423 end | |
| 424 if event == "Activate" then | |
| 425 -- dispatched manually from Activate | |
| 426 self:RegisterEvent "CHAT_MSG_LOOT" | |
| 427 _registerDBM(self) | |
| 428 elseif event == "RAID_ROSTER_UPDATE" then | |
| 429 -- event registration from onload, joined a raid, maybe show popup | |
| 430 if opts.popup_on_join and not self.popped then | |
| 431 self.popped = StaticPopup_Show "OUROL_REMIND" | |
| 432 self.popped.data = self | |
| 433 end | |
| 434 end | |
| 435 else | |
| 436 self:UnregisterEvent "CHAT_MSG_LOOT" | |
| 437 self.popped = nil | |
| 438 end | |
| 439 end | |
| 440 | |
| 441 -- helper for CHAT_MSG_LOOT handler | |
| 442 do | |
| 443 -- Recent loot cache | |
| 444 addon.recent_loot = create_new_cache ('loot', comm_cleanup_ttl) | |
| 445 | |
| 446 local GetItemInfo = GetItemInfo | |
| 447 | |
| 448 -- 'from' and onwards only present if this is triggered by a broadcast | |
| 449 function addon:_do_loot (local_override, recipient, itemid, count, from, extratext) | |
| 450 local iname, ilink, iquality, _,_,_,_,_,_, itexture = GetItemInfo(itemid) | |
| 451 if not iname then return end -- sigh | |
| 452 self.dprint('loot',">>_do_loot, R:", recipient, "I:", itemid, "C:", count, "frm:", from, "ex:", extratext) | |
| 453 | |
| 454 local i | |
| 455 itemid = tonumber(ilink:match("item:(%d+)")) | |
| 456 if local_override or ((iquality >= self.threshold) and not opts.itemfilter[itemid]) then | |
| 457 if (self.rebroadcast and (not from)) and not local_override then | |
| 458 self:broadcast('loot', recipient, itemid, count) | |
| 459 end | |
| 460 if self.enabled or local_override then | |
| 461 local signature = recipient .. iname .. (count or "") | |
| 462 if self.recent_loot:test(signature) then | |
| 463 self.dprint('cache', "loot <",signature,"> already in cache, skipping") | |
| 464 else | |
| 465 self.recent_loot:add(signature) | |
| 466 i = self._addLootEntry{ -- There is some redundancy here... | |
| 467 kind = 'loot', | |
| 468 person = recipient, | |
| 469 person_class= select(2,UnitClass(recipient)), | |
| 470 quality = iquality, | |
| 471 itemname = iname, | |
| 472 id = itemid, | |
| 473 itemlink = ilink, | |
| 474 itexture = itexture, | |
| 475 disposition = (recipient == self.sharder) and 'shard' or nil, | |
| 476 count = count, | |
| 477 bcast_from = from, | |
| 478 extratext = extratext, | |
| 479 is_heroic = self:is_heroic_item(ilink), | |
| 480 } | |
| 481 self.dprint('loot', "added entry", i) | |
| 482 self:_addHistoryEntry(i) | |
| 483 if self.display then | |
| 484 self:redisplay() | |
| 485 --[[ | |
| 486 local st = self.display:GetUserData("eoiST") | |
| 487 if st and st.frame:IsVisible() then | |
| 488 st:OuroLoot_Refresh() | |
| 489 end | |
| 490 ]] | |
| 491 end | |
| 492 end | |
| 493 end | |
| 494 end | |
| 495 self.dprint('loot',"<<_do_loot out") | |
| 496 return i | |
| 497 end | |
| 498 | |
| 499 function addon:CHAT_MSG_LOOT (event, ...) | |
| 500 if (not self.rebroadcast) and (not self.enabled) and (event ~= "manual") then return end | |
| 501 | |
| 502 --[[ | |
| 503 iname: Hearthstone | |
| 504 iquality: integer | |
| 505 ilink: clickable formatted link | |
| 506 itemstring: item:6948:.... | |
| 507 itexture: inventory icon texture | |
| 508 ]] | |
| 509 | |
| 510 if event == "CHAT_MSG_LOOT" then | |
| 511 local msg = ... | |
| 512 --ChatFrame2:AddMessage("original string: >"..(msg:gsub("\124","\124\124")).."<") | |
| 513 local person, itemstring, remainder = msg:match(self.loot_pattern) | |
| 514 self.dprint('loot', "CHAT_MSG_LOOT, person is", person, ", itemstring is", itemstring, ", rest is", remainder) | |
| 515 if not person then return end -- "So-and-So selected Greed", etc, not actual looting | |
| 516 local count = remainder and remainder:match(".*(x%d+)$") | |
| 517 | |
| 518 -- Name might be colorized, remove the highlighting | |
| 519 local p = person:match("|c%x%x%x%x%x%x%x%x(%S+)") | |
| 520 person = p or person | |
| 521 person = (person == UNIT_YOU) and my_name or person | |
| 522 | |
| 523 local id = tonumber((select(2, strsplit(":", itemstring)))) | |
| 524 | |
| 525 return self:_do_loot (false, person, id, count) | |
| 526 | |
| 527 elseif event == "broadcast" then | |
| 528 return self:_do_loot(false, ...) | |
| 529 | |
| 530 elseif event == "manual" then | |
| 531 local r,i,n = ... | |
| 532 return self:_do_loot(true, r,i,nil,nil,n) | |
| 533 end | |
| 534 end | |
| 535 end | |
| 536 | |
| 537 | |
| 538 ------ Slash command handler | |
| 539 -- Thought about breaking this up into a table-driven dispatcher. But | |
| 540 -- that would result in a pile of teensy functions, most of which would | |
| 541 -- never be called. Too much overhead. (2.0: Most of these removed now | |
| 542 -- that GUI is in place.) | |
| 543 function addon:OnSlash (txt) --, editbox) | |
| 544 txt = strtrim(txt:lower()) | |
| 545 local cmd, arg = "" | |
| 546 do | |
| 547 local s,e = txt:find("^%a+") | |
| 548 if s then | |
| 549 cmd = txt:sub(s,e) | |
| 550 s = txt:find("%S", e+2) | |
| 551 if s then arg = txt:sub(s,-1) end | |
| 552 end | |
| 553 end | |
| 554 | |
| 555 if cmd == "" then | |
| 556 if InCombatLockdown() then | |
| 557 return self:Print("Can't display window in combat.") | |
| 558 else | |
| 559 return self:BuildMainDisplay() | |
| 560 end | |
| 561 | |
| 562 elseif cmd:find("^thre") then | |
| 563 self:SetThreshold(arg) | |
| 564 | |
| 565 elseif cmd == "on" then self:Activate(arg) | |
| 566 elseif cmd == "off" then self:Deactivate() | |
| 567 elseif cmd == "broadcast" or cmd == "bcast" then self:Activate(nil,true) | |
| 568 | |
| 569 elseif cmd == "fake" then -- maybe comment this out for real users | |
| 570 self:_mark_boss_kill (self._addLootEntry{ | |
| 571 kind='boss',reason='kill',bosskill="Baron Steamroller",instance=instance_tag(),duration=0 | |
| 572 }) | |
| 573 self:CHAT_MSG_LOOT ('manual', my_name, 54797) | |
| 574 if self.display then | |
| 575 self:redisplay() | |
| 576 end | |
| 577 self:Print "Baron Steamroller has been slain. Congratulations on your rug." | |
| 578 | |
| 579 elseif cmd == "debug" then | |
| 580 if arg then | |
| 581 self.debug[arg] = not self.debug[arg] | |
| 582 _G.print(arg,self.debug[arg]) | |
| 583 if self.debug[arg] then self.DEBUG_PRINT = true end | |
| 584 else | |
| 585 self.DEBUG_PRINT = not self.DEBUG_PRINT | |
| 586 end | |
| 587 | |
| 588 elseif cmd == "save" and arg and arg:len() > 0 then | |
| 589 self:save_saveas(arg) | |
| 590 elseif cmd == "list" then | |
| 591 self:save_list() | |
| 592 elseif cmd == "restore" and arg and arg:len() > 0 then | |
| 593 self:save_restore(tonumber(arg)) | |
| 594 elseif cmd == "delete" and arg and arg:len() > 0 then | |
| 595 self:save_delete(tonumber(arg)) | |
| 596 | |
| 597 elseif cmd == "help" then | |
| 598 self:BuildMainDisplay('help') | |
| 599 elseif cmd == "toggle" then | |
| 600 if self.display then | |
| 601 self.display:Hide() | |
| 602 else | |
| 603 return self:BuildMainDisplay() | |
| 604 end | |
| 605 | |
| 606 else | |
| 607 if self:OpenMainDisplayToTab(cmd) then | |
| 608 return | |
| 609 end | |
| 610 self:Print("Unknown command '%s'. %s to see the help window.", | |
| 611 cmd, self.format_hypertext('help',"Click here",ITEM_QUALITY_UNCOMMON)) | |
| 612 end | |
| 613 end | |
| 614 | |
| 615 function addon:SetThreshold (arg, quiet_p) | |
| 616 local q = tonumber(arg) | |
| 617 if q then | |
| 618 q = math.floor(q+0.001) | |
| 619 if q<0 or q>6 then | |
| 620 return self:Print("Threshold must be 0-6.") | |
| 621 end | |
| 622 else | |
| 623 q = qualnames[arg] | |
| 624 if not q then | |
| 625 return self:Print("Unrecognized item quality argument.") | |
| 626 end | |
| 627 end | |
| 628 self.threshold = q | |
| 629 if not quiet_p then self:Print("Threshold now set to %s.", self.thresholds[q]) end | |
| 630 end | |
| 631 | |
| 632 | |
| 633 ------ On/off | |
| 634 function addon:Activate (opt_threshold, opt_bcast_only) | |
| 635 self:RegisterEvent "RAID_ROSTER_UPDATE" | |
| 636 self.popped = true | |
| 637 if GetNumRaidMembers() > 0 then | |
| 638 self:RAID_ROSTER_UPDATE("Activate") | |
| 639 elseif self.debug.notraid then | |
| 640 self:RegisterEvent "CHAT_MSG_LOOT" | |
| 641 _registerDBM(self) | |
| 642 elseif g_restore_p then | |
| 643 g_restore_p = nil | |
| 644 if #g_loot == 0 then return end -- only saved texts, not worth verbage | |
| 645 self:Print("Ouro Raid Loot restored previous data, but not in a raid", | |
| 646 "and 5-person mode not active. |cffff0505NOT tracking loot|r;", | |
| 647 "use 'enable' to activate loot tracking, or 'clear' to erase", | |
| 648 "previous data, or 'help' to read about saved-texts commands.") | |
| 649 self.popped = nil -- get the reminder if later joining a raid | |
| 650 return | |
| 651 end | |
| 652 self.rebroadcast = true -- hardcode to true; this used to be more complicated | |
| 653 self.enabled = not opt_bcast_only | |
| 654 if opt_threshold then | |
| 655 self:SetThreshold (opt_threshold, --[[quiet_p=]]true) | |
| 656 end | |
| 657 self:Print("Ouro Raid Loot is %s. Threshold currently %s.", | |
| 658 self.enabled and "tracking" or "only broadcasting", | |
| 659 self.thresholds[self.threshold]) | |
| 660 end | |
| 661 | |
| 662 -- Note: running '/loot off' will also avoid the popup reminder when | |
| 663 -- joining a raid, but will not change the saved option setting. | |
| 664 function addon:Deactivate() | |
| 665 self.enabled = false | |
| 666 self.rebroadcast = false | |
| 667 self:UnregisterEvent "RAID_ROSTER_UPDATE" | |
| 668 self:UnregisterEvent "CHAT_MSG_LOOT" | |
| 669 self:Print("Ouro Raid Loot deactivated.") | |
| 670 end | |
| 671 | |
| 672 function addon:Clear(verbose_p) | |
| 673 local repopup, st | |
| 674 if self.display then | |
| 675 -- in the new version, this is likely to always be the case | |
| 676 repopup = true | |
| 677 st = self.display:GetUserData("eoiST") | |
| 678 if not st then | |
| 679 self.dprint('flow', "Clear: display visible but eoiST not set??") | |
| 680 end | |
| 681 self.display:Hide() | |
| 682 end | |
| 683 g_restore_p = nil | |
| 684 OuroLootSV = nil | |
| 685 self:_reset_timestamps() | |
| 686 g_saved_tmp = g_loot.saved | |
| 687 if verbose_p then | |
| 688 if (g_saved_tmp and #g_saved_tmp>0) then | |
| 689 self:Print("Current loot data cleared, %d saved sets remaining.", #g_saved_tmp) | |
| 690 else | |
| 691 self:Print("Current loot data cleared.") | |
| 692 end | |
| 693 end | |
| 694 _init(self,st) | |
| 695 if repopup then | |
| 696 addon:BuildMainDisplay() | |
| 697 end | |
| 698 end | |
| 699 | |
| 700 | |
| 701 ------ Behind the scenes routines | |
| 702 -- Adds indices to traverse the tables in a nice sorted order. | |
| 703 do | |
| 704 local byindex, temp = {}, {} | |
| 705 local function sort (src, dest) | |
| 706 for k in pairs(src) do | |
| 707 temp[#temp+1] = k | |
| 708 end | |
| 709 table.sort(temp) | |
| 710 table.wipe(dest) | |
| 711 for i = 1, #temp do | |
| 712 dest[i] = src[temp[i]] | |
| 713 end | |
| 714 end | |
| 715 | |
| 716 function addon.sender_list.sort() | |
| 717 sort (addon.sender_list.active, byindex) | |
| 718 table.wipe(temp) | |
| 719 addon.sender_list.activeI = #byindex | |
| 720 sort (addon.sender_list.names, byindex) | |
| 721 table.wipe(temp) | |
| 722 end | |
| 723 addon.sender_list.namesI = byindex | |
| 724 end | |
| 725 | |
| 726 -- Message sending. | |
| 727 -- See OCR_funcs.tag at the end of this file for incoming message treatment. | |
| 728 do | |
| 729 local function assemble(...) | |
| 730 local msg = ... | |
| 731 for i = 2, select('#',...) do | |
| 732 msg = msg .. '\a' .. (select(i,...) or "") | |
| 733 end | |
| 734 return msg | |
| 735 end | |
| 736 | |
| 737 -- broadcast('tag', <stuff>) | |
| 738 function addon:broadcast(...) | |
| 739 local msg = assemble(...) | |
| 740 self.dprint('comm', "<broadcast>:", msg) | |
| 741 -- the "GUILD" here is just so that we can also pick up on it | |
| 742 self:SendCommMessage(self.ident, msg, self.debug.comm and "GUILD" or "RAID") | |
| 743 end | |
| 744 -- whispercast(<to>, 'tag', <stuff>) | |
| 745 function addon:whispercast(to,...) | |
| 746 local msg = assemble(...) | |
| 747 self.dprint('comm', "<whispercast>@", to, ":", msg) | |
| 748 self:SendCommMessage(self.identTg, msg, "WHISPER", to) | |
| 749 end | |
| 750 end | |
| 751 | |
| 752 -- Generic helpers | |
| 753 function addon._find_next_after (kind, index) | |
| 754 index = index + 1 | |
| 755 while index <= #g_loot do | |
| 756 if g_loot[index].kind == kind then | |
| 757 return index, g_loot[index] | |
| 758 end | |
| 759 index = index + 1 | |
| 760 end | |
| 761 end | |
| 762 | |
| 763 -- Iterate through g_loot entries according to the KIND field. Loop variables | |
| 764 -- are g_loot indices and the corresponding entries (essentially ipairs + some | |
| 765 -- conditionals). | |
| 766 function addon:filtered_loot_iter (filter_kind) | |
| 767 return self._find_next_after, filter_kind, 0 | |
| 768 end | |
| 769 | |
| 770 do | |
| 771 local itt | |
| 772 local function create() | |
| 773 local tip, lefts = CreateFrame("GameTooltip"), {} | |
| 774 for i = 1, 2 do -- scanning idea here also snagged from Talented | |
| 775 local L,R = tip:CreateFontString(), tip:CreateFontString() | |
| 776 L:SetFontObject(GameFontNormal) | |
| 777 R:SetFontObject(GameFontNormal) | |
| 778 tip:AddFontStrings(L,R) | |
| 779 lefts[i] = L | |
| 780 end | |
| 781 tip.lefts = lefts | |
| 782 return tip | |
| 783 end | |
| 784 function addon:is_heroic_item(item) -- returns true or *nil* | |
| 785 itt = itt or create() | |
| 786 itt:SetOwner(UIParent,"ANCHOR_NONE") | |
| 787 itt:ClearLines() | |
| 788 itt:SetHyperlink(item) | |
| 789 local t = itt.lefts[2]:GetText() | |
| 790 itt:Hide() | |
| 791 return (t == ITEM_HEROIC) or nil | |
| 792 end | |
| 793 end | |
| 794 | |
| 795 -- Called when first loading up, and then also when a 'clear' is being | |
| 796 -- performed. If SV's are present then restore_p will be true. | |
| 797 function _init (self, possible_st) | |
| 798 self.dprint('flow',"_init running") | |
| 799 self.loot_clean = nil | |
| 800 self.hist_clean = nil | |
| 801 if g_restore_p then | |
| 802 g_loot = OuroLootSV | |
| 803 self.popped = true | |
| 804 self.dprint('flow', "restoring", #g_loot, "entries") | |
| 805 self:ScheduleTimer("Activate", 8, g_loot.threshold) | |
| 806 -- FIXME printed could be too large if entries were deleted, how much do we care? | |
| 807 self.sharder = g_loot.autoshard | |
| 808 else | |
| 809 g_loot = { printed = {} } | |
| 810 g_loot.saved = g_saved_tmp; g_saved_tmp = nil -- potentially restore across a clear | |
| 811 end | |
| 812 | |
| 813 self.threshold = g_loot.threshold or self.threshold -- in the case of restoring but not tracking | |
| 814 self:gui_init(g_loot) | |
| 815 | |
| 816 if g_restore_p then | |
| 817 self:zero_printed_fenceposts() -- g_loot.printed.* = previous/safe values | |
| 818 else | |
| 819 self:zero_printed_fenceposts(0) -- g_loot.printed.* = 0 | |
| 820 end | |
| 821 if possible_st then | |
| 822 possible_st:SetData(g_loot) | |
| 823 end | |
| 824 | |
| 825 self.status_text = ("v2r%d communicating as ident %s"):format(self.revision,self.ident) | |
| 826 self:RegisterComm(self.ident) | |
| 827 self:RegisterComm(self.identTg, "OnCommReceivedNocache") | |
| 828 | |
| 829 if self.author_debug then | |
| 830 _G.OL = self | |
| 831 _G.Oloot = g_loot | |
| 832 end | |
| 833 end | |
| 834 | |
| 835 -- Tie-ins with Deadly Boss Mods | |
| 836 do | |
| 837 local candidates, location | |
| 838 local function fixup_durations (cache) | |
| 839 if candidates == nil then return end -- this is called for *all* cache expirations, including non-boss | |
| 840 local boss, bossi | |
| 841 boss = candidates[1] | |
| 842 if #candidates == 1 then | |
| 843 -- (1) or (2) | |
| 844 boss.duration = boss.duration or 0 | |
| 845 addon.dprint('loot', "only one candidate") | |
| 846 else | |
| 847 -- (3), should only be one 'cast entry and our local entry | |
| 848 if #candidates ~= 2 then | |
| 849 -- could get a bunch of 'cast entries on the heels of one another | |
| 850 -- before the local one ever fires, apparently... sigh | |
| 851 --addon:Print("<warning> s3 cache has %d entries, does that seem right to you?", #candidates) | |
| 852 end | |
| 853 if candidates[2].duration == nil then | |
| 854 --addon:Print("<warning> s3's second entry is not the local trigger, does that seem right to you?") | |
| 855 end | |
| 856 -- try and be generic anyhow | |
| 857 for i,c in ipairs(candidates) do | |
| 858 if c.duration then | |
| 859 boss = c | |
| 860 addon.dprint('loot', "fixup found candidate", i, "duration", c.duration) | |
| 861 break | |
| 862 end | |
| 863 end | |
| 864 end | |
| 865 bossi = addon._addLootEntry(boss) | |
| 866 addon.dprint('loot', "added entry", bossi) | |
| 867 if boss.reason == 'kill' then | |
| 868 addon:_mark_boss_kill (bossi) | |
| 869 if opts.chatty_on_kill then | |
| 870 addon:Print("Registered kill for '%s' in %s!", boss.bosskill, boss.instance) | |
| 871 end | |
| 872 end | |
| 873 candidates = nil | |
| 874 end | |
| 875 addon.recent_boss = create_new_cache ('boss', 10, fixup_durations) | |
| 876 | |
| 877 -- Similar to _do_loot, but duration+ parms only present when locally generated. | |
| 878 local function _do_boss (self, reason, bossname, intag, duration, raiders) | |
| 879 self.dprint('loot',">>_do_boss, R:", reason, "B:", bossname, "T:", intag, | |
| 880 "D:", duration, "RL:", (raiders and #raiders or 'nil')) | |
| 881 if self.rebroadcast and duration then | |
| 882 self:broadcast('boss', reason, bossname, intag) | |
| 883 end | |
| 884 -- This is only a loop to make jumping out of it easy, and still do cleanup below. | |
| 885 while self.enabled do | |
| 886 if reason == 'wipe' and opts.no_tracking_wipes then break end | |
| 887 bossname = (opts.snarky_boss and self.boss_abbrev[bossname] or bossname) or bossname | |
| 888 local not_from_local = duration == nil | |
| 889 local signature = bossname .. reason | |
| 890 if not_from_local and self.recent_boss:test(signature) then | |
| 891 self.dprint('cache', "boss <",signature,"> already in cache, skipping") | |
| 892 else | |
| 893 self.recent_boss:add(signature) | |
| 894 -- Possible scenarios: (1) we don't see a boss event at all (e.g., we're | |
| 895 -- outside the instance) and so this only happens once as a non-local event, | |
| 896 -- (2) we see a local event first and all non-local events are filtered | |
| 897 -- by the cache, (3) we happen to get some non-local events before doing | |
| 898 -- our local event (not because of network weirdness but because our local | |
| 899 -- DBM might not trigger for a while). | |
| 900 local c = { | |
| 901 kind = 'boss', | |
| 902 bosskill = bossname, -- minor misnomer, might not actually be a kill | |
| 903 reason = reason, | |
| 904 instance = intag, | |
| 905 duration = duration, -- these two deliberately may be nil | |
| 906 raiderlist = raiders and table.concat(raiders, ", ") | |
| 907 } | |
| 908 candidates = candidates or {} | |
| 909 tinsert(candidates,c) | |
| 910 break | |
| 911 end | |
| 912 end | |
| 913 self.dprint('loot',"<<_do_boss out") | |
| 914 end | |
| 915 -- No wrapping layer for now | |
| 916 addon.on_boss_broadcast = _do_boss | |
| 917 | |
| 918 function addon:_mark_boss_kill (index) | |
| 919 local e = g_loot[index] | |
| 920 if not e.bosskill then | |
| 921 return self:Print("Something horribly wrong;", index, "is not a boss entry!") | |
| 922 end | |
| 923 if e.reason ~= 'wipe' then | |
| 924 -- enh, bail | |
| 925 self.loot_clean = index-1 | |
| 926 end | |
| 927 local attempts = 1 | |
| 928 local first | |
| 929 | |
| 930 local i,d = 1,g_loot[1] | |
| 931 while d ~= e do | |
| 932 if d.bosskill and | |
| 933 d.bosskill == e.bosskill and | |
| 934 d.reason == 'wipe' | |
| 935 then | |
| 936 first = first or i | |
| 937 attempts = attempts + 1 | |
| 938 assert(tremove(g_loot,i)==d,"_mark_boss_kill screwed up data badly") | |
| 939 else | |
| 940 i = i + 1 | |
| 941 end | |
| 942 d = g_loot[i] | |
| 943 end | |
| 944 e.reason = 'kill' | |
| 945 e.attempts = attempts | |
| 946 self.loot_clean = first or index-1 | |
| 947 end | |
| 948 | |
| 949 local GetRaidRosterInfo = GetRaidRosterInfo | |
| 950 function addon:DBMBossCallback (reason, mod, ...) | |
| 951 if (not self.rebroadcast) and (not self.enabled) then return end | |
| 952 | |
| 953 local name | |
| 954 if mod.combatInfo and mod.combatInfo.name then | |
| 955 name = mod.combatInfo.name | |
| 956 elseif mod.id then | |
| 957 name = mod.id | |
| 958 else | |
| 959 name = "Unknown Boss" | |
| 960 end | |
| 961 | |
| 962 local it = location or instance_tag() | |
| 963 location = nil | |
| 964 | |
| 965 local duration = 0 | |
| 966 if mod.combatInfo and mod.combatInfo.pull then | |
| 967 duration = math.floor (GetTime() - mod.combatInfo.pull) | |
| 968 end | |
| 969 | |
| 970 -- attendance: maybe put people in groups 6,7,8 into a "backup/standby" | |
| 971 -- list? probably too specific to guild practices. | |
| 972 local raiders = {} | |
| 973 for i = 1, GetNumRaidMembers() do | |
| 974 tinsert(raiders, (GetRaidRosterInfo(i))) | |
| 975 end | |
| 976 table.sort(raiders) | |
| 977 | |
| 978 return _do_boss (self, reason, name, it, duration, raiders) | |
| 979 end | |
| 980 | |
| 981 local callback = function(...) addon:DBMBossCallback(...) end | |
| 982 function _registerDBM(self) | |
| 983 if DBM then | |
| 984 if not self.dbm_registered then | |
| 985 local rev = tonumber(DBM.Revision) or 0 | |
| 986 if rev < 1503 then | |
| 987 self.status_text = "|cffff1010Deadly Boss Mods must be version 1.26 or newer to work with Ouro Loot.|r" | |
| 988 return | |
| 989 end | |
| 990 local r = DBM:RegisterCallback("kill", callback) | |
| 991 DBM:RegisterCallback("wipe", callback) | |
| 992 DBM:RegisterCallback("pull", function() location = instance_tag() end) | |
| 993 self.dbm_registered = r > 0 | |
| 994 end | |
| 995 else | |
| 996 self.status_text = "|cffff1010Ouro Loot cannot find Deadly Boss Mods, loot will not be grouped by boss.|r" | |
| 997 end | |
| 998 end | |
| 999 end -- DBM tie-ins | |
| 1000 | |
| 1001 -- Adding entries to the loot record, and tracking the corresponding timestamp. | |
| 1002 do | |
| 1003 -- This shouldn't be required. /sadface | |
| 1004 local loot_entry_mt = { | |
| 1005 __index = function (e,key) | |
| 1006 if key == 'cols' then | |
| 1007 pprint('mt', e.kind) | |
| 1008 --tabledump(e) -- not actually that useful | |
| 1009 addon:_fill_out_eoi_data(1) | |
| 1010 end | |
| 1011 return rawget(e,key) | |
| 1012 end | |
| 1013 } | |
| 1014 | |
| 1015 -- Given a loot index, searches backwards for a timestamp. Returns that | |
| 1016 -- index and the time entry, or nil if it falls off the beginning. Pass an | |
| 1017 -- optional second index to search no earlier than it. | |
| 1018 -- May also be able to make good use of this in forum-generation routine. | |
| 1019 function addon:find_previous_time_entry(i,stop) | |
| 1020 local stop = stop or 0 | |
| 1021 while i > stop do | |
| 1022 if g_loot[i].kind == 'time' then | |
| 1023 return i, g_loot[i] | |
| 1024 end | |
| 1025 i = i - 1 | |
| 1026 end | |
| 1027 end | |
| 1028 | |
| 1029 -- format_timestamp (["format_string"], Day, [Loot]) | |
| 1030 -- DAY is a loot entry with kind=='time', and controls the date printed. | |
| 1031 -- LOOT may be any kind of entry in the g_loot table. If present, it | |
| 1032 -- overrides the hour and minute printed; if absent, those values are | |
| 1033 -- taken from the DAY entry. | |
| 1034 -- FORMAT_STRING may contain $x (x in Y/M/D/h/m) tokens. | |
| 1035 local format_timestamp_values, point2dee = {}, "%.2d" | |
| 1036 function addon:format_timestamp (fmt_opt, day_entry, time_entry_opt) | |
| 1037 if not time_entry_opt then | |
| 1038 if type(fmt_opt) == 'table' then -- Two entries, default format | |
| 1039 time_entry_opt, day_entry = day_entry, fmt_opt | |
| 1040 fmt_opt = "$Y/$M/$D $h:$m" | |
| 1041 --elseif type(fmt_opt) == "string" then -- Day entry only, specified format | |
| 1042 end | |
| 1043 end | |
| 1044 --format_timestamp_values.Y = point2dee:format (day_entry.startday.year % 100) | |
| 1045 format_timestamp_values.Y = ("%.4d"):format (day_entry.startday.year) | |
| 1046 format_timestamp_values.M = point2dee:format (day_entry.startday.month) | |
| 1047 format_timestamp_values.D = point2dee:format (day_entry.startday.day) | |
| 1048 format_timestamp_values.h = point2dee:format ((time_entry_opt or day_entry).hour) | |
| 1049 format_timestamp_values.m = point2dee:format ((time_entry_opt or day_entry).minute) | |
| 1050 return fmt_opt:gsub ('%$([YMDhm])', format_timestamp_values) | |
| 1051 end | |
| 1052 | |
| 1053 local done_todays_date | |
| 1054 function addon:_reset_timestamps() | |
| 1055 done_todays_date = nil | |
| 1056 end | |
| 1057 local function do_todays_date() | |
| 1058 local text, M, D, Y = makedate() | |
| 1059 local found,ts = #g_loot+1 | |
| 1060 repeat | |
| 1061 found,ts = addon:find_previous_time_entry(found-1) | |
| 1062 if found and ts.startday.text == text then | |
| 1063 done_todays_date = true | |
| 1064 end | |
| 1065 until done_todays_date or (not found) | |
| 1066 if done_todays_date then | |
| 1067 g_today = ts | |
| 1068 else | |
| 1069 done_todays_date = true | |
| 1070 g_today = g_loot[addon._addLootEntry{ | |
| 1071 kind = 'time', | |
| 1072 startday = { | |
| 1073 text = text, month = M, day = D, year = Y | |
| 1074 } | |
| 1075 }] | |
| 1076 end | |
| 1077 addon:_fill_out_eoi_data(1) | |
| 1078 end | |
| 1079 | |
| 1080 -- Adding anything original to g_loot goes through this routine. | |
| 1081 function addon._addLootEntry (e) | |
| 1082 setmetatable(e,loot_entry_mt) | |
| 1083 | |
| 1084 if not done_todays_date then do_todays_date() end | |
| 1085 | |
| 1086 local h, m = GetGameTime() | |
| 1087 local localuptime = math.floor(GetTime()) | |
| 1088 e.hour = h | |
| 1089 e.minute = m | |
| 1090 e.stamp = localuptime | |
| 1091 local index = #g_loot + 1 | |
| 1092 g_loot[index] = e | |
| 1093 return index | |
| 1094 end | |
| 1095 end | |
| 1096 | |
| 1097 | |
| 1098 ------ Saved texts | |
| 1099 function addon:check_saved_table(silent_p) | |
| 1100 local s = g_loot.saved | |
| 1101 if s and (#s > 0) then return s end | |
| 1102 g_loot.saved = nil | |
| 1103 if not silent_p then self:Print("There are no saved loot texts.") end | |
| 1104 end | |
| 1105 | |
| 1106 function addon:save_list() | |
| 1107 local s = self:check_saved_table(); if not s then return end; | |
| 1108 for i,t in ipairs(s) do | |
| 1109 self:Print("#%d %s %d entries %s", i, t.date, t.count, t.name) | |
| 1110 end | |
| 1111 end | |
| 1112 | |
| 1113 function addon:save_saveas(name) | |
| 1114 g_loot.saved = g_loot.saved or {} | |
| 1115 local n = #(g_loot.saved) + 1 | |
| 1116 local save = { | |
| 1117 name = name, | |
| 1118 date = makedate(), | |
| 1119 count = #g_loot, | |
| 1120 forum = g_loot.forum, | |
| 1121 attend = g_loot.attend, | |
| 1122 } | |
| 1123 self:Print("Saving current loot texts to #%d '%s'", n, name) | |
| 1124 g_loot.saved[n] = save | |
| 1125 return self:save_list() | |
| 1126 end | |
| 1127 | |
| 1128 function addon:save_restore(num) | |
| 1129 local s = self:check_saved_table(); if not s then return end; | |
| 1130 if (not num) or (num > #s) then | |
| 1131 return self:Print("Saved text number must be 1 - "..#s) | |
| 1132 end | |
| 1133 local save = s[num] | |
| 1134 self:Print("Overwriting current loot data with saved text #%d '%s'", num, save.name) | |
| 1135 self:Clear(--[[verbose_p=]]false) | |
| 1136 -- Clear will already have displayed the window, and re-selected the first | |
| 1137 -- tab. Set these up for when the text tabs are clicked. | |
| 1138 g_loot.forum = save.forum | |
| 1139 g_loot.attend = save.attend | |
| 1140 end | |
| 1141 | |
| 1142 function addon:save_delete(num) | |
| 1143 local s = self:check_saved_table(); if not s then return end; | |
| 1144 if (not num) or (num > #s) then | |
| 1145 return self:Print("Saved text number must be 1 - "..#s) | |
| 1146 end | |
| 1147 self:Print("Deleting saved text #"..num) | |
| 1148 tremove(s,num) | |
| 1149 return self:save_list() | |
| 1150 end | |
| 1151 | |
| 1152 | |
| 1153 ------ Loot histories | |
| 1154 -- history_all = { | |
| 1155 -- ["Kilrogg"] = { | |
| 1156 -- ["realm"] = "Kilrogg", -- not saved | |
| 1157 -- ["st"] = { lib-st display table }, -- not saved | |
| 1158 -- ["byname"] = { -- not saved | |
| 1159 -- ["OtherPlayer"] = 2, | |
| 1160 -- ["Farmbuyer"] = 1, | |
| 1161 -- } | |
| 1162 -- [1] = { | |
| 1163 -- ["name"] = "Farmbuyer", | |
| 1164 -- [1] = { id = nnnnn, when = "formatted timestamp for displaying" } -- most recent loot | |
| 1165 -- [2] = { ......., [count = "x3"] } -- previous loot | |
| 1166 -- }, | |
| 1167 -- [2] = { | |
| 1168 -- ["name"] = "OtherPlayer", | |
| 1169 -- ...... | |
| 1170 -- }, ...... | |
| 1171 -- }, | |
| 1172 -- ["OtherRealm"] = ...... | |
| 1173 -- } | |
| 1174 do | |
| 1175 -- Builds the map of names to array indices. | |
| 1176 function addon:_build_history_names (opt_hist) | |
| 1177 local hist = opt_hist or self.history | |
| 1178 local m = {} | |
| 1179 for i = 1, #hist do | |
| 1180 m[hist[i].name] = i | |
| 1181 end | |
| 1182 hist.byname = m | |
| 1183 end | |
| 1184 | |
| 1185 -- Maps a name to an array index, creating new tables if needed. Returns | |
| 1186 function addon:get_loot_history (name) | |
| 1187 local i | |
| 1188 i = self.history.byname[name] | |
| 1189 if not i then | |
| 1190 i = #self.history + 1 | |
| 1191 self.history[i] = { name=name } | |
| 1192 self.history.byname[name] = i | |
| 1193 end | |
| 1194 return self.history[i] | |
| 1195 end | |
| 1196 | |
| 1197 -- Prepares and returns table to be used as self.history. | |
| 1198 function addon:_prep_new_history_category (prev_table, realmname) | |
| 1199 local t = prev_table or { | |
| 1200 --kind = 'realm', | |
| 1201 realm = realmname, | |
| 1202 } | |
| 1203 | |
| 1204 --[[ | |
| 1205 t.cols = setmetatable({ | |
| 1206 { value = realmname }, | |
| 1207 }, self.time_column1_used_mt) | |
| 1208 ]] | |
| 1209 | |
| 1210 if not t.byname then | |
| 1211 self:_build_history_names (t) | |
| 1212 end | |
| 1213 | |
| 1214 return t | |
| 1215 end | |
| 1216 | |
| 1217 function addon:_addHistoryEntry (lootindex) | |
| 1218 local e = g_loot[lootindex] | |
| 1219 local h = self:get_loot_history(e.person) | |
| 1220 local n = { | |
| 1221 id = e.id, | |
| 1222 when = self:format_timestamp (g_today, e), | |
| 1223 count = e.count, | |
| 1224 } | |
| 1225 h[#h+1] = n | |
| 1226 end | |
| 1227 end | |
| 1228 | |
| 1229 | |
| 1230 ------ Player communication | |
| 1231 do | |
| 1232 local function adduser (name, status, active) | |
| 1233 if status then addon.sender_list.names[name] = status end | |
| 1234 if active then addon.sender_list.active[name] = active end | |
| 1235 end | |
| 1236 | |
| 1237 -- Incoming handler functions. All take the sender name and the incoming | |
| 1238 -- tag as the first two arguments. All of these are active even when the | |
| 1239 -- player is not tracking loot, so test for that when appropriate. | |
| 1240 local OCR_funcs = {} | |
| 1241 | |
| 1242 OCR_funcs.ping = function (sender) | |
| 1243 pprint('comm', "incoming ping from", sender) | |
| 1244 addon:whispercast (sender, 'pong', addon.revision, | |
| 1245 addon.enabled and "tracking" or (addon.rebroadcast and "broadcasting" or "disabled")) | |
| 1246 end | |
| 1247 OCR_funcs.pong = function (sender, _, rev, status) | |
| 1248 local s = ("|cff00ff00%s|r v2r%s is |cff00ffff%s|r"):format(sender,rev,status) | |
| 1249 addon:Print("Echo: ", s) | |
| 1250 adduser (sender, s, status=="tracking" or status=="broadcasting" or nil) | |
| 1251 end | |
| 1252 | |
| 1253 OCR_funcs.loot = function (sender, _, recip, item, count, extratext) | |
| 1254 addon.dprint('comm', "DOTloot, sender", sender, "recip", recip, "item", item, "count", count) | |
| 1255 if not addon.enabled then return end | |
| 1256 adduser (sender, nil, true) | |
| 1257 addon:CHAT_MSG_LOOT ("broadcast", recip, item, count, sender, extratext) | |
| 1258 end | |
| 1259 | |
| 1260 OCR_funcs.boss = function (sender, _, reason, bossname, instancetag) | |
| 1261 addon.dprint('comm', "DOTboss, sender", sender, "reason", reason, "name", bossname, "it", instancetag) | |
| 1262 if not addon.enabled then return end | |
| 1263 adduser (sender, nil, true) | |
| 1264 addon:on_boss_broadcast (reason, bossname, instancetag) | |
| 1265 end | |
| 1266 | |
| 1267 OCR_funcs.bcast_req = function (sender) | |
| 1268 if addon.debug.comm or ((not g_wafer_thin) and (not addon.rebroadcast)) | |
| 1269 then | |
| 1270 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. Or do nothing for now to see if other requests arrive.", | |
| 1271 sender, | |
| 1272 addon.format_hypertext('bcaston',"the red pill",'|cffff4040'), | |
| 1273 addon.format_hypertext('waferthin',"the blue pill",'|cff0070dd')) | |
| 1274 end | |
| 1275 self.popped = true | |
| 1276 end | |
| 1277 | |
| 1278 OCR_funcs.bcast_responder = function (sender) | |
| 1279 if addon.debug.comm or addon.requesting or | |
| 1280 ((not g_wafer_thin) and (not addon.rebroadcast)) | |
| 1281 then | |
| 1282 addon:Print(sender, "has answered the call and is now broadcasting loot.") | |
| 1283 end | |
| 1284 end | |
| 1285 -- remove this tag once it's all tested | |
| 1286 OCR_funcs.bcast_denied = function (sender) | |
| 1287 if addon.requesting then addon:Print(sender, "declines futher broadcast requests.") end | |
| 1288 end | |
| 1289 | |
| 1290 -- Incoming message dispatcher | |
| 1291 local function dotdotdot (sender, tag, ...) | |
| 1292 local f = OCR_funcs[tag] | |
| 1293 addon.dprint('comm', ":... processing",tag,"from",sender) | |
| 1294 if f then return f(sender,tag,...) end | |
| 1295 addon.dprint('comm', "unknown comm message",tag",from", sender) | |
| 1296 end | |
| 1297 -- Recent message cache | |
| 1298 addon.recent_messages = create_new_cache ('comm', comm_cleanup_ttl) | |
| 1299 | |
| 1300 function addon:OnCommReceived (prefix, msg, distribution, sender) | |
| 1301 if prefix ~= self.ident then return end | |
| 1302 if not self.debug.comm then | |
| 1303 if distribution ~= "RAID" and distribution ~= "WHISPER" then return end | |
| 1304 if sender == my_name then return end | |
| 1305 end | |
| 1306 self.dprint('comm', ":OCR from", sender, "message is", msg) | |
| 1307 | |
| 1308 if self.recent_messages:test(msg) then | |
| 1309 return self.dprint('cache', "message <",msg,"> already in cache, skipping") | |
| 1310 end | |
| 1311 self.recent_messages:add(msg) | |
| 1312 | |
| 1313 -- Nothing is actually returned, just (ab)using tail calls. | |
| 1314 return dotdotdot(sender,strsplit('\a',msg)) | |
| 1315 end | |
| 1316 | |
| 1317 function addon:OnCommReceivedNocache (prefix, msg, distribution, sender) | |
| 1318 if prefix ~= self.identTg then return end | |
| 1319 if not self.debug.comm then | |
| 1320 if distribution ~= "WHISPER" then return end | |
| 1321 if sender == my_name then return end | |
| 1322 end | |
| 1323 self.dprint('comm', ":OCRN from", sender, "message is", msg) | |
| 1324 return dotdotdot(sender,strsplit('\a',msg)) | |
| 1325 end | |
| 1326 end | |
| 1327 | |
| 1328 -- vim:noet |
