--------------------------------------------------------------------------------------- -- NxCom - Communication code -- Copyright 2007-2012 Carbon Based Creations, LLC --------------------------------------------------------------------------------------- --------------------------------------------------------------------------------------- -- Carbonite - Addon for World of Warcraft(tm) -- Copyright 2007-2012 Carbon Based Creations, LLC -- -- This program is free software: you can redistribute it and/or modify -- it under the terms of the GNU General Public License as published by -- the Free Software Foundation, either version 3 of the License, or -- (at your option) any later version. -- -- This program is distributed in the hope that it will be useful, -- but WITHOUT ANY WARRANTY; without even the implied warranty of -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -- GNU General Public License for more details. -- -- You should have received a copy of the GNU General Public License -- along with this program. If not, see <http://www.gnu.org/licenses/>. --------------------------------------------------------------------------------------- -- Warning: "\" in send data can lead to invalid escape codes (ok, since only escaped if in literal string?) -- Bytes 35 (#) + 57 == 92 (\) -- -- Byte 124 == |. Must be |c or creates invalid escape code. Not in addon channel, only chat -- Byte 128 or higher == invalid UTF-8 error. Not in addon channel, only chat ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- -- Com NxCOMOPTS_VERSION = .01 NxComOpts = { Version = 0 } NxComOptsDefaults = { Version = NxCOMOPTS_VERSION, } -------- -- Open and init com function Nx.Com:Init() -- if NxData.NXVerDebug then -- Test!!! -- Nx.Timer:Start ("ComShowVer", 2, self, self.ShowVerTimer) -- end -- if NxComOpts.Version < NxCOMOPTS_VERSION then if NxComOpts.Version ~= 0 then Nx.prt ("Com options reset (%f, %f)", NxComOpts.Version, NxComOptsDefaults.Version) end NxComOpts = NxComOptsDefaults end -- self.Created = false self.Data = {} self.Data.Rcv = {} self.Data.Send = {} self.Name = "Crb" self.ChanALetter = Nx.Free and "Y" or Nx.Ads and "M" or "B" -- Global letter. Z is used by zone channel self.SendRate = 1 self.SendQNames = { ["Chan"] = 1, ["Guild"] = 2, ["Friend"] = 3, ["Zone"] = 4 } local sq = {} self.SendQ = sq sq[1] = {} -- Channel sq[2] = {} -- Guild sq[3] = {} -- Friends sq[4] = {} -- Zone self.SendQMode = 1 self.PalsInfo = {} -- Friends and guildy info (position) self.PalsSendQ = {} self.PalNames = {} self.MemberNames = {} -- Names in party or raid self.Friends = {} self.Punks = {} self.ZPInfo = {} -- Zone player info (position) self.ZStatus = {} -- Zones status. Indexed with map id self.ZMonitor = {} -- Zones to monitor self.VerPlayers = {} -- Version messages from players (for debug) self.SendChanQ = {} self.PosSendNext = -2 self.SendZSkip = 1 self.TypeColors = { "|cff80ff80", "|cffff4040", "|cffffff40", "|cffffffe0", "|cffc0c0ff" } self.ClassNames = { [0] = "?", "Druid", "Hunter", "Mage", "Paladin", "Priest", "Rogue", "Shaman", "Warlock", "Warrior", "Deathknight", "Monk" } for k, v in ipairs (self.ClassNames) do self.ClassNames[v] = k self.ClassNames[strupper (v)] = k -- All caps version end self.Created = true -- Used??? self.List.Opened = false self.List.Sorted = {} -- self.List:Open() --[[ for n = 1, 25 do Nx.Timer:Start ("ComTest"..n, 1 + n * .1, self, self.OnTestTimer) end --]] self.SentBytes = 0 -- Debugging self.SentBytesSec = 0 self.SentBytesTime = GetTime() Nx.Timer:Start ("ComBytesSec", 1, self, self.OnBytesSecTimer) -- Nx.Timer:Start ("ComVerTest", 3, self, function() self:ShowVersionMsg() end) hooksecurefunc ("SendChatMessage", self.SendChatHook) end -------- function Nx.Com:Test (a1, a2) self:SendSecG ("? }a", "") -- Ask for it --[[ local start = tonumber (a1) or 1 local num = tonumber (a2) or 8 local s = "" for n = start, start+19 do Nx.prt ("%d", n) s = format ("%d |d%c", n, n) self:SendChatMessageFixed (s, "CHANNEL", num) self:SendChatMessageFixed (s, "PARTY") -- SendAddonMessage (self.Name, format ("%d |%c \%c", n, n, n), "PARTY") end --]] end -------- -- function Nx.Com:OnTestTimer (name) self:SendPals ("!"..name) if random() < .5 then -- local i = random (1, 80) -- self:OnPlayer_level_up (i) end return .1 + random() * 5 --15 end -------- -- function Nx.Com:OnBytesSecTimer (name) local tm = GetTime() self.SentBytesSec = self.SentBytes / (tm - self.SentBytesTime) self.SentBytes = 0 self.SentBytesTime = tm return 1 end -------- -- On com event function Nx.Com:OnEvent (event) local self = Nx.Com -- Nx.prt ("Com Event: %s", event) if event == "PLAYER_LOGIN" then RegisterAddonMessagePrefix (self.Name) -- 4.1 must register for guild messages self.PlyrName = UnitName ("player") self.PlyrMapId = Nx.Map:GetRealMapId() self.PlyrX = 0 self.PlyrY = 0 local _, tCls = UnitClass ("player") -- Non localized uppercase version self.PlyrClassI = self.ClassNames[tCls] or 0 self.List:AddInfo ("", "PLAYER_LOGIN") self.SendTime = GetTime() self.SendPosTime = GetTime() self.SendChanTime = GetTime() self:LeaveChan ("A") self:LeaveChan ("Z") Nx.Timer:Start ("ComLogin", 3 + random() * 1, self, self.OnLoginTimer) -- Nx.prt ("Com PLAYER_LOGIN") if IsInGuild() then GuildRoster() end ShowFriends() elseif event == "ZONE_CHANGED_NEW_AREA" then self.List:AddInfo ("", "ZONE_CHANGED_NEW_AREA") if not Nx.Timer:IsActive ("ComLogin") then self:UpdateChannels() end elseif event == "PLAYER_LEAVINGWORLD" then self:LeaveChan ("A") self:LeaveChan ("Z") end self.List:Update() end function Nx.Com:OnLoginTimer() if UnitOnTaxi ("player") then -- Detect login on taxi, which will not join channels until you land local id = GetChannelName (1) -- Detect if reload if id ~= 1 then self.WasOnTaxi = true return .5 end end if self.WasOnTaxi then self.WasOnTaxi = nil return 3 end local opts = Nx:GetGlobalOpts() if IsControlKeyDown() and IsAltKeyDown() then Nx.prt ("Disabling com functions!") opts["ComNoGlobal"] = true opts["ComNoZone"] = true end local need = 2 if opts["ComNoGlobal"] then need = 1 end if opts["ComNoZone"] then need = need - 1 end local free = max (10 - self:GetChanCount(), 0) if need > free then Nx.prt ("|cffff9f5fNeed %d chat channel(s)!", need - free) Nx.prt ("|cffff9f5fThis will disable some communication features") Nx.prt ("|cffff9f5fYou may free channels using the chat tab") end -- Should not find any since we left all zone channels a few seconds ago self:ScanChans() self:UpdateChannels() self:JoinChan ("A") -- Addon -- self:JoinChan ("Z") -- Zone -- self:SendA (format ("TEST")) end function Nx.Com:OnVersionTimer() -- Nx.prt ("Com Test") -- self:SendSecG ("? 0123 ABCD abcd [\]^_'", "") self:SendSecG ("V ", self:MakeVersionMsg()) if IsInGuild() then GuildRoster() -- Force update end -- if Nx.Free then -- Nx.Timer:Start ("ComLeaveA", 60 * 60, self, self.OnLeaveATimer) -- end self:LeaveChans ("A") -- Old end function Nx.Com:MakeVersionMsg() -- local r, c = Nx.Sec:GetRCMsg() local r = "" local dt = date ("%y%m%d", time()) local qCnt = Nx.Quest:CaptureGetCount() local lvl = UnitLevel ("player") return format ("%f^%s^^%s^%f^%d^%x^%x", Nx.VERSION, r, dt, NxData.NXVer1, qCnt, lvl, self.PlyrMapId) end function Nx.Com:OnLeaveATimer() -- Nx.prt ("Com OnLeaveATimer") self:LeaveChan ("A") end -------- -- We leveled function Nx.Com:OnPlayer_level_up (event, arg1) if arg1 >= 1 then -- Level # self:SendPals (format ("L%s", strchar (35 + arg1))) end end -------- -- Friends list changed. Build list of connected non guild friends function Nx.Com:OnFriendguild_update() local self = Nx.Com -- Nx.prt ("OnFriendguild_update") local gNames = {} local gNum = GetNumGuildMembers() for n = 1, gNum do local name, _, _, _, _, _, _, _, online = GetGuildRosterInfo (n) if online then gNames [name] = true end end self.Friends = {} local i = 1 for n = 1, GetNumFriends() do local name, lvl, class, area, con, status = GetFriendInfo (n) if con then if not gNames[name] then -- Nx.prt ("Add friend %s", name) self.Friends[i] = name i = i + 1 end end end for k, v in ipairs (self.Friends) do gNames[v] = false end self.PalNames = gNames end -------- -- On com event function Nx.Com:OnChatEvent (event, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) local self = Nx.Com -- Nx.prt ("ComChatEvent: %s %s", event, arg9) if strsub (arg9, 1, 3) == self.Name then if event == "CHAT_MSG_CHANNEL_JOIN" then self.List:AddInfo ("CJ:"..arg9, format ("%s", arg2)) elseif event == "CHAT_MSG_CHANNEL_NOTICE" then self.List:AddInfo ("CN:"..arg9, format ("%s", arg1)) local nameRoot = strsplit ("I", arg9) -- Drop I and # if arg1 == "YOU_JOINED" then local typ = strupper (strsub (arg9, 4, 4)) if typ == self.ChanALetter then self.ChanAName = arg9 -- Nx.prt ("Join %s", arg9) Nx.Timer:Stop ("ComA") Nx.Timer:Start ("ComVerSend", 3, self, self.OnVersionTimer) elseif typ == "Z" then local mapId = tonumber (strsub (nameRoot, 5)) if mapId then local zs = self.ZStatus[mapId] or {} zs.ChanName = arg9 self.ZStatus[mapId] = zs Nx.Timer:Stop ("ComZ" .. mapId) self:UpdateChannels() end -- Nx.prt ("Join %s", arg9) end elseif arg1 == "YOU_LEFT" then local typ = strupper (strsub (arg9, 4, 4)) if typ == "Z" then local mapId = tonumber (strsub (nameRoot, 5)) if mapId then local zs = self.ZStatus[mapId] or {} zs.ChanName = nil self.ZStatus[mapId] = zs end end end elseif event == "CHAT_MSG_CHANNEL_LEAVE" then self.List:AddInfo ("CL:"..arg9, format ("%s", arg2)) end self.List:Update() end end -------- -- On channel message event function Nx.Com:OnChat_msg_channel (event, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) local self = Nx.Com if strsub (arg9, 1, 3) == self.Name then local name = arg2 if name ~= self.PlyrName then local msg = self:RestoreChars (arg1) -- self.List:AddInfo ("C:"..arg9, format ("(%s) %s", name, msg)) -- self.List:Update() local id = strbyte (msg) if id == 83 then -- S (status) Check 1st for performance if not self.PalsInfo[name] then -- Not a pal we have? if #msg >= 16 then local pl = self.ZPInfo[name] if not pl then pl = {} self.ZPInfo[name] = pl end self:ParsePlyrStatus (name, pl, msg) end end elseif id == 86 then -- V (Version and registered name) self:OnMsgVersion (name, msg, arg2, arg9) end end end end -------- -- On addon message event function Nx.Com:OnChat_msg_addon (event, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) local self = Nx.Com -- Nx.prt ("ComChatAddonEvent: %s %s %s", event, arg1, arg4) if strsub (arg1, 1, 3) == self.Name then local name = arg4 -- if 1 then if name ~= self.PlyrName then -- Ignore myself -- self.List:AddInfo ("A:"..arg1, format ("(%s %s) %s", name, arg3, arg2)) local data = { strsplit ("\t", arg2) } for k, msg in ipairs (data) do local id = strbyte (msg) if id == 83 then -- S (status) Check 1st for performance if self.PalNames[name] ~= nil then if #msg >= 16 then local pal = self.PalsInfo[name] if not pal then pal = {} self.PalsInfo[name] = pal end self:ParsePlyrStatus (name, pal, msg) end end elseif id == 76 then -- L (Level) local opts = Nx:GetGlobalOpts() if opts["InfoLvlUpShow"] then local s = format ("%s reached level %d!", name, strbyte (msg, 2) - 35) Nx.prt (s) Nx.UEvents:AddInfo (s) end elseif id == 81 then -- Q (Quest) Nx.Quest:OnMsgQuest (name, msg) elseif id == 86 then -- V (Version and registered name) self:OnMsgVersion (name, msg, arg2, arg9) end end end -- self.List:Update() elseif arg1 == "LGP" then -- Cartographer LibGuildPositions -- Nx.prt ("ComChatAddonEvent: %s %s %s", event, arg1, arg4) -- Nx.prt (" %s", arg2) -- Nx.prtStrHex ("Pos", arg2) local name = arg4 if name ~= self.PlyrName then if self.PalNames[name] ~= nil then self:ParseLGP (name, arg2) end end end end -------- -- Parse a player status message function Nx.Com:ParsePlyrStatus (name, info, msg) -- Flags -- 1 combat -- 2 target data -- 4 quest data -- 8 punks data local flags = strbyte (msg, 2) - 35 info.F = flags info.Quest = nil -- Player info local mapId = tonumber (strsub (msg, 3, 6), 16) -- Map id local winfo = Nx.Map.MapWorldInfo[mapId] if not winfo then info.T = 0 -- Cause it to die -- Nx.prt ("Die %s", mapId) return end info.T = GetTime() info.MId = mapId info.EntryMId = mapId if winfo.EntryMId then info.EntryMId = winfo.EntryMId end info.X = tonumber (strsub (msg, 7, 9), 16) / 0xfff * 100 info.Y = (tonumber (strsub (msg, 10, 13), 16) or 0) / 0xfff * 100 -- Includes dungeon level offset (dzzz) info.Health = (strbyte (msg, 14) - 48) / 20 * 100 info.Lvl = strbyte (msg, 15) - 35 info.Cls = self.ClassNames[strbyte (msg, 16) - 35] or "?" -- Nx.prt ("%s %d %d", name, info.X, info.Y) -- if not self.ClassNames[strbyte (msg, 16) - 35] then -- Nx.prt ("com cls? %s", strbyte (msg, 16) - 35) -- end info.Tip = format ("%s %s%%\n %s %s", name, info.Health, info.Lvl, info.Cls) -- Nx.prtVar ("Cominfo", info) local off = 17 -- Target data if bit.band (flags, 2) > 0 then -- Type, level, class, health, name len, name info.TType = strbyte (msg, 17) - 35 local col = self.TypeColors[info.TType] or "" -- User had a nil info.TLvl = strbyte (msg, 18) - 35 info.TCls = self.ClassNames[strbyte (msg, 19) - 35] or "?" -- if not self.ClassNames[strbyte (msg, 19) - 35] then -- Nx.prt ("com tcls? %s", strbyte (msg, 19) - 35) -- end info.TH = (strbyte (msg, 20) - 35) / 20 * 100 local len = strbyte (msg, 21) - 35 info.TName = strsub (msg, 22, 22 + len - 1) local lvl = info.TLvl if lvl < 0 then lvl = "??" end info.TStr = format ("\n%s%s %s %s %d%%", col, info.TName, lvl, info.TCls, info.TH) off = 22 + len else info.TType = nil info.TStr = nil end -- Quest tracking data if bit.band (flags, 4) > 0 then local len = Nx.Quest:DecodeComRcv (info, strsub (msg, off)) if not len then -- Error? return end off = off + len else info.QStr = nil end -- Punks data if bit.band (flags, 8) > 0 then -- First byte is string length. Skip Nx.Social:DecodeComRcvPunks (name, info, strsub (msg, off + 1)) end end -------- -- Parse Cartographer LibGuildPositions function Nx.Com:ParseLGP (name, msg) if strbyte (msg) == 0x50 then -- P (position) local x, x2, y, y2, len = strbyte (msg, 2, 6) if len and len > 1 then x = ((x - 1) * 255 + x2 - 1) / (255 ^ 2) * 100 y = ((y - 1) * 255 + y2 - 1) / (255 ^ 2) * 100 local zoneName = strsub (msg, 7, 5 + len) -- Len is +1 real length -- Nx.prt ("%s %s %s", zoneName, x, y) local mapId = Nx.MapOverlayToMapId[strlower (zoneName)] -- Nx.prt ("mapId %s", mapId or "nil") if mapId then local info = self.PalsInfo[name] if not info then info = {} self.PalsInfo[name] = info end info.T = GetTime() info.MId = mapId info.EntryMId = mapId info.X = x info.Y = y info.F = 0 info.Tip = name end end end end -------- -- Update channels function Nx.Com:UpdateChannels() Nx.Timer:Start ("ComUC", 0, self, self.UpdateChannelsTimer) end function Nx.Com:UpdateChannelsTimer() if Nx.Timer:IsActive ("ComLogin") then return 0 end local opts = Nx:GetGlobalOpts() local curMapId = Nx.Map:GetRealMapId() if UnitIsAFK ("player") or opts["ComNoZone"] then -- No current zone channel? curMapId = nil else if Nx.Map:IsNormalMap (curMapId) then local zs = self.ZStatus[curMapId] or {} zs.Join = true self.ZStatus[curMapId] = zs end end -- Monitor for mapId, mode in pairs (self.ZMonitor) do if mode == 0 then self.ZMonitor[mapId] = 1 local zs = self.ZStatus[mapId] or {} zs.Join = true self.ZStatus[mapId] = zs elseif mode == -1 then self.ZMonitor[mapId] = nil end end -- Update status (join or leave channels) for mapId, status in pairs (self.ZStatus) do if status.ChanName then if curMapId ~= mapId and not self.ZMonitor[mapId] then status.Leave = true end end if status.Leave then status.Leave = false Nx.Timer:Stop ("ComZ" .. mapId) if status.ChanName then LeaveChannelByName (status.ChanName) end end if status.Join then status.Join = false if not status.ChanName then local timerName = "ComZ" .. mapId if not Nx.Timer:IsActive (timerName) then -- Nx.prt ("Com Status Join %s", mapId) local timer = Nx.Timer:Start (timerName, 2, self, self.OnJoinChanZTimer) timer.UMapId = mapId timer.UTryCnt = 0 end end end end end -------- -- Join a channel function Nx.Com:JoinChan (chanId) local opts = Nx:GetGlobalOpts() -- if self:InChan (chanId) then -- Already in? -- return -- end -- local chanCnt = self:GetChanCount() -- Nx.prt ("JoinChan %s (%s)", chanId, chanCnt) if chanId == "A" then -- Addon channel (global) if not opts["ComNoGlobal"] then self.ChanAName = nil self.TryA = 0 Nx.Timer:Start ("ComA", 0, self, self.OnJoinChanATimer) end elseif chanId == "Z" then -- Our zone if not opts["ComNoZone"] then local mapId = Nx.Map:GetRealMapId() if Nx.Map:IsNormalMap (mapId) then local timer = Nx.Timer:Start ("ComZ", 0, self, self.OnJoinChanZTimer) timer.UMapId = mapId timer.UTryCnt = 0 end end else Nx.prt ("JoinChan Err %s", chanId) end end function Nx.Com:OnJoinChanATimer() self.List:AddInfo ("", "OnJoinChanATimer") if self:GetChanCount() >= 10 then return 10 end -- if self.TryA > 0 then -- Nx.prt ("Trying A%d", self.TryA + 1) -- end self.TryA = self.TryA + 1 JoinChannelByName (self.Name .. self.ChanALetter .. self.TryA) return 3 end function Nx.Com:OnJoinChanZTimer (name, timer) self.List:AddInfo ("", "OnJoinChanZTimer " .. name) if self:GetChanCount() >= 10 then return 5 end -- if self.TryZ > 0 then -- Nx.prt ("Trying Z%d", self.TryZ + 1) -- end timer.UTryCnt = timer.UTryCnt + 1 local name = format ("%sZ%dI%d", self.Name, timer.UMapId, timer.UTryCnt) if self:InChan (name) then return end JoinChannelByName (name) return 3 end -------- -- Count channels being used function Nx.Com:GetChanCount() local chanCnt = 0 for n = 1, GetNumDisplayChannels() do local chname, header, collapsed, chanNumber, plCnt, active, category, voiceEnabled, voiceActive = GetChannelDisplayInfo (n) if not header then chanCnt = chanCnt + 1 end end return chanCnt end -------- -- Leave a channel function Nx.Com:LeaveChan (chanId) if chanId == "A" then self.ChanAName = nil self:LeaveChans (self.ChanALetter) elseif chanId == "Z" then self:LeaveChans (chanId) end end -------- -- Leave a type of channel function Nx.Com:LeaveChans (typeName) for n = 1, 10 do local id, name = GetChannelName (n) if id > 0 and name then -- Nx.prt ("Leave Chan N %d %s", id, name) local name3 = strsub (name, 1, 3) if name3 == self.Name then local typ = strupper (strsub (name, 4, 4)) if typ == typeName then if typ == "Z" then local nameRoot = strsplit ("I", name) -- Drop I and # local id = tonumber (strsub (nameRoot, 5)) -- Nx.prtVar ("Com leave id", id) if not self.ZMonitor[id] then -- Not monitored? LeaveChannelByName (name) end else LeaveChannelByName (name) end end end end end end -------- -- Scan channels and add missing to status function Nx.Com:ScanChans() -- Nx.prt ("Com scan") local baseName = self.Name .. "Z" for n = 1, 10 do local id, name = GetChannelName (n) if id > 0 and name then -- Nx.prt ("Com scan %s", name) local name4 = strsub (name, 1, 4) if name4 == baseName then local nameRoot = strsplit ("I", name) -- Drop I and # local mapId = tonumber (strsub (nameRoot, 5)) if mapId then local zs = self.ZStatus[mapId] or {} zs.ChanName = name self.ZStatus[mapId] = zs end end end end end -------- -- Check if in a type of channel function Nx.Com:InChanType (typeName) for n = 1, 10 do local _, name = GetChannelName (n) if name then local name3 = strsub (name, 1, 3) if name3 == self.Name then local typ = strsub (name, 4, 4) if typ == typeName then return true end end end end end -------- -- Check if in a named channel function Nx.Com:InChan (chanName) for n = 1, 10 do local _, name = GetChannelName (n) if chanName == name then return true end end end -------- -- Set send pals mask. Called by options init. Don't do stuff needing com init function Nx.Com:SetSendPalsMask (mask) self.SendPMask = mask end -------- -- Send message to pals function Nx.Com:SendPals (msg) assert (msg) self.PalsSendQ[#self.PalsSendQ + 1] = msg end -------- -- Send a secure message to the global addon named channel function Nx.Com:SendSecG (pre, msg) -- Nx.prt ("Send G") if self.ChanAName then -- Should always be set, but was nil once local num = GetChannelName (self.ChanAName) if num ~= 0 then local cs = self:Chksum (msg) local str = self:Encode (format ("%s%c%c%s", pre, floor (cs / 16) + 65, bit.band (cs, 15) + 65, msg)) self:SendChan (num, str) else Nx.prt ("SendSecG Error: %s", pre) end end end -------- -- Send a secure message to player using addon whisper function Nx.Com:SendSecW (pre, msg, plName) local cs = self:Chksum (msg) local str = self:Encode (format ("%s%c%c%s", pre, floor (cs / 16) + 65, bit.band (cs, 15) + 65, msg)) self.SentBytes = self.SentBytes + #str + 54 + 20 -- Packet overhead + some WOW overhead SendAddonMessage (self.Name, str, "WHISPER", plName) end -------- -- Send a message to a named channel function Nx.Com:Send (chanId, msg, plName) assert (msg) if chanId == "Z" then -- Zone chat channel local mapId = Nx.Map:GetRealMapId() local chanName = self.ZStatus[mapId] and self.ZStatus[mapId].ChanName if chanName then local num = GetChannelName (chanName) if num ~= 0 then self:SendChan (num, msg) end end else self.SentBytes = self.SentBytes + #msg + 54 + 20 -- Packet overhead + some WOW overhead if chanId == "g" then -- Addon guild if IsInGuild() then SendAddonMessage (self.Name, msg, "GUILD") end elseif chanId == "p" then -- Addon party SendAddonMessage (self.Name, msg, "PARTY") elseif chanId == "W" then -- Addon whisper -- Nx.prt ("Send W %s", plName) SendAddonMessage (self.Name, msg, "WHISPER", plName) elseif chanId == "P" then -- Party channel if GetNumSubgroupMembers() > 0 then self:SendChatMessageFixed (msg, "PARTY") end else assert (false) end end end -------- -- Send a message to a numbered chat channel using a buffer function Nx.Com:SendChan (num, msg) local data = {} data.ChanNum = num data.Msg = msg tinsert (self.SendChanQ, data) end -------- -- Send chat hook function Nx.Com.SendChatHook (msg, chanName) if chanName == "CHANNEL" then -- Nx.prt ("SendChat CHANNEL %s", msg) Nx.Com.SendChanTime = GetTime() -- Reset time end end -------- -- Calc a message checksum function Nx.Com:Chksum (msg) local v = 0 local xor = bit.bxor for n = 1, #msg do v = xor (v, strbyte (msg, n)) end return v end function Nx.Com:IsChksumOK (msg) -- Nx.prt ("Com chksum rcv %s", #msg) if #msg >= 4 then -- skip 2 bytes at head local ck = (strbyte (msg, 3) - 65) * 16 + (strbyte (msg, 4) - 65) local v = 0 local xor = bit.bxor for n = 5, #msg do v = xor (v, strbyte (msg, n)) end return ck == v end end -------- -- Encode a message function Nx.Com:Encode (msg) local s = {} s[1] = strsub (msg, 1, 2) for n = 3, #msg do s[n - 1] = strchar (strbyte (msg, n) - 1) end -- Nx.prt (table.concat (s)) return table.concat (s) end -------- -- Decode a message function Nx.Com:Decode (msg) local s = {} s[1] = strsub (msg, 1, 2) for n = 3, #msg do s[n - 1] = strchar (strbyte (msg, n) + 1) end return table.concat (s) end function Nx.Com:SendChatMessageFixed (msg, typ, num) -- Fix invalid chat characters local s1 = strfind (msg, "|") if s1 then if strbyte (msg, s1 + 1) ~= 99 then -- not c -- Nx.prt ("chat OR in %s (%s)", strsub (msg, 1, 2), strsub (msg, s1, s1 + 5)) msg = gsub (msg, "|", "\1") end end -- p i i i i x x x y y y h f qi qi qi qi ob f lb c to -- 70 20 37 64 63 39 66 38 38 32 63 44 2b 31 37 30 64 23 25 24 25 87 << 100 termites. overflow -- 7 d c 9 f 8 8 2 c D 8 1 7 0 d 1 0 100 -- 70 20 62 62 62 62 36 63 63 36 66 44 4b 24 83 2c 37 2b 4b 69 -- b b b b f 8 8 2 c D 28 $ ?? , 7 + -- "p%4x%3x%3x%c%c%s%s%s", self.PlyrMapId, x, y, hper+48, flgs+35, tStr, qStr, enStr)) -- typ lvl cls he 13 O a s i s S n a p j a w -- 70 20 33 66 62 38 65 35 36 66 39 44 43 26 32 2c d9 30 4f 61 73 69 73 20 53 6e 61 70 6a 61 77 -- 20 local ok = pcall (SendChatMessage, msg, typ, nil, num) if not ok then Nx.prtStrHex (typ .. " SendChat failed", msg) end end -------- -- Restore invalid chat characters function Nx.Com:RestoreChars (msg) local s1 = strfind (msg, "\1") if s1 then -- Nx.prt ("Com restored char") return gsub (msg, "\1", "|") end return msg end -------- -- Monitor a zone function Nx.Com:MonitorZone (mapId, enable) local i = self.ZMonitor[mapId] if enable then if not i or i < 0 then if self:GetChanCount() >= 10 then Nx.prt ("|cffff4040Monitor Error: All 10 chat channels are in use") else Nx.prt ("|cff40ff40Monitored:") end self.ZMonitor[mapId] = 0 for mapId, mode in pairs (self.ZMonitor) do if mode >= 0 then local zs = self.ZStatus[mapId] if zs and zs.ChanName then Nx.prt (" %s", Nx.MapIdToName[mapId]) else Nx.prt (" %s (pending)", Nx.MapIdToName[mapId]) end end end end else if i and i >= 0 then self.ZMonitor[mapId] = -1 end end self:UpdateChannels() end function Nx.Com:IsZoneMonitored (mapId) local i = self.ZMonitor[mapId] return i and i >= 0 end -------- -- Capture punk names from combat events function Nx.Com:OnCombat_log_event_unfiltered (event, ...) local sName, sFlags, sFlags2, dId, dName, dFlags = select (5, ...) -- Nx.prt ("Com:Combat %s %x, %s %x", sName or "nil", sFlags, dName or "nil", dFlags) -- local COMBATLOG_OBJECT_REACTION_HOSTILE = 0x40 -- local COMBATLOG_OBJECT_TYPE_PLAYER = 0x400 if sName and bit.band (sFlags, 0x440) == 0x440 then -- Nx.prt ("punk-s %s", sName) local near if dName and bit.band (dFlags, 0x440) == 0x400 then near = dName end Nx.Social:AddLocalPunk (sName, near) if not Nx.InBG then Nx.Com.Punks[sName] = 0 end end if dName and dName ~= sName and bit.band (dFlags, 0x440) == 0x440 then -- Nx.prt ("punk-d %s", dName) local near if sName and bit.band (sFlags, 0x440) == 0x400 then near = sName end Nx.Social:AddLocalPunk (dName, near) if not Nx.InBG then Nx.Com.Punks[dName] = 0 end end end -------- -- Frame update. Called by main addon frame function Nx.Com:OnUpdate (elapsed) local Nx = Nx local bgmap = Nx.InBG local targetName = UnitName ("target") -- Punk targets if UnitIsPlayer ("target") and UnitIsEnemy ("player", "target") then local lvl = UnitLevel ("target") or 0 if not bgmap then self.Punks[targetName] = lvl end Nx.Social:AddLocalPunk (targetName, nil, lvl, UnitClass ("target")) end if UnitIsPlayer ("mouseover") and UnitIsEnemy ("player", "mouseover") then local moName = UnitName ("mouseover") if moName ~= targetName then local lvl = UnitLevel ("mouseover") or 0 if not bgmap then self.Punks[moName] = lvl end Nx.Social:AddLocalPunk (moName, nil, lvl, UnitClass ("mouseover")) end end -- local tm = GetTime() local tdiff = tm - self.SendTime if tdiff < .2 then -- Never go faster than this return end -- Handle AFK if UnitIsAFK ("player") then if not self.AFK then self:UpdateChannels() end self.AFK = true else if self.AFK then self:UpdateChannels() end self.AFK = nil end -- Position send local map = Nx.Map:GetMap (1) local delay = 10 if self.PlyrChange then if not UnitOnTaxi ("player") then delay = 3.1 end end if Nx.InCombat then delay = map.InstanceId and 4.5 or 2.2 end delay = delay * self.SendRate if bgmap then delay = 25 end if self.AFK then delay = 120 end if next (self.Punks) then -- Have a punk? Override all times delay = min (6, delay) end -- Nx.prt ("send delay %s", delay) if tm - self.SendPosTime >= delay then self.SendPosTime = tm self.PlyrChange = false -- Reset local flgs = 0 -- Player info if Nx.InCombat then flgs = 1 end local x, y = GetPlayerMapPosition ("player") if x ~= 0 or y ~= 0 then self.PlyrMapId = map:GetCurrentMapId() self.PlyrX = x self.PlyrY = y + max (GetCurrentMapDungeonLevel(), 1) - 1 -- 0 to 1 + dlvl else if map.InstanceId then self.PlyrMapId = map.InstanceId if not Nx.Map.InstanceInfo[self.PlyrMapId] then self.PlyrX = 0 self.PlyrY = 0 end end end x = max (min (self.PlyrX, .999), 0) * 0xfff y = max (min (self.PlyrY, 9.999), 0) * 0xfff local h = UnitHealth ("player") if UnitIsDeadOrGhost ("player") then h = 0 end local hm = UnitHealthMax ("player") local hper = h / hm * 20 if hper > 0 then hper = max (hper, 1) end hper = floor (hper + .5) local plyrLvl = min (UnitLevel ("player"), 90) -- 93 or above makes 0x80+ illegal chat char -- Target info local tStr = "" if targetName then flgs = flgs + 2 -- Have target local tType = 5 if UnitIsFriend ("player", "target") then tType = 1 else if UnitIsPlayer ("target") then tType = 2 elseif UnitIsEnemy ("player", "target") then tType = 3 if Nx:UnitIsPlusMob ("target") then tType = 4 end end end local tLvl = min (UnitLevel ("target"), 90) -- 93 or above makes 0x80+ illegal chat char -- Nx.prt ("%s", tLvl) local _, tCls = UnitClass ("target") -- Non localized uppercase version tCls = self.ClassNames[tCls] or 0 local h = UnitHealth ("target") if UnitIsDeadOrGhost ("target") then h = 0 end local hm = max (UnitHealthMax ("target"), 1) local hper = h / hm * 20 if hper > 0 then -- Alive? hper = max (hper, 1) end hper = min (floor (hper + .5), 20) -- Nx.prt ("THealth %s", hper) -- tLvl will cause a "\" at lvl 57 which could form a bad escape char with tCls tStr = format ("%c%c%c%c%c%s", tType+35, tLvl+35, tCls+35, hper+35, #targetName+35, targetName) end local qStr, qFlg = Nx.Quest:BuildComSend() flgs = flgs + qFlg -- 0 or 4 -- Punks info local enStr = "" if next (self.Punks) then for name, lvl in pairs (self.Punks) do -- enStr = enStr .. format ("%2x%s!", lvl, name) -- Old, sends 0xffffffff for -1 level enStr = enStr .. format ("%2x%s!", lvl >= 0 and lvl or 0, name) if #enStr > 50 then break end end self.Punks = {} self.SendZSkip = 1 -- So we get sent to zone for sure flgs = flgs + 8 enStr = strchar (#enStr - 1 + 35) .. strsub (enStr, 1, -2) -- Nx.prt ("En: %s", enStr) end --[[ Nx.prt ("tStr '%s'", tStr) Nx.prt ("qStr '%s'", qStr) Nx.prt ("enStr '%s'", enStr) --]] -- Send self:SendPals (format ("S%c%4x%3x%4x%c%c%c%s%s%s", flgs+35, self.PlyrMapId, x, y, hper+48, plyrLvl+35, self.PlyrClassI+35, tStr, qStr, enStr)) end -- Check for pals message if not self.PalsSendMsg then if #self.PalsSendQ > 0 then self.PalsSendMsg = self.PalsSendQ[1] self.PalsSendQ[1] = nil for n = 2, #self.PalsSendQ do self.PalsSendMsg = self.PalsSendMsg .. "\t" .. self.PalsSendQ[n] self.PalsSendQ[n] = nil -- table.remove (self.PalsSendQ, 1) end self.PosSendNext = -2 end end -- Send pals the next message if tdiff >= .25 then local msg = self.PalsSendMsg if msg then self.PosSendNext = self.PosSendNext + 1 if self.PosSendNext > #self.Friends then -- Reset now that guild and all friends are sent self.PosSendNext = -2 self.PalsSendMsg = nil else if self.PosSendNext == -1 then if bit.band (self.SendPMask, 2) > 0 then self:Send ("g", msg) end elseif self.PosSendNext == 0 then if self.SendChanQ[1] == nil and not bgmap and not Nx:FindActiveChatFrameEditBox() then -- if self.SendChanQ[1] == nil and not bgmap and not ChatFrameEditBox:IsVisible() then if bit.band (self.SendPMask, 4) > 0 then local sk = self.SendZSkip - 1 -- Zone skipped to reduce rate if sk < 1 then sk = 4 self:Send ("Z", msg) Nx.Quest.QLastChanged = nil -- Now that zone has it kill it end self.SendZSkip = sk end end else if bit.band (self.SendPMask, 1) > 0 then self:Send ("W", msg, self.Friends[self.PosSendNext]) end end self.SendTime = tm end end end -- Send numbered channel queue if Nx:FindActiveChatFrameEditBox() then Nx.Com.SendChanTime = tm -- Reset time else if tm - self.SendChanTime >= .5 then if self.SendChanQ[1] then local data = self.SendChanQ[1] tremove (self.SendChanQ, 1) self.SentBytes = self.SentBytes + #data.Msg + 54 + 20 -- Packet overhead + some WOW overhead self:SendChatMessageFixed (data.Msg, "CHANNEL", data.ChanNum) self.SendChanTime = tm end end end --[[ for n = 1, #self.SendQ do local q = self.SendQ[n] self.SendTime = tm end --]] end -------- -- Update map icons (called by map) function Nx.Com:UpdateIcons (map) if Nx.Tick % 20 == 1 then local memberNames = {} self.MemberNames = memberNames local members = MAX_PARTY_MEMBERS local unitName = "party" if IsInRaid() then members = MAX_RAID_MEMBERS unitName = "raid" end local mapId = map.MapId local palsInfo = self.PalsInfo for n = 1, members do local unit = unitName .. n local name = UnitName (unit) if name then local x, y = GetPlayerMapPosition (unit) if x ~= 0 or y ~= 0 then memberNames[name] = 1 else local info = palsInfo[name] if info and info.EntryMId == mapId then memberNames[name] = 1 end end end end end -- local alt = IsAltKeyDown() if alt then map.Level = map.Level + 3 end local opts = Nx:GetGlobalOpts() self.TrackX = nil if map:GetWorldZone (map.RMapId).City then if opts["MapShowOthersInCities"] then self:UpdatePlyrIcons (self.ZPInfo, map, "IconPlyrZ") end if opts["MapShowPalsInCities"] then self:UpdatePlyrIcons (self.PalsInfo, map, "IconPlyrG") end else if opts["MapShowOthersInZ"] then self:UpdatePlyrIcons (self.ZPInfo, map, "IconPlyrZ") end self:UpdatePlyrIcons (self.PalsInfo, map, "IconPlyrG") end if alt then map.Level = map.Level - 3 end return self.TrackName, self.TrackX, self.TrackY end -------- -- Update icons function Nx.Com:UpdatePlyrIcons (info, map, iconName) local memberNames = self.MemberNames local idToName = Nx.MapIdToName local alt = IsAltKeyDown() local redGlow = abs (GetTime() * 400 % 200 - 100) / 200 + .5 local inBG = Nx.InBG local t = GetTime() local showTargetText = not Nx.Free for name, pl in pairs (info) do -- Nx.prt ("%s", name) if t - pl.T > 35 then info[name] = nil -- Nx.prt ("Com del plyr %s", name) elseif not memberNames[name] and (not inBG or map.MapId ~= pl.MId) and pl.Y then -- Y can be nil somehow local mapId = pl.MId local wx, wy = map:GetWorldPos (mapId, pl.X, pl.Y) -- Nx.prt ("%f %f", wx, wy) local sz = 14 * map.DotZoneScale if self.PalNames[name] ~= nil then sz = 17 * map.DotPalScale end if map.TrackPlyrs[name] then sz = 22 * map.DotPalScale self.TrackName = name self.TrackX, self.TrackY = wx, wy end local f = map:GetIcon() if map:ClipFrameW (f, wx, wy, sz, sz, 0) then f.NXType = 1000 f.NXData2 = name local mapName = idToName[mapId] or "?" local tStr = pl.TStr or "" local qStr = pl.QStr or "" f.NxTip = format ("%s\n %s (%d,%d)%s%s", pl.Tip, mapName, pl.X, pl.Y, tStr, qStr) local txName = iconName if self.PalNames[name] == false then txName = "IconPlyrF" end if bit.band (pl.F, 1) > 0 then -- In combat? txName = txName .. "C" end f.texture:SetTexture ("Interface\\AddOns\\Carbonite\\Gfx\\Map\\"..txName) if alt then -- tStr has \n local s = pl.TType == 2 and showTargetText and (name .. tStr) or name local txt = map:GetText (s) map:MoveTextToIcon (txt, f, 15, 1) end end -- Show health --PAIDS! if pl.Health then -- No health if from Cartographer f = map:GetIconNI (1) local per = pl.Health / 100 if per >= .33 then local sc = map.ScaleDraw map:ClipFrameTL (f, wx - 8 / sc, wy - 8 / sc, 14 * per / sc, 1 / sc) f.texture:SetTexture (1, 1, 1, 1) else map:ClipFrameW (f, wx, wy, 8, 8, 0) if per > 0 then f.texture:SetTexture (1, .1, .1, 1 - per * 2) else f.texture:SetTexture (0, 0, 0, .5) end end local tt = pl.TType if tt then -- Target? local per = pl.TH / 100 local f = map:GetIconNI (1) local sc = map.ScaleDraw if tt == 1 then -- Horizontal green bar map:ClipFrameTL (f, wx - 8 / sc, wy - 2 / sc, 14 * per / sc, 1 / sc) f.texture:SetTexture (0, 1, 0, 1) else -- Vertical bar map:ClipFrameTL (f, wx - 8 / sc, wy - 7 / sc, 1 / sc, 13 * per / sc) if tt == 2 then f.texture:SetTexture (redGlow, .1, 0, 1) elseif tt == 3 then f.texture:SetTexture (1, 1, 0, 1) elseif tt == 4 then f.texture:SetTexture (1, .4, 1, 1) else f.texture:SetTexture (.7, .7, 1, 1) end end end end --PAIDE! end end end function Nx.Com:GetPlyrQStr (name) local info = self.PalsInfo[name] or self.ZPInfo[name] return info and info.QStr end -------- -- Show newer version message function Nx.Com:ShowVersionMsg() --[[ local pop = StaticPopupDialogs["NxVerMsg"] if not pop then pop = { ["button1"] = "OK", ["timeout"] = 0, ["whileDead"] = 1, ["hideOnEscape"] = 1 } StaticPopupDialogs["NxVerMsg"] = pop end pop["text"] = format ("A newer CARBONITE version has been detected.") StaticPopup_Show ("NxVerMsg") --]] local s1 = format ("A newer version of %s is available", NXTITLEFULL) local s2 = format ("Visit %s%s|cffffffff for an update", Nx.TXTBLUE, Nx.WebSite) UIErrorsFrame:AddMessage (s2, 1, 1, 1, 1) -- Flip order so it show correctly UIErrorsFrame:AddMessage (s1, 1, 1, 0, 1) Nx.prt (s1) Nx.prt (s2) end -------- -- Show version data from player function Nx.Com:ShowPlyrVersion (name) self:SendSecW ("V?", "", name) -- Ask for it end -------- -- Handle version messages function Nx.Com:OnMsgVersion (name, enmsg, arg2, arg9) -- Header is VxCC local msg = self:Decode (enmsg) -- Nx.prt ("Ver %s", msg) if self:IsChksumOK (msg) then local subType = strsub (msg, 2, 2) if subType == " " then -- Global version? local ver, r, c, dt, ver1, qCnt = strsplit ("^", msg) ver = tonumber (strsub (ver, 5)) if ver then if Nx.VERMINOR <= 0 then -- Not test? local vermajor = floor (ver * 1000) / 1000 local verminor = ver - vermajor if verminor > 0 then -- Is test? return -- Ignore end end -- ver = ver + 9 -- Test the message if ver - .0000001 > Nx.VERSION and not self.NewVerMsg then self.NewVerMsg = true Nx.Timer:Start ("ComShowVer", 60, self, self.ShowVerTimer) -- local s = format ("%s Ver %f > %f", arg2, ver, Nx.VERSION) -- Nx.prt (s) end -- Is arg2 always correct?? Look at the 2 callers?? self.List:AddInfo ("C:"..arg9, format ("(%s) ver %s", arg2, ver)) self:RcvVersion (name, msg) end elseif subType == "?" then -- Request? local str = self:MakeVersionMsg() self:SendSecW ("V!", str, name) -- Reply elseif subType == "!" then -- Reply? self:RcvVersion (name, msg) end else if NxData.DebugCom then Nx.prt ("Ver chksum fail %s", msg) end end end -------- -- function Nx.Com:RcvVersion (name, msg) if NxData.NXVerDebug then local ver, r, c, dt, ver1, qCnt, lvl, mapId = strsplit ("^", msg) ver = tonumber (strsub (ver, 5)) lvl = tonumber (lvl or 0, 16) mapId = tonumber (mapId or 0, 16) Nx.prt ("Ver %s %s (%s) %s %s %s Q%s L%s %s", name, ver, ver1 or "", r, c, dt, qCnt or "", lvl, mapId) if ver >= 1.6 then self.VerPlayers[name] = msg Nx.Social.List:Update() end end end -------- -- Bug user with update function Nx.Com:ShowVerTimer() if UnitAffectingCombat ("player") or UnitIsAFK ("player") then -- Nx.prt ("ShowVerTimer delay") return 5 end local lasttm = NxData.NXVerT local tm = time() -- Nx.prt ("ShowVerTimer %s %s = %s", lasttm or 0, tm, difftime (tm, lasttm or 0)) if not lasttm or difftime (tm, lasttm) > 4 * 3600 then -- Hours since last message? local map = Nx.Map:GetMap (1) if map.InstanceId then -- Nx.prt ("ShowVerTimer Instance") return 60 end NxData.NXVerT = tm self:ShowVersionMsg() end return 60 end -------- function Nx.Com:GetUserVer() self.VerPlayers = {} Nx.Timer:Start ("ComGetUserVer", 0, self, self.GetUserVerTimer) end -------- function Nx.Com:GetUserVerTimer() for n = 1, GetNumDisplayChannels() do local chname, header, collapsed, chanNumber, plCnt, active, category, voiceEnabled, voiceActive = GetChannelDisplayInfo (n) if not header then if chname == "General" then SetSelectedDisplayChannel (n) -- Force roster update end -- Nx.prt ("Chan %s (%s) Cnt %s", chname or "nil", n, plCnt or "nil") local s1 = strfind (strlower (chname), "^crbb") if s1 then SetSelectedDisplayChannel (n) self.GettingVersion = true return end end end local s = "crbb1" Nx.prt ("Joining %s", s) JoinChannelByName (s) return 2 end function Nx.Com:OnChannel_roster_update (event, arg1, arg2) local self = Nx.Com if not self.GettingVersion then return end Nx.prt ("OnChannel_roster_update %s, %s", arg1, arg2 or "nil") local n = arg1 local chname, header, collapsed, chanNumber, plCnt, active, category, voiceEnabled, voiceActive = GetChannelDisplayInfo (n) if not header then Nx.prt ("Chan %s (%s) Cnt %s", chname or "nil", n, plCnt or "nil") local s1 = strfind (strlower (chname), "^crbb") if s1 then if plCnt then self.GettingVersion = false Nx.prt ("Found %s %s (%s)", chname, plCnt, n) local names = {} for n2 = 1, plCnt do local plName, owner, moderator, muted, active, enabled = GetChannelRosterInfo (n, n2) if plName ~= UnitName ("player") then tinsert (names, plName) end end self.GetUserVerNames = names self.GetUserVerI = 1 Nx.Timer:Start ("GetUserVer", 0, self, self.OnGetUserVerTimer) end end end end function Nx.Com:OnGetUserVerTimer() local i = self.GetUserVerI if i <= #self.GetUserVerNames then local plName = self.GetUserVerNames[i] -- Nx.prt (" %s", plName) self:SendSecW ("V?", "", plName) -- Ask for it self.GetUserVerI = i + 1 return .1 end end ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- -- Com message list -------- -- Open and init or toggle Com frame function Nx.Com.List:Open() end ------ -- Add info to list function Nx.Com.List:AddInfo (type, name) end -------- -- Update list function Nx.Com.List:Update() if not self.Opened then return end -- Title self.Win:SetTitle (format ("Com %d Bytes sec %d", #self.Sorted, Nx.Com.SentBytesSec or 0)) -- List local list = self.List local isLast = list:IsShowLast() list:Empty() for k, v in pairs (self.Sorted) do list:ItemAdd() list:ItemSet (1, date ("%d %H:%M:%S", v.Time)) list:ItemSet (2, v.Type) list:ItemSet (3, v.Name) end list:Update (isLast) end -------- -- Sort compare function Nx.Com.List.SortCmp (v1, v2) return v1.Time < v2.Time end -------- function Nx.Com.List:Sort() local rcv = Nx.Com.Data.Rcv self.Sorted = {} local t = self.Sorted local i = 1 for k, v in pairs (rcv) do t[i] = v i = i + 1 end sort (self.Sorted, self.SortCmp) end ------------------------------------------------------------------------------- --EOF