vanilla-wow-addons – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 office 1 --[[
2 Name: AceComm-2.0
3 Revision: $Rev: 15440 $
4 Developed by: The Ace Development Team (http://www.wowace.com/index.php/The_Ace_Development_Team)
5 Inspired By: Ace 1.x by Turan (turan@gryphon.com)
6 Website: http://www.wowace.com/
7 Documentation: http://www.wowace.com/index.php/AceComm-2.0
8 SVN: http://svn.wowace.com/wowace/trunk/Ace2/AceComm-2.0
9 Description: Mixin to allow for inter-player addon communications.
10 Dependencies: AceLibrary, AceOO-2.0, AceEvent-2.0,
11 ChatThrottleLib by Mikk (included)
12 ]]
13  
14 local MAJOR_VERSION = "AceComm-2.0"
15 local MINOR_VERSION = "$Revision: 15440 $"
16  
17 if not AceLibrary then error(MAJOR_VERSION .. " requires AceLibrary") end
18 if not AceLibrary:IsNewVersion(MAJOR_VERSION, MINOR_VERSION) then return end
19  
20 if not AceLibrary:HasInstance("AceOO-2.0") then error(MAJOR_VERSION .. " requires AceOO-2.0") end
21  
22 local _G = getfenv(0)
23  
24 local AceOO = AceLibrary("AceOO-2.0")
25 local Mixin = AceOO.Mixin
26 local AceComm = Mixin {
27 "SendCommMessage",
28 "SendPrioritizedCommMessage",
29 "RegisterComm",
30 "UnregisterComm",
31 "UnregisterAllComms",
32 "IsCommRegistered",
33 "SetDefaultCommPriority",
34 "SetCommPrefix",
35 "RegisterMemoizations",
36 "IsUserInChannel",
37 }
38 AceComm.hooks = {}
39  
40 local AceEvent = AceLibrary:HasInstance("AceEvent-2.0") and AceLibrary("AceEvent-2.0")
41  
42 local table_setn
43 do
44 local version = GetBuildInfo()
45 if string.find(version, "^2%.") then
46 -- 2.0.0
47 table_setn = function() end
48 else
49 table_setn = table.setn
50 end
51 end
52  
53 local new, del
54 do
55 local list = setmetatable({}, {__mode="k"})
56  
57 function new()
58 local t = next(list)
59 if t then
60 list[t] = nil
61 else
62 t = {}
63 end
64 return t
65 end
66  
67 function del(t)
68 setmetatable(t, nil)
69 for k in pairs(t) do
70 t[k] = nil
71 end
72 table_setn(t, 0)
73 list[t] = true
74 return nil
75 end
76 end
77  
78 local string_byte = string.byte
79  
80 local byte_a = string_byte('a')
81 local byte_z = string_byte('z')
82 local byte_A = string_byte('A')
83 local byte_Z = string_byte('Z')
84 local byte_fake_s = string_byte('\015')
85 local byte_fake_S = string_byte('\020')
86 local byte_deg = string_byte('°')
87 local byte_percent = string_byte('%') -- 37
88  
89 local byte_b = string_byte('b')
90 local byte_nil = string_byte('/')
91 local byte_plus = string_byte('+')
92 local byte_minus = string_byte('-')
93 local byte_d = string_byte('d')
94 local byte_D = string_byte('D')
95 local byte_e = string_byte('e')
96 local byte_E = string_byte('E')
97 local byte_m = string_byte('m')
98 local byte_s = string_byte('s')
99 local byte_S = string_byte('S')
100 local byte_o = string_byte('o')
101 local byte_O = string_byte('O')
102 local byte_t = string_byte('t')
103 local byte_T = string_byte('T')
104 local byte_u = string_byte('u')
105 local byte_U = string_byte('U')
106 local byte_i = string_byte('i')
107 local byte_I = string_byte('I')
108 local byte_j = string_byte('j')
109 local byte_J = string_byte('J')
110 local byte_inf = string_byte('@')
111 local byte_ninf = string_byte('$')
112 local byte_nan = string_byte('!')
113  
114 local inf = 1/0
115 local nan = 0/0
116  
117 local math_floor = math.floor
118 local math_mod = math.mod or math.fmod
119 local string_gfind = string.gmatch or string.gfind
120 local string_char = string.char
121 local string_len = string.len
122 local string_format = string.format
123 local string_gsub = string.gsub
124 local string_find = string.find
125 local table_insert = table.insert
126 local string_sub = string.sub
127 local table_concat = table.concat
128 local table_remove = table.remove
129  
130 local type = type
131 local unpack = unpack
132 local pairs = pairs
133 local next = next
134  
135 local player = UnitName("player")
136  
137 local NumericCheckSum, HexCheckSum, BinaryCheckSum
138 local TailoredNumericCheckSum, TailoredHexCheckSum, TailoredBinaryCheckSum
139 do
140 local SOME_PRIME = 16777213
141 function NumericCheckSum(text)
142 local counter = 1
143 local len = string_len(text)
144 for i = 1, len, 3 do
145 counter = math_mod(counter*8257, 16777259) +
146 (string_byte(text,i)) +
147 ((string_byte(text,i+1) or 1)*127) +
148 ((string_byte(text,i+2) or 2)*16383)
149 end
150 return math_mod(counter, 16777213)
151 end
152  
153 function HexCheckSum(text)
154 return string_format("%06x", NumericCheckSum(text))
155 end
156  
157 function BinaryCheckSum(text)
158 local num = NumericCheckSum(text)
159 return string_char(num / 65536, math_mod(num / 256, 256), math_mod(num, 256))
160 end
161  
162 function TailoredNumericCheckSum(text)
163 local hash = NumericCheckSum(text)
164 local a = math_floor(hash / 65536)
165 local b = math_floor(math_mod(hash / 256, 256))
166 local c = math_mod(hash, 256)
167 -- \000, \n, |, °, s, S, \015, \020
168 if a == 0 or a == 10 or a == 124 or a == 176 or a == 115 or a == 83 or a == 15 or a == 20 or a == 37 then
169 a = a + 1
170 -- \t, \255
171 elseif a == 9 or a == 255 then
172 a = a - 1
173 end
174 if b == 0 or b == 10 or b == 124 or b == 176 or b == 115 or b == 83 or b == 15 or b == 20 or b == 37 then
175 b = b + 1
176 elseif b == 9 or b == 255 then
177 b = b - 1
178 end
179 if c == 0 or c == 10 or c == 124 or c == 176 or c == 115 or c == 83 or c == 15 or c == 20 or c == 37 then
180 c = c + 1
181 elseif c == 9 or c == 255 then
182 c = c - 1
183 end
184 return a * 65536 + b * 256 + c
185 end
186  
187 function TailoredHexCheckSum(text)
188 return string_format("%06x", TailoredNumericCheckSum(text))
189 end
190  
191 function TailoredBinaryCheckSum(text)
192 local num = TailoredNumericCheckSum(text)
193 return string_char(num / 65536, math_mod(num / 256, 256), math_mod(num, 256))
194 end
195 end
196  
197 local function GetLatency()
198 local _,_,lag = GetNetStats()
199 return lag / 1000
200 end
201  
202 local function IsInChannel(chan)
203 return GetChannelName(chan) ~= 0
204 end
205  
206 -- Package a message for transmission
207 local function Encode(text, drunk)
208 text = string_gsub(text, "°", "°±")
209 if drunk then
210 text = string_gsub(text, "\020", "°\021")
211 text = string_gsub(text, "\015", "°\016")
212 text = string_gsub(text, "S", "\020")
213 text = string_gsub(text, "s", "\015")
214 -- change S and s to a different set of character bytes.
215 end
216 text = string_gsub(text, "\255", "°\254") -- \255 (this is here because \000 is more common)
217 text = string_gsub(text, "%z", "\255") -- \000
218 text = string_gsub(text, "\010", "°\011") -- \n
219 text = string_gsub(text, "\124", "°\125") -- |
220 text = string_gsub(text, "%%", "°\038") -- %
221 -- encode assorted prohibited characters
222 return text
223 end
224  
225 local func
226 -- Clean a received message
227 local function Decode(text, drunk)
228 if drunk then
229 local _,x = string_find(text, "^.*°")
230 text = string_gsub(text, "^(.*)°.-$", "%1")
231 -- get rid of " ...hic!"
232 end
233 if not func then
234 func = function(text)
235 if text == "\016" then
236 return "\015"
237 elseif text == "\021" then
238 return "\020"
239 elseif text == "±" then
240 return "°"
241 elseif text == "\254" then
242 return "\255"
243 elseif text == "\011" then
244 return "\010"
245 elseif text == "\125" then
246 return "\124"
247 elseif text == "\038" then
248 return "\037"
249 end
250 end
251 end
252 text = string_gsub(text, "\255", "\000")
253 if drunk then
254 text = string_gsub(text, "\020", "S")
255 text = string_gsub(text, "\015", "s")
256 end
257 text = string_gsub(text, drunk and "°([\016\021±\254\011\125\038])" or "°([±\254\011\125\038])", func)
258 -- remove the hidden character and refix the prohibited characters.
259 return text
260 end
261  
262 local lastChannelJoined
263  
264 function AceComm.hooks:JoinChannelByName(orig, channel, a,b,c,d,e,f,g,h,i)
265 lastChannelJoined = channel
266 return orig(channel, a,b,c,d,e,f,g,h,i)
267 end
268  
269 local function JoinChannel(channel)
270 if not IsInChannel(channel) then
271 LeaveChannelByName(channel)
272 AceComm:ScheduleEvent(JoinChannelByName, 0, channel)
273 end
274 end
275  
276 local function LeaveChannel(channel)
277 if IsInChannel(channel) then
278 LeaveChannelByName(channel)
279 end
280 end
281  
282 local switches = {}
283  
284 local function SwitchChannel(former, latter)
285 if IsInChannel(former) then
286 LeaveChannelByName(former)
287 local t = new()
288 t.former = former
289 t.latter = latter
290 switches[t] = true
291 return
292 end
293 if not IsInChannel(latter) then
294 JoinChannelByName(latter)
295 end
296 end
297  
298 local shutdown = false
299  
300 local zoneCache
301 local function GetCurrentZoneChannel()
302 if not zoneCache then
303 zoneCache = "AceCommZone" .. HexCheckSum(GetRealZoneText())
304 end
305 return zoneCache
306 end
307  
308 local AceComm_registry
309  
310 local function SupposedToBeInChannel(chan)
311 if not string_find(chan, "^AceComm") then
312 return true
313 elseif shutdown or not AceEvent:IsFullyInitialized() then
314 return false
315 end
316  
317 if chan == "AceComm" then
318 return AceComm_registry.GLOBAL and next(AceComm_registry.GLOBAL) and true or false
319 elseif string_find(chan, "^AceCommZone%x%x%x%x%x%x$") then
320 if chan == GetCurrentZoneChannel() then
321 return AceComm_registry.ZONE and next(AceComm_registry.ZONE) and true or false
322 else
323 return false
324 end
325 else
326 return AceComm_registry.CUSTOM and AceComm_registry.CUSTOM[chan] and next(AceComm_registry.CUSTOM[chan]) and true or false
327 end
328 end
329  
330 local function LeaveAceCommChannels(all)
331 if all then
332 shutdown = true
333 end
334 local _,a,_,b,_,c,_,d,_,e,_,f,_,g,_,h,_,i,_,j = GetChannelList()
335 local t = new()
336 t[1] = a
337 t[2] = b
338 t[3] = c
339 t[4] = d
340 t[5] = e
341 t[6] = f
342 t[7] = g
343 t[8] = h
344 t[9] = i
345 t[10] = j
346 for _,v in ipairs(t) do
347 if v and string_find(v, "^AceComm") then
348 if not SupposedToBeInChannel(v) then
349 LeaveChannelByName(v)
350 end
351 end
352 end
353 t = del(t)
354 end
355  
356 local lastRefix = 0
357 local function RefixAceCommChannelsAndEvents()
358 if GetTime() - lastRefix <= 5 then
359 AceComm:ScheduleEvent(RefixAceCommChannelsAndEvents, 1)
360 return
361 end
362 lastRefix = GetTime()
363 LeaveAceCommChannels(false)
364  
365 local channel = false
366 local whisper = false
367 local addon = false
368 if SupposedToBeInChannel("AceComm") then
369 JoinChannel("AceComm")
370 channel = true
371 end
372 if SupposedToBeInChannel(GetCurrentZoneChannel()) then
373 JoinChannel(GetCurrentZoneChannel())
374 channel = true
375 end
376 if AceComm_registry.CUSTOM then
377 for k,v in pairs(AceComm_registry.CUSTOM) do
378 if next(v) then
379 JoinChannel(k)
380 channel = true
381 end
382 end
383 end
384 if AceComm_registry.WHISPER then
385 whisper = true
386 end
387 if AceComm_registry.GROUP or AceComm_registry.PARTY or AceComm_registry.RAID or AceComm_registry.BATTLEGROUND or AceComm_registry.GUILD then
388 addon = true
389 end
390  
391 if channel then
392 if not AceComm:IsEventRegistered("CHAT_MSG_CHANNEL") then
393 AceComm:RegisterEvent("CHAT_MSG_CHANNEL")
394 end
395 if not AceComm:IsEventRegistered("CHAT_MSG_CHANNEL_LIST") then
396 AceComm:RegisterEvent("CHAT_MSG_CHANNEL_LIST")
397 end
398 if not AceComm:IsEventRegistered("CHAT_MSG_CHANNEL_JOIN") then
399 AceComm:RegisterEvent("CHAT_MSG_CHANNEL_JOIN")
400 end
401 if not AceComm:IsEventRegistered("CHAT_MSG_CHANNEL_LEAVE") then
402 AceComm:RegisterEvent("CHAT_MSG_CHANNEL_LEAVE")
403 end
404 else
405 if AceComm:IsEventRegistered("CHAT_MSG_CHANNEL") then
406 AceComm:UnregisterEvent("CHAT_MSG_CHANNEL")
407 end
408 if AceComm:IsEventRegistered("CHAT_MSG_CHANNEL_LIST") then
409 AceComm:UnregisterEvent("CHAT_MSG_CHANNEL_LIST")
410 end
411 if AceComm:IsEventRegistered("CHAT_MSG_CHANNEL_JOIN") then
412 AceComm:UnregisterEvent("CHAT_MSG_CHANNEL_JOIN")
413 end
414 if AceComm:IsEventRegistered("CHAT_MSG_CHANNEL_LEAVE") then
415 AceComm:UnregisterEvent("CHAT_MSG_CHANNEL_LEAVE")
416 end
417 end
418  
419 if whisper then
420 if not AceComm:IsEventRegistered("CHAT_MSG_WHISPER") then
421 AceComm:RegisterEvent("CHAT_MSG_WHISPER")
422 end
423 else
424 if AceComm:IsEventRegistered("CHAT_MSG_WHISPER") then
425 AceComm:UnregisterEvent("CHAT_MSG_WHISPER")
426 end
427 end
428  
429 if addon then
430 if not AceComm:IsEventRegistered("CHAT_MSG_ADDON") then
431 AceComm:RegisterEvent("CHAT_MSG_ADDON")
432 end
433 else
434 if AceComm:IsEventRegistered("CHAT_MSG_ADDON") then
435 AceComm:UnregisterEvent("CHAT_MSG_ADDON")
436 end
437 end
438 end
439  
440  
441 do
442 local myFunc = function(k)
443 if not IsInChannel(k.latter) then
444 JoinChannelByName(k.latter)
445 end
446 del(k)
447 switches[k] = nil
448 end
449  
450 function AceComm:CHAT_MSG_CHANNEL_NOTICE(kind, _, _, deadName, _, _, _, num, channel)
451 if kind == "YOU_LEFT" then
452 if not string_find(channel, "^AceComm") then
453 return
454 end
455 for k in pairs(switches) do
456 if k.former == channel then
457 self:ScheduleEvent(myFunc, 0, k)
458 end
459 end
460 if channel == GetCurrentZoneChannel() then
461 self:TriggerEvent("AceComm_LeftChannel", "ZONE")
462 elseif channel == "AceComm" then
463 self:TriggerEvent("AceComm_LeftChannel", "GLOBAL")
464 else
465 self:TriggerEvent("AceComm_LeftChannel", "CUSTOM", string_sub(channel, 8))
466 end
467 if string_find(channel, "^AceComm") and SupposedToBeInChannel(channel) then
468 self:ScheduleEvent(JoinChannel, 0, channel)
469 end
470 if AceComm.userRegistry[channel] then
471 AceComm.userRegistry[channel] = del(AceComm.userRegistry[channel])
472 end
473 elseif kind == "YOU_JOINED" then
474 if not string_find(num == 0 and deadName or channel, "^AceComm") then
475 return
476 end
477 if num == 0 then
478 self:ScheduleEvent(LeaveChannelByName, 0, deadName)
479 local t = new()
480 t.former = deadName
481 t.latter = deadName
482 switches[t] = true
483 elseif channel == GetCurrentZoneChannel() then
484 self:TriggerEvent("AceComm_JoinedChannel", "ZONE")
485 elseif channel == "AceComm" then
486 self:TriggerEvent("AceComm_JoinedChannel", "GLOBAL")
487 else
488 self:TriggerEvent("AceComm_JoinedChannel", "CUSTOM", string_sub(channel, 8))
489 end
490 if num ~= 0 then
491 if not SupposedToBeInChannel(channel) then
492 LeaveChannel(channel)
493 else
494 ListChannelByName(channel)
495 end
496 end
497 end
498 end
499 end
500  
501 local Serialize
502 do
503 local recurse
504 local function _Serialize(v, textToHash)
505 local kind = type(v)
506 if kind == "boolean" then
507 if v then
508 return "by"
509 else
510 return "bn"
511 end
512 elseif not v then -- nil
513 return "/"
514 elseif kind == "number" then
515 if v == math_floor(v) then
516 if v <= 127 and v >= -128 then
517 if v < 0 then
518 v = v + 256
519 end
520 return string_char(byte_d, v)
521 elseif v <= 32767 and v >= -32768 then
522 if v < 0 then
523 v = v + 65536
524 end
525 return string_char(byte_D, v / 256, math_mod(v, 256))
526 elseif v <= 2147483647 and v >= -2147483648 then
527 if v < 0 then
528 v = v + 4294967296
529 end
530 return string_char(byte_e, v / 16777216, math_mod(v / 65536, 256), math_mod(v / 256, 256), math_mod(v, 256))
531 elseif v <= 9223372036854775807 and v >= -9223372036854775808 then
532 if v < 0 then
533 v = v + 18446744073709551616
534 end
535 return string_char(byte_E, v / 72057594037927936, math_mod(v / 281474976710656, 256), math_mod(v / 1099511627776, 256), math_mod(v / 4294967296, 256), math_mod(v / 16777216, 256), math_mod(v / 65536, 256), math_mod(v / 256, 256), math_mod(v, 256))
536 end
537 elseif v == inf then
538 return string_char(64 --[[byte_inf]])
539 elseif v == -inf then
540 return string_char(36 --[[byte_ninf]])
541 elseif v ~= v then
542 return string_char(33 --[[byte_nan]])
543 end
544 -- do
545 -- local s = tostring(v)
546 -- local len = string_len(s)
547 -- return string_char(byte_plus, len) .. s
548 -- end
549 local sign = v < 0 or v == 0 and tostring(v) == "-0"
550 if sign then
551 v = -v
552 end
553 local m, exp = math.frexp(v)
554 m = m * 9007199254740992
555 local x = exp + 1023
556 local b = math_mod(m, 256)
557 local c = math_mod(math_floor(m / 256), 256)
558 m = math_floor(m / 65536)
559 m = m + x * 137438953472
560 return string_char(sign and byte_minus or byte_plus, math_mod(m / 1099511627776, 256), math_mod(m / 4294967296, 256), math_mod(m / 16777216, 256), math_mod(m / 65536, 256), math_mod(m / 256, 256), math_mod(m, 256), c, b)
561 elseif kind == "string" then
562 local hash = textToHash and textToHash[v]
563 if hash then
564 return string_char(byte_m, hash / 65536, math_mod(hash / 256, 256), math_mod(hash, 256))
565 end
566 local _,_,A,B,C = string_find(v, "|cff%x%x%x%x%x%x|Hitem:(%d+):(%d+):(%d+):%d+|h%[.+%]|h|r")
567 if A then
568 -- item link
569 A = tonumber(A)
570 B = tonumber(B)
571 C = tonumber(C)
572 if C ~= 0 then
573 if B ~= 0 then
574 return string_char(byte_I, math_mod(A / 65536, 256), math_mod(A / 256, 256), math_mod(A, 256), math_mod(B / 256, 256), math_mod(B, 256), math_mod(C / 256, 256), math_mod(C, 256))
575 else
576 return string_char(byte_j, math_mod(A / 65536, 256), math_mod(A / 256, 256), math_mod(A, 256), math_mod(C / 256, 256), math_mod(C, 256))
577 end
578 else
579 if B ~= 0 then
580 return string_char(byte_J, math_mod(A / 65536, 256), math_mod(A / 256, 256), math_mod(A, 256), math_mod(B / 256, 256), math_mod(B, 256))
581 else
582 return string_char(byte_i, math_mod(A / 65536, 256), math_mod(A / 256, 256), math_mod(A, 256))
583 end
584 end
585 else
586 -- normal string
587 local len = string_len(v)
588 if len <= 255 then
589 return string_char(byte_s, len) .. v
590 else
591 return string_char(byte_S, len / 256, math_mod(len, 256)) .. v
592 end
593 end
594 elseif kind == "function" then
595 AceComm:error("Cannot serialize a function")
596 elseif kind == "table" then
597 if recurse[v] then
598 for k in pairs(recurse) do
599 recurse[k] = nil
600 end
601 AceComm:error("Cannot serialize a recursive table")
602 return
603 end
604 recurse[v] = true
605 if AceOO.inherits(v, AceOO.Class) then
606 if not v.class then
607 AceComm:error("Cannot serialize an AceOO class, can only serialize objects")
608 elseif type(v.Serialize) ~= "function" then
609 AceComm:error("Cannot serialize an AceOO object without the `Serialize' method.")
610 elseif type(v.class.Deserialize) ~= "function" then
611 AceComm:error("Cannot serialize an AceOO object without the `Deserialize' static method.")
612 elseif type(v.class.GetLibraryVersion) ~= "function" or not AceLibrary:HasInstance(v.class:GetLibraryVersion()) then
613 AceComm:error("Cannot serialize an AceOO object if the class is not registered with AceLibrary.")
614 end
615 local classHash = TailoredBinaryCheckSum(v.class:GetLibraryVersion())
616 local a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20 = v:Serialize()
617 local t = new()
618 t[2] = a1
619 t[3] = a2
620 t[4] = a3
621 t[5] = a4
622 t[6] = a5
623 t[7] = a6
624 t[8] = a7
625 t[9] = a8
626 t[10] = a9
627 t[11] = a10
628 t[12] = a11
629 t[13] = a12
630 t[14] = a13
631 t[15] = a14
632 t[16] = a15
633 t[17] = a16
634 t[18] = a17
635 t[19] = a18
636 t[20] = a19
637 t[21] = a20
638 local n = 21
639 while n > 1 do
640 if t[i] ~= nil then
641 break
642 end
643 n = n - 1
644 end
645 for i = 2, n do
646 t[i] = _Serialize(t[i], textToHash)
647 end
648 t[1] = classHash
649 if not notFirst then
650 for k in pairs(recurse) do
651 recurse[k] = nil
652 end
653 end
654 table_setn(t, n)
655 local s = table.concat(t)
656 t = del(t)
657 local len = string_len(s)
658 if len <= 255 then
659 return string_char(byte_o, len) .. s
660 else
661 return string_char(byte_O, len / 256, math_mod(len, 256)) .. s
662 end
663 end
664 local t = new()
665 local islist = false
666 local n = table.getn(v)
667 if n >= 1 or n <= 40 then
668 islist = true
669 for k,u in pairs(v) do
670 if (type(k) ~= "number" or k > n or k < 1) and (k ~= "n" or type(v) ~= "number") then
671 islist = false
672 break
673 end
674 end
675 end
676 if islist then
677 for i = 1, n do
678 t[i] = _Serialize(v[i], textToHash)
679 end
680 table_setn(t, n)
681 else
682 local i = 1
683 for k,u in pairs(v) do
684 t[i] = _Serialize(k, textToHash)
685 t[i+1] = _Serialize(u, textToHash)
686 i = i + 2
687 end
688 table_setn(t, i - 1)
689 end
690 if not notFirst then
691 for k in pairs(recurse) do
692 recurse[k] = nil
693 end
694 end
695 local s = table.concat(t)
696 t = del(t)
697 local len = string_len(s)
698 if islist then
699 if len <= 255 then
700 return string_char(byte_u, len) .. s
701 else
702 return "U" .. string_char(len / 256, math_mod(len, 256)) .. s
703 end
704 else
705 if len <= 255 then
706 return "t" .. string_char(len) .. s
707 else
708 return "T" .. string_char(len / 256, math_mod(len, 256)) .. s
709 end
710 end
711 end
712 end
713  
714 function Serialize(value, textToHash)
715 if not recurse then
716 recurse = new()
717 end
718 local chunk = _Serialize(value, textToHash)
719 for k in pairs(recurse) do
720 recurse[k] = nil
721 end
722 return chunk
723 end
724 end
725  
726 local Deserialize
727 do
728 local function _Deserialize(value, position, hashToText)
729 if not position then
730 position = 1
731 end
732 local x = string_byte(value, position)
733 if x == byte_b then
734 -- boolean
735 local v = string_byte(value, position + 1)
736 if v == 110 then -- 'n'
737 return false, position + 1
738 elseif v == 121 then -- 'y'
739 return true, position + 1
740 else
741 error("Improper serialized value provided")
742 end
743 elseif x == byte_nil then
744 -- nil
745 return nil, position
746 elseif x == byte_I then
747 -- 7-byte item link
748 local a1 = string_byte(value, position + 1)
749 local a2 = string_byte(value, position + 2)
750 local a3 = string_byte(value, position + 3)
751 local b1 = string_byte(value, position + 4)
752 local b2 = string_byte(value, position + 5)
753 local c1 = string_byte(value, position + 6)
754 local c2 = string_byte(value, position + 7)
755 local A = a1 * 65536 + a2 * 256 + a3
756 local B = b1 * 256 + b2
757 local C = c1 * 256 + c2
758 local s = "item:" .. A .. ":" .. B .. ":" .. C .. ":0"
759 local name, code, quality = GetItemInfo(s)
760 if name then
761 local _,_,_,color = GetItemQualityColor(quality)
762 return color .. "|H" .. code .. "|h[" .. name .. "]|h|r", position + 7
763 else
764 return nil, position + 7
765 end
766 elseif x == byte_i then
767 -- 3-byte item link
768 local a1 = string_byte(value, position + 1)
769 local a2 = string_byte(value, position + 2)
770 local a3 = string_byte(value, position + 3)
771 local A = a1 * 65536 + a2 * 256 + a3
772 local s = "item:" .. A .. ":0:0:0"
773 local name, code, quality = GetItemInfo(s)
774 if name then
775 local _,_,_,color = GetItemQualityColor(quality)
776 return color .. "|H" .. code .. "|h[" .. name .. "]|h|r", position + 3
777 else
778 return nil, position + 3
779 end
780 elseif x == byte_j then
781 -- 5-byte item link
782 local a1 = string_byte(value, position + 1)
783 local a2 = string_byte(value, position + 2)
784 local a3 = string_byte(value, position + 3)
785 local c1 = string_byte(value, position + 4)
786 local c2 = string_byte(value, position + 5)
787 local A = a1 * 65536 + a2 * 256 + a3
788 local C = c1 * 256 + c2
789 local s = "item:" .. A .. ":0:" .. C .. ":0"
790 local name, code, quality = GetItemInfo(s)
791 if name then
792 local _,_,_,color = GetItemQualityColor(quality)
793 return color .. "|H" .. code .. "|h[" .. name .. "]|h|r", position + 5
794 else
795 return nil, position + 5
796 end
797 elseif x == byte_J then
798 -- 5-byte item link
799 local a1 = string_byte(value, position + 1)
800 local a2 = string_byte(value, position + 2)
801 local a3 = string_byte(value, position + 3)
802 local b1 = string_byte(value, position + 4)
803 local b2 = string_byte(value, position + 5)
804 local A = a1 * 65536 + a2 * 256 + a3
805 local B = b1 * 256 + b2
806 local s = "item:" .. A .. ":" .. B .. ":0:0"
807 local name, code, quality = GetItemInfo(s)
808 if name then
809 local _,_,_,color = GetItemQualityColor(quality)
810 return color .. "|H" .. code .. "|h[" .. name .. "]|h|r", position + 5
811 else
812 return nil, position + 5
813 end
814 elseif x == byte_m then
815 local hash = string_byte(value, position + 1) * 65536 + string_byte(value, position + 2) * 256 + string_byte(value, position + 3)
816 return hashToText[hash], position + 3
817 elseif x == byte_s then
818 -- 0-255-byte string
819 local len = string_byte(value, position + 1)
820 return string.sub(value, position + 2, position + 1 + len), position + 1 + len
821 elseif x == byte_S then
822 -- 256-65535-byte string
823 local len = string_byte(value, position + 1) * 256 + string_byte(value, position + 2)
824 return string.sub(value, position + 3, position + 2 + len), position + 2 + len
825 elseif x == 64 --[[byte_inf]] then
826 return inf, position
827 elseif x == 36 --[[byte_ninf]] then
828 return -inf, position
829 elseif x == 33 --[[byte_nan]] then
830 return nan, position
831 elseif x == byte_d then
832 -- 1-byte integer
833 local a = string_byte(value, position + 1)
834 if a >= 128 then
835 a = a - 256
836 end
837 return a, position + 1
838 elseif x == byte_D then
839 -- 2-byte integer
840 local a = string_byte(value, position + 1)
841 local b = string_byte(value, position + 2)
842 local N = a * 256 + b
843 if N >= 32768 then
844 N = N - 65536
845 end
846 return N, position + 2
847 elseif x == byte_e then
848 -- 4-byte integer
849 local a = string_byte(value, position + 1)
850 local b = string_byte(value, position + 2)
851 local c = string_byte(value, position + 3)
852 local d = string_byte(value, position + 4)
853 local N = a * 16777216 + b * 65536 + c * 256 + d
854 if N >= 2147483648 then
855 N = N - 4294967296
856 end
857 return N, position + 4
858 elseif x == byte_E then
859 -- 8-byte integer
860 local a = string_byte(value, position + 1)
861 local b = string_byte(value, position + 2)
862 local c = string_byte(value, position + 3)
863 local d = string_byte(value, position + 4)
864 local e = string_byte(value, position + 5)
865 local f = string_byte(value, position + 6)
866 local g = string_byte(value, position + 7)
867 local h = string_byte(value, position + 8)
868 local N = a * 72057594037927936 + b * 281474976710656 + c * 1099511627776 + d * 4294967296 + e * 16777216 + f * 65536 + g * 256 + h
869 if N >= 9223372036854775808 then
870 N = N - 18446744073709551616
871 end
872 return N, position + 8
873 elseif x == byte_plus or x == byte_minus then
874 local a = string_byte(value, position + 1)
875 local b = string_byte(value, position + 2)
876 local c = string_byte(value, position + 3)
877 local d = string_byte(value, position + 4)
878 local e = string_byte(value, position + 5)
879 local f = string_byte(value, position + 6)
880 local g = string_byte(value, position + 7)
881 local h = string_byte(value, position + 8)
882 local N = a * 1099511627776 + b * 4294967296 + c * 16777216 + d * 65536 + e * 256 + f
883 local sign = x
884 local x = math.floor(N / 137438953472)
885 local m = math_mod(N, 137438953472) * 65536 + g * 256 + h
886 local mantissa = m / 9007199254740992
887 local exp = x - 1023
888 local val = math.ldexp(mantissa, exp)
889 if sign == byte_minus then
890 return -val, position + 8
891 end
892 return val, position + 8
893 elseif x == byte_u or x == byte_U then
894 -- numerically-indexed table
895 local finish
896 local start
897 if x == byte_u then
898 local len = string_byte(value, position + 1)
899 finish = position + 1 + len
900 start = position + 2
901 else
902 local len = string_byte(value, position + 1) * 256 + string_byte(value, position + 2)
903 finish = position + 2 + len
904 start = position + 3
905 end
906 local t = new()
907 local n = 0
908 local curr = start - 1
909 while curr < finish do
910 local v
911 v, curr = _Deserialize(value, curr + 1, hashToText)
912 n = n + 1
913 t[n] = v
914 end
915 table_setn(t, n)
916 return t, finish
917 elseif x == byte_o or x == byte_O then
918 -- numerically-indexed table
919 local finish
920 local start
921 if x == byte_o then
922 local len = string_byte(value, position + 1)
923 finish = position + 1 + len
924 start = position + 2
925 else
926 local len = string_byte(value, position + 1) * 256 + string_byte(value, position + 2)
927 finish = position + 2 + len
928 start = position + 3
929 end
930 local hash = string_byte(value, start) * 65536 + string_byte(value, start + 1) * 256 + string_byte(value, start + 2)
931 local curr = start + 2
932 if not AceComm.classes[hash] then
933 return nil, finish
934 end
935 local class = AceComm.classes[hash]
936 if type(class.Deserialize) ~= "function" or type(class.prototype.Serialize) ~= "function" then
937 return nil, finish
938 end
939 local t = new()
940 local n = 0
941 while curr < finish do
942 local v
943 v, curr = _Deserialize(value, curr + 1, hashToText)
944 n = n + 1
945 t[n] = v
946 end
947 table_setn(t, n)
948 local object = class:Deserialize(unpack(t))
949 del(t)
950 return object, finish
951 elseif x == byte_t or x == byte_T then
952 -- table
953 local finish
954 local start
955 if x == byte_t then
956 local len = string_byte(value, position + 1)
957 finish = position + 1 + len
958 start = position + 2
959 else
960 local len = string_byte(value, position + 1) * 256 + string_byte(value, position + 2)
961 finish = position + 2 + len
962 start = position + 3
963 end
964 local t = new()
965 local curr = start - 1
966 while curr < finish do
967 local key, l = _Deserialize(value, curr + 1, hashToText)
968 local value, m = _Deserialize(value, l + 1, hashToText)
969 curr = m
970 t[key] = value
971 end
972 if type(t.n) ~= "number" then
973 local i = 1
974 while t[i] ~= nil do
975 i = i + 1
976 end
977 table_setn(t, i - 1)
978 end
979 return t, finish
980 else
981 error("Improper serialized value provided")
982 end
983 end
984  
985 function Deserialize(value, hashToText)
986 local ret,msg = pcall(_Deserialize, value, nil, hashToText)
987 if ret then
988 return msg
989 end
990 end
991 end
992  
993 local function GetCurrentGroupDistribution()
994 if MiniMapBattlefieldFrame.status == "active" then
995 return "BATTLEGROUND"
996 elseif UnitInRaid("player") then
997 return "RAID"
998 elseif UnitInParty("player") then
999 return "PARTY"
1000 else
1001 return nil
1002 end
1003 end
1004  
1005 local function IsInDistribution(dist, customChannel)
1006 if dist == "GROUP" then
1007 return GetCurrentGroupDistribution() and true or false
1008 elseif dist == "BATTLEGROUND" then
1009 return MiniMapBattlefieldFrame.status == "active"
1010 elseif dist == "RAID" then
1011 return UnitInRaid("player") == 1
1012 elseif dist == "PARTY" then
1013 return UnitInParty("player") == 1
1014 elseif dist == "GUILD" then
1015 return IsInGuild() == 1
1016 elseif dist == "GLOBAL" then
1017 return IsInChannel("AceComm")
1018 elseif dist == "ZONE" then
1019 return IsInChannel(GetCurrentZoneChannel())
1020 elseif dist == "WHISPER" then
1021 return true
1022 elseif dist == "CUSTOM" then
1023 return IsInChannel(customChannel)
1024 end
1025 error("unknown distribution: " .. dist, 2)
1026 end
1027  
1028 function AceComm:RegisterComm(prefix, distribution, method, a4)
1029 AceComm:argCheck(prefix, 2, "string")
1030 AceComm:argCheck(distribution, 3, "string")
1031 if distribution ~= "GLOBAL" and distribution ~= "WHISPER" and distribution ~= "PARTY" and distribution ~= "RAID" and distribution ~= "GUILD" and distribution ~= "BATTLEGROUND" and distribution ~= "GROUP" and distribution ~= "ZONE" and distribution ~= "CUSTOM" then
1032 AceComm:error('Argument #3 to `RegisterComm\' must be either "GLOBAL", "ZONE", "WHISPER", "PARTY", "RAID", "GUILD", "BATTLEGROUND", "GROUP", or "CUSTOM". %q is not appropriate', distribution)
1033 end
1034 local customChannel
1035 if distribution == "CUSTOM" then
1036 customChannel, method = method, a4
1037 AceComm:argCheck(customChannel, 4, "string")
1038 if string_len(customChannel) == 0 then
1039 AceComm:error('Argument #4 to `RegisterComm\' must be a non-zero-length string.')
1040 elseif string_find(customChannel, "%s") then
1041 AceComm:error('Argument #4 to `RegisterComm\' must not have spaces.')
1042 end
1043 end
1044 if self == AceComm then
1045 AceComm:argCheck(method, customChannel and 5 or 4, "function", "table")
1046 self = method
1047 else
1048 AceComm:argCheck(method, customChannel and 5 or 4, "string", "function", "table", "nil")
1049 end
1050 if not method then
1051 method = "OnCommReceive"
1052 end
1053 if type(method) == "string" and type(self[method]) ~= "function" and type(self[method]) ~= "table" then
1054 AceEvent:error("Cannot register comm %q to method %q, it does not exist", prefix, method)
1055 end
1056  
1057 local registry = AceComm_registry
1058 if not registry[distribution] then
1059 registry[distribution] = new()
1060 end
1061 if customChannel then
1062 customChannel = "AceComm" .. customChannel
1063 if not registry[distribution][customChannel] then
1064 registry[distribution][customChannel] = new()
1065 end
1066 if not registry[distribution][customChannel][prefix] then
1067 registry[distribution][customChannel][prefix] = new()
1068 end
1069 registry[distribution][customChannel][prefix][self] = method
1070 else
1071 if not registry[distribution][prefix] then
1072 registry[distribution][prefix] = new()
1073 end
1074 registry[distribution][prefix][self] = method
1075 end
1076  
1077 RefixAceCommChannelsAndEvents()
1078 end
1079  
1080 function AceComm:UnregisterComm(prefix, distribution, customChannel)
1081 AceComm:argCheck(prefix, 2, "string")
1082 AceComm:argCheck(distribution, 3, "string", "nil")
1083 if distribution and distribution ~= "GLOBAL" and distribution ~= "WHISPER" and distribution ~= "PARTY" and distribution ~= "RAID" and distribution ~= "GUILD" and distribution ~= "BATTLEGROUND" and distribution ~= "GROUP" and distribution ~= "CUSTOM" then
1084 AceComm:error('Argument #3 to `UnregisterComm\' must be either nil, "GLOBAL", "WHISPER", "PARTY", "RAID", "GUILD", "BATTLEGROUND", "GROUP", or "CUSTOM". %q is not appropriate', distribution)
1085 end
1086 if distribution == "CUSTOM" then
1087 AceComm:argCheck(customChannel, 3, "string")
1088 if string_len(customChannel) == 0 then
1089 AceComm:error('Argument #3 to `UnregisterComm\' must be a non-zero-length string.')
1090 end
1091 else
1092 AceComm:argCheck(customChannel, 3, "nil")
1093 end
1094  
1095 local registry = AceComm_registry
1096 if not distribution then
1097 for k,v in pairs(registry) do
1098 if k == "CUSTOM" then
1099 for l,u in pairs(v) do
1100 if u[prefix] and u[prefix][self] then
1101 AceComm.UnregisterComm(self, prefix, k, string.sub(l, 8))
1102 if not registry[k] then
1103 break
1104 end
1105 end
1106 end
1107 else
1108 if v[prefix] and v[prefix][self] then
1109 AceComm.UnregisterComm(self, prefix, k)
1110 end
1111 end
1112 end
1113 return
1114 end
1115 if self == AceComm then
1116 if distribution == "CUSTOM" then
1117 error(string_format("Cannot unregister comm %q::%q. Improperly unregistering from AceComm-2.0.", distribution, customChannel), 2)
1118 else
1119 error(string_format("Cannot unregister comm %q. Improperly unregistering from AceComm-2.0.", distribution), 2)
1120 end
1121 end
1122 if distribution == "CUSTOM" then
1123 customChannel = "AceComm" .. customChannel
1124 if not registry[distribution] or not registry[distribution][customChannel] or not registry[distribution][customChannel][prefix] or not registry[distribution][customChannel][prefix][self] then
1125 AceComm:error("Cannot unregister comm %q. %q is not registered with it.", distribution, self)
1126 end
1127 registry[distribution][customChannel][prefix][self] = nil
1128  
1129 if not next(registry[distribution][customChannel][prefix]) then
1130 registry[distribution][customChannel][prefix] = del(registry[distribution][customChannel][prefix])
1131 end
1132  
1133 if not next(registry[distribution][customChannel]) then
1134 registry[distribution][customChannel] = del(registry[distribution][customChannel])
1135 end
1136 else
1137 if not registry[distribution] or not registry[distribution][prefix] or not registry[distribution][prefix][self] then
1138 AceComm:error("Cannot unregister comm %q. %q is not registered with it.", distribution, self)
1139 end
1140 registry[distribution][prefix][self] = nil
1141  
1142 if not next(registry[distribution][prefix]) then
1143 registry[distribution][prefix] = del(registry[distribution][prefix])
1144 end
1145 end
1146  
1147 if not next(registry[distribution]) then
1148 registry[distribution] = del(registry[distribution])
1149 end
1150  
1151 RefixAceCommChannelsAndEvents()
1152 end
1153  
1154 function AceComm:UnregisterAllComms()
1155 local registry = AceComm_registry
1156 for k, distribution in pairs(registry) do
1157 if k == "CUSTOM" then
1158 for l, channel in pairs(distribution) do
1159 local j = next(channel)
1160 while j ~= nil do
1161 local prefix = channel[j]
1162 if prefix[self] then
1163 AceComm.UnregisterComm(self, j)
1164 if distribution[l] and registry[k] then
1165 j = next(channel)
1166 else
1167 l = nil
1168 k = nil
1169 break
1170 end
1171 else
1172 j = next(channel, j)
1173 end
1174 end
1175 if k == nil then
1176 break
1177 end
1178 end
1179 else
1180 local j = next(distribution)
1181 while j ~= nil do
1182 local prefix = distribution[j]
1183 if prefix[self] then
1184 AceComm.UnregisterComm(self, j)
1185 if registry[k] then
1186 j = next(distribution)
1187 else
1188 k = nil
1189 break
1190 end
1191 else
1192 j = next(distribution, j)
1193 end
1194 end
1195 end
1196 end
1197 end
1198  
1199 function AceComm:IsCommRegistered(prefix, distribution, customChannel)
1200 AceComm:argCheck(prefix, 2, "string")
1201 AceComm:argCheck(distribution, 3, "string", "nil")
1202 if distribution and distribution ~= "GLOBAL" and distribution ~= "WHISPER" and distribution ~= "PARTY" and distribution ~= "RAID" and distribution ~= "GUILD" and distribution ~= "BATTLEGROUND" and distribution ~= "GROUP" and distribution ~= "ZONE" and distribution ~= "CUSTOM" then
1203 AceComm:error('Argument #3 to `IsCommRegistered\' must be either "GLOBAL", "WHISPER", "PARTY", "RAID", "GUILD", "BATTLEGROUND", "GROUP", "ZONE", or "CUSTOM". %q is not appropriate', distribution)
1204 end
1205 if distribution == "CUSTOM" then
1206 AceComm:argCheck(customChannel, 4, "nil", "string")
1207 if customChannel then
1208 AceComm:error('Argument #4 to `IsCommRegistered\' must be a non-zero-length string or nil.')
1209 end
1210 else
1211 AceComm:argCheck(customChannel, 4, "nil")
1212 end
1213 local registry = AceComm_registry
1214 if not distribution then
1215 for k,v in pairs(registry) do
1216 if distribution == "CUSTOM" then
1217 for l,u in pairs(v) do
1218 if u[prefix] and u[prefix][self] then
1219 return true
1220 end
1221 end
1222 else
1223 if v[prefix] and v[prefix][self] then
1224 return true
1225 end
1226 end
1227 end
1228 return false
1229 elseif distribution == "CUSTOM" and not customChannel then
1230 if not registry[destination] then
1231 return false
1232 end
1233 for l,u in pairs(registry[destination]) do
1234 if u[prefix] and u[prefix][self] then
1235 return true
1236 end
1237 end
1238 return false
1239 elseif distribution == "CUSTOM" then
1240 customChannel = "AceComm" .. customChannel
1241 return registry[destination] and registry[destination][customChannel] and registry[destination][customChannel][prefix] and registry[destination][customChannel][prefix][self] and true or false
1242 end
1243 return registry[destination] and registry[destination][prefix] and registry[destination][prefix][self] and true or false
1244 end
1245  
1246 function AceComm:OnEmbedDisable(target)
1247 self.UnregisterAllComms(target)
1248 end
1249  
1250 local id = byte_Z
1251  
1252 local function encodedChar(x)
1253 if x == 10 then
1254 return "°\011"
1255 elseif x == 0 then
1256 return "\255"
1257 elseif x == 255 then
1258 return "°\254"
1259 elseif x == 124 then
1260 return "°\125"
1261 elseif x == byte_s then
1262 return "\015"
1263 elseif x == byte_S then
1264 return "\020"
1265 elseif x == 15 then
1266 return "°\016"
1267 elseif x == 20 then
1268 return "°\021"
1269 elseif x == byte_deg then
1270 return "°±"
1271 elseif x == 37 then
1272 return "°\038"
1273 end
1274 return string_char(x)
1275 end
1276  
1277 local function soberEncodedChar(x)
1278 if x == 10 then
1279 return "°\011"
1280 elseif x == 0 then
1281 return "\255"
1282 elseif x == 255 then
1283 return "°\254"
1284 elseif x == 124 then
1285 return "°\125"
1286 elseif x == byte_deg then
1287 return "°±"
1288 elseif x == 37 then
1289 return "°\038"
1290 end
1291 return string_char(x)
1292 end
1293  
1294 local function SendMessage(prefix, priority, distribution, person, message, textToHash)
1295 if distribution == "CUSTOM" then
1296 person = "AceComm" .. person
1297 end
1298 if not IsInDistribution(distribution, person) then
1299 return false
1300 end
1301 if distribution == "GROUP" then
1302 distribution = GetCurrentGroupDistribution()
1303 if not distribution then
1304 return false
1305 end
1306 end
1307 if id == byte_Z then
1308 id = byte_a
1309 elseif id == byte_z then
1310 id = byte_A
1311 else
1312 id = id + 1
1313 end
1314 if id == byte_s or id == byte_S then
1315 id = id + 1
1316 end
1317 local id = string_char(id)
1318 local drunk = distribution == "GLOBAL" or distribution == "WHISPER" or distribution == "ZONE" or distribution == "CUSTOM"
1319 prefix = Encode(prefix, drunk)
1320 message = Serialize(message, textToHash)
1321 message = Encode(message, drunk)
1322 local headerLen = string_len(prefix) + 6
1323 local messageLen = string_len(message)
1324 if distribution == "WHISPER" then
1325 AceComm.recentWhispers[string.lower(person)] = GetTime()
1326 end
1327 local max = math_floor(messageLen / (250 - headerLen) + 1)
1328 if max > 1 then
1329 local segment = math_floor(messageLen / max + 0.5)
1330 local last = 0
1331 local good = true
1332 for i = 1, max do
1333 local bit
1334 if i == max then
1335 bit = string_sub(message, last + 1)
1336 else
1337 local next = segment * i
1338 if string_byte(message, next) == byte_deg then
1339 next = next + 1
1340 end
1341 bit = string_sub(message, last + 1, next)
1342 last = next
1343 end
1344 if distribution == "WHISPER" then
1345 bit = "/" .. prefix .. "\t" .. id .. encodedChar(i) .. encodedChar(max) .. "\t" .. bit .. "°"
1346 ChatThrottleLib:SendChatMessage(priority, prefix, bit, "WHISPER", nil, person)
1347 elseif distribution == "GLOBAL" or distribution == "ZONE" or distribution == "CUSTOM" then
1348 bit = prefix .. "\t" .. id .. encodedChar(i) .. encodedChar(max) .. "\t" .. bit .. "°"
1349 local channel
1350 if distribution == "GLOBAL" then
1351 channel = "AceComm"
1352 elseif distribution == "ZONE" then
1353 channel = GetCurrentZoneChannel()
1354 elseif distribution == "CUSTOM" then
1355 channel = person
1356 end
1357 local index = GetChannelName(channel)
1358 if index and index > 0 then
1359 ChatThrottleLib:SendChatMessage(priority, prefix, bit, "CHANNEL", nil, index)
1360 else
1361 good = false
1362 end
1363 else
1364 bit = id .. soberEncodedChar(i) .. soberEncodedChar(max) .. "\t" .. bit
1365 ChatThrottleLib:SendAddonMessage(priority, prefix, bit, distribution)
1366 end
1367 end
1368 return good
1369 else
1370 if distribution == "WHISPER" then
1371 message = "/" .. prefix .. "\t" .. id .. string_char(1) .. string_char(1) .. "\t" .. message .. "°"
1372 ChatThrottleLib:SendChatMessage(priority, prefix, message, "WHISPER", nil, person)
1373 return true
1374 elseif distribution == "GLOBAL" or distribution == "ZONE" or distribution == "CUSTOM" then
1375 message = prefix .. "\t" .. id .. string_char(1) .. string_char(1) .. "\t" .. message .. "°"
1376 local channel
1377 if distribution == "GLOBAL" then
1378 channel = "AceComm"
1379 elseif distribution == "ZONE" then
1380 channel = GetCurrentZoneChannel()
1381 elseif distribution == "CUSTOM" then
1382 channel = person
1383 end
1384 local index = GetChannelName(channel)
1385 if index and index > 0 then
1386 ChatThrottleLib:SendChatMessage(priority, prefix, message, "CHANNEL", nil, index)
1387 return true
1388 end
1389 else
1390 message = id .. string_char(1) .. string_char(1) .. "\t" .. message
1391 ChatThrottleLib:SendAddonMessage(priority, prefix, message, distribution)
1392 return true
1393 end
1394 end
1395 return false
1396 end
1397  
1398 function AceComm:SendPrioritizedCommMessage(priority, distribution, person, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
1399 AceComm:argCheck(priority, 2, "string")
1400 if priority ~= "NORMAL" and priority ~= "BULK" and priority ~= "ALERT" then
1401 AceComm:error('Argument #2 to `SendPrioritizedCommMessage\' must be either "NORMAL", "BULK", or "ALERT"')
1402 end
1403 AceComm:argCheck(distribution, 3, "string")
1404 if distribution == "WHISPER" or distribution == "CUSTOM" then
1405 AceComm:argCheck(person, 4, "string")
1406 if string_len(person) == 0 then
1407 AceComm:error("Argument #4 to `SendPrioritizedCommMessage' must be a non-zero-length string")
1408 end
1409 else
1410 a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20 = person, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19
1411 end
1412 if self == AceComm then
1413 AceComm:error("Cannot send a comm message from AceComm directly.")
1414 end
1415 if distribution and distribution ~= "GLOBAL" and distribution ~= "WHISPER" and distribution ~= "PARTY" and distribution ~= "RAID" and distribution ~= "GUILD" and distribution ~= "BATTLEGROUND" and distribution ~= "GROUP" and distribution ~= "ZONE" and distribution ~= "CUSTOM" then
1416 AceComm:error('Argument #4 to `SendPrioritizedCommMessage\' must be either nil, "GLOBAL", "ZONE", "WHISPER", "PARTY", "RAID", "GUILD", "BATTLEGROUND", "GROUP", or "CUSTOM". %q is not appropriate', distribution)
1417 end
1418  
1419 local prefix = self.commPrefix
1420 if type(prefix) ~= "string" then
1421 AceComm:error("`SetCommPrefix' must be called before sending a message.")
1422 end
1423  
1424 local message
1425 if a2 == nil and type(a1) ~= "table" then
1426 message = a1
1427 else
1428 message = new()
1429 message[1] = a1
1430 message[2] = a2
1431 message[3] = a3
1432 message[4] = a4
1433 message[5] = a5
1434 message[6] = a6
1435 message[7] = a7
1436 message[8] = a8
1437 message[9] = a9
1438 message[10] = a10
1439 message[11] = a11
1440 message[12] = a12
1441 message[13] = a13
1442 message[14] = a14
1443 message[15] = a15
1444 message[16] = a16
1445 message[17] = a17
1446 message[18] = a18
1447 message[19] = a19
1448 message[20] = a20
1449 local n = 20
1450 while n > 0 do
1451 if message[n] ~= nil then
1452 break
1453 end
1454 n = n - 1
1455 end
1456 table_setn(message, n)
1457 end
1458  
1459 local ret = SendMessage(AceComm.prefixTextToHash[prefix], priority, distribution, person, message, self.commMemoTextToHash)
1460  
1461 if message ~= a1 then
1462 message = del(message)
1463 end
1464  
1465 return ret
1466 end
1467  
1468 function AceComm:SendCommMessage(distribution, person, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
1469 AceComm:argCheck(distribution, 2, "string")
1470 if distribution == "WHISPER" or distribution == "CUSTOM" then
1471 AceComm:argCheck(person, 3, "string")
1472 if string_len(person) == 0 then
1473 AceComm:error("Argument #3 to `SendCommMessage' must be a non-zero-length string")
1474 end
1475 else
1476 a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20 = person, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19
1477 end
1478 if self == AceComm then
1479 AceComm:error("Cannot send a comm message from AceComm directly.")
1480 end
1481 if distribution and distribution ~= "GLOBAL" and distribution ~= "WHISPER" and distribution ~= "PARTY" and distribution ~= "RAID" and distribution ~= "GUILD" and distribution ~= "BATTLEGROUND" and distribution ~= "GROUP" and distribution ~= "ZONE" and distribution ~= "CUSTOM" then
1482 AceComm:error('Argument #2 to `SendCommMessage\' must be either nil, "GLOBAL", "ZONE", "WHISPER", "PARTY", "RAID", "GUILD", "BATTLEGROUND", "GROUP", or "CUSTOM". %q is not appropriate', distribution)
1483 end
1484  
1485 local prefix = self.commPrefix
1486 if type(prefix) ~= "string" then
1487 AceComm:error("`SetCommPrefix' must be called before sending a message.")
1488 end
1489  
1490 local message
1491 if a2 == nil and type(a1) ~= "table" then
1492 message = a1
1493 else
1494 message = new()
1495 message[1] = a1
1496 message[2] = a2
1497 message[3] = a3
1498 message[4] = a4
1499 message[5] = a5
1500 message[6] = a6
1501 message[7] = a7
1502 message[8] = a8
1503 message[9] = a9
1504 message[10] = a10
1505 message[11] = a11
1506 message[12] = a12
1507 message[13] = a13
1508 message[14] = a14
1509 message[15] = a15
1510 message[16] = a16
1511 message[17] = a17
1512 message[18] = a18
1513 message[19] = a19
1514 message[20] = a20
1515 local n = 20
1516 while n > 0 do
1517 if message[n] ~= nil then
1518 break
1519 end
1520 n = n - 1
1521 end
1522 table_setn(message, n)
1523 end
1524  
1525 local priority = self.commPriority or "NORMAL"
1526  
1527 local ret = SendMessage(AceComm.prefixTextToHash[prefix], priority, distribution, person, message, self.commMemoTextToHash)
1528  
1529 if message ~= a1 then
1530 message = del(message)
1531 end
1532  
1533 return ret
1534 end
1535  
1536 function AceComm:SetDefaultCommPriority(priority)
1537 AceComm:argCheck(priority, 2, "string")
1538 if priority ~= "NORMAL" and priority ~= "BULK" and priority ~= "ALERT" then
1539 AceComm:error('Argument #2 must be either "NORMAL", "BULK", or "ALERT"')
1540 end
1541  
1542 if self.commPriority then
1543 AceComm:error("Cannot call `SetDefaultCommPriority' more than once")
1544 end
1545  
1546 self.commPriority = priority
1547 end
1548  
1549 function AceComm:SetCommPrefix(prefix)
1550 AceComm:argCheck(prefix, 2, "string")
1551  
1552 if self.commPrefix then
1553 AceComm:error("Cannot call `SetCommPrefix' more than once.")
1554 end
1555  
1556 if AceComm.prefixes[prefix] then
1557 AceComm:error("Cannot set prefix to %q, it is already in use.", prefix)
1558 end
1559  
1560 local hash = TailoredBinaryCheckSum(prefix)
1561 if AceComm.prefixHashToText[hash] then
1562 AceComm:error("Cannot set prefix to %q, its hash is used by another prefix: %q", prefix, AceComm.prefixHashToText[hash])
1563 end
1564  
1565 AceComm.prefixes[prefix] = true
1566 self.commPrefix = prefix
1567 AceComm.prefixHashToText[hash] = prefix
1568 AceComm.prefixTextToHash[prefix] = hash
1569 end
1570  
1571 function AceComm:RegisterMemoizations(values)
1572 AceComm:argCheck(values, 2, "table")
1573 for k,v in pairs(values) do
1574 if type(k) ~= "number" then
1575 AceComm:error("Bad argument #2 to `RegisterMemoizations'. All keys must be numbers")
1576 elseif type(v) ~= "string" then
1577 AceComm:error("Bad argument #2 to `RegisterMemoizations'. All values must be strings")
1578 end
1579 end
1580 if self.commMemoHashToText or self.commMemoTextToHash then
1581 AceComm:error("You can only call `RegisterMemoizations' once.")
1582 elseif not self.commPrefix then
1583 AceComm:error("You can only call `RegisterCommPrefix' before calling `RegisterMemoizations'.")
1584 elseif AceComm.prefixMemoizations[self.commPrefix] then
1585 AceComm:error("Another addon with prefix %q has already registered memoizations.", self.commPrefix)
1586 end
1587 local hashToText = new()
1588 local textToHash = new()
1589 for _,text in ipairs(values) do
1590 local hash = TailoredNumericCheckSum(text)
1591 if hashToText[hash] then
1592 AceComm:error("%q and %q have the same checksum. You must remove one of them for memoization to work properly", hashToText[hash], text)
1593 else
1594 textToHash[text] = hash
1595 hashToText[hash] = text
1596 end
1597 end
1598 values = del(values)
1599 self.commMemoHashToText = hashToText
1600 self.commMemoTextToHash = textToHash
1601 AceComm.prefixMemoizations[self.commPrefix] = hashToText
1602 end
1603  
1604 local DeepReclaim
1605 do
1606 local recurse
1607 local function _DeepReclaim(t)
1608 if recurse[t] then
1609 return
1610 end
1611 recurse[t] = true
1612 for k,v in pairs(t) do
1613 if type(k) == "table" and not AceOO.inherits(k, AceOO.Class) then
1614 _DeepReclaim(k)
1615 end
1616 if type(v) == "table" and not AceOO.inherits(v, AceOO.Class) then
1617 _DeepReclaim(v)
1618 end
1619 end
1620 del(t)
1621 end
1622 function DeepReclaim(t)
1623 recurse = new()
1624 _DeepReclaim(t)
1625 recurse = del(recurse)
1626 end
1627 end
1628  
1629 local lastCheck = GetTime()
1630 local function CheckRefix()
1631 if GetTime() - lastCheck >= 120 then
1632 lastCheck = GetTime()
1633 RefixAceCommChannelsAndEvents()
1634 end
1635 end
1636  
1637 local function HandleMessage(prefix, message, distribution, sender, customChannel)
1638 local isGroup = GetCurrentGroupDistribution() == distribution
1639 local isCustom = distribution == "CUSTOM"
1640 if (not AceComm_registry[distribution] and (not isGroup or not AceComm_registry.GROUP)) or (isCustom and not AceComm_registry.CUSTOM[customChannel]) then
1641 return CheckRefix()
1642 end
1643 local _, id, current, max
1644 if not message then
1645 if distribution == "WHISPER" then
1646 _,_, prefix, id, current, max, message = string_find(prefix, "^/(...)\t(.)(.)(.)\t(.*)$")
1647 else
1648 _,_, prefix, id, current, max, message = string_find(prefix, "^(...)\t(.)(.)(.)\t(.*)$")
1649 end
1650 prefix = AceComm.prefixHashToText[prefix]
1651 if not prefix then
1652 return CheckRefix()
1653 end
1654 if isCustom then
1655 if not AceComm_registry.CUSTOM[customChannel][prefix] then
1656 return CheckRefix()
1657 end
1658 else
1659 if (not AceComm_registry[distribution] or not AceComm_registry[distribution][prefix]) and (not isGroup or not AceComm_registry.GROUP or not AceComm_registry.GROUP[prefix]) then
1660 return CheckRefix()
1661 end
1662 end
1663 else
1664 _,_, id, current, max, message = string_find(message, "^(.)(.)(.)\t(.*)$")
1665 end
1666 if not message then
1667 return
1668 end
1669 local smallCustomChannel = customChannel and string_sub(customChannel, 8)
1670 current = string_byte(current)
1671 max = string_byte(max)
1672 if max > 1 then
1673 local queue = AceComm.recvQueue
1674 local x
1675 if distribution == "CUSTOM" then
1676 x = prefix .. ":" .. sender .. distribution .. customChannel .. id
1677 else
1678 x = prefix .. ":" .. sender .. distribution .. id
1679 end
1680 if not queue[x] then
1681 if current ~= 1 then
1682 return
1683 end
1684 queue[x] = new()
1685 end
1686 local chunk = queue[x]
1687 chunk.time = GetTime()
1688 chunk[current] = message
1689 if current == max then
1690 table_setn(chunk, max)
1691 message = table_concat(chunk)
1692 queue[x] = del(queue[x])
1693 else
1694 return
1695 end
1696 end
1697 message = Deserialize(message, AceComm.prefixMemoizations[prefix])
1698 local isTable = type(message) == "table"
1699 if AceComm_registry[distribution] then
1700 if isTable then
1701 if isCustom then
1702 if AceComm_registry.CUSTOM[customChannel][prefix] then
1703 for k,v in pairs(AceComm_registry.CUSTOM[customChannel][prefix]) do
1704 local type_v = type(v)
1705 if type_v == "string" then
1706 local f = k[v]
1707 if type(f) == "table" then
1708 local a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20 = unpack(message)
1709 local g = f[a1]
1710 if g then
1711 if type(g) == "table" then
1712 local h = g[a2]
1713 if h then
1714 h(k, prefix, sender, distribution, smallCustomChannel, a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
1715 end
1716 else -- function
1717 g(k, prefix, sender, distribution, smallCustomChannel, a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
1718 end
1719 end
1720 else -- function
1721 f(k, prefix, sender, distribution, smallCustomChannel, unpack(message))
1722 end
1723 elseif type_v == "table" then
1724 local a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20 = unpack(message)
1725 local g = v[a1]
1726 if g then
1727 if type(g) == "table" then
1728 local h = g[a2]
1729 if h then
1730 h(prefix, sender, distribution, smallCustomChannel, a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
1731 end
1732 else -- function
1733 g(prefix, sender, distribution, smallCustomChannel, a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
1734 end
1735 end
1736 else -- function
1737 v(prefix, sender, distribution, smallCustomChannel, unpack(message))
1738 end
1739 end
1740 end
1741 else
1742 if AceComm_registry[distribution][prefix] then
1743 for k,v in pairs(AceComm_registry[distribution][prefix]) do
1744 local type_v = type(v)
1745 if type_v == "string" then
1746 local f = k[v]
1747 if type(f) == "table" then
1748 local a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20 = unpack(message)
1749 local g = f[a1]
1750 if g then
1751 if type(g) == "table" then
1752 local h = g[a2]
1753 if h then
1754 h(k, prefix, sender, distribution, a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
1755 end
1756 else -- function
1757 g(k, prefix, sender, distribution, a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
1758 end
1759 end
1760 else -- function
1761 f(k, prefix, sender, distribution, unpack(message))
1762 end
1763 elseif type_v == "table" then
1764 local a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20 = unpack(message)
1765 local g = v[a1]
1766 if g then
1767 if type(g) == "table" then
1768 local h = g[a2]
1769 if h then
1770 h(prefix, sender, distribution, a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
1771 end
1772 else -- function
1773 g(prefix, sender, distribution, a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
1774 end
1775 end
1776 else -- function
1777 v(prefix, sender, distribution, unpack(message))
1778 end
1779 end
1780 end
1781 end
1782 else
1783 if isCustom then
1784 if AceComm_registry.CUSTOM[customChannel][prefix] then
1785 for k,v in pairs(AceComm_registry.CUSTOM[customChannel][prefix]) do
1786 local type_v = type(v)
1787 if type_v == "string" then
1788 local f = k[v]
1789 if type(f) == "table" then
1790 local g = f[message]
1791 if g and type(g) == "function" then
1792 g(k, prefix, sender, distribution, smallCustomChannel)
1793 end
1794 else -- function
1795 f(k, prefix, sender, distribution, smallCustomChannel, message)
1796 end
1797 elseif type_v == "table" then
1798 local g = v[message]
1799 if g and type(g) == "function" then
1800 g(k, prefix, sender, distribution, smallCustomChannel)
1801 end
1802 else -- function
1803 v(prefix, sender, distribution, smallCustomChannel, message)
1804 end
1805 end
1806 end
1807 else
1808 if AceComm_registry[distribution][prefix] then
1809 for k,v in pairs(AceComm_registry[distribution][prefix]) do
1810 local type_v = type(v)
1811 if type_v == "string" then
1812 local f = k[v]
1813 if type(f) == "table" then
1814 local g = f[message]
1815 if g and type(g) == "function" then
1816 g(k, prefix, sender, distribution)
1817 end
1818 else -- function
1819 f(k, prefix, sender, distribution, message)
1820 end
1821 elseif type_v == "table" then
1822 local g = v[message]
1823 if g and type(g) == "function" then
1824 g(k, prefix, sender, distribution)
1825 end
1826 else -- function
1827 v(prefix, sender, distribution, message)
1828 end
1829 end
1830 end
1831 end
1832 end
1833 end
1834 if isGroup and AceComm_registry.GROUP and AceComm_registry.GROUP[prefix] then
1835 if isTable then
1836 for k,v in pairs(AceComm_registry.GROUP[prefix]) do
1837 local type_v = type(v)
1838 if type_v == "string" then
1839 local f = k[v]
1840 if type(f) == "table" then
1841 local a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20 = unpack(message)
1842 local g = f[a1]
1843 if g then
1844 if type(g) == "table" then
1845 local h = g[a2]
1846 if h then
1847 h(k, prefix, sender, "GROUP", a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
1848 end
1849 else -- function
1850 g(k, prefix, sender, "GROUP", a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
1851 end
1852 end
1853 else -- function
1854 f(k, prefix, sender, "GROUP", unpack(message))
1855 end
1856 elseif type_v == "table" then
1857 local a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20 = unpack(message)
1858 local g = v[a1]
1859 if g then
1860 if type(g) == "table" then
1861 local h = g[a2]
1862 if h then
1863 h(prefix, sender, "GROUP", a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
1864 end
1865 else -- function
1866 g(prefix, sender, "GROUP", a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
1867 end
1868 end
1869 else -- function
1870 v(prefix, sender, "GROUP", unpack(message))
1871 end
1872 end
1873 else
1874 for k,v in pairs(AceComm_registry.GROUP[prefix]) do
1875 local type_v = type(v)
1876 if type_v == "string" then
1877 local f = k[v]
1878 if type(f) == "table" then
1879 local g = f[message]
1880 if g and type(g) == "function" then
1881 g(k, prefix, sender, "GROUP")
1882 end
1883 else -- function
1884 f(k, prefix, sender, "GROUP", message)
1885 end
1886 elseif type_v == "table" then
1887 local g = v[message]
1888 if g and type(g) == "function" then
1889 g(k, prefix, sender, "GROUP")
1890 end
1891 else -- function
1892 v(prefix, sender, "GROUP", message)
1893 end
1894 end
1895 end
1896 end
1897 if isTable then
1898 DeepReclaim(message)
1899 end
1900 end
1901  
1902 function AceComm:CHAT_MSG_ADDON(prefix, message, distribution, sender)
1903 if sender == player then
1904 return
1905 end
1906 prefix = self.prefixHashToText[prefix]
1907 if not prefix then
1908 return CheckRefix()
1909 end
1910 local isGroup = GetCurrentGroupDistribution() == distribution
1911 if not AceComm_registry[distribution] and (not isGroup or not AceComm_registry.GROUP) then
1912 return CheckRefix()
1913 end
1914 prefix = Decode(prefix)
1915 if (not AceComm_registry[distribution] or not AceComm_registry[distribution][prefix]) and (not isGroup or not AceComm_registry.GROUP or not AceComm_registry.GROUP[prefix]) then
1916 return CheckRefix()
1917 end
1918 message = Decode(message)
1919 return HandleMessage(prefix, message, distribution, sender)
1920 end
1921  
1922 function AceComm:CHAT_MSG_WHISPER(text, sender)
1923 if not string_find(text, "^/") then
1924 return
1925 end
1926 text = Decode(text, true)
1927 return HandleMessage(text, nil, "WHISPER", sender)
1928 end
1929  
1930 function AceComm:CHAT_MSG_CHANNEL(text, sender, _, _, _, _, _, _, channel)
1931 if sender == player or not string_find(channel, "^AceComm") then
1932 return
1933 end
1934 text = Decode(text, true)
1935 local distribution
1936 local customChannel
1937 if channel == "AceComm" then
1938 distribution = "GLOBAL"
1939 elseif channel == GetCurrentZoneChannel() then
1940 distribution = "ZONE"
1941 else
1942 distribution = "CUSTOM"
1943 customChannel = channel
1944 end
1945 return HandleMessage(text, nil, distribution, sender, customChannel)
1946 end
1947  
1948 function AceComm:IsUserInChannel(userName, distribution, customChannel)
1949 AceComm:argCheck(userName, 2, "string", "nil")
1950 if not userName then
1951 userName = player
1952 end
1953 AceComm:argCheck(distribution, 3, "string")
1954 local channel
1955 if distribution == "GLOBAL" then
1956 channel = "AceComm"
1957 elseif distribution == "ZONE" then
1958 channel = GetCurrentZoneChannel()
1959 elseif distribution == "CUSTOM" then
1960 AceComm:argCheck(customChannel, 4, "string")
1961 channel = "AceComm" .. customChannel
1962 else
1963 AceComm:error('Argument #3 to `IsUserInChannel\' must be "GLOBAL", "CUSTOM", or "ZONE"')
1964 end
1965  
1966 return AceComm.userRegistry[channel] and AceComm.userRegistry[channel][userName] or false
1967 end
1968  
1969 function AceComm:CHAT_MSG_CHANNEL_LIST(text, _, _, _, _, _, _, _, channel)
1970 if not string_find(channel, "^AceComm") then
1971 return
1972 end
1973  
1974 if not AceComm.userRegistry[channel] then
1975 AceComm.userRegistry[channel] = new()
1976 end
1977 local t = AceComm.userRegistry[channel]
1978 for k in string_gfind(text, "[^, @%*#]+") do
1979 t[k] = true
1980 end
1981 end
1982  
1983 function AceComm:CHAT_MSG_CHANNEL_JOIN(_, user, _, _, _, _, _, _, channel)
1984 if not string_find(channel, "^AceComm") then
1985 return
1986 end
1987  
1988 if not AceComm.userRegistry[channel] then
1989 AceComm.userRegistry[channel] = {}
1990 end
1991 local t = AceComm.userRegistry[channel]
1992 if not t[user] then
1993 t[user] = true
1994 end
1995 end
1996  
1997 function AceComm:CHAT_MSG_CHANNEL_LEAVE(_, user, _, _, _, _, _, _, channel)
1998 if not string_find(channel, "^AceComm") then
1999 return
2000 end
2001  
2002 if not AceComm.userRegistry[channel] then
2003 AceComm.userRegistry[channel] = {}
2004 end
2005 local t = AceComm.userRegistry[channel]
2006 if t[user] then
2007 t[user] = nil
2008 end
2009 end
2010  
2011 function AceComm:AceEvent_FullyInitialized()
2012 RefixAceCommChannelsAndEvents()
2013 end
2014  
2015 function AceComm:PLAYER_LOGOUT()
2016 LeaveAceCommChannels(true)
2017 end
2018  
2019 function AceComm:ZONE_CHANGED_NEW_AREA()
2020 local lastZone = zoneCache
2021 zoneCache = nil
2022 local newZone = GetCurrentZoneChannel()
2023 if self.registry.ZONE and next(self.registry.ZONE) then
2024 if lastZone then
2025 SwitchChannel(lastZone, newZone)
2026 else
2027 JoinChannel(newZone)
2028 end
2029 end
2030 end
2031  
2032 function AceComm:embed(target)
2033 self.super.embed(self, target)
2034 if not AceEvent then
2035 AceComm:error(MAJOR_VERSION .. " requires AceEvent-2.0")
2036 end
2037 end
2038  
2039 function AceComm.hooks:ChatFrame_OnEvent(orig, event)
2040 if event == "CHAT_MSG_WHISPER" or event == "CHAT_MSG_WHISPER_INFORM" then
2041 if string_find(arg1, "^/") then
2042 return
2043 end
2044 elseif event == "CHAT_MSG_AFK" or event == "CHAT_MSG_DND" then
2045 local t = self.recentWhispers[string.lower(arg2)]
2046 if t and GetTime() - t <= 15 then
2047 return
2048 end
2049 elseif event == "CHAT_MSG_CHANNEL" or event == "CHAT_MSG_CHANNEL_LIST" then
2050 if string_find(arg9, "^AceComm") then
2051 return
2052 end
2053 end
2054 return orig(event)
2055 end
2056  
2057 local id, loggingOut
2058 function AceComm.hooks:Logout(orig)
2059 if IsResting() then
2060 LeaveAceCommChannels(true)
2061 else
2062 id = self:ScheduleEvent(LeaveAceCommChannels, 15, true)
2063 end
2064 loggingOut = true
2065 return orig()
2066 end
2067  
2068 function AceComm.hooks:CancelLogout(orig)
2069 shutdown = false
2070 if id then
2071 self:CancelScheduledEvent(id)
2072 id = nil
2073 end
2074 RefixAceCommChannelsAndEvents()
2075 loggingOut = false
2076 return orig()
2077 end
2078  
2079 function AceComm.hooks:Quit(orig)
2080 if IsResting() then
2081 LeaveAceCommChannels(true)
2082 else
2083 id = self:ScheduleEvent(LeaveAceCommChannels, 15, true)
2084 end
2085 loggingOut = true
2086 return orig()
2087 end
2088  
2089 function AceComm.hooks:FCFDropDown_LoadChannels(orig, a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
2090 local arg = {a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20}
2091 for i = 1, table.getn(arg), 2 do
2092 if not arg[i] then
2093 break
2094 end
2095 if type(arg[i + 1]) == "string" and string_find(arg[i + 1], "^AceComm") then
2096 table.remove(arg, i + 1)
2097 table.remove(arg, i)
2098 i = i - 2
2099 end
2100 end
2101 return orig(unpack(arg))
2102 end
2103  
2104 function AceComm:CHAT_MSG_SYSTEM(text)
2105 if text ~= ERR_TOO_MANY_CHAT_CHANNELS then
2106 return
2107 end
2108  
2109 local chan = lastChannelJoined
2110 if not chan then
2111 return
2112 end
2113 if not string_find(lastChannelJoined, "^AceComm") then
2114 return
2115 end
2116  
2117 local text
2118 if chan == "AceComm" then
2119 local addon = self.registry.GLOBAL and next(AceComm_registry.GLOBAL)
2120 if not addon then
2121 return
2122 end
2123 addon = tostring(addon)
2124 text = string_format("%s has tried to join the AceComm global channel, but there are not enough channels available. %s may not work because of this", addon, addon)
2125 elseif chan == GetCurrentZoneChannel() then
2126 local addon = AceComm_registry.ZONE and next(AceComm_registry.ZONE)
2127 if not addon then
2128 return
2129 end
2130 addon = tostring(addon)
2131 text = string_format("%s has tried to join the AceComm zone channel, but there are not enough channels available. %s may not work because of this", addon, addon)
2132 else
2133 local addon = AceComm_registry.CUSTOM and AceComm_registry.CUSTOM[chan] and next(AceComm_registry.CUSTOM[chan])
2134 if not addon then
2135 return
2136 end
2137 addon = tostring(addon)
2138 text = string_format("%s has tried to join the AceComm custom channel %s, but there are not enough channels available. %s may not work because of this", addon, chan, addon)
2139 end
2140  
2141 StaticPopupDialogs["ACECOMM_TOO_MANY_CHANNELS"] = {
2142 text = text,
2143 button1 = CLOSE,
2144 timeout = 0,
2145 whileDead = 1,
2146 hideOnEscape = 1,
2147 }
2148 StaticPopup_Show("ACECOMM_TOO_MANY_CHANNELS")
2149 end
2150  
2151 local function activate(self, oldLib, oldDeactivate)
2152 AceComm = self
2153 self:activate(oldLib, oldDeactivate)
2154  
2155 if oldLib then
2156 self.recvQueue = oldLib.recvQueue
2157 self.registry = oldLib.registry
2158 self.channels = oldLib.channels
2159 self.prefixes = oldLib.prefixes
2160 self.classes = oldLib.classes
2161 self.prefixMemoizations = oldLib.prefixMemoizations
2162 self.prefixHashToText = oldLib.prefixHashToText
2163 self.prefixTextToHash = oldLib.prefixTextToHash
2164 self.recentWhispers = oldLib.recentWhispers
2165 self.userRegistry = oldLib.userRegistry
2166 else
2167 local old_ChatFrame_OnEvent = ChatFrame_OnEvent
2168 function ChatFrame_OnEvent(event)
2169 if self.hooks.ChatFrame_OnEvent then
2170 return self.hooks.ChatFrame_OnEvent(self, old_ChatFrame_OnEvent, event)
2171 else
2172 return old_ChatFrame_OnEvent(event)
2173 end
2174 end
2175 local id
2176 local loggingOut = false
2177 local old_Logout = Logout
2178 function Logout()
2179 if self.hooks.Logout then
2180 return self.hooks.Logout(self, old_Logout)
2181 else
2182 return old_Logout()
2183 end
2184 end
2185 local old_CancelLogout = CancelLogout
2186 function CancelLogout()
2187 if self.hooks.CancelLogout then
2188 return self.hooks.CancelLogout(self, old_CancelLogout)
2189 else
2190 return old_CancelLogout()
2191 end
2192 end
2193 local old_Quit = Quit
2194 function Quit()
2195 if self.hooks.Quit then
2196 return self.hooks.Quit(self, old_Quit)
2197 else
2198 return old_Quit()
2199 end
2200 end
2201 local old_FCFDropDown_LoadChannels = FCFDropDown_LoadChannels
2202 function FCFDropDown_LoadChannels(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
2203 local arg = {a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20}
2204 if self.hooks.FCFDropDown_LoadChannels then
2205 return self.hooks.FCFDropDown_LoadChannels(self, old_FCFDropDown_LoadChannels, unpack(arg))
2206 else
2207 return old_FCFDropDown_LoadChannels(unpack(arg))
2208 end
2209 end
2210 local old_JoinChannelByName = JoinChannelByName
2211 function JoinChannelByName(a,b,c,d,e,f,g,h,i,j)
2212 if self.hooks.JoinChannelByName then
2213 return self.hooks.JoinChannelByName(self, old_JoinChannelByName, a,b,c,d,e,f,g,h,i,j)
2214 else
2215 return old_JoinChannelByName(a,b,c,d,e,f,g,h,i,j)
2216 end
2217 end
2218 end
2219  
2220 if not self.recvQueue then
2221 self.recvQueue = {}
2222 end
2223 if not self.registry then
2224 self.registry = {}
2225 end
2226 AceComm_registry = self.registry
2227 if not self.prefixes then
2228 self.prefixes = {}
2229 end
2230 if not self.classes then
2231 self.classes = {}
2232 else
2233 for k in pairs(self.classes) do
2234 self.classes[k] = nil
2235 end
2236 end
2237 if not self.prefixMemoizations then
2238 self.prefixMemoizations = {}
2239 end
2240 if not self.prefixHashToText then
2241 self.prefixHashToText = {}
2242 end
2243 if not self.prefixTextToHash then
2244 self.prefixTextToHash = {}
2245 end
2246 if not self.recentWhispers then
2247 self.recentWhispers = {}
2248 end
2249 if not self.userRegistry then
2250 self.userRegistry = {}
2251 end
2252  
2253 if oldDeactivate then
2254 oldDeactivate(oldLib)
2255 end
2256 end
2257  
2258 local function external(self, major, instance)
2259 if major == "AceEvent-2.0" then
2260 AceEvent = instance
2261  
2262 AceEvent:embed(AceComm)
2263  
2264 self:UnregisterAllEvents()
2265 self:CancelAllScheduledEvents()
2266  
2267 if AceEvent:IsFullyInitialized() then
2268 self:AceEvent_FullyInitialized()
2269 else
2270 self:RegisterEvent("AceEvent_FullyInitialized", "AceEvent_FullyInitialized", true)
2271 end
2272  
2273 self:RegisterEvent("PLAYER_LOGOUT")
2274 self:RegisterEvent("ZONE_CHANGED_NEW_AREA")
2275 self:RegisterEvent("CHAT_MSG_CHANNEL_NOTICE")
2276 self:RegisterEvent("CHAT_MSG_SYSTEM")
2277 else
2278 if AceOO.inherits(instance, AceOO.Class) and not instance.class then
2279 self.classes[TailoredNumericCheckSum(major)] = instance
2280 end
2281 end
2282 end
2283  
2284 AceLibrary:Register(AceComm, MAJOR_VERSION, MINOR_VERSION, activate, nil, external)
2285  
2286  
2287  
2288  
2289  
2290 --
2291 -- ChatThrottleLib by Mikk
2292 --
2293 -- Manages AddOn chat output to keep player from getting kicked off.
2294 --
2295 -- ChatThrottleLib.SendChatMessage/.SendAddonMessage functions that accept
2296 -- a Priority ("BULK", "NORMAL", "ALERT") as well as prefix for SendChatMessage.
2297 --
2298 -- Priorities get an equal share of available bandwidth when fully loaded.
2299 -- Communication channels are separated on extension+chattype+destination and
2300 -- get round-robinned. (Destination only matters for whispers and channels,
2301 -- obviously)
2302 --
2303 -- Will install hooks for SendChatMessage and SendAdd[Oo]nMessage to measure
2304 -- bandwidth bypassing the library and use less bandwidth itself.
2305 --
2306 --
2307 -- Fully embeddable library. Just copy this file into your addon directory,
2308 -- add it to the .toc, and it's done.
2309 --
2310 -- Can run as a standalone addon also, but, really, just embed it! :-)
2311 --
2312  
2313 local CTL_VERSION = 13
2314  
2315 local MAX_CPS = 800 -- 2000 seems to be safe if NOTHING ELSE is happening. let's call it 800.
2316 local MSG_OVERHEAD = 40 -- Guesstimate overhead for sending a message; source+dest+chattype+protocolstuff
2317  
2318 local BURST = 4000 -- WoW's server buffer seems to be about 32KB. 8KB should be safe, but seen disconnects on _some_ servers. Using 4KB now.
2319  
2320 local MIN_FPS = 20 -- Reduce output CPS to half (and don't burst) if FPS drops below this value
2321  
2322 if(ChatThrottleLib and ChatThrottleLib.version>=CTL_VERSION) then
2323 -- There's already a newer (or same) version loaded. Buh-bye.
2324 return;
2325 end
2326  
2327  
2328  
2329 if(not ChatThrottleLib) then
2330 ChatThrottleLib = {}
2331 end
2332  
2333 local ChatThrottleLib = ChatThrottleLib
2334 local strlen = strlen
2335 local setmetatable = setmetatable
2336 local getn = getn
2337 local tremove = tremove
2338 local tinsert = tinsert
2339 local tostring = tostring
2340 local GetTime = GetTime
2341 local format = format
2342  
2343 ChatThrottleLib.version=CTL_VERSION;
2344  
2345  
2346 -----------------------------------------------------------------------
2347 -- Double-linked ring implementation
2348  
2349 local Ring = {}
2350 local RingMeta = { __index=Ring }
2351  
2352 function Ring:New()
2353 local ret = {}
2354 setmetatable(ret, RingMeta)
2355 return ret;
2356 end
2357  
2358 function Ring:Add(obj) -- Append at the "far end" of the ring (aka just before the current position)
2359 if(self.pos) then
2360 obj.prev = self.pos.prev;
2361 obj.prev.next = obj;
2362 obj.next = self.pos;
2363 obj.next.prev = obj;
2364 else
2365 obj.next = obj;
2366 obj.prev = obj;
2367 self.pos = obj;
2368 end
2369 end
2370  
2371 function Ring:Remove(obj)
2372 obj.next.prev = obj.prev;
2373 obj.prev.next = obj.next;
2374 if(self.pos == obj) then
2375 self.pos = obj.next;
2376 if(self.pos == obj) then
2377 self.pos = nil;
2378 end
2379 end
2380 end
2381  
2382  
2383  
2384 -----------------------------------------------------------------------
2385 -- Recycling bin for pipes (kept in a linked list because that's
2386 -- how they're worked with in the rotating rings; just reusing members)
2387  
2388 ChatThrottleLib.PipeBin = { count=0 }
2389  
2390 function ChatThrottleLib.PipeBin:Put(pipe)
2391 for i=getn(pipe),1,-1 do
2392 tremove(pipe, i);
2393 end
2394 pipe.prev = nil;
2395 pipe.next = self.list;
2396 self.list = pipe;
2397 self.count = self.count+1;
2398 end
2399  
2400 function ChatThrottleLib.PipeBin:Get()
2401 if(self.list) then
2402 local ret = self.list;
2403 self.list = ret.next;
2404 ret.next=nil;
2405 self.count = self.count - 1;
2406 return ret;
2407 end
2408 return {};
2409 end
2410  
2411 function ChatThrottleLib.PipeBin:Tidy()
2412 if(self.count < 25) then
2413 return;
2414 end
2415  
2416 if(self.count > 100) then
2417 n=self.count-90;
2418 else
2419 n=10;
2420 end
2421 for i=2,n do
2422 self.list = self.list.next;
2423 end
2424 local delme = self.list;
2425 self.list = self.list.next;
2426 delme.next = nil;
2427 end
2428  
2429  
2430  
2431  
2432 -----------------------------------------------------------------------
2433 -- Recycling bin for messages
2434  
2435 ChatThrottleLib.MsgBin = {}
2436  
2437 function ChatThrottleLib.MsgBin:Put(msg)
2438 msg.text = nil;
2439 tinsert(self, msg);
2440 end
2441  
2442 function ChatThrottleLib.MsgBin:Get()
2443 local ret = tremove(self, getn(self));
2444 if(ret) then return ret; end
2445 return {};
2446 end
2447  
2448 function ChatThrottleLib.MsgBin:Tidy()
2449 if(getn(self)<50) then
2450 return;
2451 end
2452 if(getn(self)>150) then -- "can't happen" but ...
2453 for n=getn(self),120,-1 do
2454 tremove(self,n);
2455 end
2456 else
2457 for n=getn(self),getn(self)-20,-1 do
2458 tremove(self,n);
2459 end
2460 end
2461 end
2462  
2463  
2464 -----------------------------------------------------------------------
2465 -- ChatThrottleLib:Init
2466 -- Initialize queues, set up frame for OnUpdate, etc
2467  
2468  
2469 function ChatThrottleLib:Init()
2470  
2471 -- Set up queues
2472 if(not self.Prio) then
2473 self.Prio = {}
2474 self.Prio["ALERT"] = { ByName={}, Ring = Ring:New(), avail=0 };
2475 self.Prio["NORMAL"] = { ByName={}, Ring = Ring:New(), avail=0 };
2476 self.Prio["BULK"] = { ByName={}, Ring = Ring:New(), avail=0 };
2477 end
2478  
2479 -- v4: total send counters per priority
2480 for _,Prio in pairs(self.Prio) do
2481 Prio.nTotalSent = Prio.nTotalSent or 0;
2482 end
2483  
2484 self.avail = self.avail or 0; -- v5
2485 self.nTotalSent = self.nTotalSent or 0; -- v5
2486  
2487  
2488 -- Set up a frame to get OnUpdate events
2489 if(not self.Frame) then
2490 self.Frame = CreateFrame("Frame");
2491 self.Frame:Hide();
2492 end
2493 self.Frame.Show = self.Frame.Show; -- cache for speed
2494 self.Frame.Hide = self.Frame.Hide; -- cache for speed
2495 self.Frame:SetScript("OnUpdate", self.OnUpdate);
2496 self.Frame:SetScript("OnEvent", self.OnEvent); -- v11: Monitor P_E_W so we can throttle hard for a few seconds
2497 self.Frame:RegisterEvent("PLAYER_ENTERING_WORLD");
2498 self.OnUpdateDelay=0;
2499 self.LastAvailUpdate=GetTime();
2500 self.HardThrottlingBeginTime=GetTime(); -- v11: Throttle hard for a few seconds after startup
2501  
2502 -- Hook SendChatMessage and SendAddonMessage so we can measure unpiped traffic and avoid overloads (v7)
2503 if(not self.ORIG_SendChatMessage) then
2504 --SendChatMessage
2505 self.ORIG_SendChatMessage = SendChatMessage;
2506 SendChatMessage = function(a1,a2,a3,a4) return ChatThrottleLib.Hook_SendChatMessage(a1,a2,a3,a4); end
2507 --SendAdd[Oo]nMessage
2508 if(SendAddonMessage or SendAddOnMessage) then -- v10: don't pretend like it doesn't exist if it doesn't!
2509 self.ORIG_SendAddonMessage = SendAddonMessage or SendAddOnMessage;
2510 SendAddonMessage = function(a1,a2,a3) return ChatThrottleLib.Hook_SendAddonMessage(a1,a2,a3); end
2511 if(SendAddOnMessage) then -- in case Slouken changes his mind...
2512 SendAddOnMessage = SendAddonMessage;
2513 end
2514 end
2515 end
2516 self.nBypass = 0;
2517 end
2518  
2519  
2520 -----------------------------------------------------------------------
2521 -- ChatThrottleLib.Hook_SendChatMessage / .Hook_SendAddonMessage
2522 function ChatThrottleLib.Hook_SendChatMessage(text, chattype, language, destination)
2523 local self = ChatThrottleLib;
2524 local size = strlen(tostring(text or "")) + strlen(tostring(chattype or "")) + strlen(tostring(destination or "")) + 40;
2525 self.avail = self.avail - size;
2526 self.nBypass = self.nBypass + size;
2527 return self.ORIG_SendChatMessage(text, chattype, language, destination);
2528 end
2529 function ChatThrottleLib.Hook_SendAddonMessage(prefix, text, chattype)
2530 local self = ChatThrottleLib;
2531 local size = strlen(tostring(text or "")) + strlen(tostring(chattype or "")) + strlen(tostring(prefix or "")) + 40;
2532 self.avail = self.avail - size;
2533 self.nBypass = self.nBypass + size;
2534 return self.ORIG_SendAddonMessage(prefix, text, chattype);
2535 end
2536  
2537  
2538  
2539 -----------------------------------------------------------------------
2540 -- ChatThrottleLib:UpdateAvail
2541 -- Update self.avail with how much bandwidth is currently available
2542  
2543 function ChatThrottleLib:UpdateAvail()
2544 local now = GetTime();
2545 local newavail = MAX_CPS * (now-self.LastAvailUpdate);
2546  
2547 if(now - self.HardThrottlingBeginTime < 5) then
2548 -- First 5 seconds after startup/zoning: VERY hard clamping to avoid irritating the server rate limiter, it seems very cranky then
2549 self.avail = min(self.avail + (newavail*0.1), MAX_CPS*0.5);
2550 elseif(GetFramerate()<MIN_FPS) then -- GetFrameRate call takes ~0.002 secs
2551 newavail = newavail * 0.5;
2552 self.avail = min(MAX_CPS, self.avail + newavail);
2553 self.bChoking = true; -- just for stats
2554 else
2555 self.avail = min(BURST, self.avail + newavail);
2556 self.bChoking = false;
2557 end
2558  
2559 self.avail = max(self.avail, 0-(MAX_CPS*2)); -- Can go negative when someone is eating bandwidth past the lib. but we refuse to stay silent for more than 2 seconds; if they can do it, we can.
2560 self.LastAvailUpdate = now;
2561  
2562 return self.avail;
2563 end
2564  
2565  
2566 -----------------------------------------------------------------------
2567 -- Despooling logic
2568  
2569 function ChatThrottleLib:Despool(Prio)
2570 local ring = Prio.Ring;
2571 while(ring.pos and Prio.avail>ring.pos[1].nSize) do
2572 local msg = tremove(Prio.Ring.pos, 1);
2573 if(not Prio.Ring.pos[1]) then
2574 local pipe = Prio.Ring.pos;
2575 Prio.Ring:Remove(pipe);
2576 Prio.ByName[pipe.name] = nil;
2577 self.PipeBin:Put(pipe);
2578 else
2579 Prio.Ring.pos = Prio.Ring.pos.next;
2580 end
2581 Prio.avail = Prio.avail - msg.nSize;
2582 msg.f(msg[1], msg[2], msg[3], msg[4]);
2583 Prio.nTotalSent = Prio.nTotalSent + msg.nSize;
2584 self.MsgBin:Put(msg);
2585 end
2586 end
2587  
2588  
2589 function ChatThrottleLib.OnEvent()
2590 -- v11: We know that the rate limiter is touchy after login. Assume that it's touch after zoning, too.
2591 self = ChatThrottleLib;
2592 if(event == "PLAYER_ENTERING_WORLD") then
2593 self.HardThrottlingBeginTime=GetTime(); -- Throttle hard for a few seconds after zoning
2594 self.avail = 0;
2595 end
2596 end
2597  
2598  
2599 function ChatThrottleLib.OnUpdate()
2600 self = ChatThrottleLib;
2601  
2602 self.OnUpdateDelay = self.OnUpdateDelay + arg1;
2603 if(self.OnUpdateDelay < 0.08) then
2604 return;
2605 end
2606 self.OnUpdateDelay = 0;
2607  
2608 self:UpdateAvail();
2609  
2610 if(self.avail<0) then
2611 return; -- argh. some bastard is spewing stuff past the lib. just bail early to save cpu.
2612 end
2613  
2614 -- See how many of or priorities have queued messages
2615 local n=0;
2616 for prioname,Prio in pairs(self.Prio) do
2617 if(Prio.Ring.pos or Prio.avail<0) then
2618 n=n+1;
2619 end
2620 end
2621  
2622 -- Anything queued still?
2623 if(n<1) then
2624 -- Nope. Move spillover bandwidth to global availability gauge and clear self.bQueueing
2625 for prioname,Prio in pairs(self.Prio) do
2626 self.avail = self.avail + Prio.avail;
2627 Prio.avail = 0;
2628 end
2629 self.bQueueing = false;
2630 self.Frame:Hide();
2631 return;
2632 end
2633  
2634 -- There's stuff queued. Hand out available bandwidth to priorities as needed and despool their queues
2635 local avail= self.avail/n;
2636 self.avail = 0;
2637  
2638 for prioname,Prio in pairs(self.Prio) do
2639 if(Prio.Ring.pos or Prio.avail<0) then
2640 Prio.avail = Prio.avail + avail;
2641 if(Prio.Ring.pos and Prio.avail>Prio.Ring.pos[1].nSize) then
2642 self:Despool(Prio);
2643 end
2644 end
2645 end
2646  
2647 -- Expire recycled tables if needed
2648 self.MsgBin:Tidy();
2649 self.PipeBin:Tidy();
2650 end
2651  
2652  
2653  
2654  
2655 -----------------------------------------------------------------------
2656 -- Spooling logic
2657  
2658  
2659 function ChatThrottleLib:Enqueue(prioname, pipename, msg)
2660 local Prio = self.Prio[prioname];
2661 local pipe = Prio.ByName[pipename];
2662 if(not pipe) then
2663 self.Frame:Show();
2664 pipe = self.PipeBin:Get();
2665 pipe.name = pipename;
2666 Prio.ByName[pipename] = pipe;
2667 Prio.Ring:Add(pipe);
2668 end
2669  
2670 tinsert(pipe, msg);
2671  
2672 self.bQueueing = true;
2673 end
2674  
2675  
2676  
2677 function ChatThrottleLib:SendChatMessage(prio, prefix, text, chattype, language, destination)
2678 if(not (self and prio and text and self.Prio[prio] ) ) then
2679 error('Usage: ChatThrottleLib:SendChatMessage("{BULK||NORMAL||ALERT}", "prefix" or nil, "text"[, "chattype"[, "language"[, "destination"]]]', 2);
2680 end
2681  
2682 prefix = prefix or tostring(this); -- each frame gets its own queue if prefix is not given
2683  
2684 local nSize = strlen(text) + MSG_OVERHEAD;
2685  
2686 -- Check if there's room in the global available bandwidth gauge to send directly
2687 if(not self.bQueueing and nSize < self:UpdateAvail()) then
2688 self.avail = self.avail - nSize;
2689 self.ORIG_SendChatMessage(text, chattype, language, destination);
2690 self.Prio[prio].nTotalSent = self.Prio[prio].nTotalSent + nSize;
2691 return;
2692 end
2693  
2694 -- Message needs to be queued
2695 msg=self.MsgBin:Get();
2696 msg.f=self.ORIG_SendChatMessage
2697 msg[1]=text;
2698 msg[2]=chattype or "SAY";
2699 msg[3]=language;
2700 msg[4]=destination;
2701 msg.n = 4
2702 msg.nSize = nSize;
2703  
2704 self:Enqueue(prio, format("%s/%s/%s", prefix, chattype, destination or ""), msg);
2705 end
2706  
2707  
2708 function ChatThrottleLib:SendAddonMessage(prio, prefix, text, chattype)
2709 if(not (self and prio and prefix and text and chattype and self.Prio[prio] ) ) then
2710 error('Usage: ChatThrottleLib:SendAddonMessage("{BULK||NORMAL||ALERT}", "prefix", "text", "chattype")', 0);
2711 end
2712  
2713 local nSize = strlen(prefix) + 1 + strlen(text) + MSG_OVERHEAD;
2714  
2715 -- Check if there's room in the global available bandwidth gauge to send directly
2716 if(not self.bQueueing and nSize < self:UpdateAvail()) then
2717 self.avail = self.avail - nSize;
2718 self.ORIG_SendAddonMessage(prefix, text, chattype);
2719 self.Prio[prio].nTotalSent = self.Prio[prio].nTotalSent + nSize;
2720 return;
2721 end
2722  
2723 -- Message needs to be queued
2724 msg=self.MsgBin:Get();
2725 msg.f=self.ORIG_SendAddonMessage;
2726 msg[1]=prefix;
2727 msg[2]=text;
2728 msg[3]=chattype;
2729 msg.n = 3
2730 msg.nSize = nSize;
2731  
2732 self:Enqueue(prio, format("%s/%s", prefix, chattype), msg);
2733 end
2734  
2735  
2736  
2737  
2738 -----------------------------------------------------------------------
2739 -- Get the ball rolling!
2740  
2741 ChatThrottleLib:Init();
2742  
2743 --[[ WoWBench debugging snippet
2744 if(WOWB_VER) then
2745 local function SayTimer()
2746 print("SAY: "..GetTime().." "..arg1);
2747 end
2748 ChatThrottleLib.Frame:SetScript("OnEvent", SayTimer);
2749 ChatThrottleLib.Frame:RegisterEvent("CHAT_MSG_SAY");
2750 end
2751 ]]
2752  
2753