diff --git a/README.md b/README.md index 34cc2da..8ee1010 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ +Modified version of aux that shares scan data with your guild. +This is experimental and I am unsure how well it will work, might run into issues when scanning lots of pages. + # aux - WoW 1.12 AddOn The most advanced auction house addOn for the 1.12 client with some features more advanced than anything even on retail. diff --git a/aux-addon.toc b/aux-addon.toc index 919f718..4de0f8b 100644 --- a/aux-addon.toc +++ b/aux-addon.toc @@ -7,6 +7,8 @@ libs\package.lua libs\T.lua +libs\AceComm-2.0\AceComm-2.0.lua +libs\AceAddon-2.0\AceAddon-2.0.lua aux-addon.lua util.lua diff --git a/core/scan.lua b/core/scan.lua index f0b8edc..f5505da 100644 --- a/core/scan.lua +++ b/core/scan.lua @@ -1,16 +1,27 @@ module 'aux.core.scan' +AuxAddon = AceLibrary("AceAddon-2.0"):new("AceComm-2.0") --im unsure this is done correctly but seems to work lol + +local commPrefix = "AuxAddon"; +AuxAddon:SetCommPrefix(commPrefix) + local T = require 'T' local aux = require 'aux' local info = require 'aux.util.info' local history = require 'aux.core.history' + local PAGE_SIZE = 50 function aux.handle.CLOSE() abort() end +function AuxAddon:OnEnable() + --self:RegisterComm(commPrefix, "GROUP", "OnCommReceive") for testing purposes, not really useful in real world I think + self:RegisterComm(commPrefix, "GUILD", "OnCommReceive"); +end + do local scan_states = {} @@ -146,6 +157,8 @@ function scan_page(i) auction_info.query_type = get_state().params.type history.process_auction(auction_info) + --AuxAddon:SendCommMessage("GROUP", auction_info); for testing purposes, not really useful in real world I think + AuxAddon:SendCommMessage("GUILD", auction_info); if (get_state().params.auto_buy_validator or pass)(auction_info) and auction_info.buyout_price >0 and auction_info.owner ~= UnitName("player") then local send_signal, signal_received = aux.signal() @@ -163,6 +176,11 @@ function scan_page(i) return scan_page(i + 1) end +function AuxAddon:OnCommReceive(prefix, sender, distribution, auction_info) + history.process_auction(auction_info) +end + + function wait_for_results() if get_state().params.type == 'bidder' then return aux.when(function() return aux.bids_loaded() end, accept_results) diff --git a/libs/AceAddon-2.0/AceAddon-2.0.lua b/libs/AceAddon-2.0/AceAddon-2.0.lua new file mode 100644 index 0000000..c985077 --- /dev/null +++ b/libs/AceAddon-2.0/AceAddon-2.0.lua @@ -0,0 +1,1074 @@ +--[[ + Name: AceAddon-2.0 + Revision: $Rev: 17957 $ + Developed by: The Ace Development Team (http://www.wowace.com/index.php/The_Ace_Development_Team) + Inspired By: Ace 1.x by Turan (turan@gryphon.com) + Website: http://www.wowace.com/ + Documentation: http://www.wowace.com/index.php/AceAddon-2.0 + SVN: http://svn.wowace.com/root/trunk/Ace2/AceAddon-2.0 + Description: Base for all Ace addons to inherit from. + Dependencies: AceLibrary, AceOO-2.0, AceEvent-2.0, (optional) AceConsole-2.0 +]] + +local MAJOR_VERSION = "AceAddon-2.0" +local MINOR_VERSION = "$Revision: 17957 $" + +-- This ensures the code is only executed if the libary doesn't already exist, or is a newer version +if not AceLibrary then error(MAJOR_VERSION .. " requires AceLibrary.") end +if not AceLibrary:IsNewVersion(MAJOR_VERSION, MINOR_VERSION) then return end + +if loadstring("return function(...) return ... end") and AceLibrary:HasInstance(MAJOR_VERSION) then return end -- lua51 check +if not AceLibrary:HasInstance("AceOO-2.0") then error(MAJOR_VERSION .. " requires AceOO-2.0.") end + +local function safecall(func,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10) + local success, err = pcall(func,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10) + if not success then geterrorhandler()(err) end +end + +-- Localization +local STANDBY, TITLE, NOTES, VERSION, AUTHOR, DATE, CATEGORY, EMAIL, CREDITS, WEBSITE, COMMANDS, CATEGORIES, ABOUT, PRINT_ADDON_INFO +if GetLocale() == "deDE" then + STANDBY = "|cffff5050(Standby)|r" -- capitalized + + TITLE = "Titel" + NOTES = "Anmerkung" + VERSION = "Version" + AUTHOR = "Autor" + DATE = "Datum" + CATEGORY = "Kategorie" + EMAIL = "E-mail" + WEBSITE = "Webseite" + CREDITS = "Credits" -- fix + COMMANDS = "Commands" + + ABOUT = "\195\188ber" + PRINT_ADDON_INFO = "Gibt Addondaten aus" + + CATEGORIES = { + ["Action Bars"] = "Aktionsleisten", + ["Auction"] = "Auktion", + ["Audio"] = "Audio", + ["Battlegrounds/PvP"] = "Schlachtfeld/PvP", + ["Buffs"] = "Buffs", + ["Chat/Communication"] = "Chat/Kommunikation", + ["Druid"] = "Druide", + ["Hunter"] = "Jäger", + ["Mage"] = "Magier", + ["Paladin"] = "Paladin", + ["Priest"] = "Priester", + ["Rogue"] = "Schurke", + ["Shaman"] = "Schamane", + ["Warlock"] = "Hexenmeister", + ["Warrior"] = "Krieger", + ["Healer"] = "Heiler", + ["Tank"] = "Tank", -- noone use "Brecher"... + ["Caster"] = "Caster", + ["Combat"] = "Kampf", + ["Compilations"] = "Compilations", -- whats that o_O + ["Data Export"] = "Datenexport", + ["Development Tools"] = "Entwicklungs Tools", + ["Guild"] = "Gilde", + ["Frame Modification"] = "Frame Modifikation", + ["Interface Enhancements"] = "Interface Verbesserungen", + ["Inventory"] = "Inventar", + ["Library"] = "Library", + ["Map"] = "Map", + ["Mail"] = "Mail", + ["Miscellaneous"] = "Diverses", + ["Quest"] = "Quest", + ["Raid"] = "Schlachtzug", + ["Tradeskill"] = "Handelsf\195\164higkeit", + ["UnitFrame"] = "UnitFrame", + } +elseif GetLocale() == "frFR" then + STANDBY = "|cffff5050(attente)|r" + + TITLE = "Titre" + NOTES = "Notes" + VERSION = "Version" + AUTHOR = "Auteur" + DATE = "Date" + CATEGORY = "Cat\195\169gorie" + EMAIL = "E-mail" + WEBSITE = "Site web" + CREDITS = "Credits" -- fix + COMMANDS = "Commands" + + ABOUT = "A propos" + PRINT_ADDON_INFO = "Afficher les informations sur l'addon" + + CATEGORIES = { + ["Action Bars"] = "Barres d'action", + ["Auction"] = "H\195\180tel des ventes", + ["Audio"] = "Audio", + ["Battlegrounds/PvP"] = "Champs de bataille/JcJ", + ["Buffs"] = "Buffs", + ["Chat/Communication"] = "Chat/Communication", + ["Druid"] = "Druide", + ["Hunter"] = "Chasseur", + ["Mage"] = "Mage", + ["Paladin"] = "Paladin", + ["Priest"] = "Pr\195\170tre", + ["Rogue"] = "Voleur", + ["Shaman"] = "Chaman", + ["Warlock"] = "D\195\169moniste", + ["Warrior"] = "Guerrier", + ["Healer"] = "Soigneur", + ["Tank"] = "Tank", + ["Caster"] = "Casteur", + ["Combat"] = "Combat", + ["Compilations"] = "Compilations", + ["Data Export"] = "Exportation de donn\195\169es", + ["Development Tools"] = "Outils de d\195\169veloppement", + ["Guild"] = "Guilde", + ["Frame Modification"] = "Modification des fen\195\170tres", + ["Interface Enhancements"] = "Am\195\169liorations de l'interface", + ["Inventory"] = "Inventaire", + ["Library"] = "Biblioth\195\168ques", + ["Map"] = "Carte", + ["Mail"] = "Courrier", + ["Miscellaneous"] = "Divers", + ["Quest"] = "Qu\195\170tes", + ["Raid"] = "Raid", + ["Tradeskill"] = "M\195\169tiers", + ["UnitFrame"] = "Fen\195\170tres d'unit\195\169", + } +elseif GetLocale() == "koKR" then + STANDBY = "|cffff5050(사용가능)|r" + + TITLE = "제목" + NOTES = "노트" + VERSION = "버전" + AUTHOR = "저작자" + DATE = "날짜" + CATEGORY = "분류" + EMAIL = "E-mail" + WEBSITE = "웹사이트" + CREDITS = "Credits" -- fix + COMMANDS = "Commands" + + ABOUT = "정보" + PRINT_ADDON_INFO = "애드온 정보 출력" + + CATEGORIES = { + ["Action Bars"] = "액션바", + ["Auction"] = "경매", + ["Audio"] = "음향", + ["Battlegrounds/PvP"] = "전장/PvP", + ["Buffs"] = "버프", + ["Chat/Communication"] = "대화/의사소통", + ["Druid"] = "드루이드", + ["Hunter"] = "사냥꾼", + ["Mage"] = "마법사", + ["Paladin"] = "성기사", + ["Priest"] = "사제", + ["Rogue"] = "도적", + ["Shaman"] = "주술사", + ["Warlock"] = "흑마법사", + ["Warrior"] = "전사", + ["Healer"] = "힐러", + ["Tank"] = "탱커", + ["Caster"] = "캐스터", + ["Combat"] = "전투", + ["Compilations"] = "복합", + ["Data Export"] = "자료 출력", + ["Development Tools"] = "개발 도구", + ["Guild"] = "길드", + ["Frame Modification"] = "구조 변경", + ["Interface Enhancements"] = "인터페이스 강화", + ["Inventory"] = "인벤토리", + ["Library"] = "라이브러리", + ["Map"] = "지도", + ["Mail"] = "우편", + ["Miscellaneous"] = "기타", + ["Quest"] = "퀘스트", + ["Raid"] = "공격대", + ["Tradeskill"] = "전문기술", + ["UnitFrame"] = "유닛 프레임", + } +elseif GetLocale() == "zhTW" then + STANDBY = "|cffff5050(待命)|r" + + TITLE = "標題" + NOTES = "註記" + VERSION = "版本" + AUTHOR = "作者" + DATE = "日期" + CATEGORY = "類別" + EMAIL = "E-mail" + WEBSITE = "網站" + CREDITS = "Credits" -- fix + COMMANDS = "Commands" + + ABOUT = "關於" + PRINT_ADDON_INFO = "顯示插件資訊" + + CATEGORIES = { + ["Action Bars"] = "動作列", + ["Auction"] = "拍賣", + ["Audio"] = "音樂", + ["Battlegrounds/PvP"] = "戰場/PvP", + ["Buffs"] = "增益", + ["Chat/Communication"] = "聊天/通訊", + ["Druid"] = "德魯伊", + ["Hunter"] = "獵人", + ["Mage"] = "法師", + ["Paladin"] = "聖騎士", + ["Priest"] = "牧師", + ["Rogue"] = "盜賊", + ["Shaman"] = "薩滿", + ["Warlock"] = "術士", + ["Warrior"] = "戰士", + ["Healer"] = "治療者", + ["Tank"] = "坦克", + ["Caster"] = "施法者", + ["Combat"] = "戰鬥", + ["Compilations"] = "編輯", + ["Data Export"] = "資料匯出", + ["Development Tools"] = "開發工具", + ["Guild"] = "公會", + ["Frame Modification"] = "框架修改", + ["Interface Enhancements"] = "介面增強", + ["Inventory"] = "背包", + ["Library"] = "資料庫", + ["Map"] = "地圖", + ["Mail"] = "郵件", + ["Miscellaneous"] = "綜合", + ["Quest"] = "任務", + ["Raid"] = "團隊", + ["Tradeskill"] = "商業技能", + ["UnitFrame"] = "單位框架", + } +elseif GetLocale() == "zhCN" then + STANDBY = "|cffff5050(暂挂)|r" + + TITLE = "标题" + NOTES = "附注" + VERSION = "版本" + AUTHOR = "作者" + DATE = "日期" + CATEGORY = "分类" + EMAIL = "电子邮件" + WEBSITE = "网站" + CREDITS = "Credits" -- fix + COMMANDS = "Commands" + + ABOUT = "关于" + PRINT_ADDON_INFO = "印列出插件信息" + + CATEGORIES = { + ["Action Bars"] = "动作条", + ["Auction"] = "拍卖", + ["Audio"] = "音频", + ["Battlegrounds/PvP"] = "战场/PvP", + ["Buffs"] = "增益魔法", + ["Chat/Communication"] = "聊天/交流", + ["Druid"] = "德鲁伊", + ["Hunter"] = "猎人", + ["Mage"] = "法师", + ["Paladin"] = "圣骑士", + ["Priest"] = "牧师", + ["Rogue"] = "盗贼", + ["Shaman"] = "萨满祭司", + ["Warlock"] = "术士", + ["Warrior"] = "战士", + ["Healer"] = "治疗保障", + ["Tank"] = "近战控制", + ["Caster"] = "远程输出", + ["Combat"] = "战斗", + ["Compilations"] = "编译", + ["Data Export"] = "数据导出", + ["Development Tools"] = "开发工具", + ["Guild"] = "公会", + ["Frame Modification"] = "框架修改", + ["Interface Enhancements"] = "界面增强", + ["Inventory"] = "背包", + ["Library"] = "库", + ["Map"] = "地图", + ["Mail"] = "邮件", + ["Miscellaneous"] = "杂项", + ["Quest"] = "任务", + ["Raid"] = "团队", + ["Tradeskill"] = "商业技能", + ["UnitFrame"] = "头像框架", + } +elseif GetLocale() == "ruRU" then + STANDBY = "|cffff5050(в режиме ожидания)|r" + + TITLE = "Название" + NOTES = "Заметки" + VERSION = "Версия" + AUTHOR = "Автор" + DATE = "Дата" + CATEGORY = "Категория" + EMAIL = "Почта" + WEBSITE = "Сайт" + CREDITS = "Авторы" + COMMANDS = "Команды" + + ABOUT = "Об аддоне" + PRINT_ADDON_INFO = "Печать информации об аддоне" + + CATEGORIES = { + ["Action Bars"] = "Панели действий", + ["Auction"] = "Аукцион", + ["Audio"] = "Аудио", + ["Battlegrounds/PvP"] = "Поля боя/PvP", + ["Buffs"] = "Баффы", + ["Chat/Communication"] = "Чат/Коммуникация", + ["Druid"] = "Друид", + ["Hunter"] = "Охотник", + ["Mage"] = "Маг", + ["Paladin"] = "Паладин", + ["Priest"] = "Жрец", + ["Rogue"] = "Разбойник", + ["Shaman"] = "Шаман", + ["Warlock"] = "Чернокнижник", + ["Warrior"] = "Воин", + ["Healer"] = "Целитель", + ["Tank"] = "Танк", + ["Caster"] = "Заклинатель", + ["Combat"] = "Бой", + ["Compilations"] = "Компиляция", + ["Data Export"] = "Экспорт данных", + ["Development Tools"] = "Инструменты разработчика", + ["Guild"] = "Гильдия", + ["Frame Modification"] = "Модификация фреймов", + ["Interface Enhancements"] = "Улучшения интерфейса", + ["Inventory"] = "Инвентарь", + ["Library"] = "Библиотека", + ["Map"] = "Карта", + ["Mail"] = "Почта", + ["Miscellaneous"] = "Разное", + ["Quest"] = "Задание", + ["Raid"] = "Рейд", + ["Tradeskill"] = "Умения", + ["UnitFrame"] = "Фреймы персонажей", + } +else -- enUS + STANDBY = "|cffff5050(standby)|r" + + TITLE = "Title" + NOTES = "Notes" + VERSION = "Version" + AUTHOR = "Author" + DATE = "Date" + CATEGORY = "Category" + EMAIL = "E-mail" + WEBSITE = "Website" + CREDITS = "Credits" + COMMANDS = "Commands" + + ABOUT = "About" + PRINT_ADDON_INFO = "Print out addon info" + + CATEGORIES = { + ["Action Bars"] = "Action Bars", + ["Auction"] = "Auction", + ["Audio"] = "Audio", + ["Battlegrounds/PvP"] = "Battlegrounds/PvP", + ["Buffs"] = "Buffs", + ["Chat/Communication"] = "Chat/Communication", + ["Druid"] = "Druid", + ["Hunter"] = "Hunter", + ["Mage"] = "Mage", + ["Paladin"] = "Paladin", + ["Priest"] = "Priest", + ["Rogue"] = "Rogue", + ["Shaman"] = "Shaman", + ["Warlock"] = "Warlock", + ["Warrior"] = "Warrior", + ["Healer"] = "Healer", + ["Tank"] = "Tank", + ["Caster"] = "Caster", + ["Combat"] = "Combat", + ["Compilations"] = "Compilations", + ["Data Export"] = "Data Export", + ["Development Tools"] = "Development Tools", + ["Guild"] = "Guild", + ["Frame Modification"] = "Frame Modification", + ["Interface Enhancements"] = "Interface Enhancements", + ["Inventory"] = "Inventory", + ["Library"] = "Library", + ["Map"] = "Map", + ["Mail"] = "Mail", + ["Miscellaneous"] = "Miscellaneous", + ["Quest"] = "Quest", + ["Raid"] = "Raid", + ["Tradeskill"] = "Tradeskill", + ["UnitFrame"] = "UnitFrame", + } +end + +setmetatable(CATEGORIES, { __index = function(self, key) -- case-insensitive + local lowerKey = string.lower(key) + for k,v in pairs(CATEGORIES) do + if string.lower(k) == lowerKey then + return v + end + end +end }) + +-- Create the library object + +local AceOO = AceLibrary("AceOO-2.0") +local AceAddon = AceOO.Class() +local AceEvent +local AceConsole +local AceModuleCore + +function AceAddon:ToString() + return "AceAddon" +end + +local function print(text) + DEFAULT_CHAT_FRAME:AddMessage(text) +end + +function AceAddon:ADDON_LOADED(name) + while table.getn(self.nextAddon) > 0 do + local addon = table.remove(self.nextAddon, 1) + table.insert(self.addons, addon) + if not self.addons[name] then + self.addons[name] = addon + end + self:InitializeAddon(addon, name) + end +end + +local function RegisterOnEnable(self) + if DEFAULT_CHAT_FRAME and DEFAULT_CHAT_FRAME.defaultLanguage then -- HACK + AceAddon.playerLoginFired = true + end + if AceAddon.playerLoginFired then + AceAddon.addonsStarted[self] = true + if (type(self.IsActive) ~= "function" or self:IsActive()) and (not AceModuleCore or not AceModuleCore:IsModule(self) or AceModuleCore:IsModuleActive(self)) then + local current = self.class + while true do + if current == AceOO.Class then + break + end + if current.mixins then + for mixin in pairs(current.mixins) do + if type(mixin.OnEmbedEnable) == "function" then + safecall(mixin.OnEmbedEnable,mixin,self) + end + end + end + current = current.super + end + if type(self.OnEnable) == "function" then + safecall(self.OnEnable,self) + end + if AceEvent then + AceEvent:TriggerEvent("Ace2_AddonEnabled", self) + end + end + else + if not AceAddon.addonsToOnEnable then + AceAddon.addonsToOnEnable = {} + end + table.insert(AceAddon.addonsToOnEnable, self) + end +end + +local function stripSpaces(text) + if type(text) == "string" then + return (string.gsub(string.gsub(text, "^%s*(.-)%s*$", "%1"), "%s%s+", " ")) + end + return text +end + +function AceAddon:InitializeAddon(addon, name) + if addon.name == nil then + addon.name = name + end + if GetAddOnMetadata then + -- TOC checks + if addon.title == nil then + addon.title = GetAddOnMetadata(name, "Title") + end + if addon.title then + local num = string.find(addon.title, " |cff7fff7f %-Ace2%-|r$") + if num then + addon.title = string.sub(addon.title, 1, num - 1) + end + addon.title = stripSpaces(addon.title) + end + + if addon.notes == nil then + addon.notes = GetAddOnMetadata(name, "Notes") + addon.notes = stripSpaces(addon.notes) + end + if addon.version == nil then + addon.version = GetAddOnMetadata(name, "Version") + end + if addon.version then + if string.find(addon.version, "%$Revision: (%d+) %$") then + addon.version = string.gsub(addon.version, "%$Revision: (%d+) %$", "%1") + elseif string.find(addon.version, "%$Rev: (%d+) %$") then + addon.version = string.gsub(addon.version, "%$Rev: (%d+) %$", "%1") + elseif string.find(addon.version, "%$LastChangedRevision: (%d+) %$") then + addon.version = string.gsub(addon.version, "%$LastChangedRevision: (%d+) %$", "%1") + end + end + addon.version = stripSpaces(addon.version) + if addon.author == nil then + addon.author = GetAddOnMetadata(name, "Author") + addon.author = stripSpaces(addon.author) + end + if addon.credits == nil then + addon.credits = GetAddOnMetadata(name, "X-Credits") + addon.credits = stripSpaces(addon.credits) + end + if addon.date == nil then + addon.date = GetAddOnMetadata(name, "X-Date") or GetAddOnMetadata(name, "X-ReleaseDate") + end + if addon.date then + if string.find(addon.date, "%$Date: (.-) %$") then + addon.date = string.gsub(addon.date, "%$Date: (.-) %$", "%1") + elseif string.find(addon.date, "%$LastChangedDate: (.-) %$") then + addon.date = string.gsub(addon.date, "%$LastChangedDate: (.-) %$", "%1") + end + end + addon.date = stripSpaces(addon.date) + + if addon.category == nil then + addon.category = GetAddOnMetadata(name, "X-Category") + addon.category = stripSpaces(addon.category) + end + if addon.email == nil then + addon.email = GetAddOnMetadata(name, "X-eMail") or GetAddOnMetadata(name, "X-Email") + addon.email = stripSpaces(addon.email) + end + if addon.website == nil then + addon.website = GetAddOnMetadata(name, "X-Website") + addon.website = stripSpaces(addon.website) + end + if addon.commands == nil then + addon.commands = GetAddOnMetadata(name, "X-Commands") + addon.commands = stripSpaces(addon.commands) + end + end + local current = addon.class + while true do + if current == AceOO.Class then + break + end + if current.mixins then + for mixin in pairs(current.mixins) do + if type(mixin.OnEmbedInitialize) == "function" then + mixin:OnEmbedInitialize(addon, name) + end + end + end + current = current.super + end + if type(addon.OnInitialize) == "function" then + safecall(addon.OnInitialize, addon, name) + end + if AceEvent then + AceEvent:TriggerEvent("Ace2_AddonInitialized", addon) + end + RegisterOnEnable(addon) +end + +function AceAddon.prototype:PrintAddonInfo() + local x + if self.title then + x = "|cffffff7f" .. tostring(self.title) .. "|r" + elseif self.name then + x = "|cffffff7f" .. tostring(self.name) .. "|r" + else + x = "|cffffff7f<" .. tostring(self.class) .. " instance>|r" + end + if type(self.IsActive) == "function" then + if not self:IsActive() then + x = x .. " " .. STANDBY + end + end + if self.version then + x = x .. " - |cffffff7f" .. tostring(self.version) .. "|r" + end + if self.notes then + x = x .. " - " .. tostring(self.notes) + end + print(x) + if self.author then + print(" - |cffffff7f" .. AUTHOR .. ":|r " .. tostring(self.author)) + end + if self.credits then + print(" - |cffffff7f" .. CREDITS .. ":|r " .. tostring(self.credits)) + end + if self.date then + print(" - |cffffff7f" .. DATE .. ":|r " .. tostring(self.date)) + end + if self.category then + local category = CATEGORIES[self.category] + if category then + print(" - |cffffff7f" .. CATEGORY .. ":|r " .. category) + end + end + if self.email then + print(" - |cffffff7f" .. EMAIL .. ":|r " .. tostring(self.email)) + end + if self.website then + print(" - |cffffff7f" .. WEBSITE .. ":|r " .. tostring(self.website)) + end + if self.commands then + print(" - |cffffff7f" .. COMMANDS .. ":|r " .. tostring(self.commands)) + end +end + +local options +function AceAddon:GetAceOptionsDataTable(target) + if not options then + options = { + about = { + name = ABOUT, + desc = PRINT_ADDON_INFO, + type = "execute", + func = "PrintAddonInfo", + order = -1, + } + } + end + return options +end + +function AceAddon:PLAYER_LOGIN() + self.playerLoginFired = true + if self.addonsToOnEnable then + while table.getn(self.addonsToOnEnable) > 0 do + local addon = table.remove(self.addonsToOnEnable, 1) + self.addonsStarted[addon] = true + if (type(addon.IsActive) ~= "function" or addon:IsActive()) and (not AceModuleCore or not AceModuleCore:IsModule(addon) or AceModuleCore:IsModuleActive(addon)) then + local current = addon.class + while true do + if current == AceOO.Class then + break + end + if current.mixins then + for mixin in pairs(current.mixins) do + if type(mixin.OnEmbedEnable) == "function" then + safecall(mixin.OnEmbedEnable,mixin,addon) + end + end + end + current = current.super + end + if type(addon.OnEnable) == "function" then + safecall(addon.OnEnable,addon) + end + if AceEvent then + AceEvent:TriggerEvent("Ace2_AddonEnabled", addon) + end + end + end + self.addonsToOnEnable = nil + end +end + +function AceAddon.prototype:Inject(t) + AceAddon:argCheck(t, 2, "table") + for k,v in pairs(t) do + self[k] = v + end +end + +function AceAddon.prototype:init() + if not AceEvent then + error(MAJOR_VERSION .. " requires AceEvent-2.0", 4) + end + AceAddon.super.prototype.init(self) + + self.super = self.class.prototype + + AceAddon:RegisterEvent("ADDON_LOADED", "ADDON_LOADED", true) + table.insert(AceAddon.nextAddon, self) +end + +function AceAddon.prototype:ToString() + local x + if type(self.title) == "string" then + x = self.title + elseif type(self.name) == "string" then + x = self.name + else + x = "<" .. tostring(self.class) .. " instance>" + end + if (type(self.IsActive) == "function" and not self:IsActive()) or (AceModuleCore and AceModuleCore:IsModule(addon) and AceModuleCore:IsModuleActive(addon)) then + x = x .. " " .. STANDBY + end + return x +end + +AceAddon.new = function(self, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15, m16, m17, m18, m19, m20) + local class = AceAddon:pcall(AceOO.Classpool, self, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15, m16, m17, m18, m19, m20) + return class:new() +end + +local function external(self, major, instance) + if major == "AceEvent-2.0" then + AceEvent = instance + + AceEvent:embed(self) + + self:RegisterEvent("PLAYER_LOGIN", "PLAYER_LOGIN", true) + elseif major == "AceConsole-2.0" then + AceConsole = instance + + local slashCommands = { "/ace2" } + local function listAddon(addon, depth) + if not depth then + depth = 0 + end + + local s = string.rep(" ", depth) .. " - " .. tostring(addon) + if rawget(addon, 'version') then + s = s .. " - |cffffff7f" .. tostring(addon.version) .. "|r" + end + if rawget(addon, 'slashCommand') then + s = s .. " |cffffff7f(" .. tostring(addon.slashCommand) .. ")|r" + end + print(s) + if type(rawget(addon, 'modules')) == "table" then + local i = 0 + for k,v in pairs(addon.modules) do + i = i + 1 + if i == 6 then + print(string.rep(" ", depth + 1) .. " - more...") + break + else + listAddon(v, depth + 1) + end + end + end + end + local function listNormalAddon(i) + local name,_,_,enabled,loadable = GetAddOnInfo(i) + if not loadable then + enabled = false + end + if self.addons[name] then + local addon = self.addons[name] + if not AceCoreAddon or not AceCoreAddon:IsModule(addon) then + listAddon(addon) + end + else + local s = " - " .. tostring(GetAddOnMetadata(i, "Title") or name) + local version = GetAddOnMetadata(i, "Version") + if version then + if string.find(version, "%$Revision: (%d+) %$") then + version = string.gsub(version, "%$Revision: (%d+) %$", "%1") + elseif string.find(version, "%$Rev: (%d+) %$") then + version = string.gsub(version, "%$Rev: (%d+) %$", "%1") + elseif string.find(version, "%$LastChangedRevision: (%d+) %$") then + version = string.gsub(version, "%$LastChangedRevision: (%d+) %$", "%1") + end + s = s .. " - |cffffff7f" .. version .. "|r" + end + if not enabled then + s = s .. " |cffff0000(disabled)|r" + end + if IsAddOnLoadOnDemand(i) then + s = s .. " |cff00ff00[LoD]|r" + end + print(s) + end + end + local function mySort(alpha, bravo) + return tostring(alpha) < tostring(bravo) + end + AceConsole.RegisterChatCommand(self, slashCommands, { + desc = "AddOn development framework", + name = "Ace2", + type = "group", + args = { + about = { + desc = "Get information about Ace2", + name = "About", + type = "execute", + func = function() + print("|cffffff7fAce2|r - |cffffff7f2.0." .. string.gsub(MINOR_VERSION, "%$Revision: (%d+) %$", "%1") .. "|r - AddOn development framework") + print(" - |cffffff7f" .. AUTHOR .. ":|r Ace Development Team") + print(" - |cffffff7f" .. WEBSITE .. ":|r http://www.wowace.com/") + end + }, + list = { + desc = "List addons", + name = "List", + type = "group", + args = { + ace2 = { + desc = "List addons using Ace2", + name = "Ace2", + type = "execute", + func = function() + print("|cffffff7fAddon list:|r") + local AceCoreAddon = AceLibrary:HasInstance("AceCoreAddon-2.0") and AceLibrary("AceCoreAddon-2.0") + table.sort(self.addons, mySort) + for _,v in ipairs(self.addons) do + if not AceCoreAddon or not AceCoreAddon:IsModule(v) then + listAddon(v) + end + end + end + }, + all = { + desc = "List all addons", + name = "All", + type = "execute", + func = function() + print("|cffffff7fAddon list:|r") + local AceCoreAddon = AceLibrary:HasInstance("AceCoreAddon-2.0") and AceLibrary("AceCoreAddon-2.0") + local count = GetNumAddOns() + for i = 1, count do + listNormalAddon(i) + end + end + }, + enabled = { + desc = "List all enabled addons", + name = "Enabled", + type = "execute", + func = function() + print("|cffffff7fAddon list:|r") + local AceCoreAddon = AceLibrary:HasInstance("AceCoreAddon-2.0") and AceLibrary("AceCoreAddon-2.0") + local count = GetNumAddOns() + for i = 1, count do + local _,_,_,enabled,loadable = GetAddOnInfo(i) + if enabled and loadable then + listNormalAddon(i) + end + end + end + }, + disabled = { + desc = "List all disabled addons", + name = "Disabled", + type = "execute", + func = function() + print("|cffffff7fAddon list:|r") + local AceCoreAddon = AceLibrary:HasInstance("AceCoreAddon-2.0") and AceLibrary("AceCoreAddon-2.0") + local count = GetNumAddOns() + for i = 1, count do + local _,_,_,enabled,loadable = GetAddOnInfo(i) + if not enabled or not loadable then + listNormalAddon(i) + end + end + end + }, + lod = { + desc = "List all LoadOnDemand addons", + name = "LoadOnDemand", + type = "execute", + func = function() + print("|cffffff7fAddon list:|r") + local AceCoreAddon = AceLibrary:HasInstance("AceCoreAddon-2.0") and AceLibrary("AceCoreAddon-2.0") + local count = GetNumAddOns() + for i = 1, count do + if IsAddOnLoadOnDemand(i) then + listNormalAddon(i) + end + end + end + }, + ace1 = { + desc = "List all addons using Ace1", + name = "Ace 1.x", + type = "execute", + func = function() + print("|cffffff7fAddon list:|r") + local count = GetNumAddOns() + for i = 1, count do + local dep1, dep2, dep3, dep4 = GetAddOnDependencies(i) + if dep1 == "Ace" or dep2 == "Ace" or dep3 == "Ace" or dep4 == "Ace" then + listNormalAddon(i) + end + end + end + }, + libs = { + desc = "List all libraries using AceLibrary", + name = "Libraries", + type = "execute", + func = function() + if type(AceLibrary) == "table" and type(AceLibrary.libs) == "table" then + print("|cffffff7fLibrary list:|r") + for name, data in pairs(AceLibrary.libs) do + local s + if data.minor then + s = " - " .. tostring(name) .. "." .. tostring(data.minor) + else + s = " - " .. tostring(name) + end + if AceLibrary(name).slashCommand then + s = s .. " |cffffff7f(" .. tostring(AceLibrary(name).slashCommand) .. "|cffffff7f)" + end + print(s) + end + end + end + }, + search = { + desc = "Search by name", + name = "Search", + type = "text", + usage = "", + input = true, + get = false, + set = function(...) + for i,v in ipairs(arg) do + arg[i] = string.lower(string.gsub(string.gsub(v, '%*', '.*'), '%%', '%%%%')) + end + local count = GetNumAddOns() + for i = 1, count do + local name = GetAddOnInfo(i) + local good = true + for _,v in ipairs(arg) do + if not string.find(string.lower(name), v) then + good = false + break + end + end + if good then + listNormalAddon(i) + end + end + end + } + }, + }, + enable = { + desc = "Enable addon", + name = "Enable", + type = "text", + usage = "", + get = false, + set = function(text) + local name,title,_,_,_,reason = GetAddOnInfo(text) + if reason == "MISSING" then + print(string.format("|cffffff7fAce2:|r AddOn %q does not exist", text)) + else + EnableAddOn(text) + print(string.format("|cffffff7fAce2:|r %s is now enabled", title or name)) + end + end, + }, + disable = { + desc = "Disable addon", + name = "Disable", + type = "text", + usage = "", + get = false, + set = function(text) + local name,title,_,_,_,reason = GetAddOnInfo(text) + if reason == "MISSING" then + print(string.format("|cffffff7fAce2:|r AddOn %q does not exist", text)) + else + DisableAddOn(text) + print(string.format("|cffffff7fAce2:|r %s is now disabled", title or name)) + end + end, + }, + load = { + desc = "Load addon", + name = "Load", + type = "text", + usage = "", + get = false, + set = function(text) + local name,title,_,_,loadable,reason = GetAddOnInfo(text) + if reason == "MISSING" then + print(string.format("|cffffff7fAce2:|r AddOn %q does not exist.", text)) + elseif not loadable then + print(string.format("|cffffff7fAce2:|r AddOn %q is not loadable. Reason: %s", text, reason)) + else + LoadAddOn(text) + print(string.format("|cffffff7fAce2:|r %s is now loaded", title or name)) + end + end + }, + info = { + desc = "Display information", + name = "Information", + type = "execute", + func = function() + local mem, threshold = gcinfo() + print(string.format(" - |cffffff7fMemory usage [|r%.3f MiB|cffffff7f]|r", mem / 1024)) + print(string.format(" - |cffffff7fThreshold [|r%.3f MiB|cffffff7f]|r", threshold / 1024)) + print(string.format(" - |cffffff7fFramerate [|r%.0f fps|cffffff7f]|r", GetFramerate())) + local bandwidthIn, bandwidthOut, latency = GetNetStats() + bandwidthIn, bandwidthOut = floor(bandwidthIn * 1024), floor(bandwidthOut * 1024) + print(string.format(" - |cffffff7fLatency [|r%.0f ms|cffffff7f]|r", latency)) + print(string.format(" - |cffffff7fBandwidth in [|r%.0f B/s|cffffff7f]|r", bandwidthIn)) + print(string.format(" - |cffffff7fBandwidth out [|r%.0f B/s|cffffff7f]|r", bandwidthOut)) + print(string.format(" - |cffffff7fTotal addons [|r%d|cffffff7f]|r", GetNumAddOns())) + print(string.format(" - |cffffff7fAce2 addons [|r%d|cffffff7f]|r", table.getn(self.addons))) + local ace = 0 + local enabled = 0 + local disabled = 0 + local lod = 0 + for i = 1, GetNumAddOns() do + local dep1, dep2, dep3, dep4 = GetAddOnDependencies(i) + if dep1 == "Ace" or dep2 == "Ace" or dep3 == "Ace" or dep4 == "Ace" then + ace = ace + 1 + end + if IsAddOnLoadOnDemand(i) then + lod = lod + 1 + end + local _,_,_,IsActive,loadable = GetAddOnInfo(i) + if not IsActive or not loadable then + disabled = disabled + 1 + else + enabled = enabled + 1 + end + end + print(string.format(" - |cffffff7fAce 1.x addons [|r%d|cffffff7f]|r", ace)) + print(string.format(" - |cffffff7fLoadOnDemand addons [|r%d|cffffff7f]|r", lod)) + print(string.format(" - |cffffff7fenabled addons [|r%d|cffffff7f]|r", enabled)) + print(string.format(" - |cffffff7fdisabled addons [|r%d|cffffff7f]|r", disabled)) + local libs = 0 + if type(AceLibrary) == "table" and type(AceLibrary.libs) == "table" then + for _ in pairs(AceLibrary.libs) do + libs = libs + 1 + end + end + print(string.format(" - |cffffff7fAceLibrary instances [|r%d|cffffff7f]|r", libs)) + end + } + } + }) + elseif major == "AceModuleCore-2.0" then + AceModuleCore = instance + end +end + +local function activate(self, oldLib, oldDeactivate) + AceAddon = self + + if oldLib then + self.playerLoginFired = oldLib.playerLoginFired or DEFAULT_CHAT_FRAME and DEFAULT_CHAT_FRAME.defaultLanguage + self.addonsToOnEnable = oldLib.addonsToOnEnable + self.addons = oldLib.addons + self.nextAddon = oldLib.nextAddon + self.addonsStarted = oldLib.addonsStarted + end + if not self.addons then + self.addons = {} + end + if not self.nextAddon then + self.nextAddon = {} + end + if not self.addonsStarted then + self.addonsStarted = {} + end + if oldDeactivate then + oldDeactivate(oldLib) + end +end + +AceLibrary:Register(AceAddon, MAJOR_VERSION, MINOR_VERSION, activate, nil, external) +AceAddon = AceLibrary(MAJOR_VERSION) \ No newline at end of file diff --git a/libs/AceComm-2.0/AceComm-2.0.lua b/libs/AceComm-2.0/AceComm-2.0.lua new file mode 100644 index 0000000..5fa9b6a --- /dev/null +++ b/libs/AceComm-2.0/AceComm-2.0.lua @@ -0,0 +1,2752 @@ +--[[ + Name: AceComm-2.0 + Revision: $Rev: 17723 $ + Developed by: The Ace Development Team (http://www.wowace.com/index.php/The_Ace_Development_Team) + Inspired By: Ace 1.x by Turan (turan@gryphon.com) + Website: http://www.wowace.com/ + Documentation: http://www.wowace.com/index.php/AceComm-2.0 + SVN: http://svn.wowace.com/wowace/trunk/Ace2/AceComm-2.0 + Description: Mixin to allow for inter-player addon communications. + Dependencies: AceLibrary, AceOO-2.0, AceEvent-2.0, + ChatThrottleLib by Mikk (included) +]] + +local MAJOR_VERSION = "AceComm-2.0" +local MINOR_VERSION = "$Revision: 17723 $" + +if not AceLibrary then error(MAJOR_VERSION .. " requires AceLibrary") end +if not AceLibrary:IsNewVersion(MAJOR_VERSION, MINOR_VERSION) then return end + +if loadstring("return function(...) return ... end") and AceLibrary:HasInstance(MAJOR_VERSION) then return end -- lua51 check +if not AceLibrary:HasInstance("AceOO-2.0") then error(MAJOR_VERSION .. " requires AceOO-2.0") end + +local _G = getfenv(0) + +local AceOO = AceLibrary("AceOO-2.0") +local Mixin = AceOO.Mixin +local AceComm = Mixin { + "SendCommMessage", + "SendPrioritizedCommMessage", + "RegisterComm", + "UnregisterComm", + "UnregisterAllComms", + "IsCommRegistered", + "SetDefaultCommPriority", + "SetCommPrefix", + "RegisterMemoizations", + "IsUserInChannel", +} +AceComm.hooks = {} + +local AceEvent = AceLibrary:HasInstance("AceEvent-2.0") and AceLibrary("AceEvent-2.0") + +local table_setn +do + local version = GetBuildInfo() + if string.find(version, "^2%.") then + -- 2.0.0 + table_setn = function() end + else + table_setn = table.setn + end +end + +local new, del +do + local list = setmetatable({}, {__mode="k"}) + + function new() + local t = next(list) + if t then + list[t] = nil + else + t = {} + end + return t + end + + function del(t) + setmetatable(t, nil) + for k in pairs(t) do + t[k] = nil + end + table_setn(t, 0) + list[t] = true + return nil + end +end + +local string_byte = string.byte + +local byte_a = string_byte('a') +local byte_z = string_byte('z') +local byte_A = string_byte('A') +local byte_Z = string_byte('Z') +local byte_fake_s = string_byte('\015') +local byte_fake_S = string_byte('\020') +local byte_deg = string_byte('�') +local byte_percent = string_byte('%') -- 37 + +local byte_b = string_byte('b') +local byte_nil = string_byte('/') +local byte_plus = string_byte('+') +local byte_minus = string_byte('-') +local byte_d = string_byte('d') +local byte_D = string_byte('D') +local byte_e = string_byte('e') +local byte_E = string_byte('E') +local byte_m = string_byte('m') +local byte_s = string_byte('s') +local byte_S = string_byte('S') +local byte_o = string_byte('o') +local byte_O = string_byte('O') +local byte_t = string_byte('t') +local byte_T = string_byte('T') +local byte_u = string_byte('u') +local byte_U = string_byte('U') +local byte_i = string_byte('i') +local byte_I = string_byte('I') +local byte_j = string_byte('j') +local byte_J = string_byte('J') +local byte_inf = string_byte('@') +local byte_ninf = string_byte('$') +local byte_nan = string_byte('!') + +local inf = 1/0 +local nan = 0/0 + +local math_floor = math.floor +local math_mod = math.mod or math.fmod +local string_gfind = string.gmatch or string.gfind +local string_char = string.char +local string_len = string.len +local string_format = string.format +local string_gsub = string.gsub +local string_find = string.find +local table_insert = table.insert +local string_sub = string.sub +local table_concat = table.concat +local table_remove = table.remove + +local type = type +local unpack = unpack +local pairs = pairs +local next = next + +local player = UnitName("player") + +local NumericCheckSum, HexCheckSum, BinaryCheckSum +local TailoredNumericCheckSum, TailoredHexCheckSum, TailoredBinaryCheckSum +do + local SOME_PRIME = 16777213 + function NumericCheckSum(text) + local counter = 1 + local len = string_len(text) + for i = 1, len, 3 do + counter = math_mod(counter*8257, 16777259) + + (string_byte(text,i)) + + ((string_byte(text,i+1) or 1)*127) + + ((string_byte(text,i+2) or 2)*16383) + end + return math_mod(counter, 16777213) + end + + function HexCheckSum(text) + return string_format("%06x", NumericCheckSum(text)) + end + + function BinaryCheckSum(text) + local num = NumericCheckSum(text) + return string_char(num / 65536, math_mod(num / 256, 256), math_mod(num, 256)) + end + + function TailoredNumericCheckSum(text) + local hash = NumericCheckSum(text) + local a = math_floor(hash / 65536) + local b = math_floor(math_mod(hash / 256, 256)) + local c = math_mod(hash, 256) + -- \000, \n, |, �, s, S, \015, \020 + 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 + a = a + 1 + -- \t, \255 + elseif a == 9 or a == 255 then + a = a - 1 + end + 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 + b = b + 1 + elseif b == 9 or b == 255 then + b = b - 1 + end + 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 + c = c + 1 + elseif c == 9 or c == 255 then + c = c - 1 + end + return a * 65536 + b * 256 + c + end + + function TailoredHexCheckSum(text) + return string_format("%06x", TailoredNumericCheckSum(text)) + end + + function TailoredBinaryCheckSum(text) + local num = TailoredNumericCheckSum(text) + return string_char(num / 65536, math_mod(num / 256, 256), math_mod(num, 256)) + end +end + +local function GetLatency() + local _,_,lag = GetNetStats() + return lag / 1000 +end + +local function IsInChannel(chan) + return GetChannelName(chan) ~= 0 +end + +-- Package a message for transmission +local function Encode(text, drunk) + text = string_gsub(text, "�", "��") + if drunk then + text = string_gsub(text, "\020", "�\021") + text = string_gsub(text, "\015", "�\016") + text = string_gsub(text, "S", "\020") + text = string_gsub(text, "s", "\015") + -- change S and s to a different set of character bytes. + end + text = string_gsub(text, "\255", "�\254") -- \255 (this is here because \000 is more common) + text = string_gsub(text, "%z", "\255") -- \000 + text = string_gsub(text, "\010", "�\011") -- \n + text = string_gsub(text, "\124", "�\125") -- | + text = string_gsub(text, "%%", "�\038") -- % + -- encode assorted prohibited characters + return text +end + +local func +-- Clean a received message +local function Decode(text, drunk) + if drunk then + local _,x = string_find(text, "^.*�") + text = string_gsub(text, "^(.*)�.-$", "%1") + -- get rid of " ...hic!" + end + if not func then + func = function(text) + if text == "\016" then + return "\015" + elseif text == "\021" then + return "\020" + elseif text == "�" then + return "�" + elseif text == "\254" then + return "\255" + elseif text == "\011" then + return "\010" + elseif text == "\125" then + return "\124" + elseif text == "\038" then + return "\037" + end + end + end + text = string_gsub(text, "\255", "\000") + if drunk then + text = string_gsub(text, "\020", "S") + text = string_gsub(text, "\015", "s") + end + text = string_gsub(text, drunk and "�([\016\021�\254\011\125\038])" or "�([�\254\011\125\038])", func) + -- remove the hidden character and refix the prohibited characters. + return text +end + +local lastChannelJoined + +function AceComm.hooks:JoinChannelByName(orig, channel, a,b,c,d,e,f,g,h,i) + lastChannelJoined = channel + return orig(channel, a,b,c,d,e,f,g,h,i) +end + +local function JoinChannel(channel) + if not IsInChannel(channel) then + LeaveChannelByName(channel) + AceComm:ScheduleEvent(JoinChannelByName, 0, channel) + end +end + +local function LeaveChannel(channel) + if IsInChannel(channel) then + LeaveChannelByName(channel) + end +end + +local switches = {} + +local function SwitchChannel(former, latter) + if IsInChannel(former) then + LeaveChannelByName(former) + local t = new() + t.former = former + t.latter = latter + switches[t] = true + return + end + if not IsInChannel(latter) then + JoinChannelByName(latter) + end +end + +local shutdown = false + +local zoneCache +local function GetCurrentZoneChannel() + if not zoneCache then + zoneCache = "AceCommZone" .. HexCheckSum(GetRealZoneText()) + end + return zoneCache +end + +local AceComm_registry + +local function SupposedToBeInChannel(chan) + if not string_find(chan, "^AceComm") then + return true + elseif shutdown or not AceEvent:IsFullyInitialized() then + return false + end + + if chan == "AceComm" then + return AceComm_registry.GLOBAL and next(AceComm_registry.GLOBAL) and true or false + elseif string_find(chan, "^AceCommZone%x%x%x%x%x%x$") then + if chan == GetCurrentZoneChannel() then + return AceComm_registry.ZONE and next(AceComm_registry.ZONE) and true or false + else + return false + end + else + return AceComm_registry.CUSTOM and AceComm_registry.CUSTOM[chan] and next(AceComm_registry.CUSTOM[chan]) and true or false + end +end + +local function LeaveAceCommChannels(all) + if all then + shutdown = true + end + local _,a,_,b,_,c,_,d,_,e,_,f,_,g,_,h,_,i,_,j = GetChannelList() + local t = new() + t[1] = a + t[2] = b + t[3] = c + t[4] = d + t[5] = e + t[6] = f + t[7] = g + t[8] = h + t[9] = i + t[10] = j + for _,v in ipairs(t) do + if v and string_find(v, "^AceComm") then + if not SupposedToBeInChannel(v) then + LeaveChannelByName(v) + end + end + end + t = del(t) +end + +local lastRefix = 0 +local function RefixAceCommChannelsAndEvents() + if GetTime() - lastRefix <= 5 then + AceComm:ScheduleEvent(RefixAceCommChannelsAndEvents, 1) + return + end + lastRefix = GetTime() + LeaveAceCommChannels(false) + + local channel = false + local whisper = false + local addon = false + if SupposedToBeInChannel("AceComm") then + JoinChannel("AceComm") + channel = true + end + if SupposedToBeInChannel(GetCurrentZoneChannel()) then + JoinChannel(GetCurrentZoneChannel()) + channel = true + end + if AceComm_registry.CUSTOM then + for k,v in pairs(AceComm_registry.CUSTOM) do + if next(v) and SupposedToBeInChannel(k) then + JoinChannel(k) + channel = true + end + end + end + if AceComm_registry.WHISPER then + whisper = true + end + if AceComm_registry.GROUP or AceComm_registry.PARTY or AceComm_registry.RAID or AceComm_registry.BATTLEGROUND or AceComm_registry.GUILD then + addon = true + end + + if channel then + if not AceComm:IsEventRegistered("CHAT_MSG_CHANNEL") then + AceComm:RegisterEvent("CHAT_MSG_CHANNEL") + end + if not AceComm:IsEventRegistered("CHAT_MSG_CHANNEL_LIST") then + AceComm:RegisterEvent("CHAT_MSG_CHANNEL_LIST") + end + if not AceComm:IsEventRegistered("CHAT_MSG_CHANNEL_JOIN") then + AceComm:RegisterEvent("CHAT_MSG_CHANNEL_JOIN") + end + if not AceComm:IsEventRegistered("CHAT_MSG_CHANNEL_LEAVE") then + AceComm:RegisterEvent("CHAT_MSG_CHANNEL_LEAVE") + end + else + if AceComm:IsEventRegistered("CHAT_MSG_CHANNEL") then + AceComm:UnregisterEvent("CHAT_MSG_CHANNEL") + end + if AceComm:IsEventRegistered("CHAT_MSG_CHANNEL_LIST") then + AceComm:UnregisterEvent("CHAT_MSG_CHANNEL_LIST") + end + if AceComm:IsEventRegistered("CHAT_MSG_CHANNEL_JOIN") then + AceComm:UnregisterEvent("CHAT_MSG_CHANNEL_JOIN") + end + if AceComm:IsEventRegistered("CHAT_MSG_CHANNEL_LEAVE") then + AceComm:UnregisterEvent("CHAT_MSG_CHANNEL_LEAVE") + end + end + + if whisper then + if not AceComm:IsEventRegistered("CHAT_MSG_WHISPER") then + AceComm:RegisterEvent("CHAT_MSG_WHISPER") + end + else + if AceComm:IsEventRegistered("CHAT_MSG_WHISPER") then + AceComm:UnregisterEvent("CHAT_MSG_WHISPER") + end + end + + if addon then + if not AceComm:IsEventRegistered("CHAT_MSG_ADDON") then + AceComm:RegisterEvent("CHAT_MSG_ADDON") + end + else + if AceComm:IsEventRegistered("CHAT_MSG_ADDON") then + AceComm:UnregisterEvent("CHAT_MSG_ADDON") + end + end +end + + +do + local myFunc = function(k) + if not IsInChannel(k.latter) then + JoinChannelByName(k.latter) + end + del(k) + switches[k] = nil + end + + function AceComm:CHAT_MSG_CHANNEL_NOTICE(kind, _, _, deadName, _, _, _, num, channel) + if kind == "YOU_LEFT" then + if not string_find(channel, "^AceComm") then + return + end + for k in pairs(switches) do + if k.former == channel then + self:ScheduleEvent(myFunc, 0, k) + end + end + if channel == GetCurrentZoneChannel() then + self:TriggerEvent("AceComm_LeftChannel", "ZONE") + elseif channel == "AceComm" then + self:TriggerEvent("AceComm_LeftChannel", "GLOBAL") + else + self:TriggerEvent("AceComm_LeftChannel", "CUSTOM", string_sub(channel, 8)) + end + if string_find(channel, "^AceComm") and SupposedToBeInChannel(channel) then + self:ScheduleEvent(JoinChannel, 0, channel) + end + if AceComm.userRegistry[channel] then + AceComm.userRegistry[channel] = del(AceComm.userRegistry[channel]) + end + elseif kind == "YOU_JOINED" then + if not string_find(num == 0 and deadName or channel, "^AceComm") then + return + end + if num == 0 then + self:ScheduleEvent(LeaveChannelByName, 0, deadName) + local t = new() + t.former = deadName + t.latter = deadName + switches[t] = true + elseif channel == GetCurrentZoneChannel() then + self:TriggerEvent("AceComm_JoinedChannel", "ZONE") + elseif channel == "AceComm" then + self:TriggerEvent("AceComm_JoinedChannel", "GLOBAL") + else + self:TriggerEvent("AceComm_JoinedChannel", "CUSTOM", string_sub(channel, 8)) + end + if num ~= 0 then + if not SupposedToBeInChannel(channel) then + LeaveChannel(channel) + else + ListChannelByName(channel) + end + end + end + end +end + +local Serialize +do + local recurse + local function _Serialize(v, textToHash) + local kind = type(v) + if kind == "boolean" then + if v then + return "by" + else + return "bn" + end + elseif not v then -- nil + return "/" + elseif kind == "number" then + if v == math_floor(v) then + if v <= 127 and v >= -128 then + if v < 0 then + v = v + 256 + end + return string_char(byte_d, v) + elseif v <= 32767 and v >= -32768 then + if v < 0 then + v = v + 65536 + end + return string_char(byte_D, v / 256, math_mod(v, 256)) + elseif v <= 2147483647 and v >= -2147483648 then + if v < 0 then + v = v + 4294967296 + end + return string_char(byte_e, v / 16777216, math_mod(v / 65536, 256), math_mod(v / 256, 256), math_mod(v, 256)) + elseif v <= 9223372036854775807 and v >= -9223372036854775808 then + if v < 0 then + v = v + 18446744073709551616 + end + return string_char(byte_E, v / 72057594037927936, math_mod(v / 281474976710656, 256), math_mod(v / 1099511627776, 256), math_mod(v / 4294967296, 256), math_mod(v / 16777216, 256), math_mod(v / 65536, 256), math_mod(v / 256, 256), math_mod(v, 256)) + end + elseif v == inf then + return string_char(64 --[[byte_inf]]) + elseif v == -inf then + return string_char(36 --[[byte_ninf]]) + elseif v ~= v then + return string_char(33 --[[byte_nan]]) + end + -- do + -- local s = tostring(v) + -- local len = string_len(s) + -- return string_char(byte_plus, len) .. s + -- end + local sign = v < 0 or v == 0 and tostring(v) == "-0" + if sign then + v = -v + end + local m, exp = math.frexp(v) + m = m * 9007199254740992 + local x = exp + 1023 + local b = math_mod(m, 256) + local c = math_mod(math_floor(m / 256), 256) + m = math_floor(m / 65536) + m = m + x * 137438953472 + return string_char(sign and byte_minus or byte_plus, math_mod(m / 1099511627776, 256), math_mod(m / 4294967296, 256), math_mod(m / 16777216, 256), math_mod(m / 65536, 256), math_mod(m / 256, 256), math_mod(m, 256), c, b) + elseif kind == "string" then + local hash = textToHash and textToHash[v] + if hash then + return string_char(byte_m, hash / 65536, math_mod(hash / 256, 256), math_mod(hash, 256)) + end + local _,_,A,B,C = string_find(v, "|cff%x%x%x%x%x%x|Hitem:(%d+):(%d+):(%d+):%d+|h%[.+%]|h|r") + if A then + -- item link + A = tonumber(A) + B = tonumber(B) + C = tonumber(C) + if C ~= 0 then + if B ~= 0 then + return string_char(byte_I, math_mod(A / 65536, 256), math_mod(A / 256, 256), math_mod(A, 256), math_mod(B / 256, 256), math_mod(B, 256), math_mod(C / 256, 256), math_mod(C, 256)) + else + return string_char(byte_j, math_mod(A / 65536, 256), math_mod(A / 256, 256), math_mod(A, 256), math_mod(C / 256, 256), math_mod(C, 256)) + end + else + if B ~= 0 then + return string_char(byte_J, math_mod(A / 65536, 256), math_mod(A / 256, 256), math_mod(A, 256), math_mod(B / 256, 256), math_mod(B, 256)) + else + return string_char(byte_i, math_mod(A / 65536, 256), math_mod(A / 256, 256), math_mod(A, 256)) + end + end + else + -- normal string + local len = string_len(v) + if len <= 255 then + return string_char(byte_s, len) .. v + else + return string_char(byte_S, len / 256, math_mod(len, 256)) .. v + end + end + elseif kind == "function" then + AceComm:error("Cannot serialize a function") + elseif kind == "table" then + if recurse[v] then + for k in pairs(recurse) do + recurse[k] = nil + end + AceComm:error("Cannot serialize a recursive table") + return + end + recurse[v] = true + if AceOO.inherits(v, AceOO.Class) then + if not v.class then + AceComm:error("Cannot serialize an AceOO class, can only serialize objects") + elseif type(v.Serialize) ~= "function" then + AceComm:error("Cannot serialize an AceOO object without the `Serialize' method.") + elseif type(v.class.Deserialize) ~= "function" then + AceComm:error("Cannot serialize an AceOO object without the `Deserialize' static method.") + elseif type(v.class.GetLibraryVersion) ~= "function" or not AceLibrary:HasInstance(v.class:GetLibraryVersion()) then + AceComm:error("Cannot serialize an AceOO object if the class is not registered with AceLibrary.") + end + local classHash = TailoredBinaryCheckSum(v.class:GetLibraryVersion()) + local a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20 = v:Serialize() + local t = new() + t[2] = a1 + t[3] = a2 + t[4] = a3 + t[5] = a4 + t[6] = a5 + t[7] = a6 + t[8] = a7 + t[9] = a8 + t[10] = a9 + t[11] = a10 + t[12] = a11 + t[13] = a12 + t[14] = a13 + t[15] = a14 + t[16] = a15 + t[17] = a16 + t[18] = a17 + t[19] = a18 + t[20] = a19 + t[21] = a20 + local n = 21 + while n > 1 do + if t[i] ~= nil then + break + end + n = n - 1 + end + for i = 2, n do + t[i] = _Serialize(t[i], textToHash) + end + t[1] = classHash + if not notFirst then + for k in pairs(recurse) do + recurse[k] = nil + end + end + table_setn(t, n) + local s = table.concat(t) + t = del(t) + local len = string_len(s) + if len <= 255 then + return string_char(byte_o, len) .. s + else + return string_char(byte_O, len / 256, math_mod(len, 256)) .. s + end + end + local t = new() + local islist = false + local n = table.getn(v) + if n >= 1 or n <= 40 then + islist = true + for k,u in pairs(v) do + if (type(k) ~= "number" or k > n or k < 1) and (k ~= "n" or type(v) ~= "number") then + islist = false + break + end + end + end + if islist then + for i = 1, n do + t[i] = _Serialize(v[i], textToHash) + end + table_setn(t, n) + else + local i = 1 + for k,u in pairs(v) do + t[i] = _Serialize(k, textToHash) + t[i+1] = _Serialize(u, textToHash) + i = i + 2 + end + table_setn(t, i - 1) + end + if not notFirst then + for k in pairs(recurse) do + recurse[k] = nil + end + end + local s = table.concat(t) + t = del(t) + local len = string_len(s) + if islist then + if len <= 255 then + return string_char(byte_u, len) .. s + else + return "U" .. string_char(len / 256, math_mod(len, 256)) .. s + end + else + if len <= 255 then + return "t" .. string_char(len) .. s + else + return "T" .. string_char(len / 256, math_mod(len, 256)) .. s + end + end + end + end + + function Serialize(value, textToHash) + if not recurse then + recurse = new() + end + local chunk = _Serialize(value, textToHash) + for k in pairs(recurse) do + recurse[k] = nil + end + return chunk + end +end + +local Deserialize +do + local function _Deserialize(value, position, hashToText) + if not position then + position = 1 + end + local x = string_byte(value, position) + if x == byte_b then + -- boolean + local v = string_byte(value, position + 1) + if v == 110 then -- 'n' + return false, position + 1 + elseif v == 121 then -- 'y' + return true, position + 1 + else + error("Improper serialized value provided") + end + elseif x == byte_nil then + -- nil + return nil, position + elseif x == byte_I then + -- 7-byte item link + local a1 = string_byte(value, position + 1) + local a2 = string_byte(value, position + 2) + local a3 = string_byte(value, position + 3) + local b1 = string_byte(value, position + 4) + local b2 = string_byte(value, position + 5) + local c1 = string_byte(value, position + 6) + local c2 = string_byte(value, position + 7) + local A = a1 * 65536 + a2 * 256 + a3 + local B = b1 * 256 + b2 + local C = c1 * 256 + c2 + local s = "item:" .. A .. ":" .. B .. ":" .. C .. ":0" + local name, code, quality = GetItemInfo(s) + if name then + local _,_,_,color = GetItemQualityColor(quality) + return color .. "|H" .. code .. "|h[" .. name .. "]|h|r", position + 7 + else + return nil, position + 7 + end + elseif x == byte_i then + -- 3-byte item link + local a1 = string_byte(value, position + 1) + local a2 = string_byte(value, position + 2) + local a3 = string_byte(value, position + 3) + local A = a1 * 65536 + a2 * 256 + a3 + local s = "item:" .. A .. ":0:0:0" + local name, code, quality = GetItemInfo(s) + if name then + local _,_,_,color = GetItemQualityColor(quality) + return color .. "|H" .. code .. "|h[" .. name .. "]|h|r", position + 3 + else + return nil, position + 3 + end + elseif x == byte_j then + -- 5-byte item link + local a1 = string_byte(value, position + 1) + local a2 = string_byte(value, position + 2) + local a3 = string_byte(value, position + 3) + local c1 = string_byte(value, position + 4) + local c2 = string_byte(value, position + 5) + local A = a1 * 65536 + a2 * 256 + a3 + local C = c1 * 256 + c2 + local s = "item:" .. A .. ":0:" .. C .. ":0" + local name, code, quality = GetItemInfo(s) + if name then + local _,_,_,color = GetItemQualityColor(quality) + return color .. "|H" .. code .. "|h[" .. name .. "]|h|r", position + 5 + else + return nil, position + 5 + end + elseif x == byte_J then + -- 5-byte item link + local a1 = string_byte(value, position + 1) + local a2 = string_byte(value, position + 2) + local a3 = string_byte(value, position + 3) + local b1 = string_byte(value, position + 4) + local b2 = string_byte(value, position + 5) + local A = a1 * 65536 + a2 * 256 + a3 + local B = b1 * 256 + b2 + local s = "item:" .. A .. ":" .. B .. ":0:0" + local name, code, quality = GetItemInfo(s) + if name then + local _,_,_,color = GetItemQualityColor(quality) + return color .. "|H" .. code .. "|h[" .. name .. "]|h|r", position + 5 + else + return nil, position + 5 + end + elseif x == byte_m then + local hash = string_byte(value, position + 1) * 65536 + string_byte(value, position + 2) * 256 + string_byte(value, position + 3) + return hashToText[hash], position + 3 + elseif x == byte_s then + -- 0-255-byte string + local len = string_byte(value, position + 1) + return string.sub(value, position + 2, position + 1 + len), position + 1 + len + elseif x == byte_S then + -- 256-65535-byte string + local len = string_byte(value, position + 1) * 256 + string_byte(value, position + 2) + return string.sub(value, position + 3, position + 2 + len), position + 2 + len + elseif x == 64 --[[byte_inf]] then + return inf, position + elseif x == 36 --[[byte_ninf]] then + return -inf, position + elseif x == 33 --[[byte_nan]] then + return nan, position + elseif x == byte_d then + -- 1-byte integer + local a = string_byte(value, position + 1) + if a >= 128 then + a = a - 256 + end + return a, position + 1 + elseif x == byte_D then + -- 2-byte integer + local a = string_byte(value, position + 1) + local b = string_byte(value, position + 2) + local N = a * 256 + b + if N >= 32768 then + N = N - 65536 + end + return N, position + 2 + elseif x == byte_e then + -- 4-byte integer + local a = string_byte(value, position + 1) + local b = string_byte(value, position + 2) + local c = string_byte(value, position + 3) + local d = string_byte(value, position + 4) + local N = a * 16777216 + b * 65536 + c * 256 + d + if N >= 2147483648 then + N = N - 4294967296 + end + return N, position + 4 + elseif x == byte_E then + -- 8-byte integer + local a = string_byte(value, position + 1) + local b = string_byte(value, position + 2) + local c = string_byte(value, position + 3) + local d = string_byte(value, position + 4) + local e = string_byte(value, position + 5) + local f = string_byte(value, position + 6) + local g = string_byte(value, position + 7) + local h = string_byte(value, position + 8) + local N = a * 72057594037927936 + b * 281474976710656 + c * 1099511627776 + d * 4294967296 + e * 16777216 + f * 65536 + g * 256 + h + if N >= 9223372036854775808 then + N = N - 18446744073709551616 + end + return N, position + 8 + elseif x == byte_plus or x == byte_minus then + local a = string_byte(value, position + 1) + local b = string_byte(value, position + 2) + local c = string_byte(value, position + 3) + local d = string_byte(value, position + 4) + local e = string_byte(value, position + 5) + local f = string_byte(value, position + 6) + local g = string_byte(value, position + 7) + local h = string_byte(value, position + 8) + local N = a * 1099511627776 + b * 4294967296 + c * 16777216 + d * 65536 + e * 256 + f + local sign = x + local x = math.floor(N / 137438953472) + local m = math_mod(N, 137438953472) * 65536 + g * 256 + h + local mantissa = m / 9007199254740992 + local exp = x - 1023 + local val = math.ldexp(mantissa, exp) + if sign == byte_minus then + return -val, position + 8 + end + return val, position + 8 + elseif x == byte_u or x == byte_U then + -- numerically-indexed table + local finish + local start + if x == byte_u then + local len = string_byte(value, position + 1) + finish = position + 1 + len + start = position + 2 + else + local len = string_byte(value, position + 1) * 256 + string_byte(value, position + 2) + finish = position + 2 + len + start = position + 3 + end + local t = new() + local n = 0 + local curr = start - 1 + while curr < finish do + local v + v, curr = _Deserialize(value, curr + 1, hashToText) + n = n + 1 + t[n] = v + end + table_setn(t, n) + return t, finish + elseif x == byte_o or x == byte_O then + -- numerically-indexed table + local finish + local start + if x == byte_o then + local len = string_byte(value, position + 1) + finish = position + 1 + len + start = position + 2 + else + local len = string_byte(value, position + 1) * 256 + string_byte(value, position + 2) + finish = position + 2 + len + start = position + 3 + end + local hash = string_byte(value, start) * 65536 + string_byte(value, start + 1) * 256 + string_byte(value, start + 2) + local curr = start + 2 + if not AceComm.classes[hash] then + return nil, finish + end + local class = AceComm.classes[hash] + if type(class.Deserialize) ~= "function" or type(class.prototype.Serialize) ~= "function" then + return nil, finish + end + local t = new() + local n = 0 + while curr < finish do + local v + v, curr = _Deserialize(value, curr + 1, hashToText) + n = n + 1 + t[n] = v + end + table_setn(t, n) + local object = class:Deserialize(unpack(t)) + del(t) + return object, finish + elseif x == byte_t or x == byte_T then + -- table + local finish + local start + if x == byte_t then + local len = string_byte(value, position + 1) + finish = position + 1 + len + start = position + 2 + else + local len = string_byte(value, position + 1) * 256 + string_byte(value, position + 2) + finish = position + 2 + len + start = position + 3 + end + local t = new() + local curr = start - 1 + while curr < finish do + local key, l = _Deserialize(value, curr + 1, hashToText) + local value, m = _Deserialize(value, l + 1, hashToText) + curr = m + t[key] = value + end + if type(t.n) ~= "number" then + local i = 1 + while t[i] ~= nil do + i = i + 1 + end + table_setn(t, i - 1) + end + return t, finish + else + error("Improper serialized value provided") + end + end + + function Deserialize(value, hashToText) + local ret,msg = pcall(_Deserialize, value, nil, hashToText) + if ret then + return msg + end + end +end + +local function GetCurrentGroupDistribution() + if MiniMapBattlefieldFrame.status == "active" then + return "BATTLEGROUND" + elseif UnitInRaid("player") then + return "RAID" + elseif UnitInParty("player") then + return "PARTY" + else + return nil + end +end + +local function IsInDistribution(dist, customChannel) + if dist == "GROUP" then + return GetCurrentGroupDistribution() and true or false + elseif dist == "BATTLEGROUND" then + return MiniMapBattlefieldFrame.status == "active" + elseif dist == "RAID" then + return UnitInRaid("player") == 1 + elseif dist == "PARTY" then + return UnitInParty("player") == 1 + elseif dist == "GUILD" then + return IsInGuild() == 1 + elseif dist == "GLOBAL" then + return IsInChannel("AceComm") + elseif dist == "ZONE" then + return IsInChannel(GetCurrentZoneChannel()) + elseif dist == "WHISPER" then + return true + elseif dist == "CUSTOM" then + return IsInChannel(customChannel) + end + error("unknown distribution: " .. dist, 2) +end + +function AceComm:RegisterComm(prefix, distribution, method, a4) + AceComm:argCheck(prefix, 2, "string") + AceComm:argCheck(distribution, 3, "string") + 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 + AceComm:error('Argument #3 to `RegisterComm\' must be either "GLOBAL", "ZONE", "WHISPER", "PARTY", "RAID", "GUILD", "BATTLEGROUND", "GROUP", or "CUSTOM". %q is not appropriate', distribution) + end + local customChannel + if distribution == "CUSTOM" then + customChannel, method = method, a4 + AceComm:argCheck(customChannel, 4, "string") + if string_len(customChannel) == 0 then + AceComm:error('Argument #4 to `RegisterComm\' must be a non-zero-length string.') + elseif string_find(customChannel, "%s") then + AceComm:error('Argument #4 to `RegisterComm\' must not have spaces.') + end + end + if self == AceComm then + AceComm:argCheck(method, customChannel and 5 or 4, "function", "table") + self = method + else + AceComm:argCheck(method, customChannel and 5 or 4, "string", "function", "table", "nil") + end + if not method then + method = "OnCommReceive" + end + if type(method) == "string" and type(self[method]) ~= "function" and type(self[method]) ~= "table" then + AceEvent:error("Cannot register comm %q to method %q, it does not exist", prefix, method) + end + + local registry = AceComm_registry + if not registry[distribution] then + registry[distribution] = new() + end + if customChannel then + customChannel = "AceComm" .. customChannel + if not registry[distribution][customChannel] then + registry[distribution][customChannel] = new() + end + if not registry[distribution][customChannel][prefix] then + registry[distribution][customChannel][prefix] = new() + end + registry[distribution][customChannel][prefix][self] = method + else + if not registry[distribution][prefix] then + registry[distribution][prefix] = new() + end + registry[distribution][prefix][self] = method + end + + RefixAceCommChannelsAndEvents() +end + +function AceComm:UnregisterComm(prefix, distribution, customChannel) + AceComm:argCheck(prefix, 2, "string") + AceComm:argCheck(distribution, 3, "string", "nil") + 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 + AceComm:error('Argument #3 to `UnregisterComm\' must be either nil, "GLOBAL", "WHISPER", "PARTY", "RAID", "GUILD", "BATTLEGROUND", "GROUP", or "CUSTOM". %q is not appropriate', distribution) + end + if distribution == "CUSTOM" then + AceComm:argCheck(customChannel, 3, "string") + if string_len(customChannel) == 0 then + AceComm:error('Argument #3 to `UnregisterComm\' must be a non-zero-length string.') + end + else + AceComm:argCheck(customChannel, 3, "nil") + end + + local registry = AceComm_registry + if not distribution then + for k,v in pairs(registry) do + if k == "CUSTOM" then + for l,u in pairs(v) do + if u[prefix] and u[prefix][self] then + AceComm.UnregisterComm(self, prefix, k, string.sub(l, 8)) + if not registry[k] then + break + end + end + end + else + if v[prefix] and v[prefix][self] then + AceComm.UnregisterComm(self, prefix, k) + end + end + end + return + end + if self == AceComm then + if distribution == "CUSTOM" then + error(string_format("Cannot unregister comm %q::%q. Improperly unregistering from AceComm-2.0.", distribution, customChannel), 2) + else + error(string_format("Cannot unregister comm %q. Improperly unregistering from AceComm-2.0.", distribution), 2) + end + end + if distribution == "CUSTOM" then + customChannel = "AceComm" .. customChannel + if not registry[distribution] or not registry[distribution][customChannel] or not registry[distribution][customChannel][prefix] or not registry[distribution][customChannel][prefix][self] then + AceComm:error("Cannot unregister comm %q. %q is not registered with it.", distribution, self) + end + registry[distribution][customChannel][prefix][self] = nil + + if not next(registry[distribution][customChannel][prefix]) then + registry[distribution][customChannel][prefix] = del(registry[distribution][customChannel][prefix]) + end + + if not next(registry[distribution][customChannel]) then + registry[distribution][customChannel] = del(registry[distribution][customChannel]) + end + else + if not registry[distribution] or not registry[distribution][prefix] or not registry[distribution][prefix][self] then + AceComm:error("Cannot unregister comm %q. %q is not registered with it.", distribution, self) + end + registry[distribution][prefix][self] = nil + + if not next(registry[distribution][prefix]) then + registry[distribution][prefix] = del(registry[distribution][prefix]) + end + end + + if not next(registry[distribution]) then + registry[distribution] = del(registry[distribution]) + end + + RefixAceCommChannelsAndEvents() +end + +function AceComm:UnregisterAllComms() + local registry = AceComm_registry + for k, distribution in pairs(registry) do + if k == "CUSTOM" then + for l, channel in pairs(distribution) do + local j = next(channel) + while j ~= nil do + local prefix = channel[j] + if prefix[self] then + AceComm.UnregisterComm(self, j) + if distribution[l] and registry[k] then + j = next(channel) + else + l = nil + k = nil + break + end + else + j = next(channel, j) + end + end + if k == nil then + break + end + end + else + local j = next(distribution) + while j ~= nil do + local prefix = distribution[j] + if prefix[self] then + AceComm.UnregisterComm(self, j) + if registry[k] then + j = next(distribution) + else + k = nil + break + end + else + j = next(distribution, j) + end + end + end + end +end + +function AceComm:IsCommRegistered(prefix, distribution, customChannel) + AceComm:argCheck(prefix, 2, "string") + AceComm:argCheck(distribution, 3, "string", "nil") + 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 + AceComm:error('Argument #3 to `IsCommRegistered\' must be either "GLOBAL", "WHISPER", "PARTY", "RAID", "GUILD", "BATTLEGROUND", "GROUP", "ZONE", or "CUSTOM". %q is not appropriate', distribution) + end + if distribution == "CUSTOM" then + AceComm:argCheck(customChannel, 4, "nil", "string") + if customChannel == "" then + AceComm:error('Argument #4 to `IsCommRegistered\' must be a non-zero-length string or nil.') + end + else + AceComm:argCheck(customChannel, 4, "nil") + end + local registry = AceComm_registry + if not distribution then + for k,v in pairs(registry) do + if k == "CUSTOM" then + for l,u in pairs(v) do + if u[prefix] and u[prefix][self] then + return true + end + end + else + if v[prefix] and v[prefix][self] then + return true + end + end + end + return false + elseif distribution == "CUSTOM" and not customChannel then + if not registry[distribution] then + return false + end + for l,u in pairs(registry[distribution]) do + if u[prefix] and u[prefix][self] then + return true + end + end + return false + elseif distribution == "CUSTOM" then + customChannel = "AceComm" .. customChannel + return registry[distribution] and registry[distribution][customChannel] and registry[distribution][customChannel][prefix] and registry[distribution][customChannel][prefix][self] and true or false + end + return registry[distribution] and registry[distribution][prefix] and registry[distribution][prefix][self] and true or false +end + +function AceComm:OnEmbedDisable(target) + self.UnregisterAllComms(target) +end + +local id = byte_Z + +local function encodedChar(x) + if x == 10 then + return "�\011" + elseif x == 0 then + return "\255" + elseif x == 255 then + return "�\254" + elseif x == 124 then + return "�\125" + elseif x == byte_s then + return "\015" + elseif x == byte_S then + return "\020" + elseif x == 15 then + return "�\016" + elseif x == 20 then + return "�\021" + elseif x == byte_deg then + return "��" + elseif x == 37 then + return "�\038" + end + return string_char(x) +end + +local function soberEncodedChar(x) + if x == 10 then + return "�\011" + elseif x == 0 then + return "\255" + elseif x == 255 then + return "�\254" + elseif x == 124 then + return "�\125" + elseif x == byte_deg then + return "��" + elseif x == 37 then + return "�\038" + end + return string_char(x) +end + +local function SendMessage(prefix, priority, distribution, person, message, textToHash) + if distribution == "CUSTOM" then + person = "AceComm" .. person + end + if not IsInDistribution(distribution, person) then + return false + end + if distribution == "GROUP" then + distribution = GetCurrentGroupDistribution() + if not distribution then + return false + end + end + if id == byte_Z then + id = byte_a + elseif id == byte_z then + id = byte_A + else + id = id + 1 + end + if id == byte_s or id == byte_S then + id = id + 1 + end + local id = string_char(id) + local drunk = distribution == "GLOBAL" or distribution == "WHISPER" or distribution == "ZONE" or distribution == "CUSTOM" + prefix = Encode(prefix, drunk) + message = Serialize(message, textToHash) + message = Encode(message, drunk) + local headerLen = string_len(prefix) + 6 + local messageLen = string_len(message) + if distribution == "WHISPER" then + AceComm.recentWhispers[string.lower(person)] = GetTime() + end + local max = math_floor(messageLen / (250 - headerLen) + 1) + if max > 1 then + local segment = math_floor(messageLen / max + 0.5) + local last = 0 + local good = true + for i = 1, max do + local bit + if i == max then + bit = string_sub(message, last + 1) + else + local next = segment * i + if string_byte(message, next) == byte_deg then + next = next + 1 + end + bit = string_sub(message, last + 1, next) + last = next + end + if distribution == "WHISPER" then + bit = "/" .. prefix .. "\t" .. id .. encodedChar(i) .. encodedChar(max) .. "\t" .. bit .. "�" + ChatThrottleLib:SendChatMessage(priority, prefix, bit, "WHISPER", nil, person) + elseif distribution == "GLOBAL" or distribution == "ZONE" or distribution == "CUSTOM" then + bit = prefix .. "\t" .. id .. encodedChar(i) .. encodedChar(max) .. "\t" .. bit .. "�" + local channel + if distribution == "GLOBAL" then + channel = "AceComm" + elseif distribution == "ZONE" then + channel = GetCurrentZoneChannel() + elseif distribution == "CUSTOM" then + channel = person + end + local index = GetChannelName(channel) + if index and index > 0 then + ChatThrottleLib:SendChatMessage(priority, prefix, bit, "CHANNEL", nil, index) + else + good = false + end + else + bit = id .. soberEncodedChar(i) .. soberEncodedChar(max) .. "\t" .. bit + ChatThrottleLib:SendAddonMessage(priority, prefix, bit, distribution) + end + end + return good + else + if distribution == "WHISPER" then + message = "/" .. prefix .. "\t" .. id .. string_char(1) .. string_char(1) .. "\t" .. message .. "�" + ChatThrottleLib:SendChatMessage(priority, prefix, message, "WHISPER", nil, person) + return true + elseif distribution == "GLOBAL" or distribution == "ZONE" or distribution == "CUSTOM" then + message = prefix .. "\t" .. id .. string_char(1) .. string_char(1) .. "\t" .. message .. "�" + local channel + if distribution == "GLOBAL" then + channel = "AceComm" + elseif distribution == "ZONE" then + channel = GetCurrentZoneChannel() + elseif distribution == "CUSTOM" then + channel = person + end + local index = GetChannelName(channel) + if index and index > 0 then + ChatThrottleLib:SendChatMessage(priority, prefix, message, "CHANNEL", nil, index) + return true + end + else + message = id .. string_char(1) .. string_char(1) .. "\t" .. message + ChatThrottleLib:SendAddonMessage(priority, prefix, message, distribution) + return true + end + end + return false +end + +function AceComm:SendPrioritizedCommMessage(priority, distribution, person, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) + AceComm:argCheck(priority, 2, "string") + if priority ~= "NORMAL" and priority ~= "BULK" and priority ~= "ALERT" then + AceComm:error('Argument #2 to `SendPrioritizedCommMessage\' must be either "NORMAL", "BULK", or "ALERT"') + end + AceComm:argCheck(distribution, 3, "string") + if distribution == "WHISPER" or distribution == "CUSTOM" then + AceComm:argCheck(person, 4, "string") + if string_len(person) == 0 then + AceComm:error("Argument #4 to `SendPrioritizedCommMessage' must be a non-zero-length string") + end + else + a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20 = person, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19 + end + if self == AceComm then + AceComm:error("Cannot send a comm message from AceComm directly.") + end + 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 + 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) + end + + local prefix = self.commPrefix + if type(prefix) ~= "string" then + AceComm:error("`SetCommPrefix' must be called before sending a message.") + end + + local message + if a2 == nil and type(a1) ~= "table" then + message = a1 + else + message = new() + message[1] = a1 + message[2] = a2 + message[3] = a3 + message[4] = a4 + message[5] = a5 + message[6] = a6 + message[7] = a7 + message[8] = a8 + message[9] = a9 + message[10] = a10 + message[11] = a11 + message[12] = a12 + message[13] = a13 + message[14] = a14 + message[15] = a15 + message[16] = a16 + message[17] = a17 + message[18] = a18 + message[19] = a19 + message[20] = a20 + local n = 20 + while n > 0 do + if message[n] ~= nil then + break + end + n = n - 1 + end + table_setn(message, n) + end + + local ret = SendMessage(AceComm.prefixTextToHash[prefix], priority, distribution, person, message, self.commMemoTextToHash) + + if message ~= a1 then + message = del(message) + end + + return ret +end + +function AceComm:SendCommMessage(distribution, person, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) + AceComm:argCheck(distribution, 2, "string") + if distribution == "WHISPER" or distribution == "CUSTOM" then + AceComm:argCheck(person, 3, "string") + if string_len(person) == 0 then + AceComm:error("Argument #3 to `SendCommMessage' must be a non-zero-length string") + end + else + a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20 = person, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19 + end + if self == AceComm then + AceComm:error("Cannot send a comm message from AceComm directly.") + end + 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 + 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) + end + + local prefix = self.commPrefix + if type(prefix) ~= "string" then + AceComm:error("`SetCommPrefix' must be called before sending a message.") + end + + local message + if a2 == nil and type(a1) ~= "table" then + message = a1 + else + message = new() + message[1] = a1 + message[2] = a2 + message[3] = a3 + message[4] = a4 + message[5] = a5 + message[6] = a6 + message[7] = a7 + message[8] = a8 + message[9] = a9 + message[10] = a10 + message[11] = a11 + message[12] = a12 + message[13] = a13 + message[14] = a14 + message[15] = a15 + message[16] = a16 + message[17] = a17 + message[18] = a18 + message[19] = a19 + message[20] = a20 + local n = 20 + while n > 0 do + if message[n] ~= nil then + break + end + n = n - 1 + end + table_setn(message, n) + end + + local priority = self.commPriority or "NORMAL" + + local ret = SendMessage(AceComm.prefixTextToHash[prefix], priority, distribution, person, message, self.commMemoTextToHash) + + if message ~= a1 then + message = del(message) + end + + return ret +end + +function AceComm:SetDefaultCommPriority(priority) + AceComm:argCheck(priority, 2, "string") + if priority ~= "NORMAL" and priority ~= "BULK" and priority ~= "ALERT" then + AceComm:error('Argument #2 must be either "NORMAL", "BULK", or "ALERT"') + end + + if self.commPriority then + AceComm:error("Cannot call `SetDefaultCommPriority' more than once") + end + + self.commPriority = priority +end + +function AceComm:SetCommPrefix(prefix) + AceComm:argCheck(prefix, 2, "string") + + if self.commPrefix then + AceComm:error("Cannot call `SetCommPrefix' more than once.") + end + + if AceComm.prefixes[prefix] then + AceComm:error("Cannot set prefix to %q, it is already in use.", prefix) + end + + local hash = TailoredBinaryCheckSum(prefix) + if AceComm.prefixHashToText[hash] then + AceComm:error("Cannot set prefix to %q, its hash is used by another prefix: %q", prefix, AceComm.prefixHashToText[hash]) + end + + AceComm.prefixes[prefix] = true + self.commPrefix = prefix + AceComm.prefixHashToText[hash] = prefix + AceComm.prefixTextToHash[prefix] = hash +end + +function AceComm:RegisterMemoizations(values) + AceComm:argCheck(values, 2, "table") + for k,v in pairs(values) do + if type(k) ~= "number" then + AceComm:error("Bad argument #2 to `RegisterMemoizations'. All keys must be numbers") + elseif type(v) ~= "string" then + AceComm:error("Bad argument #2 to `RegisterMemoizations'. All values must be strings") + end + end + if self.commMemoHashToText or self.commMemoTextToHash then + AceComm:error("You can only call `RegisterMemoizations' once.") + elseif not self.commPrefix then + AceComm:error("You can only call `RegisterCommPrefix' before calling `RegisterMemoizations'.") + elseif AceComm.prefixMemoizations[self.commPrefix] then + AceComm:error("Another addon with prefix %q has already registered memoizations.", self.commPrefix) + end + local hashToText = new() + local textToHash = new() + for _,text in ipairs(values) do + local hash = TailoredNumericCheckSum(text) + if hashToText[hash] then + AceComm:error("%q and %q have the same checksum. You must remove one of them for memoization to work properly", hashToText[hash], text) + else + textToHash[text] = hash + hashToText[hash] = text + end + end + values = del(values) + self.commMemoHashToText = hashToText + self.commMemoTextToHash = textToHash + AceComm.prefixMemoizations[self.commPrefix] = hashToText +end + +local DeepReclaim +do + local recurse + local function _DeepReclaim(t) + if recurse[t] then + return + end + recurse[t] = true + for k,v in pairs(t) do + if type(k) == "table" and not AceOO.inherits(k, AceOO.Class) then + _DeepReclaim(k) + end + if type(v) == "table" and not AceOO.inherits(v, AceOO.Class) then + _DeepReclaim(v) + end + end + del(t) + end + function DeepReclaim(t) + recurse = new() + _DeepReclaim(t) + recurse = del(recurse) + end +end + +local lastCheck = GetTime() +local function CheckRefix() + if GetTime() - lastCheck >= 120 then + lastCheck = GetTime() + RefixAceCommChannelsAndEvents() + end +end + +local function HandleMessage(prefix, message, distribution, sender, customChannel) + local isGroup = GetCurrentGroupDistribution() == distribution + local isCustom = distribution == "CUSTOM" + if (not AceComm_registry[distribution] and (not isGroup or not AceComm_registry.GROUP)) or (isCustom and not AceComm_registry.CUSTOM[customChannel]) then + return CheckRefix() + end + local _, id, current, max + if not message then + if distribution == "WHISPER" then + _,_, prefix, id, current, max, message = string_find(prefix, "^/(...)\t(.)(.)(.)\t(.*)$") + else + _,_, prefix, id, current, max, message = string_find(prefix, "^(...)\t(.)(.)(.)\t(.*)$") + end + prefix = AceComm.prefixHashToText[prefix] + if not prefix then + return CheckRefix() + end + if isCustom then + if not AceComm_registry.CUSTOM[customChannel][prefix] then + return CheckRefix() + end + else + 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 + return CheckRefix() + end + end + else + _,_, id, current, max, message = string_find(message, "^(.)(.)(.)\t(.*)$") + end + if not message then + return + end + local smallCustomChannel = customChannel and string_sub(customChannel, 8) + current = string_byte(current) + max = string_byte(max) + if max > 1 then + local queue = AceComm.recvQueue + local x + if distribution == "CUSTOM" then + x = prefix .. ":" .. sender .. distribution .. customChannel .. id + else + x = prefix .. ":" .. sender .. distribution .. id + end + if not queue[x] then + if current ~= 1 then + return + end + queue[x] = new() + end + local chunk = queue[x] + chunk.time = GetTime() + chunk[current] = message + if current == max then + table_setn(chunk, max) + message = table_concat(chunk) + queue[x] = del(queue[x]) + else + return + end + end + message = Deserialize(message, AceComm.prefixMemoizations[prefix]) + local isTable = type(message) == "table" + if AceComm_registry[distribution] then + if isTable then + if isCustom then + if AceComm_registry.CUSTOM[customChannel][prefix] then + for k,v in pairs(AceComm_registry.CUSTOM[customChannel][prefix]) do + local type_v = type(v) + if type_v == "string" then + local f = k[v] + if type(f) == "table" then + local a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20 = unpack(message) + local g = f[a1] + if g then + if type(g) == "table" then + local h = g[a2] + if h then + h(k, prefix, sender, distribution, smallCustomChannel, a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20) + end + else -- function + g(k, prefix, sender, distribution, smallCustomChannel, a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20) + end + end + else -- function + f(k, prefix, sender, distribution, smallCustomChannel, unpack(message)) + end + elseif type_v == "table" then + local a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20 = unpack(message) + local g = v[a1] + if g then + if type(g) == "table" then + local h = g[a2] + if h then + h(prefix, sender, distribution, smallCustomChannel, a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20) + end + else -- function + g(prefix, sender, distribution, smallCustomChannel, a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20) + end + end + else -- function + v(prefix, sender, distribution, smallCustomChannel, unpack(message)) + end + end + end + else + if AceComm_registry[distribution][prefix] then + for k,v in pairs(AceComm_registry[distribution][prefix]) do + local type_v = type(v) + if type_v == "string" then + local f = k[v] + if type(f) == "table" then + local a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20 = unpack(message) + local g = f[a1] + if g then + if type(g) == "table" then + local h = g[a2] + if h then + h(k, prefix, sender, distribution, a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20) + end + else -- function + g(k, prefix, sender, distribution, a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20) + end + end + else -- function + f(k, prefix, sender, distribution, unpack(message)) + end + elseif type_v == "table" then + local a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20 = unpack(message) + local g = v[a1] + if g then + if type(g) == "table" then + local h = g[a2] + if h then + h(prefix, sender, distribution, a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20) + end + else -- function + g(prefix, sender, distribution, a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20) + end + end + else -- function + v(prefix, sender, distribution, unpack(message)) + end + end + end + end + else + if isCustom then + if AceComm_registry.CUSTOM[customChannel][prefix] then + for k,v in pairs(AceComm_registry.CUSTOM[customChannel][prefix]) do + local type_v = type(v) + if type_v == "string" then + local f = k[v] + if type(f) == "table" then + local g = f[message] + if g and type(g) == "function" then + g(k, prefix, sender, distribution, smallCustomChannel) + end + else -- function + f(k, prefix, sender, distribution, smallCustomChannel, message) + end + elseif type_v == "table" then + local g = v[message] + if g and type(g) == "function" then + g(k, prefix, sender, distribution, smallCustomChannel) + end + else -- function + v(prefix, sender, distribution, smallCustomChannel, message) + end + end + end + else + if AceComm_registry[distribution][prefix] then + for k,v in pairs(AceComm_registry[distribution][prefix]) do + local type_v = type(v) + if type_v == "string" then + local f = k[v] + if type(f) == "table" then + local g = f[message] + if g and type(g) == "function" then + g(k, prefix, sender, distribution) + end + else -- function + f(k, prefix, sender, distribution, message) + end + elseif type_v == "table" then + local g = v[message] + if g and type(g) == "function" then + g(k, prefix, sender, distribution) + end + else -- function + v(prefix, sender, distribution, message) + end + end + end + end + end + end + if isGroup and AceComm_registry.GROUP and AceComm_registry.GROUP[prefix] then + if isTable then + for k,v in pairs(AceComm_registry.GROUP[prefix]) do + local type_v = type(v) + if type_v == "string" then + local f = k[v] + if type(f) == "table" then + local a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20 = unpack(message) + local g = f[a1] + if g then + if type(g) == "table" then + local h = g[a2] + if h then + h(k, prefix, sender, "GROUP", a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20) + end + else -- function + g(k, prefix, sender, "GROUP", a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20) + end + end + else -- function + f(k, prefix, sender, "GROUP", unpack(message)) + end + elseif type_v == "table" then + local a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20 = unpack(message) + local g = v[a1] + if g then + if type(g) == "table" then + local h = g[a2] + if h then + h(prefix, sender, "GROUP", a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20) + end + else -- function + g(prefix, sender, "GROUP", a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20) + end + end + else -- function + v(prefix, sender, "GROUP", unpack(message)) + end + end + else + for k,v in pairs(AceComm_registry.GROUP[prefix]) do + local type_v = type(v) + if type_v == "string" then + local f = k[v] + if type(f) == "table" then + local g = f[message] + if g and type(g) == "function" then + g(k, prefix, sender, "GROUP") + end + else -- function + f(k, prefix, sender, "GROUP", message) + end + elseif type_v == "table" then + local g = v[message] + if g and type(g) == "function" then + g(k, prefix, sender, "GROUP") + end + else -- function + v(prefix, sender, "GROUP", message) + end + end + end + end + if isTable then + DeepReclaim(message) + end +end + +function AceComm:CHAT_MSG_ADDON(prefix, message, distribution, sender) + if sender == player then + return + end + prefix = self.prefixHashToText[prefix] + if not prefix then + return CheckRefix() + end + local isGroup = GetCurrentGroupDistribution() == distribution + if not AceComm_registry[distribution] and (not isGroup or not AceComm_registry.GROUP) then + return CheckRefix() + end + prefix = Decode(prefix) + 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 + return CheckRefix() + end + message = Decode(message) + return HandleMessage(prefix, message, distribution, sender) +end + +function AceComm:CHAT_MSG_WHISPER(text, sender) + if not string_find(text, "^/") then + return + end + text = Decode(text, true) + return HandleMessage(text, nil, "WHISPER", sender) +end + +function AceComm:CHAT_MSG_CHANNEL(text, sender, _, _, _, _, _, _, channel) + if sender == player or not string_find(channel, "^AceComm") then + return + end + text = Decode(text, true) + local distribution + local customChannel + if channel == "AceComm" then + distribution = "GLOBAL" + elseif channel == GetCurrentZoneChannel() then + distribution = "ZONE" + else + distribution = "CUSTOM" + customChannel = channel + end + return HandleMessage(text, nil, distribution, sender, customChannel) +end + +function AceComm:IsUserInChannel(userName, distribution, customChannel) + AceComm:argCheck(userName, 2, "string", "nil") + if not userName then + userName = player + end + AceComm:argCheck(distribution, 3, "string") + local channel + if distribution == "GLOBAL" then + channel = "AceComm" + elseif distribution == "ZONE" then + channel = GetCurrentZoneChannel() + elseif distribution == "CUSTOM" then + AceComm:argCheck(customChannel, 4, "string") + channel = "AceComm" .. customChannel + else + AceComm:error('Argument #3 to `IsUserInChannel\' must be "GLOBAL", "CUSTOM", or "ZONE"') + end + + return AceComm.userRegistry[channel] and AceComm.userRegistry[channel][userName] or false +end + +function AceComm:CHAT_MSG_CHANNEL_LIST(text, _, _, _, _, _, _, _, channel) + if not string_find(channel, "^AceComm") then + return + end + + if not AceComm.userRegistry[channel] then + AceComm.userRegistry[channel] = new() + end + local t = AceComm.userRegistry[channel] + for k in string_gfind(text, "[^, @%*#]+") do + t[k] = true + end +end + +function AceComm:CHAT_MSG_CHANNEL_JOIN(_, user, _, _, _, _, _, _, channel) + if not string_find(channel, "^AceComm") then + return + end + + if not AceComm.userRegistry[channel] then + AceComm.userRegistry[channel] = {} + end + local t = AceComm.userRegistry[channel] + if not t[user] then + t[user] = true + end +end + +function AceComm:CHAT_MSG_CHANNEL_LEAVE(_, user, _, _, _, _, _, _, channel) + if not string_find(channel, "^AceComm") then + return + end + + if not AceComm.userRegistry[channel] then + AceComm.userRegistry[channel] = {} + end + local t = AceComm.userRegistry[channel] + if t[user] then + t[user] = nil + end +end + +function AceComm:AceEvent_FullyInitialized() + RefixAceCommChannelsAndEvents() +end + +function AceComm:PLAYER_LOGOUT() + LeaveAceCommChannels(true) +end + +function AceComm:ZONE_CHANGED_NEW_AREA() + local lastZone = zoneCache + zoneCache = nil + local newZone = GetCurrentZoneChannel() + if self.registry.ZONE and next(self.registry.ZONE) then + if lastZone then + SwitchChannel(lastZone, newZone) + else + JoinChannel(newZone) + end + end +end + +function AceComm:embed(target) + self.super.embed(self, target) + if not AceEvent then + AceComm:error(MAJOR_VERSION .. " requires AceEvent-2.0") + end +end + +function AceComm.hooks:ChatFrame_OnEvent(orig, event) + if event == "CHAT_MSG_WHISPER" or event == "CHAT_MSG_WHISPER_INFORM" then + if string_find(arg1, "^/") then + return + end + elseif event == "CHAT_MSG_AFK" or event == "CHAT_MSG_DND" then + local t = self.recentWhispers[string.lower(arg2)] + if t and GetTime() - t <= 15 then + return + end + elseif event == "CHAT_MSG_CHANNEL" or event == "CHAT_MSG_CHANNEL_LIST" then + if string_find(arg9, "^AceComm") then + return + end + end + return orig(event) +end + +local id, loggingOut +function AceComm.hooks:Logout(orig) + if IsResting() then + LeaveAceCommChannels(true) + else + id = self:ScheduleEvent(LeaveAceCommChannels, 15, true) + end + loggingOut = true + return orig() +end + +function AceComm.hooks:CancelLogout(orig) + shutdown = false + if id then + self:CancelScheduledEvent(id) + id = nil + end + RefixAceCommChannelsAndEvents() + loggingOut = false + return orig() +end + +function AceComm.hooks:Quit(orig) + if IsResting() then + LeaveAceCommChannels(true) + else + id = self:ScheduleEvent(LeaveAceCommChannels, 15, true) + end + loggingOut = true + return orig() +end + +function AceComm.hooks:FCFDropDown_LoadChannels(orig, a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20) + local arg = {a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20} + for i = 1, table.getn(arg), 2 do + if not arg[i] then + break + end + if type(arg[i + 1]) == "string" and string_find(arg[i + 1], "^AceComm") then + table.remove(arg, i + 1) + table.remove(arg, i) + i = i - 2 + end + end + return orig(unpack(arg)) +end + +function AceComm:CHAT_MSG_SYSTEM(text) + if text ~= ERR_TOO_MANY_CHAT_CHANNELS then + return + end + + local chan = lastChannelJoined + if not chan then + return + end + if not string_find(lastChannelJoined, "^AceComm") then + return + end + + local text + if chan == "AceComm" then + local addon = self.registry.GLOBAL and next(AceComm_registry.GLOBAL) + if not addon then + return + end + addon = tostring(addon) + 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) + elseif chan == GetCurrentZoneChannel() then + local addon = AceComm_registry.ZONE and next(AceComm_registry.ZONE) + if not addon then + return + end + addon = tostring(addon) + 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) + else + local addon = AceComm_registry.CUSTOM and AceComm_registry.CUSTOM[chan] and next(AceComm_registry.CUSTOM[chan]) + if not addon then + return + end + addon = tostring(addon) + 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) + end + + StaticPopupDialogs["ACECOMM_TOO_MANY_CHANNELS"] = { + text = text, + button1 = CLOSE, + timeout = 0, + whileDead = 1, + hideOnEscape = 1, + } + StaticPopup_Show("ACECOMM_TOO_MANY_CHANNELS") +end + +local function activate(self, oldLib, oldDeactivate) + AceComm = self + self:activate(oldLib, oldDeactivate) + + if oldLib then + self.recvQueue = oldLib.recvQueue + self.registry = oldLib.registry + self.channels = oldLib.channels + self.prefixes = oldLib.prefixes + self.classes = oldLib.classes + self.prefixMemoizations = oldLib.prefixMemoizations + self.prefixHashToText = oldLib.prefixHashToText + self.prefixTextToHash = oldLib.prefixTextToHash + self.recentWhispers = oldLib.recentWhispers + self.userRegistry = oldLib.userRegistry + else + local old_ChatFrame_OnEvent = ChatFrame_OnEvent + function ChatFrame_OnEvent(event) + if self.hooks.ChatFrame_OnEvent then + return self.hooks.ChatFrame_OnEvent(self, old_ChatFrame_OnEvent, event) + else + return old_ChatFrame_OnEvent(event) + end + end + local id + local loggingOut = false + local old_Logout = Logout + function Logout() + if self.hooks.Logout then + return self.hooks.Logout(self, old_Logout) + else + return old_Logout() + end + end + local old_CancelLogout = CancelLogout + function CancelLogout() + if self.hooks.CancelLogout then + return self.hooks.CancelLogout(self, old_CancelLogout) + else + return old_CancelLogout() + end + end + local old_Quit = Quit + function Quit() + if self.hooks.Quit then + return self.hooks.Quit(self, old_Quit) + else + return old_Quit() + end + end + local old_FCFDropDown_LoadChannels = FCFDropDown_LoadChannels + function FCFDropDown_LoadChannels(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20) + local arg = {a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20} + if self.hooks.FCFDropDown_LoadChannels then + return self.hooks.FCFDropDown_LoadChannels(self, old_FCFDropDown_LoadChannels, unpack(arg)) + else + return old_FCFDropDown_LoadChannels(unpack(arg)) + end + end + local old_JoinChannelByName = JoinChannelByName + function JoinChannelByName(a,b,c,d,e,f,g,h,i,j) + if self.hooks.JoinChannelByName then + return self.hooks.JoinChannelByName(self, old_JoinChannelByName, a,b,c,d,e,f,g,h,i,j) + else + return old_JoinChannelByName(a,b,c,d,e,f,g,h,i,j) + end + end + end + + if not self.recvQueue then + self.recvQueue = {} + end + if not self.registry then + self.registry = {} + end + AceComm_registry = self.registry + if not self.prefixes then + self.prefixes = {} + end + if not self.classes then + self.classes = {} + else + for k in pairs(self.classes) do + self.classes[k] = nil + end + end + if not self.prefixMemoizations then + self.prefixMemoizations = {} + end + if not self.prefixHashToText then + self.prefixHashToText = {} + end + if not self.prefixTextToHash then + self.prefixTextToHash = {} + end + if not self.recentWhispers then + self.recentWhispers = {} + end + if not self.userRegistry then + self.userRegistry = {} + end + + if oldDeactivate then + oldDeactivate(oldLib) + end +end + +local function external(self, major, instance) + if major == "AceEvent-2.0" then + AceEvent = instance + + AceEvent:embed(AceComm) + + self:UnregisterAllEvents() + self:CancelAllScheduledEvents() + + if AceEvent:IsFullyInitialized() then + self:AceEvent_FullyInitialized() + else + self:RegisterEvent("AceEvent_FullyInitialized", "AceEvent_FullyInitialized", true) + end + + self:RegisterEvent("PLAYER_LOGOUT") + self:RegisterEvent("ZONE_CHANGED_NEW_AREA") + self:RegisterEvent("CHAT_MSG_CHANNEL_NOTICE") + self:RegisterEvent("CHAT_MSG_SYSTEM") + else + if AceOO.inherits(instance, AceOO.Class) and not instance.class then + self.classes[TailoredNumericCheckSum(major)] = instance + end + end +end + +AceLibrary:Register(AceComm, MAJOR_VERSION, MINOR_VERSION, activate, nil, external) + + + + + +-- +-- ChatThrottleLib by Mikk +-- +-- Manages AddOn chat output to keep player from getting kicked off. +-- +-- ChatThrottleLib.SendChatMessage/.SendAddonMessage functions that accept +-- a Priority ("BULK", "NORMAL", "ALERT") as well as prefix for SendChatMessage. +-- +-- Priorities get an equal share of available bandwidth when fully loaded. +-- Communication channels are separated on extension+chattype+destination and +-- get round-robinned. (Destination only matters for whispers and channels, +-- obviously) +-- +-- Will install hooks for SendChatMessage and SendAdd[Oo]nMessage to measure +-- bandwidth bypassing the library and use less bandwidth itself. +-- +-- +-- Fully embeddable library. Just copy this file into your addon directory, +-- add it to the .toc, and it's done. +-- +-- Can run as a standalone addon also, but, really, just embed it! :-) +-- + +local CTL_VERSION = 13 + +local MAX_CPS = 800 -- 2000 seems to be safe if NOTHING ELSE is happening. let's call it 800. +local MSG_OVERHEAD = 40 -- Guesstimate overhead for sending a message; source+dest+chattype+protocolstuff + +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. + +local MIN_FPS = 20 -- Reduce output CPS to half (and don't burst) if FPS drops below this value + +if(ChatThrottleLib and ChatThrottleLib.version>=CTL_VERSION) then + -- There's already a newer (or same) version loaded. Buh-bye. + return; +end + + + +if(not ChatThrottleLib) then + ChatThrottleLib = {} +end + +local ChatThrottleLib = ChatThrottleLib +local strlen = strlen +local setmetatable = setmetatable +local getn = getn +local tremove = tremove +local tinsert = tinsert +local tostring = tostring +local GetTime = GetTime +local format = format + +ChatThrottleLib.version=CTL_VERSION; + + +----------------------------------------------------------------------- +-- Double-linked ring implementation + +local Ring = {} +local RingMeta = { __index=Ring } + +function Ring:New() + local ret = {} + setmetatable(ret, RingMeta) + return ret; +end + +function Ring:Add(obj) -- Append at the "far end" of the ring (aka just before the current position) + if(self.pos) then + obj.prev = self.pos.prev; + obj.prev.next = obj; + obj.next = self.pos; + obj.next.prev = obj; + else + obj.next = obj; + obj.prev = obj; + self.pos = obj; + end +end + +function Ring:Remove(obj) + obj.next.prev = obj.prev; + obj.prev.next = obj.next; + if(self.pos == obj) then + self.pos = obj.next; + if(self.pos == obj) then + self.pos = nil; + end + end +end + + + +----------------------------------------------------------------------- +-- Recycling bin for pipes (kept in a linked list because that's +-- how they're worked with in the rotating rings; just reusing members) + +ChatThrottleLib.PipeBin = { count=0 } + +function ChatThrottleLib.PipeBin:Put(pipe) + for i=getn(pipe),1,-1 do + tremove(pipe, i); + end + pipe.prev = nil; + pipe.next = self.list; + self.list = pipe; + self.count = self.count+1; +end + +function ChatThrottleLib.PipeBin:Get() + if(self.list) then + local ret = self.list; + self.list = ret.next; + ret.next=nil; + self.count = self.count - 1; + return ret; + end + return {}; +end + +function ChatThrottleLib.PipeBin:Tidy() + if(self.count < 25) then + return; + end + + if(self.count > 100) then + n=self.count-90; + else + n=10; + end + for i=2,n do + self.list = self.list.next; + end + local delme = self.list; + self.list = self.list.next; + delme.next = nil; +end + + + + +----------------------------------------------------------------------- +-- Recycling bin for messages + +ChatThrottleLib.MsgBin = {} + +function ChatThrottleLib.MsgBin:Put(msg) + msg.text = nil; + tinsert(self, msg); +end + +function ChatThrottleLib.MsgBin:Get() + local ret = tremove(self, getn(self)); + if(ret) then return ret; end + return {}; +end + +function ChatThrottleLib.MsgBin:Tidy() + if(getn(self)<50) then + return; + end + if(getn(self)>150) then -- "can't happen" but ... + for n=getn(self),120,-1 do + tremove(self,n); + end + else + for n=getn(self),getn(self)-20,-1 do + tremove(self,n); + end + end +end + + +----------------------------------------------------------------------- +-- ChatThrottleLib:Init +-- Initialize queues, set up frame for OnUpdate, etc + + +function ChatThrottleLib:Init() + + -- Set up queues + if(not self.Prio) then + self.Prio = {} + self.Prio["ALERT"] = { ByName={}, Ring = Ring:New(), avail=0 }; + self.Prio["NORMAL"] = { ByName={}, Ring = Ring:New(), avail=0 }; + self.Prio["BULK"] = { ByName={}, Ring = Ring:New(), avail=0 }; + end + + -- v4: total send counters per priority + for _,Prio in pairs(self.Prio) do + Prio.nTotalSent = Prio.nTotalSent or 0; + end + + self.avail = self.avail or 0; -- v5 + self.nTotalSent = self.nTotalSent or 0; -- v5 + + + -- Set up a frame to get OnUpdate events + if(not self.Frame) then + self.Frame = CreateFrame("Frame"); + self.Frame:Hide(); + end + self.Frame.Show = self.Frame.Show; -- cache for speed + self.Frame.Hide = self.Frame.Hide; -- cache for speed + self.Frame:SetScript("OnUpdate", self.OnUpdate); + self.Frame:SetScript("OnEvent", self.OnEvent); -- v11: Monitor P_E_W so we can throttle hard for a few seconds + self.Frame:RegisterEvent("PLAYER_ENTERING_WORLD"); + self.OnUpdateDelay=0; + self.LastAvailUpdate=GetTime(); + self.HardThrottlingBeginTime=GetTime(); -- v11: Throttle hard for a few seconds after startup + + -- Hook SendChatMessage and SendAddonMessage so we can measure unpiped traffic and avoid overloads (v7) + if(not self.ORIG_SendChatMessage) then + --SendChatMessage + self.ORIG_SendChatMessage = SendChatMessage; + SendChatMessage = function(a1,a2,a3,a4) return ChatThrottleLib.Hook_SendChatMessage(a1,a2,a3,a4); end + --SendAdd[Oo]nMessage + if(SendAddonMessage or SendAddOnMessage) then -- v10: don't pretend like it doesn't exist if it doesn't! + self.ORIG_SendAddonMessage = SendAddonMessage or SendAddOnMessage; + SendAddonMessage = function(a1,a2,a3) return ChatThrottleLib.Hook_SendAddonMessage(a1,a2,a3); end + if(SendAddOnMessage) then -- in case Slouken changes his mind... + SendAddOnMessage = SendAddonMessage; + end + end + end + self.nBypass = 0; +end + + +----------------------------------------------------------------------- +-- ChatThrottleLib.Hook_SendChatMessage / .Hook_SendAddonMessage +function ChatThrottleLib.Hook_SendChatMessage(text, chattype, language, destination) + local self = ChatThrottleLib; + local size = strlen(tostring(text or "")) + strlen(tostring(chattype or "")) + strlen(tostring(destination or "")) + 40; + self.avail = self.avail - size; + self.nBypass = self.nBypass + size; + return self.ORIG_SendChatMessage(text, chattype, language, destination); +end +function ChatThrottleLib.Hook_SendAddonMessage(prefix, text, chattype) + local self = ChatThrottleLib; + local size = strlen(tostring(text or "")) + strlen(tostring(chattype or "")) + strlen(tostring(prefix or "")) + 40; + self.avail = self.avail - size; + self.nBypass = self.nBypass + size; + return self.ORIG_SendAddonMessage(prefix, text, chattype); +end + + + +----------------------------------------------------------------------- +-- ChatThrottleLib:UpdateAvail +-- Update self.avail with how much bandwidth is currently available + +function ChatThrottleLib:UpdateAvail() + local now = GetTime(); + local newavail = MAX_CPS * (now-self.LastAvailUpdate); + + if(now - self.HardThrottlingBeginTime < 5) then + -- First 5 seconds after startup/zoning: VERY hard clamping to avoid irritating the server rate limiter, it seems very cranky then + self.avail = min(self.avail + (newavail*0.1), MAX_CPS*0.5); + elseif(GetFramerate()ring.pos[1].nSize) do + local msg = tremove(Prio.Ring.pos, 1); + if(not Prio.Ring.pos[1]) then + local pipe = Prio.Ring.pos; + Prio.Ring:Remove(pipe); + Prio.ByName[pipe.name] = nil; + self.PipeBin:Put(pipe); + else + Prio.Ring.pos = Prio.Ring.pos.next; + end + Prio.avail = Prio.avail - msg.nSize; + msg.f(msg[1], msg[2], msg[3], msg[4]); + Prio.nTotalSent = Prio.nTotalSent + msg.nSize; + self.MsgBin:Put(msg); + end +end + + +function ChatThrottleLib.OnEvent() + -- v11: We know that the rate limiter is touchy after login. Assume that it's touch after zoning, too. + self = ChatThrottleLib; + if(event == "PLAYER_ENTERING_WORLD") then + self.HardThrottlingBeginTime=GetTime(); -- Throttle hard for a few seconds after zoning + self.avail = 0; + end +end + + +function ChatThrottleLib.OnUpdate() + self = ChatThrottleLib; + + self.OnUpdateDelay = self.OnUpdateDelay + arg1; + if(self.OnUpdateDelay < 0.08) then + return; + end + self.OnUpdateDelay = 0; + + self:UpdateAvail(); + + if(self.avail<0) then + return; -- argh. some bastard is spewing stuff past the lib. just bail early to save cpu. + end + + -- See how many of or priorities have queued messages + local n=0; + for prioname,Prio in pairs(self.Prio) do + if(Prio.Ring.pos or Prio.avail<0) then + n=n+1; + end + end + + -- Anything queued still? + if(n<1) then + -- Nope. Move spillover bandwidth to global availability gauge and clear self.bQueueing + for prioname,Prio in pairs(self.Prio) do + self.avail = self.avail + Prio.avail; + Prio.avail = 0; + end + self.bQueueing = false; + self.Frame:Hide(); + return; + end + + -- There's stuff queued. Hand out available bandwidth to priorities as needed and despool their queues + local avail= self.avail/n; + self.avail = 0; + + for prioname,Prio in pairs(self.Prio) do + if(Prio.Ring.pos or Prio.avail<0) then + Prio.avail = Prio.avail + avail; + if(Prio.Ring.pos and Prio.avail>Prio.Ring.pos[1].nSize) then + self:Despool(Prio); + end + end + end + + -- Expire recycled tables if needed + self.MsgBin:Tidy(); + self.PipeBin:Tidy(); +end + + + + +----------------------------------------------------------------------- +-- Spooling logic + + +function ChatThrottleLib:Enqueue(prioname, pipename, msg) + local Prio = self.Prio[prioname]; + local pipe = Prio.ByName[pipename]; + if(not pipe) then + self.Frame:Show(); + pipe = self.PipeBin:Get(); + pipe.name = pipename; + Prio.ByName[pipename] = pipe; + Prio.Ring:Add(pipe); + end + + tinsert(pipe, msg); + + self.bQueueing = true; +end + + + +function ChatThrottleLib:SendChatMessage(prio, prefix, text, chattype, language, destination) + if(not (self and prio and text and self.Prio[prio] ) ) then + error('Usage: ChatThrottleLib:SendChatMessage("{BULK||NORMAL||ALERT}", "prefix" or nil, "text"[, "chattype"[, "language"[, "destination"]]]', 2); + end + + prefix = prefix or tostring(this); -- each frame gets its own queue if prefix is not given + + local nSize = strlen(text) + MSG_OVERHEAD; + + -- Check if there's room in the global available bandwidth gauge to send directly + if(not self.bQueueing and nSize < self:UpdateAvail()) then + self.avail = self.avail - nSize; + self.ORIG_SendChatMessage(text, chattype, language, destination); + self.Prio[prio].nTotalSent = self.Prio[prio].nTotalSent + nSize; + return; + end + + -- Message needs to be queued + msg=self.MsgBin:Get(); + msg.f=self.ORIG_SendChatMessage + msg[1]=text; + msg[2]=chattype or "SAY"; + msg[3]=language; + msg[4]=destination; + msg.n = 4 + msg.nSize = nSize; + + self:Enqueue(prio, format("%s/%s/%s", prefix, chattype, destination or ""), msg); +end + + +function ChatThrottleLib:SendAddonMessage(prio, prefix, text, chattype) + if(not (self and prio and prefix and text and chattype and self.Prio[prio] ) ) then + error('Usage: ChatThrottleLib:SendAddonMessage("{BULK||NORMAL||ALERT}", "prefix", "text", "chattype")', 0); + end + + local nSize = strlen(prefix) + 1 + strlen(text) + MSG_OVERHEAD; + + -- Check if there's room in the global available bandwidth gauge to send directly + if(not self.bQueueing and nSize < self:UpdateAvail()) then + self.avail = self.avail - nSize; + self.ORIG_SendAddonMessage(prefix, text, chattype); + self.Prio[prio].nTotalSent = self.Prio[prio].nTotalSent + nSize; + return; + end + + -- Message needs to be queued + msg=self.MsgBin:Get(); + msg.f=self.ORIG_SendAddonMessage; + msg[1]=prefix; + msg[2]=text; + msg[3]=chattype; + msg.n = 3 + msg.nSize = nSize; + + self:Enqueue(prio, format("%s/%s", prefix, chattype), msg); +end + + + + +----------------------------------------------------------------------- +-- Get the ball rolling! + +ChatThrottleLib:Init(); + +--[[ WoWBench debugging snippet + if(WOWB_VER) then + local function SayTimer() + print("SAY: "..GetTime().." "..arg1); + end + ChatThrottleLib.Frame:SetScript("OnEvent", SayTimer); + ChatThrottleLib.Frame:RegisterEvent("CHAT_MSG_SAY"); + end +]] \ No newline at end of file