[MUD-Dev] example custom protocol and its uses
Chris Gray
cg at ami-cg.GraySage.Edmonton.AB.CA
Fri Dec 18 22:26:49 CET 1998
Warning: Long. Skip to the last paragraph for my point. Only if you
don't agree with me, or want to learn more about an actual implementation
of some of this stuff, do you need to read the stuff inbetween.
The protocol I use between my server and a custom client (not yet
written except on the old Amiga version) is a fully binary one. Text
messages from the server to the client are just one type of message,
are are input commands from client to server. The client maintains
two windows: one a graphics window and one a text window. The graphics
window can be disabled by the user. The text window is used for normal
text input/output. Input happens in the bottom line, and input history
manipulation is available. Output happens in the rest of the window,
and there are scrollbars, etc. for output history. The text window is
also used by the built-in editor, which can be used to edit text
strings and MUD-language procedures. When an edit session is active,
the user can toggle between it and the normal text I/O. An external
editor can also be used at the user's option.
One of the other key types of messages is the 'effects' message. More
on that later.
Here are some of the messages from server to client:
set prompt
get a password
delete a cached MUD-language proc definition (someone else changed it)
effects (more later)
edit the passed string
edit the passed MUD-language proc
get a string via a pop-up requester
flush the indicated cached symbol (the definition is now invalid)
define a new client-cached effect
query the status of a given file on the client (the answer triggers
an asynchronous event on the server)
supply an updated player name (for logging purposes)
Here are some from client to server (direct reply needed)
request connection (sort-of - its more complicated)
create new character
lookup a symbol (when the client is parsing MUD-language code)
read a MUD-language proc header
supply a new MUD-language proc body to the server
request the server run the supplied proc
Here are some client to server message not requiring a direct reply
log a message
supply a user input command line
supply a user typed raw keypress (function key, keypad, etc.)
mouse-click inside a defined "region"
mouse-click of a defined "button"
ask permission to flush a cached effect
resize the text window
reply to server with entered password
toggle graphics on/off
toggle sound on/off
toggle voice on/off
toggle music on/off
request editing of the given MUD-language proc (server will send
a textual "pretty-print" of the proc)
editing of a string is done
editing of a MUD-language proc is done
run of a long-running effect is done
answer to a query-file request
So, for example when a client is parsing a MUD-language procedure that
the user has entered, it will send symbol lookup requests, waiting for
replies from the server. It will cache the results, until it exits
or is told to flush one by the server (the server doesn't keep track
of which client has which symbol cached - it just tells all of them
to invalidate it when needed). If the result is a MUD-language proc,
the client will request the header of that proc, so that it can check
calls to it. When the entire proc parses correctly, the client will
turn it into a byte-stream and send it to the server, asking the server
to add it to the world. The server will return the new unique ID for
the new procedure. (I'm omitted some details here, like ref-count updates)
Effects:
An "effect" is a little routine, written in a language with no real
definition, which is very simple, having only sequential execution,
subroutines, and 'if's based on the existence of files on the client.
The statements in an effect routine are things like commands to draw
a line, start the playing of a sound, start some music, move the drawing
pen, display a background image, display a sprite, add an icon, add
a clickable "button" to the display, add a clickable "region", define
an effects subroutine, etc. Only certain kinds make sense inside subroutines.
Effects are all stored in the client as subroutines, identified by a
unique ID. The server is aware of which effects each client has
cached, so that they need only be sent once per session. The client
does not cache them between sessions. I use these effects to draw
rough drawings of areas if the client has no image of the area. I also
use them for controls like the buttons, and for sound stuff. All of
this is triggered by "builtin" functions in the server, which are
native-code routines callable from the MUD-language. I think its time
for some examples:
This is a MUD-language subroutine which is called when trying to draw
a view of something that might need simple door representations. The
use of this could be inside an effects conditional that checks for a
full background image on the client to use instead of the drawing.
define t_graphics proc public HorizontalDoor()void:
if not KnowsEffect(nil, HORIZONTAL_DOOR_ID) then
DefineEffect(nil, HORIZONTAL_DOOR_ID);
GRMove(nil, 0.0, -0.01);
GRDraw(nil, 0.0, 0.021);
GRMove(nil, 0.0, -0.01);
GRDraw(nil, 0.03125, 0.0);
GRMove(nil, 0.0, -0.01);
GRDraw(nil, 0.0, 0.021);
EndEffect();
fi;
CallEffect(nil, HORIZONTAL_DOOR_ID);
corp;
HORIZONTAL_DOOR_ID is the unique ID for this effect subroutine, and
is created by a unique-id generator. Basically, if the active client
('nil') does not have a cached copy of this effect subroutine, the
code defines that effect routine and sends it to the client. In any
case, the effect routine is "called", resulting in a rough horizontal
door being draw at the current drawing position with the active pen.
All of this would be sent to the client inside an 'effects' request,
which are only flushed to the client when the current effect doesn't
fit in the buffer, or the input request (e.g. 'walk north') that
triggered this activity is completed.
The effect routine is thus only sent over once per client session,
but might be used many times. A fixed drawing for an area that used
the door routine could in itself be a routine cached in the client,
so that, in order to draw the picture, only a few bytes need to be
sent from server to client to trigger that effects routine.
The top level of an area drawing MUD-language procedure might look
like this:
define tp_streets STREETS_ID NextEffectId()$
define tp_streets proc drawStreets()void:
if not KnowsEffect(nil, STREETS_ID) then
DefineEffect(nil, STREETS_ID);
GSetImage(nil, "Town/streets");
IfFound(nil);
ShowCurrentImage();
Else(nil);
GSetPen(nil, C_DARK_GREY);
GAMove(nil, 0.0, 0.0);
GRectangle(nil, 0.4995, 1.0, true);
... for much more
Fi(nil);
EndEffect();
fi;
CallEffect(nil, STREETS_ID);
corp;
So, whenever we want to display the "town" area to the player, this
MUD-language routine is called (actually I attach it to all of the
room-objects in that area). The first time in a session, the whole
thing (and any effects subroutines it uses, such as the door ones) must
be sent over. After than, just a few bytes are needed to call it up.
So, even if the client has the required image ("Town/streets"), it
ends up with all the effects cached anyway.
Something else quite handy that can be done is to have MUD-language
procedures that examine the current location in order to automatically
produce a drawing of the room. None of these are at all fancy, and
currently they are all just bunches of polygons, ovals, etc. However,
with a good set of textures on the client, much more should be
possible, including 3D views. Note that none of this is even remotely
real-time!
So far, all of this effects traffic is fairly static. Consider this pair
of routines however:
define tp_streets BIRDS_SING_ID NextSoundEffectId()$
define tp_streets proc birdsSingOnce(thing client)void:
if SOn(client) then
SPlaySound(client, "birds2", 1, BIRDS_SING_ID);
IfFound(client);
Else(client);
FailText(client, "Some birds sing.");
Fi(client);
else
SPrint(client, "Some birds sing.\n");
fi;
corp;
define tp_streets proc parkBirds()status:
if Random(5) = 0 then
ForEachAgent(Here(), birdsSingOnce);
birdsSingOnce(Me());
fi;
continue
corp;
The second one is called whenever a character enters one of the
locations in the town park. One out of five times it triggers a call
that is done for each agent (PC or NPC) at that location. If the agent
is a PC, who is running the custom client, and who has sound enabled,
then an effect request is sent to that client to play a sound sample
of birds singing. If sound is off, or the sample is not available
on the client, a string is emitted into the normal text window.
Whenever a time-consuming effect like playing a sound sample is sent,
a unique-id is included with it. When the effect completes, the client
sends back to the server a message indicating that completion. Thus,
scenario code in the server can play sequences of sounds, change
sound volumes, etc. I use a repeat count of '0' to indicate that a
sound sample should be repeated until cancelled. That is handy for
things like a gurgling fountain. The cancellation is an effects message
sent by code when, e.g., the character walks away from the fountain.
Another class of effects can put mouse-clickable buttons up in the
graphics window. I tend to use the left half of the window for
current location views, and the right half for control buttons. Other
possibilities include areas for inventory display/control, equipment
usage control, etc. Then, when the user clicks on such a button, the
client sends that button's ID to the server, which triggers a call
to MUD_language code with that ID. Thus clicking on the 'N' button
is the same as typing 'move north', and is much easier for folks not
got at typing. I have whole hierarchies of buttons that can be called
up for the on-line building facility, making that facility accessible
to those not-so-good with keyboards.
Another class of effects is a mouse-clickable region. These are
invisible in the graphics window, but clicking in them sends a
message containing the region ID and the click position to the server.
I use these to allow clicking relative to the character cursor to
mean a movement request. I also use them to implement an editor for
character icons, all without having icon-editing hard-code in either the
client or the server.
Discussions in mud-dev and devmud have talked about other things that
can be done, such as popping up other windows, triggering calls to
dynamically loaded code or Java code, etc.
My point in all of this is that using a special protocol like this
can allow you to do a great deal of interesting things, all without
having to update the client or the server. I used a binary protocol,
both because it is more efficient, and because I found it easier.
By having the server (and any MUD-language code running on it) kept
informed of some user client settings, it can tailor output to those
settings. Thus, the MUD can handle text-only telnet clients freely
mixed in with custom-client users. My choice was to always send text
output to all clients, since that forced me to pay attention to it and
get it right. Actually, the graphics aspect of my system grew over time -
it started out with just text, so the text facilities were there from
the beginning, and so got used a lot.
Oh yes, by using a fairly concise protocol, the data volume is kept
quite low. This system is actually usable over a 1200 baud modem. A
typical internet connection is overkill (except for the latencies).
--
Don't design inefficiency in - it'll happen in the implementation. - me
Chris Gray cg at ami-cg.GraySage.Edmonton.AB.CA
More information about the mud-dev-archive
mailing list