Mercurial > wow > reaction
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 ]] |