Wowpedia

We have moved to Warcraft Wiki. Click here for information and the new URL.

READ MORE

Wowpedia
m (Reverted edit(s) by Kirkburn (talk) to last version by KirkBot)
 
(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 aparent that we need a community protocol for addon developers to use to standardize these comm functions.
+
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 supression.
+
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 Cladhaire and ckknight for this girthy beast.
+
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
   
function CheckSum(text)
+
CheckSum = function(text)
 
local counter = 1
 
local counter = 1
for i=1,string.len(text) do
+
local len = string.len(text)
  +
for i=1,len do
counter = counter * ((string.byte(text, i) + i) * 17)
+
counter = counter + string.byte(text, i)*math.pow(31, len- i)
end
+
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 supressed. To prevent duplicate processing a global registrar should be used to indicate if a channel is being filtered. Since one cannot effectivly unhook the filter can remain in place after established.
+
* 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.
time = 453278.329,
+
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 receiveing addon. This will allow for simple filtering of messages intended for other addons.
+
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.