since atcp2 has came out for lusternia and achaea as well.. so i am trying to make it work but with no success. Athough i did study Nick's atcp plugin to see how he does communicate with the server.
anyway.. here the code(and it does look dirty for now..)
local CLIENT_ID = "MUSHclient " .. Version ()
local IAC, SB, SE, DO = 0xFF, 0xFA, 0xF0, 0xFD
local ATCP2 = 201
function OnPluginTelnetRequest (type, data)
Note("TYPE: " .. tostring(type) .. "\nDATA:" .. tostring(data))
if type== ATCP2 and data == "WILL" then
SendPkt(string.char(IAC, DO, ATCP2))
return
end
if type == ATCP2 and data == "SENT_DO" then
ColourNote("blue", "black", "attempting to enable ATCP2\n")
ColourNote("white", "black", "\n")
SendPkt(string.char(IAC, SB, ATCP2) ..
[[ Core.Hello { "client" : "Mushclient", "version": "4.51"} ]] ..
string.char(IAC, SE))
SendPkt(string.char(IAC, SB, ATCP2) ..
' Core.Supports.Set [ "Char 1", "Char.Skills 1", "Char.Items 1" ] ' ..
string.char(IAC, SE))
end
end
function OnPluginTelnetSubnegotiation (type, option)
Echo("Telnet Subnegotiation [ TYPE: " .. tostring(type) .. "\n" ..
"OPTION" .. tostring(option) .. "]")
end
telnetsubnegotation function is just there to see what server will send with all the cool stuff i am going to see.
so far when i tried to see what server will send after negotated with it. It didn't send any message with no degree of success on my part.
i learned something more about how to communicate with the achaea server.. it seem that i just need to ignore the next 'SEND_DO' otherwise i will end up responding the same handshake repeatedly.
i did it by letting my plugin respond when its connected then disable it then enabled it(after login) and got telnet subnegotiation afterward that displays vitals and all that
ok that was interesting... it seem that i managed to fix some bugs on my side and i managed to root up the bug that cause massive lag to the server by my mere plugin.
anyway, here is a workable code using GMCP(or ATCP 2) that you can play with in your plugin.. again, here dirty and a updated code:
<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE muclient>
<muclient>
<plugin
name="ATCP2"
author="Richard M"
id="29a4c0721bef6ae11c3e9a82"
language="Lua"
purpose="Richard's dirty GMCP plugin"
date_written="2010-03-09 10:04:32"
requires="4.50"
version="1.0"
>
<description trim="y">
<![CDATA[
]]>
</description>
</plugin>
<!-- Script -->
<script>
<![CDATA[
local CLIENT_ID = "MUSHclient " .. Version ()
local IAC, SB, SE, DO = 0xFF, 0xFA, 0xF0, 0xFD
local ATCP2 = 201
local ATTEMPTED = false
function OnPluginTelnetRequest (type, data)
Note("TYPE: " .. tostring(type) .. "\nDATA:" .. tostring(data))
if type== ATCP2 and data == "WILL" and ATTEMPTED == false then
SendPkt(string.char(IAC, DO, ATCP2))
return
end
if type == ATCP2 and data == "SENT_DO" and ATTEMPTED == false then
ColourNote("blue", "black", "attempting to enable ATCP2\n")
ColourNote("white", "black", "\n")
SendPkt(string.char(IAC, SB, ATCP2) ..
[[ Core.Hello { "client" : "Mushclient", "version": "4.51"} ]] ..
string.char(IAC, SE))
SendPkt(string.char(IAC, SB, ATCP2) ..
' Core.Supports.Set [ "Char 1", "Char.Skills 1", "Char.Items 1" ] ' ..
string.char(IAC, SE))
ATTEMPTED = true
end
end
function OnPluginTelnetSubnegotiation (type, option)
Note("Telnet Subnegotiation [ TYPE: " .. tostring(type) .. "\n" ..
"OPTION" .. tostring(option) .. "]")
end
function OnPluginTelnetOption (data)
Note ("Received option string ", tostring(data))
end -- function OnPluginTelnetOption
]]>
</script>
</muclient>
Keep up the good work. :) You'll need to use some kind of JSON parser to get at the data, though. LuaJSON is a decent pure-Lua library. Fast on decoding, but slow on encoding.
thanks... after i has fun with it, then i shall clean up codes and use it with PluginBroadcast to send the gmcp message to other plugins that will need it.
thanks... after i has fun with it, then i shall clean up codes and use it with PluginBroadcast to send the gmcp message to other plugins that will need it.
fun fun fun.
As an alternative, you could use my PPI library and let other plugins register callbacks with your plugin. Then you can just convert the message data from JSON to a Lua table, then call the plugin callbacks with it.
That's not really a shameless plug. I just don't like using PluginBroadcast.
function OnPluginTelnetSubnegotiation (type, option)
local decodedtable
decodedtable = json.decode(tostring(option))
tprint(decodedtable)
this code seem don't work too well... The given error message is 'Invalid JSON data'.
Yes, that's because you're getting "Module.Message JSONdata" and you're passing all of that in, rather than just the JSONdata. You should split it at the space and take everything right of it.
okay this seem to be one of my few moment of 'doh' moment. :p
okay, i got it all working.. so far i noticed that char.vitals will always results in a string, not table. for score.status, items, etc usually results in table. interesting stuff!
Sometimes it's an object (table) or array, sometimes it's raw data. That's one of the things I don't really agree with in this protocol, since it makes it a little harder to deal with generically, and it depends on how strictly your JSON parser conforms to the JSON standard. The standard requires that all data be inside an array or an object (i.e. the top-level item), but some parsers - and note that this isn't really a bad thing, per se - allow any data type to be the top-level item.
The most cross-parser way to do it would be to check the first character of the JSON data. If it's anything but a [ or {, wrap the whole thing in [ ] first, then decode it, then get the [1] element of the resulting table. If your parser allows it though, it's not a big deal.
It comes with any of my ATCP-based plugins; I'll put it in a pastebin for you though [1]. I don't have much documentation on it, sadly, but you can get my ATCP plugin and any of the ATCP-based plugins to see how it works on both sides. [2]
Save it as 'ppi.lua'. I recommend using a structured-plugin approach, so you can keep the library packaged with the GMCP plugin. You can look at the ATCP plugin's structure, and see [3] and [4] for details.
Sometimes it's an object (table) or array, sometimes it's raw data. That's one of the things I don't really agree with in this protocol, ...
Yes and this is where I started tuning out of the design process. In order to "make it easier" the protocol does not actually send standard JSON.
According to the JSON spec (at: http://json.org/)
JSON is built on two structures:
A collection of name/value pairs. In various languages, this is realized as an object, record, struct, dictionary, hash table, keyed list, or associative array.
An ordered list of values. In most languages, this is realized as an array, vector, list, or sequence.
So, a message (from the GMCP spec) like this:
Core.Ping 120
The "120" isn't JSON (it is neither a name/value pair, nor is it an ordered list of values). Sorry guys.
Please remember as this plugin is currently in beta stage since i have few bugs to fix.. however, it is mostly good to go anyway! A example file can be found in "test_client.lua" file. That show the example how to listen to GMCP plugin(PPI required!).finally, a document regards about information on GMCP is under the document folder within zipfile.
Let me know if I am missing anything, post in this thread please. THANKS!
:)
I'm taking a peek at it right now. First thing I suggest, though, is changing GMCP_RGM.xml to plugin.xml. It's a bit more future-proof if every plugin has a plugin.xml, since you can have automated tools that go through any *.plugin/ folder to look for the plugin.xml. Otherwise you might (and do, I notice) have other *.xml files in the plugin, and it wouldn't be able to tell which one to use.
Minor nit for the test thingy: You can use require("tprint") instead of require("libraries.tprint"). It'll automatically look in the *.plugin/libraries/ folder first thanks to Plugger, and as a bonus it'll fall back to MUSHclient/lua/ if it doesn't find it there first. Since tprint comes pretty standard with MUSHclient, you could remove it from your libraries/ folder if you wanted.
EDIT: Hmm, interesting. Why did you copy libraries/plugger.xml into GMCP_RGM.xml? You can use an <include> tag to include Plugger, which would automatically call require("scripts.main") for you. (Which is another thing. Why is main.lua in the root plugin folder instead of scripts/main.lua? I mean, hey, if this is all working that's great. I'm just curious.)
EDIT 2: You can't use ReloadPlugin() to reload the calling plugin:
Documentation said: However, a plugin cannot reload itself. That would mean the script was deleted out from under itself.
EDIT: Hmm, interesting. Why did you copy libraries/plugger.xml into GMCP_RGM.xml? You can use an <include> tag to include Plugger, which would automatically call require("scripts.main") for you. (Which is another thing. Why is main.lua in the root plugin folder instead of scripts/main.lua? I mean, hey, if this is all working that's great. I'm just curious.)
I have a problem with unused tag, '$PLUGINDIR', a given error by mushclient. so that why i am forced to temporarily included the code of it to GMCP_RGM.xml. only temporarily solution until i find out why that tag don't work for me on my client. I assumed it might have worked for you somehow.
Minor nit for the test thingy: You can use require("tprint") instead of require("libraries.tprint"). It'll automatically look in the *.plugin/libraries/ folder first thanks to Plugger, and as a bonus it'll fall back to MUSHclient/lua/ if it doesn't find it there first. Since tprint comes pretty standard with MUSHclient, you could remove it from your libraries/ folder if you wanted.
duly noted, however, tprint wont work when i tried to require tprint from mushclient folder. so, that why i moved it to my gmcp's library folder. Furthermore, my GMCP plugin directory is outside the realm of mushclient directory and that is probably reason why it wont work. (I think).
Quote:
I'm taking a peek at it right now. First thing I suggest, though, is changing GMCP_RGM.xml to plugin.xml. It's a bit more future-proof if every plugin has a plugin.xml, since you can have automated tools that go through any *.plugin/ folder to look for the plugin.xml. Otherwise you might (and do, I notice) have other *.xml files in the plugin, and it wouldn't be able to tell which one to use.
good point, i will rename it then. I will try to upload this GMCP plugin to Github. Eventually.
Huh. What version of MUSHclient are you using, and what error does it give you? Also, do any of my plugins have that issue? They all use Plugger, too.
I just moved some stuff around and added the Plugger include tag back in, and it installed fine. (The plugin also works fine, so kudos there too! Haven't had a chance to muck with it more though.)
Minor nit for the test thingy: You can use require("tprint") instead of require("libraries.tprint"). It'll automatically look in the *.plugin/libraries/ folder first thanks to Plugger, and as a bonus it'll fall back to MUSHclient/lua/ if it doesn't find it there first. Since tprint comes pretty standard with MUSHclient, you could remove it from your libraries/ folder if you wanted.
duly noted, however, tprint wont work when i tried to require tprint from mushclient folder. so, that why i moved it to my gmcp's library folder. Furthermore, my GMCP plugin directory is outside the realm of mushclient directory and that is probably reason why it wont work. (I think).
Hm. Probably not, I'm pretty sure MUSHclient sets the default require() paths to be relative to the MUSHclient executable. Interesting, though.
Maxhrk said:
Quote:
I'm taking a peek at it right now. First thing I suggest, though, is changing GMCP_RGM.xml to plugin.xml. It's a bit more future-proof if every plugin has a plugin.xml, since you can have automated tools that go through any *.plugin/ folder to look for the plugin.xml. Otherwise you might (and do, I notice) have other *.xml files in the plugin, and it wouldn't be able to tell which one to use.
good point, i will rename it then. I will try to upload this GMCP plugin to Github. Eventually.
Awesome! I was going to suggest that. :)
EDIT: Do you mind if I mess around with the plugin's internals a bit and send you what I change?
Very minor nit, but I've seen it said in several places that Lua is generally indented with 2 spaces instead of tabs. Generally I have to agree, especially since Notepad expands the things to 8 spaces, which is like whooooooooa. *laughs*
I don't honestly expect you to change your habits on my whim, but I thought I'd point it out. :)
heh. :) i am using Notepad++ at the moment, i will look into the config about that one.
okay, now that i have created repo of gmcp.plugin and it is now available at GITHUB:
http://github.com/druuimai/gmcp.plugin
cheer.
EDIT: for some reason the include tag now worked... weird anyhow. it could be my computer teasing me, mushclient teasing me, or a devilishly imp trying to mess with me. :p
I found a protocol issue. It apparently works, but strictly speaking it's breaking the spec. There are to be no spaces immediately after IAC SB GMCP, or immediately before IAC SE. You have spaces on both sides of the Core.Hello and Core.Supports.Set messages in OnPluginTelnetRequest.
Another issue is that you have MUSHclient's version hardcoded in the Core.Hello message. You can use Version() to get the current version.
Maxhrk said: okay, now that i have created repo of gmcp.plugin and it is now available at GITHUB:
I found a protocol issue. It apparently works, but strictly speaking it's breaking the spec. There are to be no spaces immediately after IAC SB GMCP, or immediately before IAC SE. You have spaces on both sides of the Core.Hello and Core.Supports.Set messages in OnPluginTelnetRequest.
Another issue is that you have MUSHclient's version hardcoded in the Core.Hello message. You can use Version() to get the current version.
Maxhrk said: okay, now that i have created repo of gmcp.plugin and it is now available at GITHUB:
http://github.com/druuimai/gmcp.plugin
Awesome. :)
fixing.
EDIT: can you elaborate on by your example. I may be confused a bit by your explanation. For instance, you say that there are to be no spaces immediately after IAC SB GMCP. I see no problem in the code unless you are implying that i am to add '\n' after that?
Quote:
I found a protocol issue. It apparently works, but strictly speaking it's breaking the spec. There are to be no spaces immediately after IAC SB GMCP, or immediately before IAC SE. You have spaces on both sides of the Core.Hello and Core.Supports.Set messages in OnPluginTelnetRequest.
You've got a function UnListen, but the PPI.Expose call is referring to Unlisten (lowecase L). Personally, I prefer the lowercased name, but either way, it's not getting exposed.
No need for an intermediary function, since all you're doing is calling it yourself with the same arguments. Just pass the function itself. (I changed the lowercase things in that snippet, so careful with copying.)
EDIT: I'm not sure we need an ATTEMPTED variable either. If the server asks you, you should respond. Also, MUSHclient handles negotiation loops so we don't have to.
You've got a function UnListen, but the PPI.Expose call is referring to Unlisten (lowecase L). Personally, I prefer the lowercased name, but either way, it's not getting exposed.
No need for an intermediary function, since all you're doing is calling it yourself with the same arguments. Just pass the function itself. (I changed the lowercase things in that snippet, so careful with copying.)
okay thanks for a tip.. By the way, I have edited my previous post if you havent seen it changed that i have raised a question regarding your protocol problem.
Maxhrk said: EDIT: can you elaborate on by your example. I may be confused a bit by your explanation. For instance, you say that there are to be no spaces immediately after IAC SB GMCP. I see no problem in the code unless you are implying that i am to add '\n' after that?
Lets assume IAC = 1, SB = 2, GMCP = 3, and SE = 4. This is what you have:
okay i have fixed the several problem that you raised and pushed it to github now... ah right i forgot i should test it before i push it. lol.. checking..
EDIT:
repushed. corrected a typo 'version()' to Version(). bah.
oh well i was too hopeful for a simple example on client like this:
local dummy_service = PPI.Load(required_plugin)
dummy_service.hello() -- that should invoke that function to print 'hello' to the world!... or not.
oh well! :p
Yeah, unfortunately it's not possible. You can't guarantee that one plugin will load completely before another, so you have to say "Hey, when this plugin loads, tell me." PPI handles this with OnLoad, and you just call PPI.Refresh() from OnPluginListChanged so it knows when things change.
Closest you can get is:
PPI.OnLoad(required_plugin, function(plugin)
plugin.hello()
end)
function OnPluginListChanged()
PPI.Refresh()
end
At the risk of sounding like a broken record, would you mind not calling it GMCP? IRE somewhat appropriated that name for a community project then turned around and closed it, so it's more polite to just keep ATCP2, which is after all what it is. "GMCP" as IRE knows it isn't really what the people behind the name had in mind. :-|
Okay, I've pushed up my lot of changes. Hope you don't mind!
It's hard to provide multiple plugins in one *.plugin folder (as I'm sure you noticed), so I removed the test XMLs and left the test Lua scripts in the examples folder. I'm not sure you need a test_service.lua file either, since GMCP -is- the service and users don't really need to see how to make their own.
At the risk of sounding like a broken record, would you mind not calling it GMCP? IRE somewhat appropriated that name for a community project then turned around and closed it, so it's more polite to just keep ATCP2, which is after all what it is. "GMCP" as IRE knows it isn't really what the people behind the name had in mind. :-|
I would normally, but IRE's calling it GMCP and this is a plugin aimed at IRE's players. It would add an element of confusion where it's not needed. =/
cause error because it it attempted to concentrate "?". turn out it's content[1] that is culprit. As i guessed because you thought it was table which turn out to be a mere string after it is encoded.
So both are fixed and I will push latest code to github. After this post. :p
Yeah, actually I made a fix to that and I forgot to push it. Wrapping with {} is actually important, because it keeps things within the JSON spec. That is, the specification doesn't allow top-level values that aren't objects or arrays. ["Foo"] is valid, but "Foo" isn't. My intention was to serialize the Lua table {"Foo"} to become the JSON ["Foo"], then remove the [] from the ends, which is precisely the opposite operation to what I'm doing in OnPluginTelnetRequest.
CMUD also calls this GMCP, so ignore the naysayers that continue to call this protocol IRE-only. The JSON-based transport allows any MUD to create any packages they want. All IRE has done is define their own set of packages. As a data transport, GMCP is still the "Generic MUD Communication Protocol" and has nothing in it that is IRE-only.
Glad to see the progress on making a GMCP plugin for MUSHclient. Good job.
Zugg said: All IRE has done is define their own set of packages.
No, that is not true. Among other things, they fixed the transport mechanism, which is in fact not JSON but some variation of it, despite the fact that it was being actively discussed. You also made a few executive decisions of your own, such as killing binary data or at the very least making it inconvenient, again despite the fact that there was not agreement and some strong opposition.
Please do not dismiss and insult people, as you have been doing here and elsewhere, simply because they do not agree with you. It's ok to disagree, but it's not ok to insult people because of disagreement.
Sorry for the simplistic question, but I'm just trying to "get off the ground" with this plugin. I've got it loaded and I'm browsing through the code. Is there a simple script I can run which will just continuously dump all the incoming gmcp data to the screen or something like that? At this point I'm primarily interested is seeing exactly what the messages from IRE look like.
This is a small plugin that negotiates ATCP2, and displays what it receives. You can see the general idea of the protocol here.
To save and install the ATCP2_Logger plugin do this:
Copy the code below (in the code box) to the Clipboard
Open a text editor (such as Notepad) and paste the plugin code into it
Save to disk on your PC, preferably in your plugins directory, as ATCP2_Logger.xml
The "plugins" directory is usually under the "worlds" directory inside where you installed MUSHclient.
Go to the MUSHclient File menu -> Plugins
Click "Add"
Choose the file ATCP2_Logger.xml (which you just saved in step 3) as a plugin
Click "Close"
Save your world file, so that the plugin loads next time you open it.
<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE muclient>
<muclient>
<plugin
name="ATCP2_Logger"
author="Nick Gammon"
id="4a33087c6b66ca6c4611afed"
language="Lua"
purpose="Displays ATCP2 info"
date_written="2010-07-14"
requires="4.40"
version="1.0"
>
</plugin>
<!-- Script -->
<script>
<![CDATA[
require "json"
require "tprint"
local IAC, SB, SE, DO = 0xFF, 0xFA, 0xF0, 0xFD
local ATCP = 200
local ATCP2 = 201
function Send_Telnet_Packet (what)
SendPkt (string.char (IAC, SB, ATCP2) ..
what ..
string.char (IAC, SE))
end -- Send_Telnet_Packet
function OnPluginTelnetSubnegotiation (msg_type, data)
if msg_type ~= ATCP2 then
return
end -- if not ATCP2
ColourNote ("darkorange", "", data)
message, params = string.match (data, "([%a.]+)%s+(.*)")
if not message then
return
end -- if
if not string.match (params, "^[%[{]") then
params = "[" .. params .. "]" -- sigh
end -- if
local t = json.decode (params)
if type (t) == "table" then
tprint (t)
end -- if
end -- function OnPluginTelnetSubnegotiation
function OnPluginTelnetRequest (msg_type, data)
if msg_type == ATCP2 and data == "WILL" then
return true
end -- if
if msg_type == ATCP2 and data == "SENT_DO" then
Note ("Enabling ATCP.")
Send_Telnet_Packet (string.format ('Core.Hello { "client": "MUSHclient", "version": "%s" }', Version ()))
Send_Telnet_Packet ('Core.Supports.Set [ "Char 1", "Char.Skills 1", "Char.Items 1" ]')
return true
end -- if ATCP login needed (just sent DO)
return false
end -- function OnPluginTelnetRequest
]]>
</script>
</muclient>
The caustic voice of Pandemonium clamours from the heavens as He shouts, "If My
heritage is all that You can think to disparage, then We will soon see the
continent drown beneath Your righteous ichor! To battle! To War!"
The plugin uses the Lua JSON module to decode the JSON. If you don't have that, just omit the couple of lines that do the decoding (although you will probably want to have it, to make more sense of the JSON code).
Ah - I see. That's exactly what I wanted (to be able to see the atcp2/gmcp output from IRE). Once I restarted the "world" and fed in the "#gmcpdebug" command, I started seeing output. More than one way to skin a cat I see.
Ah - I see. That's exactly what I wanted (to be able to see the atcp2/gmcp output from IRE). Once I restarted the "world" and fed in the "#gmcpdebug" command, I started seeing output. More than one way to skin a cat I see.
Thanks so much for your help.
-C
If you want to do this the other way around and see exactly what the *server* is receiving at both byte level and JSON level, feel free to use the information on the following link:
I hope this is the best place to ask this question and voice my concern.
Maybe I am not understanding this correctly, but let me try to explain
Ok, in the GMCP spec, you can Enable Modules. Do you have to send an updated full list every time with new modules you want added, or can you just enable the new module and the server keeps all the other modules that were previously enabled in the same state?
Also, my concern is when multiple plugins use the same modules. In OnEnable/OnInstall, the plugin asks to enable a module, then in OnDisable/OnClose, the plugin asks to disable the same module. What happens to the other plugins that need this data? Do we have to keep up with what modules have been requested on the client side with a counter and then when that counter gets to 0 disable the module? I ask this because I like to save bandwidth whenever possible and I don't want modules enabled that aren't needed or the plugins that used them are not enabled any longer.
Bast said: Ok, in the GMCP spec, you can Enable Modules. Do you have to send an updated full list every time with new modules you want added, or can you just enable the new module and the server keeps all the other modules that were previously enabled in the same state?
According to the IRE GMCP spec [1], you can use Core.Supports.Add to achieve this.
Bast said: Also, my concern is when multiple plugins use the same modules. In OnEnable/OnInstall, the plugin asks to enable a module, then in OnDisable/OnClose, the plugin asks to disable the same module. What happens to the other plugins that need this data? Do we have to keep up with what modules have been requested on the client side with a counter and then when that counter gets to 0 disable the module? I ask this because I like to save bandwidth whenever possible and I don't want modules enabled that aren't needed or the plugins that used them are not enabled any longer.
Some kind of reference counting would be good. For each module, keep track of which plugins have enabled the package. When you cross from 0 to >1, send Core.Supports.Add. When you cross from >1 to 0, send Core.Supports.Remove.
According to the IRE GMCP spec [1], you can use Core.Supports.Add to achieve this.
Looks like we're already into implementation differences. On Aardwolf, once you turn an option on it stays on until you turn it off. Turning on another option does not reset everything else.
In terms of the reference count, it would be relatively easy to add the reference count mud-side so that this doesn't have to be solved in every client separately, although this might be too far away from the base GMCP spec.
According to the IRE GMCP spec [1], you can use Core.Supports.Add to achieve this.
[1] http://www.ironrealms.com/gmcp-doc
Ok, I did not see this because I thought the spec was only in the mudstandards forum and the Core spec in that forum did not include the Core.Supports.Add. Thanks for the link.
Aylorian said:
Looks like we're already into implementation differences. On Aardwolf, once you turn an option on it stays on until you turn it off. Turning on another option does not reset everything else.
In terms of the reference count, it would be relatively easy to add the reference count mud-side so that this doesn't have to be solved in every client separately, although this might be too far away from the base GMCP spec.
According to the IRE GMCP spec [1], you can use Core.Supports.Add to achieve this.
Looks like we're already into implementation differences. On Aardwolf, once you turn an option on it stays on until you turn it off. Turning on another option does not reset everything else.
Presumably they expect .Set to be used once at the start, and .Add for anything else later on. I agree that just having .Add would have been simplest.
Aylorian said: In terms of the reference count, it would be relatively easy to add the reference count mud-side so that this doesn't have to be solved in every client separately, although this might be too far away from the base GMCP spec.
Hmm... I'm not sure I agree, honestly. What if client-side issues cause one plugin to die without .Remove-ing its dependencies? The client is in the better position to handle this and other things.
It's really not that hard to implement this kind of refcounting, either. This could be done in Lua extremely simply; here's an example table:
I don't see how either end can safely remove modules. The server won't know if the client has had a plugin crash, which stopped it removing a module. Or indeed the client might erroneously remove the module once too often.
But for the client to do it, every plugin would need to know exactly what every other plugin needs, which surely is far too much interdependency?
I would suggest that, if both the client and server are designed to cope with a certain number of message types (eg. room changes, battle information, inventory) then once requested, they should be sent until the connection is broken. After all, the player installing a module, and then removing it, but remaining connected for weeks, is surely an unlikely scenario? And if the system can't cope with that many messages, then you have a problem in the first place.
Nick Gammon said: But for the client to do it, every plugin would need to know exactly what every other plugin needs, which surely is far too much interdependency?
Ehm... no. The central GMCP plugin keeps track of what the others want, and the others only deal with themselves.
Nick Gammon said: I don't see how either end can safely remove modules. The server won't know if the client has had a plugin crash, which stopped it removing a module. Or indeed the client might erroneously remove the module once too often.
The central plugin should reasonably be able to tell, using refcounting, whether or not it should add or remove a module. With MUSHclient, you can use OnPluginListChanged to tell if a plugin disappeared. The only issue arises when you introduce multiple "central plugins", which is really more of a non-issue.
Nick Gammon said: I would suggest that, if both the client and server are designed to cope with a certain number of message types (eg. room changes, battle information, inventory) then once requested, they should be sent until the connection is broken. After all, the player installing a module, and then removing it, but remaining connected for weeks, is surely an unlikely scenario? And if the system can't cope with that many messages, then you have a problem in the first place.
The player doesn't touch GMCP. The plugins that use GMCP do, and they can enable/disable modules as they please. As a very off-the-cuff example: You might have a plugin tracking your stats and your inventory, and you could disable the inventory tracking feature, so it can disable the associated module.
I also don't think it's so much about the system as it is the network bandwidth being used. If you don't need it, why send it?
My point is that if you are so concerned about something that the player is using some of the time (eg. inventory) but it uses so much bandwidth that you must disable it the moment the player stops wanting to see his inventory (and when would that happen, I ask?) well aren't you on the cusp of having major problems if one more player joins the game?
Too little gain for too much pain, is my attitude to that.
If the modules themselves have implicit contracts when they're enabled - that is, if they expect you to do something with the transferred data, as opposed to being a passive recipient - then yes, you may very well want to disable a module.
I don't think it's painful in the least, and I do believe there is gain involved. It's a lot more flexible and future-proof. GMCP is not a closed standard; modules can be developed and used that we never thought of. It is in our best interests to stay flexible, and a refcounting scheme like the one I showed is very simple to implement.
Also, all this is purely client-side. If the client has no need for plugins, it doesn't need to use this. The .Add message is wholly opt-in. If you don't want it, don't use it.
Sure, if you don't need a group of messages, don't ask for them. But once requested, surely the case of changing your mind is infrequent?
Here is an analogy (not perfect, I know) ...
Say you ring up the Acme Electricity Company and ask to have the power connected to your house. Now if you didn't do that, you wouldn't have power. But having done it, you don't ring them back and say "ah, can you disconnect the power on Tuesday nights please? I go out then and don't need it". The extra work of disconnecting and reconnecting, and keeping track of when it is needed and when it isn't, is not worth it.
I'm not so sure about that analogy! If you're going away for a short time, you certainly may want to shut off your utilities until you get back.
And again, we don't want to make assumptions about the messages being sent. If we think of GMCP modules as TV channels, there are some you become interested in and want to subscribe to, but over time your interests change and you're no longer interested in watching QVC! ;)
Which raises the question: Do you have QVC in Australia? That analogy might not make sense if you don't...
Look, it simply hasn't been justified beyond abstract principle that modules need to be deactivated. Come up with any plausible example, anything, and we can talk. But thinking about things we never thought of, and creating non-trivial complexity as a result, is just not productive. If something can't be motivated beyond "we might need it but we don't know why", it's not worth pursuing.
New messages can be added, so if it comes to it, this can be added later and everybody will be happy.
It's not non-trivial! I already showed how simple it was to implement plugin refcounting to deal with plugins each having different needs. And the Core.Supports.Add message is purely opt-in. So if it bothers you so much, just don't use it.
It's unrealistic to say that a scheme that involves turning modules on and off based on various events prone to race conditions is "non-trivial" and "simple".
Let me turn this around to you: if it bothers you so much, implement it yourself as an extension, but don't force the spec to deal with it for you. You're creating a lot of work (even if you think it's easy, in this hand-waving stage of abstract principles) and you haven't even motivated anything yet beyond "oh, maybe, dunno, might need it".
David Haley said: It's unrealistic to say that a scheme that involves turning modules on and off based on various events prone to race conditions is "non-trivial" and "simple".
Ehm... what race conditions? If a plugin, for one reason or another, doesn't need the data anymore, you decrement the refcount. Once it's 0, you turn it off. If a module moves into positive numbers, you enable it. And MUSHclient is largely single-threaded, which makes thread-related race conditions a non-issue (for it at least).
The messages should only be sent from one place (i.e. one plugin). Other plugins should never .Add or .Remove them themselves. That's why the core plugin handles the Core module, see.
I haven't yet had a concrete example of some message, that might initially be enabled, but you then decide to turn off (your inventory list maybe?). And nor have I had it explained why, if you don't turn it off, and it is really important to, to save bandwidth, how the server will cope?
Let me put it this way, after spending quite a bit of frustrating time with this when we were doing optional messages in Aardwolf, where the server and client never seemed to be in sync, that I thought ... right! Next time:
Messages will be out-of-band (eg. telnet subnegotiation) so it doesn't matter if you get more messages than you need at this particular moment.
.... and ....
You get the lot.
If the server can't cope with every client getting every message, then there is fundamental design flaw. You can however reduce the bandwidth with caching.
I still find it kind of odd that the caching proposal was dismissed as "interesting, we might look into that one day in the distant future", but a lot of effort is being put into switching messages on and off (with the same overall concept of saving bandwidth). But, and this is the big but, the switching messages off only works if the player chooses not to get a particular message (maybe they don't care what is in their inventory?). However caching saves bandwidth all the time.
I couldn't help but smile as I read Nick's post just now. By that logic, there would be no issue to a persistent cache to help lower bandwidth across different sessions, but that on the other hand was shot down as being unnecessary.
Sorry for the derail. The amusement overtook me. :)
Lets take caching as an example. Do you remember my competing proposal, that removed the hash parameter from the other modules and relied on the 'id' to refer between them? It required that you take action on that incoming message by sending a 'get' with the provided hash, which would then respond with the cached data. And the cached data is not longer sent in other messages, if you recall, so you can't get at it immediately that way.
Now lets say you don't care about caching anymore. You can't just ignore the Cache module, because its presence is keeping the data out of the other messages. So you disable the cache module.
Worstje: What? Nick's caching proposal was persistent across sessions...
Quote: Now lets say you don't care about caching anymore. You can't just ignore the Cache module, because its presence is keeping the data out of the other messages. So you disable the cache module.
It's not useful to provide contrived examples of doing something silly that then breaks things. A much better question is: why would you actually do such a thing? Only once that question is answered is it worth spending any time whatsoever on how to deal with the consequences.
Because I'd like to keep the cache module from dropping a new parameter into other messages, as Nick's proposal does. Lets say you handle each module with its own callback/subsystem/whatever. I really don't want to add caching support to each one, thank you. Keep it consolidated in the Cache module, refer to each message by an ID which is already part of nearly every message, and keep the other subsystems clean.
I couldn't help but smile as I read Nick's post just now. By that logic, there would be no issue to a persistent cache to help lower bandwidth across different sessions, but that on the other hand was shot down as being unnecessary.
I'm hoping you agreed with me here. In my many posts I am sure I mentioned that once established, a cached item could survive for ten years. After all, the hash guarantees we are looking at the same data.
Twisol said:
It required that you take action on that incoming message by sending a 'get' with the provided hash, which would then respond with the cached data.
The competing proposal wanted you to request the cached data every session.
There were three proposals, if I remember. Yours, Worstje's, and mine (which was just a derivative of yours). Mine really just handled how the communication was done. How the data was cached after that was beyond the scope of my particular suggestion.
My bad, David. You are absolutely right. I'm not finishing my thoughts before expressing them. Nick's proposal did indeed have persistency. Persistency isn't the thing I wanted to laugh about, so.. uh, not sure how I got to write about that.
I meant the lack of support for high-performance caching that would allow caching right in the primary output stream itself, a feature that would be nice to have in a protocol but was not required to be implemented by either client or server. In the end, most data comes through the primary output stream, but nobody was interested in seeing caching as something completely seperate from other protocols like ATCP/GMCP and so forth.
In either case, the idea is to save more bandwidth, which was what Nick spoke about just now, and what got me laughing.
I couldn't help but smile as I read Nick's post just now. By that logic, there would be no issue to a persistent cache to help lower bandwidth across different sessions, but that on the other hand was shot down as being unnecessary.
I'm hoping you agreed with me here. In my many posts I am sure I mentioned that once established, a cached item could survive for ten years. After all, the hash guarantees we are looking at the same data.
I agree with the concept of caching completely.
I disagreed with your implementation, and your lack of interest in the features I put forth in the MDCP+ proposal despite them being very much optional. (I think you called them unnecessary, but it's been a while and I am too lazy to look it up or drag old cows out of the well.)
Sorry about that Worstje. After the, ah, robust discussions on the mudstandards forum I concluded that either my proposal was either much too early, or much too late, to be useful. I'm still not sure which.
You're forgiven. I never really blamed you in the first place. I gave up on the place myself not too long after.
You worked out your example with plugins and in conjunction with Lasher I believe. I worked it out in a rather wordy and specific RFC that doesn't appeal to people at all. I guess that was my mistake, even though I still hope the right few people will consider it or even implement it, even if it is just the very minimal version, since it is completely independant from other protocols.
Wow, I didn't realize the discussion this would start.
I think that you should be able to disable modules. I don't want to receive data that I will never use. Muds pay for every byte they send over the wire.
My use case scenario:
My portable MUSHclient for Aardwolf has a lot of plugins. If a user doesn't want to use one of the plugins, they disable it. Sure, the next time the client starts, those modules will not be requested but the client will be receiving that extra data until that happens. I personally know of several characters who never logout if at all possible. They log channels 24/7. A couple even use MUSHclient for this.
Bast said: If I don't need the data, then why send it OOB?
Just to clarify here, the data is actually sent right inside the normal text. The client is expected to strip it out so the user doesn't see it. The Telnet OOB "feature" is not used for this at all.
Bast said: My portable MUSHclient for Aardwolf has a lot of plugins. If a user doesn't want to use one of the plugins, they disable it. Sure, the next time the client starts, those modules will not be requested but the client will be receiving that extra data until that happens. I personally know of several characters who never logout if at all possible. They log channels 24/7. A couple even use MUSHclient for this.
If I don't need the data, then why send it OOB?
So... you're saying that all this work should be done only because people don't want to restart the client once after disabling a plugin because it uses a relatively small amount of extra bandwidth? I'm sorry, but doesn't that sound just a little bit silly? Fine, they want to stay logged in 24/7 once they get running. OK, but is it so unacceptable that the first time they start up and disable some clients, they need to reconnect?
Bast said: Muds pay for every byte they send over the wire.
Unless you meant this figuratively somehow, hosting packages that charge per byte sent are rather unusual.
David Haley said: OK, but is it so unacceptable that the first time they start up and disable some clients, they need to reconnect?
Yes. As I've shown with my quite concrete caching communications, having such a Cache module active changes what the MUD sends from other modules. Being able to say "I'm done with this module" makes it very easy to disable caching. As such, other modules with side-effects are hardly inconceivable.
I've also shown - with code! - how easy it is to refcount the plugins that are using each module. I'd appreciate a concrete example of why you think this is so hard. Waving your hand and mentioning something about race conditions doesn't help either of us.
If a user doesn't want to use one of the plugins, they disable it.
I think they wouldn't install it if they didn't want it, yes?
I still have to be given a concrete example of a message that:
Would be initially sent (eg. by a default client configuration)
A lot of players would decide they didn't want
Of these players that decide they don't want to use it, they would also not disconnect from the MUD for a very long time (and the MUD doesn't time them out for inactivity)
The message itself uses so much bandwidth that disabling the sending of it saves a lot of network usage
What are we talking about here? Inventory? Combat messages? Chat messages? Spell information? Room descriptions?
I am hard pressed to think of a category of message that would fit into those criteria.
Remember, even without some fancy caching system, if you use MCCP, repetitive messages are likely to be compressed away quite significantly.
It's extra, unmotivated work, Twisol. That is what makes it "so hard" -- it's not useful.
Race conditions are easy: some module wants a plugin enabled around the same time another wants it disabled. One message goes through before the other and things get confused as to what the correct state is. Heck, you said yourself: "As such, other modules with side-effects are hardly inconceivable." It is precisely these side effects that you're concocting that make this problem non-trivial. If we adopt your position of basically anything being possible, the problem becomes more and more annoying.
Are these unsurmountable problems? No. Are these problems that nobody has ever solved before? No. Are these problems that would take weeks to solve? No.
But: Are these problems that are actually worth solving? No.
Unless you meant this figuratively somehow, hosting packages that charge per byte sent are rather unusual.
I think David is right here. Generally you pay (say) $30 per month and get a cap of how many gigabytes you can use up. They don't charge, like, a cent per megabyte.
Can I point out that you guys have been arguing for 2.5 pages about something that should/shouldn't be in the spec, and not how to actually implement the spec or a plugin that conforms to the spec?
Nick, I see why you got disgusted with the mudstandards forum discussion.
Can I point out that you guys have been arguing for 2.5 pages about something that should/shouldn't be in the spec, and not how to actually implement the spec or a plugin that conforms to the spec?
Nick, I see why you got disgusted with the mudstandards forum discussion.
While I can sympathize with your frustration, it is somewhat bizarre to be surprised that part of designing standards is discussing, um, what goes into those standards.
As far as I'm concerned these are all still open issues for some spec; whatever IRE/Zugg decide to do with ATCP2 is their affair.
David Haley said: Woe be unto you the next time you stray from a topic to discuss an issue that interests you, Twisol. ;)
Hah, I'm just pointing out that this topic is mainly relevant to IRE's brand of the protocol, so it's not as much arguing for a feature as it is arguing against its removal.
David Haley said: PS let's not perpetuate the idea that it's actually a "community effort"; it's not.
Yes, as I said, the ATCP2 thing is IRE's, and you've reserved the name GMCP for a community effort.
Unless you meant this figuratively somehow, hosting packages that charge per byte sent are rather unusual.
I think David is right here. Generally you pay (say) $30 per month and get a cap of how many gigabytes you can use up. They don't charge, like, a cent per megabyte.
Every byte costs against that cap. Once someone goes over the cap, then they are going to pay extra. I know with MCCP, there is a savings when sending the same data multiple times, but I still think it is a valid issue. I don't know why people think that bandwidth is unlimited even for a text mud. The MMO games cache, or include in their client, as much data as they can and send as little data over the wire as possible. I admit, I have never run a mud where I had to pay for bandwidth, so maybe I am over thinking this.
David Haley said:
So... you're saying that all this work should be done only because people don't want to restart the client once after disabling a plugin because it uses a relatively small amount of extra bandwidth? I'm sorry, but doesn't that sound just a little bit silly? Fine, they want to stay logged in 24/7 once they get running. OK, but is it so unacceptable that the first time they start up and disable some clients, they need to reconnect?
I am saying exactly that, this is users we are talking about. They are capable of anything and frequently do things or ask for things I never thought of. Some users are knowledgeable, some are not, some are silly, some are serious, but I never take for granted what a use case will be for anything I create.
All this work? It takes some extra code that surely would be tested, but once implemented, would likely be able to be used forever. You talk about race conditions, but really, they would happen about as often as my user scenario. And if it did happen, you reload whatever plugin isn't getting its data and boom, back in business. My scenario, the user has to restart the client. Of course, the next argument will be that if you don't do refcounting and don't care that extra data is being sent, then neither of these will ever happen. Saving bandwidth is always valid imo.
If I don't want the extra data, then don't send it to me.
My opinion on things like this is simple: there has to be symmetry. If you can opt-in, you need to be able to opt-out afterwards in a manner that mirrors the way you opted in. Disconnecting to opt out, or having to reconnect to opt in, or whatever, that's all the pillars of a crappy design.
With a good design, opting in and opting out would be possible whenever. Assuming that IRE has their ATCP2 protocol designed and implemented without the involvement of hallucanogenic substances and with the presence of sanity, they should have said symmetry.
With that as a given, you people could have implemented the few lines necessary for reasonably foolproof support of said symmetrical doodah at least five times in the time you've spent arguing the merits and necessities. Just my EUR 0.02. :)
(I haven't got a clue who 'you people' might be. As I said: TL;LT.)
Worstje said: Assuming that IRE has their ATCP2 protocol designed and implemented without the involvement of hallucanogenic substances and with the presence of sanity
They certainly took leave of their senses in one instance:
Quote: - Comm.Channel.Start
* informs the client that text that follows is something said over a communication channel
* message body is a text containing the channel name
* for tells from/to another player, the channel name is "tell Name"
* Example: Comm.Channel.Start "ct"
* Example: Comm.Channel.Start "tell Player1"
- Comm.Channel.End
* ends a channel text started by Comm.Channel.Start
* message body is a text containing the channel name
I.e. it sends an ATCP2 message, followed by regular text, followed by an ATCP2 message, and they expect you to treate that regular text differently. They're using this like it's MXP. I can't even begin to figure out how to implement this in MUSHclient, because we're handling all ATCP2 messages before we start handling the data. Interleaving the handling would practically require core client support.
I e-mailed Jeremy and told him and Tomas about this, and suggested an alternative that sends the content inside the message. Response:
Quote: I will look into the possibility of adding a message like this, which would be sent along with the existing Start/End ones.
And I tried so hard to be nice in my previous post. Ugh. Well, at least they are trying for a change.
Arguably, one might say MUSHclient does it wrong. Think of protocols like MCCP which also start affecting the output at a specific position.
Then again, I'm quite sure it is considered OOB data. Meaning that the position of the OOB data in the primary stream is not meaningfully bound to other information in said primary stream.
Worstje said: Then again, I'm quite sure it is considered OOB data. Meaning that the position of the OOB data in the primary stream is not meaningfully bound to other information in said primary stream.
Yes, exactly. It has no reliable context with the normal output and shouldn't be used that way. =/
MCCP's whole purpose is to change the data being received, and it operates at a lower level than telnet. Plus, it's such a common protocol that you don't really need to worry about writing it as a client plugin. It's not a limited-domain protocol.
I.e. it sends an ATCP2 message, followed by regular text, followed by an ATCP2 message, and they expect you to treate that regular text differently. They're using this like it's MXP. I can't even begin to figure out how to implement this in MUSHclient, because we're handling all ATCP2 messages before we start handling the data. Interleaving the handling would practically require core client support.
I e-mailed Jeremy and told him and Tomas about this, and suggested an alternative that sends the content inside the message.
Set a flag that the next line of text received is really ATCP channel data and hope you don't lose connection in the meantime?
Mind if I ask what the suggestion you emailed is? I haven't implemented comm.* for Aardwolf yet, so in the interest of considering as many options as possible before hitting the code it could be interesting to see.
I don't see why this needs to be more complex than something like:
To turn channels on/off - from client:
comm.channel { "channel": "chat", "status": "on" }
From Server:
comm.channel { "channel": "chat", "player": "Lasher", "msg": "This is a test message" }
Now this is the 50,000 foot view and I haven't taken a closer look (which usually reveals more complexity), but isn't that basically it?
Note that Aardwolf doesn't have the ability to see everyone else on a channel so we don't have to deal with that. The IRE way of doing the channels list seems fine so if everything else is equal, might as well be consistent on that part.
I'll leave on-the-fly IRC type channels out of this for now, main reason being they don't exist yet :)
Telnet subnegotiation is Out-Of-Band. It cannot say anything about the things following or preceding it, since it is part of a different layer of processing.
ATCP(2)/GMCP are supposed to be used for information that does not affect the surrounding text. It is supplemental information, fully for scripted interpretation that is not seen by the user in that form. It is NOT a markup language.
On the other hand, you have Ansi and MXP. Those are injected _directly_ into the primary textstream, modifying it in the process (which is a matter ATCP(2)/GMCP do not do as they are designed to solve a different problem). Think of the escape sequences and rather famous <font size=100 face="wingdings"> tags zmud users like to mess around with. Since they are a part of the primary text stream, they can immediately affect/involve their surroundings.
My terminology is probably a bit off, but I am sleepy and not really interested in being 100% correct. As long as the idea makes it across. :)
Edit: And your really basic overview seems to be just fine. It has the message, and does not depend on the actual textstream. So maybe I understood and you _did_ get it. Oh well, typed a lot for nothing then. :)
I can't speak for other clients (and, strictly speaking, I can't officially speak here), but MUSHclient parses a packet in two separate stages: first it parses and strips the IAC sequences in a packet, then it parses the rest. So the "last" IAC sequence in a packet will be parsed before the "first" line of normal output is handled by triggers. It basically treats the stream as two distinct, multiplexed data streams, and deals with one before the other. No context is shared between them.
I can see how a single-pass parser can be written that would solve this issue, but it would be a wholesale rewrite of the current implementation. Clearly, if IRE continues with their Begin/End messages, they have a single-pass parser. Multi-pass is arguably more intuitive and/or obvious to implement, though.
At any rate, here's the e-mail I sent to Jeremy and Tomas including my proposal:
Quote: For me, the biggest issue is that the Comm.Channel.(Start|End)
messages rely on the context of the visible output stream. At least in
MUSHclient, subnegotiations in a packet are handled before the data
goes through triggers. It's effectively a context-less information
channel. Unfortunately, the Comm.Channel.(Start|End) messages enclose
visible data like an MXP tag does.
The GMCP plugin would have to intercept the packet itself and
practically implement its own telnet stack so it has all the
information available within the same context. This subverts the
client's own implementation, and is very bug-prone to boot. That's why
I'm calling it a "nightmare" to implement. It could be argued that
this is a defect in MUSHclient, but the separate-step handling of
subnegotiations seems like a fairly logical way to do it.
As an idea for an alternative approach, maybe you could have the
channel messages only sent when you enable them for a given channel,
and instead of sandwiching the output between two messages, send it
entirely within one message. I also suggest that the channel in the
visible output be toggleable separately, because I can easily imagine
someone wanting to use Comm.Channel without actually rendering the
lines elsewhere (i.e. just for data grabbing). Possible examples:
(sent by client)
Comm.Channel.Listen "clt9" (enables Comm.Channel.Message for this channel)
Comm.Channel.Gag "clt9" (removes this channel from the visible output)
(sent by server)
Comm.Channel.Message {channel="clt9", who="Soludra", message="Blah blah blah"}
The part of that proposal, and I know this is nitpicking, that I dislike is the use of "clt9" for a clan. Use the clan short names instead because the numbers can change more easily.
And, while I agree that the 'channel start' 'text' 'channel end' way is less effective than just 'channel text,' it can't be that hard to receive the start, set a flag, and handle the next text in whatever way you want, clearing the flag when you get the end message...
Larkin said: The part of that proposal, and I know this is nitpicking, that I dislike is the use of "clt9" for a clan. Use the clan short names instead because the numbers can change more easily.
Was just going based on the ATCP2 messages I was already receiving.
Larkin said: And, while I agree that the 'channel start' 'text' 'channel end' way is less effective than just 'channel text,' it can't be that hard to receive the start, set a flag, and handle the next text in whatever way you want, clearing the flag when you get the end message...
The fact is that there's no way to do that in the current implementation of MUSHclient, and that there is also nothing inherently wrong with how it's doing it now. Since ATCP(2)/GMCP is supposedly meant to transfer OOB (not the telnet kind) data, this one exception sticks out like a sore thumb and makes it very annoying.
In other words: Considering just those two messages, yeah, it's possible. Considering the protocol as a whole, it's extremely ugly. I'm not about to special-case my way though a GMCP implementation.
From Server:
comm.channel { "channel": "chat", "player": "Lasher", "msg": "This is a test message" }
I agree with Aylorian on this one. Mixing chat messages between the ATCP protocol and normal text seems to me to defeat the whole idea. Using a protocol as in the example above, it is easy to:
Identify chat messages (including the message text itself)
Display it or not
Display it in a different place (eg. a miniwindow, or separate window of some sort)
Display it in a different colour (or partly coloured, eg. player name one colour, message another colour)
Any triggers designed to match the message text can be applied reliably to the "msg" part.
Any triggers (eg. gags) designed to match the player name can be reliably applied
Separating out chat messages into "a chat message follows", "some random text" and "a chat message has ended" seems to defeat most or all of that.
Quote: It takes some extra code that surely would be tested, but once implemented, would likely be able to be used forever.
We could implement any number of things that, once implemented and tested, would be fine. The question is whether those are really the most productive uses of our time.
Another way to look at this is to measure the amount of trouble this will cause for users vs. the amount of trouble it costs to develop, test, deploy, etc.
I believe that the start tag, plain text, end tag method was used to avoid sending the chat text twice. I mean, getting the OOB message can be quite handy, but you still have to send the message in the normal stream, too. So, what's the point of getting the message via ATCP/GMCP at all when it's pretty much just duplicate information?
I believe that the start tag, plain text, end tag method was used to avoid sending the chat text twice. I mean, getting the OOB message can be quite handy, but you still have to send the message in the normal stream, too. So, what's the point of getting the message via ATCP/GMCP at all when it's pretty much just duplicate information?
As an implementation detail, I would hope that if I ask for a channel through ATCP2/GMCP, then I don't get it in the normal stream. I am asking for it so that I can present it to the user in another way.
Is it duplicate? The output text is likely modified by adding server-side wrapping to it, just to name something. The other part is that, assuming you do it on a line-basis, you still have mixed data on the same line. If you suggest putting the OOB messages as if they were some sort of styleruns... well, as I said before, you're using the wrong technology.
OOB = Out of Band. Not part of the main text stream, as to keep maximum compatibility and to 100% cater to the scripts that need to deal with said data. Once you base it on the output, there's stuff like different lines, ordering, wrapping which may or may not be intentional, and for that matter information that is totally spurious.
So is it necessary to double the information? In my eyes, no. Once you decide you don't want the stuff in the main text stream anymore, and in some sort of window, and decide to user the OOB messages for that, you can turn said channels off for that medium ICly as they're a completely seperately 'negotiated' affair.
Edit: So maybe that Comm thing needs another feature.... 'supplemental' or 'replacement', which says whether the messages ought to replace the standard text output, or be given as an extra thing.
There is precedent for duplicate data, primarily the prompt information. Enabling ATCP doesn't suddenly cause your prompts to disappear. For 100% consistency, ATCP should only ever supplement existing MUD text, not supplant it.
I use the configuration option in Lusternia that removes all newlines normally resulting from server-side wordwrapping, so that's not an issue. Color is also not an issue because triggers can already ignore that. So, what do I gain from an ATCP packet with channel messages? Probably nothing at all.
It's little issues like this that make standards groups go nuts, as we've all witnessed with this particular standard already. :)
So, what do I gain from an ATCP packet with channel messages? Probably nothing at all.
There are two main advantages with OOB data for me:
1. The player doesn't miss important messages because they happen to be in a note editor or similar.
2. The ability to send data in a format that lends itself well to machine parsing without spamming said data to the screen.
The first of these benefits does apply to channel communication too, although on some MUDs may not be an issue depending on what type of 'quiet' modes that MUD has available.
Other than this, I would agree that there is very little value in GMCP channels vs just capturing the main output. There are other ways around it of course, such as a "send channels even when in note mode" config option.
If you are going to make a design criteria "don't send duplicated data" then you are going to tie your hands somewhat. Or "minimize the bandwith" likewise.
Surely, if a new protocol doubles your player base, you won't mind if the bandwidth, per player, increases by maybe 50%?
After all, if your primary goal is to reduce bandwidth you could remove colour codes and send everything in monochrome (that saves something like 5 bytes per colour change). But then you might *lose* players if you do that.
My other comment is, if you just want to "mark up" existing text, without duplicating it, then that is what MXP can do quite well. For example:
That lets you pull out various parts of the message without sending the whole chat message again in another format.
However you will note that the marking up itself takes quite a bit of bandwith (eg. you need <player> ... </player> ) and this method makes it harder to decide to display the chat message in another window.
I was just postulating that the logic behind the start/text/end method may have been to reduce the duplication of data, even though there are other things already being duplicated. I have no idea what they were thinking when they made this whole GMCP thing, and it is a bit of a mess now.
I have avoided MXP like the plague because MUSHclient doesn't seem to like when you get non-MXP tags that seem to match the MXP format, such as our "guild secret" channels that are surrounded with angle brackets (i.e., <<Channel>> Person says, "Blah."). The codes being inline with the text means you then have to escape the text that appears code-like, just as HTML uses < and > to display < and >, but MXP doesn't account for that, or at least the implementations I've seen so far don't do it.
Quote: I have avoided MXP like the plague because MUSHclient doesn't seem to like when you get non-MXP tags that seem to match the MXP format, such as our "guild secret" channels that are surrounded with angle brackets (i.e., <<Channel>> Person says, "Blah."). The codes being inline with the text means you then have to escape the text that appears code-like, just as HTML uses < and > to display < and >, but MXP doesn't account for that, or at least the implementations I've seen so far don't do it.
MUSHclient enforces tag escaping whereas Zugg's software does not, and it tries to guess at what was meant. This means that many MUD developers have been lax -- and have been allowed to be lax by zMud -- in escaping angle brackets. This is not really MXP's fault as a protocol except insofar as it does not, strictly speaking, demand that all non-tag angle brackets be escaped (even though this is really the only reasonable behavior).
Since a normal < symbol is interpreted as the start of an element tag, you must use < to refer to a less-than symbol directly.
(My emphasis).
The problem actually is that zMud and cMud (as far as I know anyway) don't follow the spec (in this respect).
Since they don't follow the spec, lazy MUD developers have got away with not sending < as < and say "it works in zMUD so there must be a bug in MUSHclient".
My bad -- what the spec doesn't specify is what to do when the bracket is not escaped but not meant to be a tag. It doesn't say if it should be rendered, or discarded, or if an error should be thrown, ...
It is possible to make it work, but when an implementation gets into the wild which doesn't enforce the spec in this rather critical area, confusion has unfortunately reigned.
My bad -- what the spec doesn't specify is what to do when the bracket is not escaped but not meant to be a tag. It doesn't say if it should be rendered, or discarded, or if an error should be thrown, ...
My interpretation of "you must use" is that choosing not to use a demanded representation should not be allowed. Otherwise it would be worded as "you may use".
You are right though that the spec basically describes how to handle the "success" situation rather than the failure situation. For example it is silent (as I recall) on what do to with:
<b
MUSHclient reports an error, but the spec doesn't say whether or not to do that.
Just as an aside the GMCP spec is somewhat similar ...
http://www.ironrealms.com/gmcp-doc
It describes what to do if you get, say, Core.Hello, but not what to do if you get Core.Foo (which isn't defined).
Similarly the telnet subnegotiation says that a subnegotiation sequence should look like:
IAC SB x ... IAC SE
Putting aside the case of imbedded IAC IAC, it is silent on what to do if the final IAC is not followed by SE.
Nick Gammon said: My interpretation of "you must use" is that choosing not to use a demanded representation should not be allowed. Otherwise it would be worded as "you may use".
I agree with your interpretation.
And yes, many standards are unfortunately lax in specifying precise behavior. I think it has to do with people treating any detail-oriented discussion as "annoying nitpicking". (Of course, the devil is often in the details, as you find out later after pushing through a protocol that ignores them...)
Thanks for the details on the MXP spec. I did try to qualify my statement by saying that the implementations I've seen to this point have just been less than trustworthy, primarily IRE, and so now I'm not surprised to see the same sort of thing happening with GMCP.