Ckknight10322@legacy41531455 (talk | contribs) |
Kirkburn31335@legacy41551592 (talk | contribs) |
||
(22 intermediate revisions by 9 users not shown) | |||
Line 1: | Line 1: | ||
− | As the number of addons using raid or guild communications is increasing it's become |
+ | As the number of addons using raid or guild communications is increasing it's become apparent that we need a community protocol for addon developers to use to standardize these comm functions. |
+ | |||
+ | You can find discussion of this protocol on [http://forums.worldofwarcraft.com/thread.aspx?ForumName=wow-interface-customization&ThreadID=411633 The US WoW forums] or in [[Talk:Addon Comm Protocol]] |
||
== Proposed Features == |
== Proposed Features == |
||
Line 11: | Line 13: | ||
== Channel Management == |
== Channel Management == |
||
− | There are two primary issues here, channel names and maintaining the channel connection, changing and chat |
+ | There are two primary issues here, channel names and maintaining the channel connection, changing and chat suppression. |
=== Channel Names === |
=== Channel Names === |
||
Line 39: | Line 41: | ||
end |
end |
||
− | A group channel with leader "Tekkub" would become "CommRaTekkub21cdb4"<br> |
+ | A group channel with leader "Tekkub" would become "CommRaTekkub21cdb4"<br /> |
− | A group channel with leader "Clãdhaire" would become "CommRaCldhaire23e70a"<br> |
+ | A group channel with leader "Clãdhaire" would become "CommRaCldhaire23e70a"<br /> |
− | A group channel with leader "Clædhaire" would become "CommRaCldhairebc9459"<br> |
+ | A group channel with leader "Clædhaire" would become "CommRaCldhairebc9459"<br /> |
Any addon or library maintaining connection to this channel must handle leadership changes smoothly. It must close connection to the old group channel and open connection to the new channel. |
Any addon or library maintaining connection to this channel must handle leadership changes smoothly. It must close connection to the old group channel and open connection to the new channel. |
||
Line 48: | Line 50: | ||
A guild channel mechanism should be provided. This should not restrict the user to their current guild, as many guilds keep an "app guild" or "alt guild" wherein the players may need access to communication with the main guild's addon channel. The addon managing this channel should be able to give some sort of indication of the guild channel it is maintaining, so conflicts do not arrise but many guild channels could potentially be opened. |
A guild channel mechanism should be provided. This should not restrict the user to their current guild, as many guilds keep an "app guild" or "alt guild" wherein the players may need access to communication with the main guild's addon channel. The addon managing this channel should be able to give some sort of indication of the guild channel it is maintaining, so conflicts do not arrise but many guild channels could potentially be opened. |
||
− | Guild names have the same issues as playernames for the group channels, plus they can take spaces. So a checksum is used here as well, and all non-alphanumeric chars are stripped from the name. For example:<br> |
+ | Guild names have the same issues as playernames for the group channels, plus they can take spaces. So a checksum is used here as well, and all non-alphanumeric chars are stripped from the name. For example:<br /> |
− | "Khaz Modan Brigade" would have the channel CommGuKhazModadb3bb5<br> |
+ | "Khaz Modan Brigade" would have the channel CommGuKhazModadb3bb5<br /> |
− | "Khaz Modan Reserve" would have the channel CommGuKhazModa0e3045<br> |
+ | "Khaz Modan Reserve" would have the channel CommGuKhazModa0e3045<br /> |
− | "KhazModanBrigade" would have the channel CommGuKhazModa4209dd<br> |
+ | "KhazModanBrigade" would have the channel CommGuKhazModa4209dd<br /> |
− | "KhazMødanBrigade" would have the channel CommGuKhazMdanfa944d<br> |
+ | "KhazMødanBrigade" would have the channel CommGuKhazMdanfa944d<br /> |
− | To get the guild name checksum, the following method can be used. It makes some concessions to prevent collisions in our specific case, but can be used as a general algorithm as well. In order to keep the channel names as clean as possible for the users, we've capped the checksums to six hex characters. Thanks to |
+ | To get the guild name checksum, the following method can be used. It makes some concessions to prevent collisions in our specific case, but can be used as a general algorithm as well. In order to keep the channel names as clean as possible for the users, we've capped the checksums to six hex characters. Thanks to Iriel for the suggestion. |
local SOME_PRIME = 16777213 |
local SOME_PRIME = 16777213 |
||
− | + | CheckSum = function(text) |
|
local counter = 1 |
local counter = 1 |
||
− | + | local len = string.len(text) |
|
+ | for i=1,len do |
||
− | counter = counter |
+ | counter = counter + string.byte(text, i)*math.pow(31, len- i) |
− | + | end |
|
counter = math.mod(counter, SOME_PRIME) |
counter = math.mod(counter, SOME_PRIME) |
||
return string.format("%06x", counter) |
return string.format("%06x", counter) |
||
end |
end |
||
− | |||
Using CheckSum and Strip from above, the following function could be used to get a full channel name from a string: |
Using CheckSum and Strip from above, the following function could be used to get a full channel name from a string: |
||
Line 80: | Line 82: | ||
* Leadership changes must result in leaving the old channel and joining the new one. |
* Leadership changes must result in leaving the old channel and joining the new one. |
||
* Assure that unused channels are departed, some sort of global registrar table should be used to assure the channel is not closed when someone else is using it. |
* Assure that unused channels are departed, some sort of global registrar table should be used to assure the channel is not closed when someone else is using it. |
||
− | * ChatFrames must have the comm channel messages |
+ | * ChatFrames must have the comm channel messages suppressed. To prevent duplicate processing a global registrar should be used to indicate if a channel is being filtered. Since one cannot effectively unhook the filter can remain in place after established. |
+ | * At the very beginning, about 2 seconds after PLAYER_LOGIN, a check needs to be done to get rid of dead Comm* channels. |
||
== Global Registrar == |
== Global Registrar == |
||
Line 106: | Line 109: | ||
filter = true, |
filter = true, |
||
− | -- GetTime() of the next message that can be sent. |
+ | -- GetTime() of the next raid message that can be sent. |
− | + | raidThrottle = 453278.329, |
|
+ | -- GetTime() of the next guild message that can be sent. |
||
+ | guildThrottle = 483587.467, |
||
+ | -- GetTime() of the next global message that can be sent. |
||
+ | globalThrottle = 454643.745, |
||
-- Name of the leader currently used for the group channel |
-- Name of the leader currently used for the group channel |
||
Line 127: | Line 134: | ||
AddonCommRegistrar.channels[channelname] = {} |
AddonCommRegistrar.channels[channelname] = {} |
||
end |
end |
||
⚫ | |||
+ | AddonCommRegistrar.channels[channelname]["Sample addon"] = true |
||
-- Implement a ChatFrame filter for our channel if one does not exist |
-- Implement a ChatFrame filter for our channel if one does not exist |
||
Line 158: | Line 167: | ||
==== Message throttling ==== |
==== Message throttling ==== |
||
+ | Message throttling has it's advantages, but the jury's still out on if the protocol should require throttling or not, further details and discussion can be found at [[Addon Comm Throttling]] |
||
− | Message throttling is a complicated subject, because you have to limit both incoming and outgoing traffic while only having control of outgoing. |
||
− | |||
− | The number one goal to keep in mind is that background communications should not affect foreground gameplay. |
||
− | |||
− | The maximum incoming rate for chat is as such: |
||
− | p * r = T |
||
− | Where <tt>p</tt> is the amount of players communicating, <tt>r</tt> is the maximum rate, and <tt>T</tt> is the total incoming bytes per second. |
||
− | |||
− | Thus, looking at the extreme case of 40 raid members: |
||
− | 40 * r = T |
||
− | and consequently, |
||
− | r = T / 40 |
||
− | |||
− | So in picking an arbitrary <tt>T</tt> that is sufficient to bear the load but not cause a great impact, <tt>T</tt> is 4000, and <tt>r</tt> is 100. |
||
− | |||
− | Despite it seeming low, a rate of 100 Bytes / second is not all that far-fetched. If you are transfering critical information, it should be sent in as small packets as possible, possibly with some compression mechanism. |
||
− | |||
− | Also, for raids, raid leaders should be allowed 200 Bytes / second, and raid assistants 150 Bytes / second. |
||
− | |||
− | Instead of having guild, global, and raid on the same throttle, it is better to split them up into different throttles, to prevent a large global message from preventing a needed realtime raid message. |
||
− | |||
− | Unlike the raid channel, both Guild and Global channels should not be sending realtime data back and forth, thus the need for such a high rate is not needed. |
||
− | |||
− | Thus, the formula I'll use to calculate the Guild and Global rates is this formula: |
||
− | Rp * Rr = Gp * Gr * C |
||
− | which translates to |
||
− | Gr = Rp * Rr / Gp / C |
||
− | |||
− | Where <tt>Rp</tt> is the likely maximum members in a raid (40),<br> |
||
− | <tt>Rr</tt> is the rate at which the average raid message is travelling (100 B/s),<br> |
||
− | <tt>Gp</tt> is the likely maximum members in a guild,<br> |
||
− | <tt>Gr</tt> is the rate at which the average guild message is travelling,<br> |
||
− | <tt>C</tt> is a constant to determine the need of realtime messages. |
||
− | |||
− | Guilds have a varying number of members, with the average being around 61 members per guild. Some guilds have a much larger number, though, upwards of 300. The number I will use is [http://en.wikipedia.org/wiki/Dunbar_number Dunbar's number], which is roughly '''150'''. Any guilds with an amount of members over this is unlikely to exist (and unlikely to persist). |
||
− | |||
− | The need for realtime messages is very important in a raid, so I shall choose the arbitrary and simple number 2 to represent C, which means that realtime messages are twice as important as normal messages). |
||
− | |||
− | So the numbers we have are: |
||
− | * <tt>Rp</tt> = 40 members |
||
− | * <tt>Rr</tt> = 100 B/s |
||
− | * <tt>Gp</tt> = 150 members |
||
− | * <tt>C</tt> = 2 |
||
− | |||
− | So |
||
− | Gr = Rp * Rr / Gp / C |
||
− | translates to |
||
− | Gr = 40 * 100 / 150 / 2 |
||
− | so |
||
− | Gr = 13.33333333333333 -- and so on |
||
− | |||
− | So you can send about 13 bytes/second through the guild. |
||
− | |||
− | Note: you could send one giant 250-byte message to the global channel, you merely have to wait 20 seconds before you send your next message. In turn, if you send a 10-byte message, you only have to wait 1 second to send your next message. |
||
− | |||
− | If you are the guild leader, you should have the priviledge of double that rate, at 26.66666 bytes/second. |
||
− | |||
− | Global messages are even more complicated and should be used with greatest care, since there are many players, who all want to get their voice heard. |
||
− | |||
− | I will use the same formula as above to determine the rate. |
||
− | Rp * Rr = Tp * Tr * C |
||
− | which translates to |
||
− | Tr = Rp * Rr / Tp / C |
||
− | |||
− | Where <tt>Rp</tt> is the likely maximum members in a raid (40),<br> |
||
− | <tt>Rr</tt> is the rate at which the average raid message is travelling (100 B/s),<br> |
||
− | <tt>Tp</tt> is the likely maximum members in the global channel,<br> |
||
− | <tt>Tr</tt> is the rate at which the average global message is travelling,<br> |
||
− | <tt>C</tt> is a constant to determine the need of realtime messages. (2) |
||
− | |||
− | In a populated realm, the amount of members online on a single faction can reach 4000 (and possibly go beyond), so that shall be the value for <tt>Tp</tt> |
||
− | |||
− | So the numbers we have are: |
||
− | * <tt>Rp</tt> = 40 members |
||
− | * <tt>Rr</tt> = 100 B/s |
||
− | * <tt>Gp</tt> = 4000 members |
||
− | * <tt>C</tt> = 2 |
||
− | |||
− | So |
||
− | Tr = Rp * Rr / Tp / C |
||
− | translates to |
||
− | Tr = 40 * 100 / 4000 / 2 |
||
− | so |
||
− | Tr = 0.5 |
||
− | |||
− | So you can send 0.5 chars per second through the global channel. This may seem extremely small, but it is necessary. |
||
− | |||
− | Note: you could send one giant 250-char message to the global channel, you merely have to wait 500 seconds before you send your next message. In turn, if you send a 10-char message, you only have to wait 20 seconds to send your next message. |
||
== Chatframe Filtering == |
== Chatframe Filtering == |
||
Line 262: | Line 184: | ||
== Message Protocol == |
== Message Protocol == |
||
− | A standard protocol for messages sent should be used to assure addons do not unintentionally receive other addon's messages. The proposed standard is: "<Namespace>: <message>" <Namespace> is some unique token that a develop's addons use. Some devs may choose to share a token across their addons, like "Tekkub" or they may make it addon-specific like "CTRA". Handling of the <message> portion of the communication is entirely placed upon the |
+ | A standard protocol for messages sent should be used to assure addons do not unintentionally receive other addon's messages. The proposed standard is: "<Namespace>: <message>" <Namespace> is some unique token that a develop's addons use. Some devs may choose to share a token across their addons, like "Tekkub" or they may make it addon-specific like "CTRA". Handling of the <message> portion of the communication is entirely placed upon the receiving addon. This will allow for simple filtering of messages intended for other addons. |
− | It is recommended that use of the chars 's' and 'S' in the <Namespace> section of the message be avoided, especially if the addon does not use a sobriety filter. For example, if the addon only communicates numbers the filter would not need to be implemented, it's just a wasted function in this case. |
+ | It is recommended that use of the chars 's' and 'S' in the <Namespace> section of the message be avoided, especially if the addon does not use a sobriety filter. For example, if the addon only communicates numbers the filter would not need to be implemented, it's just a wasted function in this case. Please see [[Addon Comm Sobriety Filter]] for possible implementations of this filter. |
− | === Sobriety filter === |
||
− | '''''Note:''''' This filter is not a requirement, but highly recommended. If addons do not do this drunken players could cause problems. |
||
+ | [[Category:Requests For Comment]] |
||
− | When you are drunk, your text is slurred in two possible ways: "s" is changed to "sh" and " ...hic!" (localized) is added to the end of your message.<br> |
||
− | The internal Blizzard function can be represented as such: |
||
− | function Drunkify(text) |
||
− | -- turns regular text into drunken, slurred text. |
||
− | return string.gsub(text, "([Ss])", "%1h") .. " ...hic!" |
||
− | end |
||
⚫ | |||
− | assert(Drunkify("I drank seven beers") == "I drank sheven beersh ...hic!") |
||
− | |||
− | |||
− | In order to prevent this, special care must be taken to make sure that "sh"s are turned back into "s"s, but without overkill. Also, the " ...hic!" must be stripped from the end, paying careful attention to localization. |
||
− | |||
− | Here are two functions to handle this on send and receive: |
||
− | |||
− | ''Note:'' the hidden character can be absolutely any character except "S" or "s" |
||
− | |||
− | -- Package a message for transmission |
||
− | function Encode(text) |
||
− | text = string.gsub(text, "([h°])", "°%1") -- encode a hidden character in front of all the "h"s and the same hidden character |
||
− | return text.."°" -- add the hidden character to the very end. |
||
− | end |
||
− | |||
− | -- Clean a received message |
||
− | function Decode(text) |
||
− | text = string.gsub(text, "([Ss])h", "%1") -- find "h"s added to any "s", remove. |
||
− | text = string.gsub(text, "°([h°])", "%1") -- remove the hidden character. |
||
− | text = string.gsub(text, "^(.*)°.-$", "%1") -- make sure there hasn't been further tampering with the msg. |
||
− | return text |
||
− | end |
||
− | |||
− | By definition, <tt>Decode(Encode(text)) == text</tt>, but also <tt>Decode(Drunkify(Encode(text))) == text</tt>, where <tt>Drunkify</tt> is Blizzard's internal function. |
||
− | |||
− | -- some tests: |
||
− | assert(Decode(Encode("It's 58° out!")) == "It's 58° out!") |
||
− | assert(Decode(Encode("She sells sea shells by the sea shore")) == "She sells sea shells by the sea shore") |
||
− | |||
− | assert(Decode(Drunkify(Encode("She sells sea shells by the sea shore"))) == "She sells sea shells by the sea shore") |
Latest revision as of 02:24, 8 July 2008
As the number of addons using raid or guild communications is increasing it's become apparent that we need a community protocol for addon developers to use to standardize these comm functions.
You can find discussion of this protocol on The US WoW forums or in Talk:Addon Comm Protocol
Proposed Features
- Channels for Global, Guild and Group (Raid or party) communiction should be available
- Channels are only joined if a mod needs to use it
- Chat from the comm channels must be blocked from appearing in the default UI's chat channels
- Protocol should not be tied to any one mod, but be implementable by anyone
- Multiple addons implementing the protocol must not interfere with each other
- A standard message format must be established, so addons do not conflict with each other's messages
- A sobriety filter must be applied to outgoing messages to prevent modifications caused by drunk players. This involves replacing the chars 's' and 'S' or 'h' with unique, rarely used chars. Also "...hic!" must be removed from the end of messages received.
Channel Management
There are two primary issues here, channel names and maintaining the channel connection, changing and chat suppression.
Channel Names
Channel names have a 31 char limit and should be considered case-insensitive.
Global
A simple global channel should be available, something like CommGlobal
Group
A group channel should be provided, with a unique name derrived from the party or raid leader's name. If the player is in a raid a party channel will not exist, but party--specific communication can be maintained by the addon receiving the messages by checking the sender against the party roster.
Sample channel name with raid leader named Tekkub: CommRaTekkub
Player names are unique within a realm, cannot contain spaces, and the name limit is 12 chars, but special characters are allowed. When we strip those characters out, there could be conflicts-- so we're going to use a checksum system here to prevent that. We can use the following function to strip out all special characters:
function Strip(name) return string.gsub(name, "%W", "") end
There is a possibility that a channel name could conflict due to this, so addons using this channel should probably verrify that the sender of a message is in the raid or party before the message is parsed, but the checksum system should prevent most of this.
You could use the following function to create group names (where CheckSum is defined below):
function GroupChannelName(name) local cs = CheckSum(name) name = Strip(name) name = string.sub(name, 1, 8) return "CommRa" .. name .. cs end
A group channel with leader "Tekkub" would become "CommRaTekkub21cdb4"
A group channel with leader "Clãdhaire" would become "CommRaCldhaire23e70a"
A group channel with leader "Clædhaire" would become "CommRaCldhairebc9459"
Any addon or library maintaining connection to this channel must handle leadership changes smoothly. It must close connection to the old group channel and open connection to the new channel.
Guild
A guild channel mechanism should be provided. This should not restrict the user to their current guild, as many guilds keep an "app guild" or "alt guild" wherein the players may need access to communication with the main guild's addon channel. The addon managing this channel should be able to give some sort of indication of the guild channel it is maintaining, so conflicts do not arrise but many guild channels could potentially be opened.
Guild names have the same issues as playernames for the group channels, plus they can take spaces. So a checksum is used here as well, and all non-alphanumeric chars are stripped from the name. For example:
"Khaz Modan Brigade" would have the channel CommGuKhazModadb3bb5
"Khaz Modan Reserve" would have the channel CommGuKhazModa0e3045
"KhazModanBrigade" would have the channel CommGuKhazModa4209dd
"KhazMødanBrigade" would have the channel CommGuKhazMdanfa944d
To get the guild name checksum, the following method can be used. It makes some concessions to prevent collisions in our specific case, but can be used as a general algorithm as well. In order to keep the channel names as clean as possible for the users, we've capped the checksums to six hex characters. Thanks to Iriel for the suggestion.
local SOME_PRIME = 16777213
CheckSum = function(text) local counter = 1 local len = string.len(text) for i=1,len do counter = counter + string.byte(text, i)*math.pow(31, len- i) end counter = math.mod(counter, SOME_PRIME) return string.format("%06x", counter) end
Using CheckSum and Strip from above, the following function could be used to get a full channel name from a string:
function GuildChannelName(name) local cs = CheckSum(name) name = Strip(name) name = string.sub(name, 1, 8) return "CommGu" .. name .. cs end
Channel Management
There are a few things addons will need to do if they are using a channel:
- Leadership changes must result in leaving the old channel and joining the new one.
- Assure that unused channels are departed, some sort of global registrar table should be used to assure the channel is not closed when someone else is using it.
- ChatFrames must have the comm channel messages suppressed. To prevent duplicate processing a global registrar should be used to indicate if a channel is being filtered. Since one cannot effectively unhook the filter can remain in place after established.
- At the very beginning, about 2 seconds after PLAYER_LOGIN, a check needs to be done to get rid of dead Comm* channels.
Global Registrar
A simple table could be used to maintain a registrar. Any addon using the comm protocol would look for this table when they need to register into a channel. If it doesn't exist they would create the basic empty table and add themselves in.
Proposed table structure
AddonCommRegistrar = { -- Stores a list of addons currently using a channel -- Note that the indexes here don't have to be strings, -- anything can be used as long as it's unique to the addon channels = { CommGlobal = { ["Sample addon 1"] = true, ["Sample addon 2"] = true, }, CommRa = { ["Sample raid addon"] = true, }, CommGuKhazModa12345678 = { ["Sample guild addon"] = true, }, }, -- Flag indicating that a ChatFrame fliter is in place filter = true, -- GetTime() of the next raid message that can be sent. raidThrottle = 453278.329, -- GetTime() of the next guild message that can be sent. guildThrottle = 483587.467, -- GetTime() of the next global message that can be sent. globalThrottle = 454643.745, -- Name of the leader currently used for the group channel -- this is in place so that only one addon performs a channel switch on leadership change groupleader = "Joebob", }
Registering a channel
When registering into a channel, an addon should execute this block of code:
-- Establish the registrar if not AddonCommRegistrar then AddonCommRegistrar = { channels = {}, } end -- Establish the channel registrar if not AddonCommRegistrar.channels[channelname] then AddonCommRegistrar.channels[channelname] = {} end AddonCommRegistrar.channels[channelname]["Sample addon"] = true -- Implement a ChatFrame filter for our channel if one does not exist if not AddonCommRegistrar.filter then -- Hook ChatFrame_OnEvent here AddonCommRegistrar.filter = true end -- Join the channel if GetChannelName(channelname) == 0 then JoinChannelByName(channename) end
Unregistering a channel
When an addon is done with a channel it needs to check if anyone else is using it, if noone is it should make sure the channel is departed.
AddonCommRegistrar.channels[channelname]["Sample addon"] = nil if not next(AddonCommRegistrar.channels[channelname]) then -- Leave the channel LeaveChannelByName(channelname) end
Leadership Changes
When the raid or party leader changes, each addon that uses the CommRa* channel should check to see if the switch has been made yet, if not it should perform the switch. Also it should be noted that leaving a channel has a slight delay, the best solution appears to be to delay 1 second between leaving and joining.
-- To be performed when the raid leader changes if AddonCommRegistrar.groupleader ~= newleadername then AddonCommRegistrar.groupleader = newleadername LeaveChannelByName("CommRa".. oldleadername) -- Delay 1 second, by some means... JoinChannelByName("CommRa".. newleadername) end
Message throttling
Message throttling has it's advantages, but the jury's still out on if the protocol should require throttling or not, further details and discussion can be found at Addon Comm Throttling
Chatframe Filtering
Here is a proposed hook for the ChatFrame_OnEvent to block all Comm text from ever appearing in the default UI's chatframes. Note that only the first addon to register should implement this (see sample in Registering a channel)
local filters = {"^commglobal$", "^commgu", "^commra"} local cf_oe = ChatFrame_OnEvent ChatFrame_OnEvent = function(event) if event == "CHAT_MSG_CHANNEL" then local chan = string.lower(arg9) for _,str in pairs(filters) do if string.find(chan, str) then return end end end cf_oe(event) end
Message Protocol
A standard protocol for messages sent should be used to assure addons do not unintentionally receive other addon's messages. The proposed standard is: "<Namespace>: <message>" <Namespace> is some unique token that a develop's addons use. Some devs may choose to share a token across their addons, like "Tekkub" or they may make it addon-specific like "CTRA". Handling of the <message> portion of the communication is entirely placed upon the receiving addon. This will allow for simple filtering of messages intended for other addons.
It is recommended that use of the chars 's' and 'S' in the <Namespace> section of the message be avoided, especially if the addon does not use a sobriety filter. For example, if the addon only communicates numbers the filter would not need to be implemented, it's just a wasted function in this case. Please see Addon Comm Sobriety Filter for possible implementations of this filter.