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