Mercurial > wow > ouroloot
comparison core.lua @ 100:a57133ee3c9b
- Allow event callbacks using the standard CallbackHandler scheme. Add
a debug.callback flag with usual semantics.
- Fire an initial set of events. This will take experimentation.
- If restoring g_loot, set metatables on previous entries also.
| author | Farmbuyer of US-Kilrogg <farmbuyer@gmail.com> |
|---|---|
| date | Mon, 30 Jul 2012 19:25:46 +0000 |
| parents | 966d06c8d9c9 |
| children | f7162a1cadc7 |
comparison
equal
deleted
inserted
replaced
| 99:966d06c8d9c9 | 100:a57133ee3c9b |
|---|---|
| 203 comm = false, | 203 comm = false, |
| 204 loot = false, | 204 loot = false, |
| 205 flow = false, | 205 flow = false, |
| 206 notraid = false, | 206 notraid = false, |
| 207 cache = false, | 207 cache = false, |
| 208 callback = false, | |
| 208 alsolog = false, | 209 alsolog = false, |
| 209 } | 210 } |
| 210 --@debug@ | 211 --@debug@ |
| 211 DEBUG_PRINT = true | 212 DEBUG_PRINT = true |
| 212 debug.loot = true | 213 debug.loot = true |
| 213 debug.comm = true | 214 debug.comm = true |
| 215 is_guilded = _G.IsInGuild() | |
| 214 --@end-debug@ | 216 --@end-debug@ |
| 215 | 217 |
| 216 -- This looks ugly, but it factors out the load-time decisions from | 218 -- This looks ugly, but it factors out the load-time decisions from |
| 217 -- the run-time ones. Args to [dp]print are concatenated with spaces. | 219 -- the run-time ones. Args to [dp]print are concatenated with spaces. |
| 218 if tekdebug then | 220 if tekdebug then |
| 386 local pprint, tabledump = addon.pprint, flib.tabledump | 388 local pprint, tabledump = addon.pprint, flib.tabledump |
| 387 local CopyTable = _G.CopyTable | 389 local CopyTable = _G.CopyTable |
| 388 local GetNumRaidMembers = _G.GetNumGroupMembers or _G.GetNumRaidMembers | 390 local GetNumRaidMembers = _G.GetNumGroupMembers or _G.GetNumRaidMembers |
| 389 local IsInRaid = _G.IsInRaid or (function() return GetNumRaidMembers() > 0 end) | 391 local IsInRaid = _G.IsInRaid or (function() return GetNumRaidMembers() > 0 end) |
| 390 -- En masse forward decls of symbols defined inside local blocks | 392 -- En masse forward decls of symbols defined inside local blocks |
| 391 local _register_bossmod, makedate, create_new_cache, _init, _log | 393 local _register_bossmod, makedate, create_new_cache, _init, _log, _do_loot_metas |
| 392 local _history_by_loot_id, _setup_unique_replace, _unavoidable_collision | 394 local _history_by_loot_id, _setup_unique_replace, _unavoidable_collision |
| 393 local _notify_about_change, _notify_about_remote | 395 local _notify_about_change, _notify_about_remote |
| 394 | 396 |
| 395 -- Try to extract numbers from the .toc "Version" and munge them into an | 397 -- Try to extract numbers from the .toc "Version" and munge them into an |
| 396 -- integral form for comparison. The result doesn't need to be meaningful as | 398 -- integral form for comparison. The result doesn't need to be meaningful as |
| 609 | 611 |
| 610 -- If unique keys ever change into objects instead of strings, change | 612 -- If unique keys ever change into objects instead of strings, change |
| 611 -- this into a weakly-keyed table. | 613 -- this into a weakly-keyed table. |
| 612 mt = { __metatable = 'Should be using setmode.' } | 614 mt = { __metatable = 'Should be using setmode.' } |
| 613 | 615 |
| 614 g_uniques = setmetatable (m_reset{}, mt) | 616 g_uniques = _G.setmetatable (m_reset{}, mt) |
| 615 end | 617 end |
| 616 | 618 |
| 617 | 619 |
| 618 ------ Expiring caches | 620 ------ Expiring caches |
| 619 --[[ | 621 --[[ |
| 687 add = _add, | 689 add = _add, |
| 688 test = _test, | 690 test = _test, |
| 689 cleanup = cleanup_group:CreateAnimation("Animation"), | 691 cleanup = cleanup_group:CreateAnimation("Animation"), |
| 690 func = on_alldone, | 692 func = on_alldone, |
| 691 fifo = {}, | 693 fifo = {}, |
| 692 hash = setmetatable({}, {__mode='kv'}), | 694 hash = _G.setmetatable({}, {__mode='kv'}), |
| 693 } | 695 } |
| 694 c.cleanup:SetOrder(1) | 696 c.cleanup:SetOrder(1) |
| 695 caches[name] = c | 697 caches[name] = c |
| 696 return c | 698 return c |
| 697 end | 699 end |
| 713 -- VARIABLES_LOADED has fired by this point; test if we're doing something like | 715 -- VARIABLES_LOADED has fired by this point; test if we're doing something like |
| 714 -- relogging during a raid and already have collected loot data | 716 -- relogging during a raid and already have collected loot data |
| 715 local OuroLootSV = _G.OuroLootSV | 717 local OuroLootSV = _G.OuroLootSV |
| 716 g_restore_p = OuroLootSV ~= nil | 718 g_restore_p = OuroLootSV ~= nil |
| 717 self.dprint('flow', "oninit sets restore as", g_restore_p) | 719 self.dprint('flow', "oninit sets restore as", g_restore_p) |
| 720 | |
| 721 -- Primarily for plugins, but can be of use to me also... | |
| 722 self.callbacks = _G.LibStub("CallbackHandler-1.0"):New(self) | |
| 723 --function self.callbacks:OnUsed (target_aka_self, eventname) end | |
| 724 --function self.callbacks:OnUnused (target_aka_self, eventname) end | |
| 718 | 725 |
| 719 if _G.OuroLootOptsDB == nil then | 726 if _G.OuroLootOptsDB == nil then |
| 720 local vclick = self.format_hypertext ([[click here]], ITEM_QUALITY_UNCOMMON, 'help') | 727 local vclick = self.format_hypertext ([[click here]], ITEM_QUALITY_UNCOMMON, 'help') |
| 721 self:ScheduleTimer(function(s) | 728 self:ScheduleTimer(function(s) |
| 722 for id in pairs(self.default_itemfilter) do | 729 for id in pairs(self.default_itemfilter) do |
| 1042 end | 1049 end |
| 1043 return self:NewModule(modname) | 1050 return self:NewModule(modname) |
| 1044 end | 1051 end |
| 1045 end | 1052 end |
| 1046 | 1053 |
| 1054 -- We don't want to trigger plugins and another addons as soon as something | |
| 1055 -- interesting happens, because a nontrivial amount of work happens "quickly" | |
| 1056 -- after the interesting event: cleanups/fixups, improvs from network, etc. | |
| 1057 -- So firing a callback is delayed ever so briefly by human standards. | |
| 1058 -- | |
| 1059 -- Can't *quite* use the expiring caches for this, but that's okay. | |
| 1060 do | |
| 1061 local unpack = _G.unpack | |
| 1062 local function F (t) | |
| 1063 addon.callbacks:Fire (unpack(t)) | |
| 1064 flib.del(t) | |
| 1065 end | |
| 1066 function addon:Fire (...) | |
| 1067 self.dprint('callback', ...) | |
| 1068 local capture = flib.new(...) | |
| 1069 self:ScheduleTimer (F, 1.2, capture) | |
| 1070 end | |
| 1071 end | |
| 1072 | |
| 1047 | 1073 |
| 1048 ------ Event handlers | 1074 ------ Event handlers |
| 1049 function addon:_clear_SVs() | 1075 function addon:_clear_SVs() |
| 1050 g_loot = {} -- not saved, just fooling PLAYER_LOGOUT tests | 1076 g_loot = {} -- not saved, just fooling PLAYER_LOGOUT tests |
| 1051 _G.OuroLootSV = nil | 1077 _G.OuroLootSV = nil |
| 1126 if r.online ~= R_LEFT and not UnitInRaid(name) then | 1152 if r.online ~= R_LEFT and not UnitInRaid(name) then |
| 1127 r.online = R_LEFT | 1153 r.online = R_LEFT |
| 1128 r.leave = now | 1154 r.leave = now |
| 1129 end | 1155 end |
| 1130 end | 1156 end |
| 1157 | |
| 1158 -- XXX somewhere in here, we could fire a useful callback event | |
| 1131 | 1159 |
| 1132 if redo then | 1160 if redo then |
| 1133 redo_count = redo_count + 1 | 1161 redo_count = redo_count + 1 |
| 1134 end | 1162 end |
| 1135 redo = false | 1163 redo = false |
| 1605 end | 1633 end |
| 1606 self:Print "Baron Steamroller has been slain. Congratulations on your rug." | 1634 self:Print "Baron Steamroller has been slain. Congratulations on your rug." |
| 1607 | 1635 |
| 1608 elseif cmd == "debug" then | 1636 elseif cmd == "debug" then |
| 1609 if arg then | 1637 if arg then |
| 1638 self.is_guilded = _G.IsInGuild() | |
| 1610 self.debug[arg] = not self.debug[arg] | 1639 self.debug[arg] = not self.debug[arg] |
| 1611 _G.print(arg,self.debug[arg]) | 1640 _G.print(arg,self.debug[arg]) |
| 1612 if self.debug[arg] then self.DEBUG_PRINT = true end | 1641 if self.debug[arg] then self.DEBUG_PRINT = true end |
| 1613 else | 1642 else |
| 1614 self.DEBUG_PRINT = not self.DEBUG_PRINT | 1643 self.DEBUG_PRINT = not self.DEBUG_PRINT |
| 1703 self.enabled = not opt_bcast_only | 1732 self.enabled = not opt_bcast_only |
| 1704 g_seeing_oldsigs = nil | 1733 g_seeing_oldsigs = nil |
| 1705 if opt_threshold then | 1734 if opt_threshold then |
| 1706 self:SetThreshold (opt_threshold, --[[quiet_p=]]true) | 1735 self:SetThreshold (opt_threshold, --[[quiet_p=]]true) |
| 1707 end | 1736 end |
| 1737 self:Fire ('Activate', self.enabled, self.rebroadcast, self.threshold) | |
| 1708 self:Print("Now %s; threshold currently %s.", | 1738 self:Print("Now %s; threshold currently %s.", |
| 1709 self.enabled and "tracking" or "only broadcasting", | 1739 self.enabled and "tracking" or "only broadcasting", |
| 1710 self.thresholds[self.threshold]) | 1740 self.thresholds[self.threshold]) |
| 1711 self:broadcast('revcheck',version_large) | 1741 self:broadcast('revcheck',version_large) |
| 1712 end | 1742 end |
| 1717 self.enabled = false | 1747 self.enabled = false |
| 1718 self.rebroadcast = false | 1748 self.rebroadcast = false |
| 1719 self:UnregisterEvent(RAID_ROSTER_UPDATE_EVENT) | 1749 self:UnregisterEvent(RAID_ROSTER_UPDATE_EVENT) |
| 1720 self:UnregisterEvent("PLAYER_ENTERING_WORLD") | 1750 self:UnregisterEvent("PLAYER_ENTERING_WORLD") |
| 1721 self:UnregisterEvent("CHAT_MSG_LOOT") | 1751 self:UnregisterEvent("CHAT_MSG_LOOT") |
| 1752 self:Fire ('Deactivate') | |
| 1722 self:Print("Deactivated.") | 1753 self:Print("Deactivated.") |
| 1723 end | 1754 end |
| 1724 | 1755 |
| 1725 function addon:Clear(verbose_p) | 1756 function addon:Clear(verbose_p) |
| 1726 local repopup, st | 1757 local repopup, st |
| 1742 else | 1773 else |
| 1743 self:Print("Current loot data cleared.") | 1774 self:Print("Current loot data cleared.") |
| 1744 end | 1775 end |
| 1745 end | 1776 end |
| 1746 _init(self,st) | 1777 _init(self,st) |
| 1778 self:Fire ('Reset') | |
| 1747 if repopup then | 1779 if repopup then |
| 1748 addon:BuildMainDisplay() | 1780 addon:BuildMainDisplay() |
| 1749 end | 1781 end |
| 1750 end | 1782 end |
| 1751 | 1783 |
| 1994 self.hist_clean = nil | 2026 self.hist_clean = nil |
| 1995 if g_restore_p then | 2027 if g_restore_p then |
| 1996 g_loot = _G.OuroLootSV | 2028 g_loot = _G.OuroLootSV |
| 1997 self.popped = #g_loot > 0 | 2029 self.popped = #g_loot > 0 |
| 1998 self.dprint('flow', "restoring", #g_loot, "entries") | 2030 self.dprint('flow', "restoring", #g_loot, "entries") |
| 2031 _do_loot_metas() | |
| 1999 self:ScheduleTimer("Activate", 12, opts.threshold) | 2032 self:ScheduleTimer("Activate", 12, opts.threshold) |
| 2000 -- FIXME printed could be too large if entries were deleted, how much do we care? | 2033 -- FIXME printed could be too large if entries were deleted, how much do we care? |
| 2001 self.sharder = opts.autoshard | 2034 self.sharder = opts.autoshard |
| 2002 else | 2035 else |
| 2003 g_loot = {} | 2036 g_loot = {} |
| 2078 break | 2111 break |
| 2079 end | 2112 end |
| 2080 end | 2113 end |
| 2081 end | 2114 end |
| 2082 bossi = addon._addBossEntry(boss) | 2115 bossi = addon._addBossEntry(boss) |
| 2083 -- addon. | 2116 addon:Fire ('NewBoss', boss) |
| 2084 bossi = addon._adjustBossOrder (bossi, g_boss_signpost) or bossi | 2117 bossi = addon._adjustBossOrder (bossi, g_boss_signpost) or bossi |
| 2085 g_boss_signpost = nil | 2118 g_boss_signpost = nil |
| 2086 addon.latest_instance = boss.instance | 2119 addon.latest_instance = boss.instance |
| 2087 addon.dprint('loot', "added boss entry", bossi) | 2120 addon.dprint('loot', "added boss entry", bossi) |
| 2088 if boss.reason == 'kill' then | 2121 if boss.reason == 'kill' then |
| 2228 | 2261 |
| 2229 -- Adding entries to the loot record, and tracking the corresponding timestamp. | 2262 -- Adding entries to the loot record, and tracking the corresponding timestamp. |
| 2230 do | 2263 do |
| 2231 local rawget, setmetatable = _G.rawget, _G.setmetatable | 2264 local rawget, setmetatable = _G.rawget, _G.setmetatable |
| 2232 | 2265 |
| 2233 -- This shouldn't be required. /sadface | 2266 --@debug@ |
| 2267 local tos = {} | |
| 2268 tos.time = function (e) | |
| 2269 return e.startday.text | |
| 2270 end | |
| 2271 tos.boss = function (e) | |
| 2272 return e.bossname .. '/' .. e.reason | |
| 2273 end | |
| 2274 tos.loot = function (e) | |
| 2275 return e.itemname .. '/' .. e.person .. '/' .. e.unique .. '/' | |
| 2276 .. tostring(e.disposition) .. (e.extratext and ('/'..e.extratext) or '') | |
| 2277 end | |
| 2278 --@end-debug@ | |
| 2234 local loot_entry_mt = { | 2279 local loot_entry_mt = { |
| 2235 __index = function (e,key) | 2280 __index = function (e,key) |
| 2281 -- This shouldn't be required, as the refresh should be picking | |
| 2282 -- it up already. Sigh. | |
| 2236 if key == 'cols' then | 2283 if key == 'cols' then |
| 2237 pprint('mt', e.kind, "key is", key) | 2284 pprint('mt', e.kind, "key is", key) |
| 2238 --tabledump(e) -- not actually that useful | |
| 2239 addon:_fill_out_eoi_data(1) | 2285 addon:_fill_out_eoi_data(1) |
| 2240 end | 2286 end |
| 2241 return rawget(e,key) | 2287 return rawget(e,key) |
| 2242 end | 2288 end, |
| 2289 --@debug@ | |
| 2290 __tostring = function (e) | |
| 2291 local k = e.kind | |
| 2292 if k then | |
| 2293 return ("<%s/%s>"):format(k, tos[k] and tos[k](e) or "?") | |
| 2294 end | |
| 2295 return "<unknown loot entry type>" | |
| 2296 end, | |
| 2297 --@end-debug@ | |
| 2243 } | 2298 } |
| 2299 function _do_loot_metas() | |
| 2300 for i,e in ipairs(g_loot) do | |
| 2301 setmetatable(e,loot_entry_mt) | |
| 2302 end | |
| 2303 _do_loot_metas = nil | |
| 2304 end | |
| 2244 | 2305 |
| 2245 -- Given a loot index, searches backwards for a timestamp. Returns that | 2306 -- Given a loot index, searches backwards for a timestamp. Returns that |
| 2246 -- index and the time entry, or nil if it falls off the beginning. Pass an | 2307 -- index and the time entry, or nil if it falls off the beginning. Pass an |
| 2247 -- optional second index to search no earlier than that. | 2308 -- optional second index to search no earlier than that. |
| 2248 -- May also be able to make good use of this in forum-generation routine. | 2309 -- May also be able to make good use of this in forum-generation routine. |
| 2324 e.stamp = time_t --localuptime | 2385 e.stamp = time_t --localuptime |
| 2325 if e.kind == 'loot' then | 2386 if e.kind == 'loot' then |
| 2326 if (not e.unique) or (#e.unique==0) then | 2387 if (not e.unique) or (#e.unique==0) then |
| 2327 e.unique = e.id .. (e.disposition or e.person) .. _G.date("%Y/%m/%d %H:%M",e.stamp) | 2388 e.unique = e.id .. (e.disposition or e.person) .. _G.date("%Y/%m/%d %H:%M",e.stamp) |
| 2328 end | 2389 end |
| 2390 addon:Fire ('NewLootEntry', e) | |
| 2329 end | 2391 end |
| 2330 local index = #g_loot + 1 | 2392 local index = #g_loot + 1 |
| 2331 g_loot[index] = e | 2393 g_loot[index] = e |
| 2394 addon:Fire ('NewEOIEntry', e) | |
| 2332 return index | 2395 return index |
| 2333 end | 2396 end |
| 2334 | 2397 |
| 2335 -- Safety wrapper only. | 2398 -- Safety/convenience wrapper only. |
| 2336 -- XXX Maybe pprint something here. | |
| 2337 function addon._addBossEntry (e) | 2399 function addon._addBossEntry (e) |
| 2338 local ret = addon._addLootEntry(e) | 2400 local ret = addon._addLootEntry(e) |
| 2339 assert(e.kind=='boss') | 2401 assert(e.kind=='boss') |
| 2340 local needSize = e.maxsize == nil | 2402 local needSize = e.maxsize == nil |
| 2341 local needSnap = e.raidersnap == nil | 2403 local needSnap = e.raidersnap == nil |
| 2344 local ss, max, inst = addon:snapshot_raid() | 2406 local ss, max, inst = addon:snapshot_raid() |
| 2345 if needSize then e.maxsize = max end | 2407 if needSize then e.maxsize = max end |
| 2346 if needSnap then e.raidersnap = ss end | 2408 if needSnap then e.raidersnap = ss end |
| 2347 if needInst then e.instance = inst end | 2409 if needInst then e.instance = inst end |
| 2348 end | 2410 end |
| 2411 addon:Fire ('NewBossEntry', e) | |
| 2349 return ret | 2412 return ret |
| 2350 end | 2413 end |
| 2351 | 2414 |
| 2352 -- Problem: (1) boss kill happens, (2) fast looting happens, (3) boss | 2415 -- Problem: (1) boss kill happens, (2) fast looting happens, (3) boss |
| 2353 -- cache cleanup fires. Result: loot shows up before boss kill entry. | 2416 -- cache cleanup fires. Result: loot shows up before boss kill entry. |
| 2550 local clicky | 2613 local clicky |
| 2551 function addon:horrible_horrible_error (err_msg) | 2614 function addon:horrible_horrible_error (err_msg) |
| 2552 if self.display then | 2615 if self.display then |
| 2553 local d = self.display | 2616 local d = self.display |
| 2554 if d then | 2617 if d then |
| 2618 -- Take this down a piece at a time, on the assumption that | |
| 2619 -- the main window won't be able to do so. | |
| 2555 local gui = d:GetUserData("GUI state") | 2620 local gui = d:GetUserData("GUI state") |
| 2556 local eoist = gui.eoiST | 2621 local eoist = gui.eoiST |
| 2557 if eoist then eoist:Hide() end | 2622 if eoist then eoist:Hide() end |
| 2558 local histst = gui.histST | 2623 local histst = gui.histST |
| 2559 if histst then histst:Hide() end | 2624 if histst then histst:Hide() end |
| 2885 h.when[U] = when | 2950 h.when[U] = when |
| 2886 h.id[U] = e.id | 2951 h.id[U] = e.id |
| 2887 h.count[U] = e.count | 2952 h.count[U] = e.count |
| 2888 | 2953 |
| 2889 g_uniques[U] = { loot = lootindex, history = e.person } | 2954 g_uniques[U] = { loot = lootindex, history = e.person } |
| 2955 self:Fire ('NewHistory', e.person, U) | |
| 2890 end | 2956 end |
| 2891 | 2957 |
| 2892 -- Create new history table based on current loot. | 2958 -- Create new history table based on current loot. |
| 2893 function addon:rewrite_history (realmname) | 2959 function addon:rewrite_history (realmname) |
| 2894 local r = assert(realmname) | 2960 local r = assert(realmname) |
| 3102 end | 3168 end |
| 3103 if self.display then | 3169 if self.display then |
| 3104 self.display:GetUserData("GUI state").eoiST:OuroLoot_Refresh(index) | 3170 self.display:GetUserData("GUI state").eoiST:OuroLoot_Refresh(index) |
| 3105 self:redisplay() | 3171 self:redisplay() |
| 3106 end | 3172 end |
| 3173 self:Fire ('Reassign', unique, id, e, from_name, to_name) | |
| 3107 return index | 3174 return index |
| 3108 end | 3175 end |
| 3109 | 3176 |
| 3110 local function expunge (player, index_or_unique) | 3177 local function expunge (player, index_or_unique) |
| 3111 local i,u | 3178 local i,u |
| 3126 assert(#u>0) | 3193 assert(#u>0) |
| 3127 tremove (player.unique, i) | 3194 tremove (player.unique, i) |
| 3128 player.when[u], player.id[u], player.count[u] = nil, nil, nil | 3195 player.when[u], player.id[u], player.count[u] = nil, nil, nil |
| 3129 g_uniques[u] = nil | 3196 g_uniques[u] = nil |
| 3130 addon.hist_clean = nil | 3197 addon.hist_clean = nil |
| 3198 addon:Fire ('DelHistory', player.name, u) | |
| 3131 return #player.unique | 3199 return #player.unique |
| 3132 end | 3200 end |
| 3133 | 3201 |
| 3134 -- Mirror of _addHistoryEntry. Arguments are either: | 3202 -- Mirror of _addHistoryEntry. Arguments are either: |
| 3135 -- E - loot entry | 3203 -- E - loot entry |
| 3305 -- A unique tag has been set by this point. | 3373 -- A unique tag has been set by this point. |
| 3306 if how == "local" then | 3374 if how == "local" then |
| 3307 unique = assert(e.unique) | 3375 unique = assert(e.unique) |
| 3308 self:vbroadcast('mark', unique, id, olddisp, newdisp) | 3376 self:vbroadcast('mark', unique, id, olddisp, newdisp) |
| 3309 end | 3377 end |
| 3378 self:Fire ('MarkAs', unique, id, e, olddisp or 'normal', newdisp or 'normal') | |
| 3310 return index | 3379 return index |
| 3311 end | 3380 end |
| 3312 end | 3381 end |
| 3313 | 3382 |
| 3314 | 3383 |
| 3345 function addon:broadcast(tag,...) | 3414 function addon:broadcast(tag,...) |
| 3346 local msg = assemble(tag,...) | 3415 local msg = assemble(tag,...) |
| 3347 self.dprint('comm', "<broadcast>:", msg) | 3416 self.dprint('comm', "<broadcast>:", msg) |
| 3348 self:SendCommMessage(self.ident, msg, "RAID") | 3417 self:SendCommMessage(self.ident, msg, "RAID") |
| 3349 -- this is what lets us debug our own message traffic: | 3418 -- this is what lets us debug our own message traffic: |
| 3350 if self.debug.comm then | 3419 if self.debug.comm and self.is_guilded then |
| 3351 self:SendCommMessage(self.ident, msg, "GUILD") | 3420 self:SendCommMessage(self.ident, msg, "GUILD") |
| 3352 end | 3421 end |
| 3353 end | 3422 end |
| 3354 -- whispercast(<to>, 'tag', <stuff>) | 3423 -- whispercast(<to>, 'tag', <stuff>) |
| 3355 function addon:whispercast(to,...) | 3424 function addon:whispercast(to,...) |
