comparison libs/AceComm-2.0/AceComm-2.0.lua @ 1:c11ca1d8ed91

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