[MUD-Dev] mud client development systems

Sunny Gulati sunnywiz at radiks.net
Sun Dec 6 15:38:49 New Zealand Daylight Time 1998


This is a dump of my thoughts on a particular subject that makes my brain
drool. I'm hoping to find other people similarly interested; i'm also
interested in feedback.   This is long - like 380 lines. I started writing
in a non-technical mood.. it picks after the first 40-50 lines. 

[for the impatient, skip down to STATE OF AFFAIRS]

BACKGROUND

Ever since 1989, when I was sitting in the Dungeon Cafeteria in Friley
at Iowa State University, sharing lunch with my friend Xeno (Gary
Snethen), I've had a dream.  

It was a dream of creating a mud kinda like (back them) the Temple of
Kesmai, a multi-user 2D pay-by-the-hour environment that Gary would
play.

I had been hooked since the day that I saw him trying to take out a
dragon inside a dungeon.  He kept dodging the flames and hiding behind
walls and stuff.. it was awesome.  I wanted to write my own!

Well, I tried a couple of different things. 

In 1990, I had a 80286 with 640k of memory, a 1.2M floppy, turbo
pascal, and no hard drive.  I wrote a map caching/paging thing which
allowed lookups into a large virtual map - which was mostly stored on
disk, with the most recently used parts being kept in memory.  

That's about as far as I got - I had no idea how to go about writing a
driver - especially not in Turbo pascal.  I realize now that my
confusion was from a lack of socket libraries, lack of knowledge about
sockets in general, and not knowing the wonderful world of fork().

In 1994, a friend and I started redesigning our mud - (Vincent's
Hollow, mud.vhdev.com 1991, currently in the middle of a move,
temporarily at gatekeeper.e-markets.com 1991).  I revived some of my
ideas about a 2D world, by proposing a "wasteland", where higher level
characters could go and attempt to manage economies, using a 2D-based
setup kinda like Civilization (the board game).  

Well, we got the mud up and running, but the Wasteland was shelved. 

THE STATE OF AFFAIRS

in 1996(?) something wierd happened.  I heard about this game called
Ultima Online.  OH MY GOD.  They stole my idea.  THEY COULD DO IT
RIGHT!  How dare they?  YIPPEE!!... I had mixed feelings about the
subject.  (To this day i have not played Ultima Online.  After having
been god for 7 years on a mud, its hard to be just a player.)

Since then, it seems that mudding is making a comeback - of sorts.
Everybody is playing multiplayer online games.  But they're very
graphical, real world (2d + 3d) oriented.  The clients are
pretty.. uh.. optimized, proprietary, etc.  Not high-level - where one
can deal mostly in concepts.  (That's what brought me into muds.. I
have turned out to be a mostly high-level concept/design type
programmer.  I suck at hacking tons of C, but I'm awesome at Perl/Java
etc.  LPC was ahead of its time, and I really loved developing in it.)

[Note: there are clients out there like UO for Linux - i don't know
how much source is available.. but thats still a specific
implementation of a solution. See below to my dream]

MY OVERALL DREAM

>From most general to more specific, these are my dreams: 

1. I want to build a world where the players take part in the economy
   and law of the world, where socializing is looked towards. [that's
   a whole 'nuther topic, and doesn't require dream #2 to be
   implemented first]

2. I want to build a client/server/mudlib combination that can be used
   to implement an ultima-online type of world.  This is a very tall
   order. I can't do this myself.

3. I want to publish a framework/API/convention that makes it easy for
   OTHER people to build a client/server/mudlib combination, to make
   ultima-online type of worlds.  Make it open source :), see where
   the world takes it.


MY TECHNICAL DREAM

Okay, going to get down and dirty.  This is my first time spewing
these ideas into a document, everything else was scribbled on recycled
printer banner pages (while dodging a cat who insisted on being the
writing board.)

Im going to prefix portions of "spec" with "**". 

-- Use Telnet --

** Everything still depends on "telnetting" into a mud just as we do
nowadays.  I want to layer the client/server connections on top of
telnet.

That way, you don't have to write a new driver - you just
write new objects in your mud.  The driver mods can come later after
the framework is settled.

-- how exactly the client/server relationship works -- 

I'll do this by example.  Note that I don't remember LPC syntax
anymore.

  user joe has connected to the mud. he has requested to join a
  particular channel/chatline.  We're calling a function in the
  channel server that initiates the channel. we pass Joe in as an
  arg. 

   int setupChannel(object user) { 
      object chatwindow;
      chatwindow = user->newclientobject("VH-textwindow-1.0","chat"); 
      chatwindow->print("Welcome to the Chat Channel!\n"); 
      user->setenv("chatwindow",chatwindow);
   }

okay, so there's a ton of holes in here.  The point is, the server
simply opens up a chat window on the client, and addresses it like it
like any other object.  

Specifically:

** there's a server-side object that gets the method invocations, and
passes them (over telnet) to the client - probably using a special
character to begin/end an object message.

** The client sends back its responses via a special user verb. 
   (detailed further below)

