GMCP negotiation in LDMud

A player on our MUD recently inquired about whether it would be possible for us to support GMCP, or the Generic Mud Communication Protocol.

I won't spend a long time going into back-history or preliminary details of how we'll actually use GMCP--we're still in the process of feeling the project out, but I had to do some digging to figure out just what exactly we needed to do, ourselves, so I figured I would make a quick post collecting the info for others.

The first step in this process will likely already be implemented for you if you're working on an established MUD, but I'll provide it nonetheless. In inaugurate_master of your master object (the object returned by efun::master()) you'll need to set up the H_TELNET_NEG driver hook (likely most of what you'll find in inaugurate_master is other calls to set_driver_hook which you can do like this:

1set_driver_hook(H_TELNET_NEG,"telopt_negotiate");

This indicates that any telnet negotiation requests received from a given interactive object will be passed off to the telopt_negotiate function in that object.

In our case, we were doing some forms of telnet negotiation already--if your driver hook is already set for H_TELNET_NEG, you should go looking for the indicated function in your player object (you can search for it with efun::function_exists(function, object, 3) if necessary). In our case, our player object inherits /obj/player/telopt.c which defines void telopt_negotiate( int action, int option, int *optdata ). Our handshake will involve us sending the client an IAC WILL TELOPT_GMCP (255, 251, 201) to which we expect the client to respond IAC SB TELOPT_GMCP <message> which when handed off to us by the driver hook will look like telopt_negotiate(SB, TELOPT_GMCP, <message>) where message will actually be an array of integers like ({69, 98, 110...}). Here's an abbreviated telopt_negotiate function:

 1void telopt_negotiate( int action, int option, int *optdata )
 2{
 3	string	type;
 4	int		n;
 5	switch( option )
 6	{
 7		case TELOPT_GMCP:
 8			if(action == SB)
 9			{
10				//may want to perform some actions
11				gmcp_input(optdata);
12				if(current_telopt[option]) return;
13				this_object()->set_temp("telopt/gmcp", 1);
14				current_telopt[option] = 1;
15			}
16			break;
17		default:
18			if( action == WILL ) {
19				if( current_telopt[option] ) return;
20				current_telopt[option] = 1;
21				binary_message(sprintf( "%c%c%c", IAC,DONT,option),3);
22			}
23			if( action == DO ) {
24				if( current_telopt[option] ) return;
25				current_telopt[option] = 1;
26				binary_message(sprintf( "%c%c%c", IAC,WONT,option),3);
27			}
28	}
29}

We're basically responding DONT or WONT to any request we don't recognize explicitly in the default, and setting a flag on the player to indicate their GMCP support. We also pass on any data they've sent us in their request to a function for storing the data. From here, we may either push data to the player via GMCP, or we can respond to their requests for data from a given module.

The GMCP data itself will generally come in the form of a Package.SubPackage , so you'll either need to build or install a json parser unless you're on a very new driver version supporting json out of the box. I'll leave handling this data to you for now, but I'll likely make a post in the near future detailing the rest of our implementation.

Discussing this elsewhere?
Enter 'link' for a markdown link
or 'tweet <message>' for a pre-populated Tweet :)
Want to subscribe? Enter 'rss' for a feed URL.
>