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