Now, i'm going to run into latency problems like this - don't want the
mud to pause while waiting for the new chat window to open on Joe
user's client.  He might be running a *gasp* 386 or something.  (or by
today's standards, a Pentium 60.  Heh.)

I would like this system to be runnable over a 14.4 or 28.8 link.
Actually, i have my mom's laptop - its a 486/25 running win95.  If I
can get the client written in Perl/Tk, get it running on there,
connected to the mud via PPP on a nullmodem cable running at 19200 and 
it seems to work well, then I'll say i've designed a good system.

So, I would say that: 

** Method invocations are asynchronous, call-back type functions.

The standard I would use is:

** to do a call, do: object->func(callbackfunc, payload, args) (ie,
call_client(object,function,callback,payload,args...))

** to receive a callback, somethign like: 
callback(callerinfo,status,payload,args)

This would rewrite the code snipplet above to something like: 

   int setupChannel(object user) { 
      object chatwindow;
      object userkernel = user->getClientObject(); 
      int queryid = userkernel->newobject((:this object,"setupChannel2":), 
                                          user,
                                          "VH-textwindow-1.0","chat"); 
      // queryid is a query that is in progress.  we could save it for
      // killing the query later or something. 
   }

   int setupChannel2(int query, int status, object user, mixed arg)
   { 
      if (status == FAIL) { 
         tell_object(user,"sorry, no can do. your client does not support
blah");
         return; 
      } else { 
         // arg has the user's VH-textwindow-1.0 object object in it.  
         // specifically, it has the server side mirror of the object. 
         arg->sendMessage("Welcome to the Chat Channel!\n"); 
         user->setenv("chatwindow",arg); 
      }
    }

and later on, to send a chat message to that window, you just do: 

   user->getenv("chatwindow")->sendMessage(msg); 

Now, the astute reader will note ... many things. What I noticed was:

First off, the userkernel used above - I'm not so sure about
that.  that's a client/server shared object.  Its the bootstrap object
that all other objects get created and destroyed through.  Further
down in the explanation of how exactly communication occurs, i use a
different model for initiating conversations between server and
client.

Secondly:  I need to make sure async functions can chain to other 
async functions.  This means: every async function needs to be told 
where to return to - and it will have to payload this information to 
any other async functions it calls.  Might have to make that payloading 
a little more customized to make this easier. 

Thirdly: sendMessage is not an asynchronous function.

That's right.  Not all queries require answers.  

** I specifically see three types of queries:

** queries that don't need an answer.  No need for a callback on
   these.  I figure most things would be like this - "move object o to
   location x,y" - "display this line of text" - "update the who list"
   - "play this background music now" - "play this sound now", etc.

** queries that need one answer (like, create a new object-> gives you
   back the object).

** queries that, once started, will continue to send back answers from
   time to time, till they decide to stop (somehow). (like, some sort
   of event dispatch mechanism. - like a who list which you can click
   on someone's name to send them a personal message.) (actually, that
   would be better done as a client object that initiates a conversation
   with the server to send a message).

-- client side of things -- 

What would the client side of things look like?  Well, the abovementioned
VH-textwindow-1.0 object would look something like this (in perl)

  sub new { 
    .. do some sort of nifty wizardry.  I'm waiting eagerly for the Perl/Tk 
    book to come out from orielly. 
  }

  sub print { 
    .. do something to display something in my self. 
  }

What I'm trying to say:  It should look REALLY simple.  Easy for mass 
numbers of bozos to be able to work on them. 

-- actual client/server message passing -- 

What would the actual server/client interaction look like? 

I'm kinda thinking about something like TCP, but without the
acknowledgements:

** SERVER->CLIENT: "hey I want a connection to your object xyz,
   instance foom, and my socket id is 8192".  (different socket number
   for every connection)

** CLIENT->SERVER: "8192, i refuse your connection" 

or 

** CLIENT->SERVER "8192, acknowledge connection, my socket is 1283"

then, 

** SERVER->CLIENT "8192 to 1283, message "PRINT" args "Welcome to the
   Chat Channel" "

Both the server and client would have their own maps as to which socket
number points to which object.  

-- even more detail on message passing -- 

Here's what I envision a conversation looking like to someone with a packet
sniffer: 

S->C   ~8192 0000 NEW VH-textwindow-1.0 chat<CR>
S->C   You feel better. 
C->S   MCSL 0000 8192 NAK<CR>    
   -or- 
C->S   MCSL 1283 8192 OK<CR>
S->C   ~8192 1283 MSG PRINT Welcome to the Chat Channel \n<CR>
C->S   n<CR>
S->C   You travel north. <CR>
S->C   You are in a maze of twisty little passages, all alike. <CR>
S->C   > 
S->C   ~8192 1283 MSG PRINT Sunnywiz: Hey folks, what's up?\n<CR>

Note that:

** Regular mud stuff is interspersed with the communications
   protocol.

** Note that the client responds using a verb like MCSL (or something,
   anything, to differentiate itself.)  The portion of the client that
   takes what the user types will mask out that particular verb so the
   user cannot fake a response maliciously. 

** server/client socket numbers are PER CONNECTION, ie your MCSL can
   never come in on another user's socket by using the same socket number.

** Might have to modify the receive()(?) funtion to make special cases of
   any regular "~" characters (escape them somehow), and have a
   seperate driver function to send the server/client stuff.  But for
   now, i'll just have the client pass incorrect messages to the
   main telnet screen. 

I dont have any examples in here of queries with responses.  Here's how to
handle that: 

** rather than MSG, use "Q1 <id#> method arg arg arg"

** the response to that would be "RS1 <id#> stuff stuff"

** for multiple queries, use "QN <id#>"

** the response would be "RN <id#> stuff stuff" (several times), with
   a final "RE <id#>".


Also note that the server is initiating all the connections here.  I
would go beyond that:

** the client can initiate connections too, if it wants.  

Like you could have the client .rc file run a widget takes your username and
password.. then attempts to log you into the mud. 

Maybe a single client could be connected to multiple muds, but that's
beyond the scope of my "run-over-telnet" client here.

-- multiple author support -- 

Note the name "VH-textwindow-1.0".  

"VH" stands for the author.  (I apologize to any other muds who go by
those initials - for me VH has been Vincent's Hollow for 8 years now.)

"textwindow" is the name of the protocol you're aiming for.  

"1.0" is the version you're aiming for. 

Note that I say PROTOCOL.  I don't care if your client side object is
written in Java, C++, Perl, Tcl, Visual Basic, WHATEVER - you must
provide the interface that "VH-textwindow-1.0" stands for, which is a
single "PRINT" method.

umm.. forgive the case change.  I was just thinking SMTP, POP, etc
protocols.  Maybe uppercase would be a good idea at the protocol level
- and "print()" function on the mudlib can generate a "PRINT" message
at the lower level. 


CLIENT SIDE DREAMS

I'm thinking for initial implementation, I would write the following
client-side objects:

-> something to pop up a window and display text in it

-> something to download a "library" of information, and allow other
   objects to retrieve the information from it (ie, provide a url type
   syntax.  like "library://sunnywiz1/song1.midi" would pull from this
   library, and "http://www.vhdev.com/~sunnywiz/song1.midi" would pull
   from the internet.  This "lookup" would be a client side kernel api
   function.

-> something to play a midi song (which can stop that song and start a
   different song).

-> Maybe the client kernel can request download from the server of the
   specific library mentioned above.  That would be a good test for
   client->server talks.

I don't know which language to do this in yet.. probably Java, since
it has multimedia support (or is supposed to). I've heard it has
become more stable in the last 2 years since I last used it.  Which
would be a good thing.


WRAPPING UP - WHATS NEXT

I'm showing this to everybody to gauge a couple of things: 

0. Is there any previous work on this?  I've only been on this list
   for about 2 months now.  I've tried going through the archives..
   there's a LOT of stuff out there.  One of these days I'll just
   download all the .tar.gz's and less and grep through them. (JCL,
   I could not get the "search" function to work well.. it
   never returned on me.  waited about 5-10 minutes.)

1. Is there any other interest in this? 

2. By discussion, my goals would become more refined as to what I want
   to do (ideas I like), and what I don't want to do.

3. Would anybody care to pick up on it and run with it?  Like I said
   initially, i don't know how far I'm going to get. 

4. If I did manage to get a rudimentary system put together, would
   other people be interested in adopting it and writing their
   own.. umm... widgets/client side objects?  or providing
   support for non-LP muds? or as the protocols become stabilized, 
   write a nice fast gtk or other cool crossplatform gui-based client?

5. Maybe theoretically design a 2D view window object.  I have a lot
   of ideas on this, but that's call for a seperate paper.  Lots of
   pros and cons, and I can probably find a lot of stuff in the
   archives here before I write that.  And first steps first, I need the
   communications framework.

6. MCSL = mud client server link.  Any better names?  Something cheeky
   would be cool. :)  

7. Looking like this would involve a bunch of short packets.  I'm not sure 
   what the effect is of various packet sizes regarding congestion and stuff.
   Maybe some sort of buffering is in order; bundling several individual 
   messages into one ~...<CR> and MCSL...<CR> pair?   How much of a difference
   would this make?  Maybe the layers can be designed so that actual 
   interpretation of a ~message<CR> allows for decoding of an array of messages
   (though initial implementation, only one message is returned). 

8. [after reading a thread from march..]
   This will still be susceptible to lag.  Its running over telnet which runs 
   over TCP.  Maybe the Async model should be extended to deal with a timeout 
   on the method invocations (actually, it absolutely should.)

   - could maintain a queue of dispatched async events - only allow so many 
     to build up while waiting for responses. 

   - the send-message-only-no-response system could be set up with a message#
     for each message, and have an ack packet that states the last message #
     seen. (sent at least once every N seconds?) That way, the server knows 
     how badly lagged a client is, and can choose to send a full update 
     rather than the list of changes.

Comments, Questions?  Would love to hear from you guys.  Y'all Rock. 
 // Sunny Gulati 
 // sunnywiz at radiks.net (Home) 





More information about the MUD-Dev mailing list