Archive for category Tutorials

Exit, stage west

The standard way to connect rooms in a MUD is with exits. Each room has exits, usually in standard compass directions (plus possibly up, down, in, out, things like that). To use an exit, the player types the name of the exit and their character obeys by taking that exit.

In some MUDs, there may be something blocking an exit (a lock, an obstacle, an opponent) but in this post we just get the fundamentals down: Using exits to connect rooms, and allowing players to use them.

Storing the data

Exits are an association of direction names to destinations, which can be stored as an LPC data structure called a mapping. Below is the code I added to my room.c to support exits. The only thing that would be different for you, since your rooms are probably not strange like mine, is that set_exit() will probably just take one parameter, and behave like the first case in my if. The three-argument case is so that someone can call, say, set_exit( "west", "backbone", "The Clouds" ) and not need to know the filename.

// exits
private mapping exits;
// set_exit(direction,filename) or set_exit(direction,domain,shortdesc)
varargs void set_exit ( string direction, string dom, string sdesc )
{
    if ( !sdesc )
        exits[direction] = dom; // treat it like a filename
    else
        exits[direction] = ({ dom, sdesc }); // treat it like a pair
}
void remove_exit ( string direction )
{
    map_delete( exits, direction );
}
mixed query_exit ( string direction )
{
    return exits[direction];
}
string array query_exits ()
{
    return keys( exits );
}

Now your room files (those that inherit ROOM_OB;) can, in their create() functions, set up not only the room’s descriptions, but its exits, with calls to set_exit().

Well, that’s nice, that the data is now available to the MUD, but of course the players still can’t see the exits or use them. Those are the other two pieces of this puzzle.

Seeing exits

This is a simple matter of enhancing your /command/basic/look.c command to print exits after it prints the long description. Mine gives output like the following. I’ll leave this as an exercise, since it’s easy.

> look
The Clouds:
    You are sitting in the clouds, high in the sky above the world.
There is nothing here but you.
Exits from here: east in
> 

Using exits

There are three ways you may choose to expose the exit actions to your players.

  1. Create a single command, such as “go,” that allows players to take exits. You could make /command/basic/go.c that allowed players to type things like “go west” and it would examine their environment, ensure it was an instance of ROOM_OB, and if so, move the player.
  2. Modify the commandHook() function in the user object to do this, so that each exit becomes its own verb. Thus the player just types “west,” and that function does all the work just mentioned.
  3. Modify the room object to add actions to players when they enter it, one action for each exit. I haven’t experimented much with how this would work, so I can’t say much about it. But it occurred to me as a possibility. At least, I think it’s possible. :)

I chose the second option, since it seemed like the most natural thing for the users. However, the first option is reasonable as well, especially if you have aliases that allow players to make, say, “w” short for “go west.” The new code in my commandHook() function looks like this.

    // see if it's a directional command (using a room exit)
    if ( !arg && move_command( verb ) )
        return 1;

Obviously the heavy lifting there is done by the move_command() function, so let’s see the code for that. Several points in it raise questions that I address below.

// try to interpret dir as a move command, return 1 if it worked, 0 otherwise
int move_command ( string dir )
{
    object obj = environment( this_user() );
    mixed dest;
    if ( instance_of( obj, ROOM_OB ) ) { // see comments below
        dest = obj->query_exit( dir );
        if ( dest ) { 
            if ( stringp( dest ) ) { // this is the case most MUDs deal with:
                dest = load_object( dest );
            } if ( arrayp( dest ) && ( sizeof( dest ) == 2 ) ) { // see comments below
                dest = ROOM_OB->find_room( dest[0], dest[1] );
            } else {
                write( "An error occurred with this room's exit data!\n" );
                return 1;
            }   
            if ( !dest ) { 
                write( "An error occurred loading that place!\n" );
                return 1;
            }   
            write( "You go " + dir + ".\n" );
            say( capitalize( this_user()->query_name() )
               + " goes " + dir + ".\n" );
            this_user()->move( dest );
            say( capitalize( this_user()->query_name() )
               + " arrives.\n" );
            return 1;
        }   
    }   
    return 0;
}

The above code has a few points marked as requiring further explanation. First, what’s this instance_of() function? I wanted a function that behaved like the Java(Script) instanceof operator, and LPC had no such thing. It does have inherits(), but that is a non-reflexive operator (i.e., room.c doesn’t inherit room.c, according to it). So I built my own; it’s an interesting exercise for you to do the same!

Second, a typical MUD will not need to handle two cases, as I do–asking if the destination is a string or an array. In my case, the destination may be referred to by filename (a single string, as on most MUDs) or by a pair (domain and short desc together, which I can use to determine a filename). In your situation, which is probably more typical, these two cases will not be necessary, and you can just proceed right to dest = load_object( dest );.

Odds and ends

On my MUD, I also extended my room-creation command to allow creating or changing the exits of a room. If you chose to do things my way, you’ll want to do this as well. But you probably didn’t!

Advertisements

Leave a comment

Rooms! (for real)

The past few posts were supposed to be all about getting us ready to build rooms. Well, now it’s time to really do so. This post will be a tutorial that gets the basics done, then the following few posts will be challenges to help you add the bells and whistles.

I’m doing rooms in a fairly nonstandard way in my MUD, but we’ll come back to that later. For now, I’m starting with the same basics that I suspect everyone will want. (Unless of course you’re going to do something even more nonstandard than I am, in which case–you’re on your own!)

What is a room?

A room is an environment in which player objects (and other objects) can sit, and which reports facts about itself that give the characters a sense of “being somewhere.” There is no such sense in the MUD I’ve described coding up to this point; the only sense you get is of being on a chat channel. The differences are these:

  1. Right now there is only one location in the game, and everyone is in it. We want multiple locations, so people can be in different places.
  2. Each location should know something about itself, so that people can inspect that location and see what it’s like.
  3. Players should also be able to tell who (or what) else is in the same location as them, and when people come and go.
  4. Finally, there will need to be some means of transporting players from one location to another, so that players feel in some measure in control of their whereabouts.

Let’s take the above list as a vague requirements specification for what kind of support the MUD needs to provide for rooms. Most of this we’ll put in the generic room object, /clone/room.c, but not all of it goes there. Let’s take this one step at a time. (It’s a tutorial, after all.)

Create /clone/room.c

The generic room object can be so simple to start out that I’ll actually post the entirety of its essential code here. After the code block, I explain it.

#include

inherit BASE;

// rooms cannot be moved
int move ( mixed dest )
{
}

// long dsecription
private string longdesc;
void set_long_desc ( string ld )
{
    longdesc = ld;
}
string query_long_desc ()
{
    return longdesc;
}

The first line is self-explanatory, and the second line inherits the routines common to all objects. (See /inherit/base.c for what they include.) The first function overrides the generic move operation to do nothing for rooms. In other words, you’re not allowed to move rooms in my MUD. (No rooms within bags within players within rooms–too confusing, at least for now.)

The last block of code creates a member variable for storing the long description of the room, and functions for setting and getting it. If you’re familiar with MUDs, you know that a long description is usually about a paragraph of text describing the room, which players can read to get a sense of where they are. It is most of what the player expects to see when he or she types “look.”

Once you’ve created even just this simple file, you can place yourself in a room with a few well-done eval statements. Try something like this (in which I assume you’ve already added ROOM_OB to your globals.h file):

> eval this_user()->move( new( ROOM_OB ) )
Result = #0
> eval environment(this_user())->set_long_desc( "This is a tiny castle, with a tiny long description." )
Result = #0
> eval environment(this_user())->query_long_desc()
Result = "This is a tiny castle, with a tiny long description."

Several things may come to your mind. The first is that you’re sick of typing eval environment(this_user())-> already, so you might want to do this.

alias here eval environment(this_user())->

(Remember the idea for aliases, and even the alias for “me,” in this post.)

The second is that no one in their right mind wants to look at their environment using an eval command. Right you are! So we will make a command that does it for them.

Look

A new command that belongs in the basic command set is “look.” It should just ensure that environment(this_user()) exists and returns a value from query_long_desc(). If so, it should print that value, and if not, display some shocking error message. After that you’ll have this extremely satisfying experience:

> look
This is a tiny castle, with a tiny long description.
>

HOLY COW! WE ARE SOMEWHERE! IT’S LIKE A REAL GAME! Sort of.

Leap

Most MUDs store each room’s data in a separate file. Developers create new rooms by writing the code for an object that inherits /clone/room.c, and contains a setup function like this one.

#include
inherit ROOM_OB;
void setup ()
{
    set_long_desc( "This is a tiny castle, with a tiny long description." );
    // and eventually other things will be set here, too, but we haven't built them yet
}

You’ll want to be able to create multiple such rooms and leap about them using something other than the clunky eval calls we used above. I suggest creating a room very much like the one above, saving it somewhere (say, /data/tinycastle.c) and then going to it, like this.

> me move( "/data/tinycastle" )
Result = #0
> look
This is a tiny castle, with a tiny long description.
>

In order to facilitate leaping directly to such rooms in the future, I suggest creating a command (maybe “goto” or “teleport”) that accepts a filename as parameter (and passes it through resolve_path()), then makes the above move() call for you. This way you can just type things like “teleport tinycastle” and be there.

Surely there’s more!

Indeed, what would a long desc be without a short desc? And what about teleporting to other players? And what about mazes? Don’t forget the mazes! Indeed, there is much still to do, but this is a great start. My next few posts will be on this same topic, and will cover these aspects of it:

But for this post, that’s quite enough. Now go make some overly wordy long descriptions!

Leave a comment

Filesystem commands

This is the first post en route to creating rooms in your MUD.  Rooms will be stored in files, and so you’ll want, as an administrator, to be able to dig into those files and see what’s in them.  You already have some file-related commands, but you must currently pass full paths to them.  It would be nice to be able to cd around the filesystem, cat files, ls folders, etc.  That’s the job of this post and the two following, with relate challenges and gotchas.

user::query_cwd()

The user needs a current working directory (or in Unix parlance, a pwd, for present working directory).  The implementation of this in the user object right now is rather humorous.  It looks like this.

// replace this with a functioning version.
string query_cwd()
{
    return "";
}

We need to be able to store an actual string in the user that persists, and query and update it.  Fortunately, since you’ve already learned how to do this with search paths, it should not be surprising to you that the code looks like this.

private string cwd = "/";

string query_cwd ()
{
    if ( !cwd )
        cwd = "/";
    return cwd;
}
void set_cwd ( string newcwd )
{
    cwd = newcwd;
}

So…then what?

As you’ve noticed, this does nothing except save the useless value in the user’s save file when they quit, and restore it when they log in. So you’ll need two things:

  1. A line in login.c that sets their pwd to / when they log in (if you want this).
  2. A pwd command to print their present working directory. This should be really easy to do. In fact, it’s so short I put its entire code below.
  3. A cd command to change directories. This is not as easy, but I’ll give you two tips:
    1. First, notice that resolve_path() in single/simul_efun.c does most of the work for you.
    2. Second, notice that resolve_path() in single/simul_efun.c is broken and needs fixing. I’ll post on how to fix it in my next “gotchas” post.

So here’s the one I do for you–the pwd command:

#include <globals.h>

int main ( string arg )
{
    write( this_user()->query_cwd() + "\n" );
    return 1;
}

// plus a help function on the end describing it as you see fit

You’re on your own for cd, but it’s not too hard once you use resolve_path().

One parting suggestion: Since we’ve already organized commands into folders, I’d suggest making a “files” folder of commands, which contains all commands for “read” access to the filesystem. I left all the “write” access commands in the “admin” folder.

Almost done!

Now you can try some challenges for what other filesystem commands to create.

Don’t forget to read the gotcha about resolve_path().

Then we can move one step closer to building rooms. Holy cow, this will actually be rather game-like soon.

Leave a comment

Organizing commands

This is going to be the first tutorial in which I don’t post any code.  It’s too big a topic to include all the code, so you’re on your own for it.

In fact, this is not really a tutorial so much as it is a record of what I did to organize commands on my MUD, and you can consider it inspiration on how you might do so on yours.  As with all the stuff on this blog, take and leave what is of interest to you.

The problem

The /command folder in Lil has in it commands to which everyone has access.  Some of these are quite powerful, such as eval and shutdown.  Not everyone who logs into your MUD should have this much puh-puh-puh-power-ower-ower.

My plan is to organize the commands in /command/*.c into subfolders for various topics.  One will be /command/admin, with all the powerful stuff in it, and another will be /command/basic, with all the necessaries like say, who, quit, and help.  Then access to various command folders can be granted on an as-needed basis, and security preserved.

I have recently done this in my MUD, and today’s post is just to tell you how.  If you follow my outline, you should get the same results I got.  They’re working fine on this end!  Here goes.

Solution outline

Basic data infrastructure

As you can see in /clone/user.c, in the commandHook() function, there is one globally defined COMMAND_PREFIX that tells that function where the commands are.  We don’t want this any more.  Proceed as follows.

  1. Create in the user object a private string* search_paths member.
  2. Initialize it to ({}).
  3. Create accessor methods that query, add, remove, etc., as you see fit.

Now all this does is create a space to hold the data and ways to get/put it.  It doesn’t actually use it for anything.  That’s coming up.  However, storing this data in your player object is going to be pointless if it doesn’t get restored upon each login.  So it’s time to implement save/load of player data.

Saving and loading players

I created a folder /data/users, in which I will store playername.o files containing player data, with the save_object() and restore_object() efuns.  Might as well read those docs now.  Go ahead, I can wait.  *waits*

Geez, took ya long enough.  Well, if you’re totally ready, then, we’ll move on.  Yes?  Hoookay.  *glares*

  • Create a save_filename() function within your /clone/user object that determines an appropriate save filename based on the user’s name.
  • Create save() and load() members in your /clone/user object, which call save_object() and restore_object() on save_filename().  Do not set the flag to 1 in your load() call; that doesn’t do what you want.
  • In the setup() function in the user object, call load() after the user’s name has been set.  This completes the implementation of loading.
  • Create a remove() function (overriding the one in /inherit/base) that calls save() and then base::remove().  This completes the implementation of “save right before quitting” as long as you don’t destruct the player when quitting, but remove() it instead.  (This is what /command/quit does.)
  • However, shutdown() does not call remove() in players, so you edit /command/shutdown.c and place a call to users()->remove() right before the call to shutdown().  This will invoke saves in all players right before the MUD shuts down.

Wow, you are just flying through these bullet points!  Way to go!  (You are, right?)

Give users search paths

(Do all this without your MUD running, using your ordinary filesystem commands.  Your MUD will be in an unusable state for a little while until you get this finished.)

  • After user::setup() calls load(), check to be sure everyone has the most basic search path of commands everyone should have.  I called mine /command/basic.
  • Now create that folder and move into it all the commands for everyone: say, who, help, quit.
  • Update user::commandHook() to no longer reference COMMAND_PREFIX, but to loop through the player’s search paths instead.

If you want, you can now test all this by firing up your MUD and ensuring that you have access only to the commands say, who, help, and quit.  This will therefore require you to shut down your MUD with kill -9, as described earlier.

  • Move all the other commands from /command/*.c into the admin folder.  For me, that was /command/admin/.
  • Now add this path to your admin character by editing his or her /data/users/playername.o file.  The syntax should be obvious, but don’t forget the trailing comma.  E.g.:
    search_paths: ({“/command/basic”,”/command/admin”,})

And if you like you can now re-test to be sure your player has access to all the commands, including the admin ones.

Help!

Your help command is probably now broken, because it used to look for commands only in /command/, whereas now we have subdirectories.

  • Upgrade /command/basic/help.c by putting in a loop through all the user’s search commands.
  • I also upgraded mine so that if you just type “help,” rather than list all the commands of all search paths together, it categorizes them.  The output looks nice, like this:
> help
You have access to the following commands.
You can ask for help on any one by typing "help <command>".
Admin commands:
    codefor, dest, ed, eval, path, rm, shutdown, speed, tests, update
Basic commands:
    help, quit, say, who

Make it easier

Of course, to manage search paths in the future, you won’t want to have to edit save objects on the filesystem.  You’ll just want to magically bless someone with access to new commands.  So create an admin command for doing so.  Mine fits these specs:

  • It was /command/admin/path.c.
  • Typing “path <playername>” lists all their search paths.
  • Typing “path <playername> add <fullpath>” adds a new search path to that player.
  • Typing “path <playername> remove <fullpath>” removes a search path from that player.

Feel the power (ower, ower).

Leave a comment

Create a command: Help!

We need this

As you may have noticed, if you’ve been tinkering around at all, the motd that comes with Lil is actually inaccurate.  It lists some commands that aren’t actually there (like efun) and doesn’t list some that are there (like speed).  Sure, some of these commands are for debugging/testing only, but let’s at least be accurate.

Heck, let’s not just be accurate–let’s build it right!  Let’s have an auto-generated list of the commands available to you, and the ability to ask for help on each one.  Then we can change /etc/motd to just say, “Type help for help.”  Yes, yes!

Where are the commands?

They’re in the /command directory, of course!  Check out the command folder inside your lib folder, and you’ll see things like say.c, who.c, quit.c, etc.  Any .c file you put there will have its main() function called when you issue that command.  (Any parameters you call will be passed, just as if you were writing a binary in Unix.)  How do I know this is true?  Because this principle is encoded into the user object.

In /clone/user.c, you will find this code:

int
commandHook(string arg)
{
    string cmd_path;
    object cobj;

    cmd_path = COMMAND_PREFIX + query_verb();

    cobj = load_object(cmd_path);
    if (cobj) {
        return (int)cobj->main(arg);
    } else {
        // maybe call an emote/soul daemon here
    }
    return 0;
}

It tries to load the object whose code is in “/command/VERB_YOU_TYPED” and run its main() function on any arguments passed.  This function is called as a fallback if no other actions (perhaps those your environment or possessions give you) succeeded in processing your input.  Since at this point, we have no environment or possessions, this is the whole shebang.

Let’s make a help command!

The following code comes from my newly created /command/help.c.  If you don’t understand some of the efuns in it, look ’em up!  It does two things, but I don’t have to tell you what they are, since the command itself does!  See its own help text (ah, self-reference) at the bottom of the code.  If you’re not sure what the heck @help means in the code, see the very bottom of this entry in the docs (under “Text Formatting Shortcuts”).

#include <command.h>
#include <globals.h>

string* add_action_commands () {
    return map( commands(), (: $1[0] :) ) - ({ "" });
}
string* commands_in_filesystem () {
    return map( get_dir( COMMAND_PREFIX + "*.c" ),
                (: $1[0..sizeof($1)-3] :) );
}

int main(string arg)
{
    object cobj;
    string help;
    string* cmds;

    if ( arg ) {
        if ( member_array( arg, commands_in_filesystem() ) > -1 ) {
            cobj = load_object( COMMAND_PREFIX + arg + ".c" );
            help = cobj->help();
            if ( !help )
                help = "No help available on that command.";
            if ( help[sizeof(help)-1..] != "\n" )
                help += "\n";
            write( help );
        } else if ( member_array( arg, add_action_commands() ) > -1 ) {
            write( "No help available on that command.\n" );
        } else {
            write( "There is no such command to get help on.\n" );
        }
        return 1;
    } else {
        write( "You have access to the following commands.\n"
             + "You can ask for help on any one by typing "
             + "\"help <command>\".\n" );
        cmds = add_action_commands() + commands_in_filesystem();
        write( implode( sort_array( cmds, 1 ), ", " ) + "\n" );
        return 1;
    }
}

string help ()
{
    return @help
Lists the commands available to you, if you just type "help."
Gives help on individual commands, if you type "help <command>."
help;
}

With that done, you can go right ahead and start adding string help () { return “…”; } member functions to all your /command/*.c objects, and update your /etc/motd to be…more helpful!

4 Comments

Users and passwords

So far everyone who logs into our MUD gets a name of the form “stufN” for some integer N.  Not exactly welcoming, but true to Lil’s goal of minimalism.

In this post, I’ll show you a simple username/password system I created.  Fair warning:  I know next to nothing about security, so this is minimally secure.  Heck, MUDs use telnet, for cryin’ out loud.

Supporting functions

We’ll be editing clone/login.c, as I’m sure you can guess from the previous post.  The first thing to do is create a place to store passwords.  I chose to create a file in /u/passwords/character_name_here for each character, and place an encrypted version of their password in that file.  Here are the few functions I wrote in clone/login.c to handle this.


// A function to ensure the passwords folder exists.
// This will only matter the first time a user is created,
// but let's go ahead and write robust code.
private void ensure_passwords_folder ()
{
    if ( sizeof( stat( "/u/passwords" ) ) == 0 )
        mkdir( "/u/passwords" );
}
// A function that embodies the password file naming convention.
// The parameter is the character's name (e.g., "Dave").
private string password_file ( string name )
{
    return "/u/passwords/" + name;
}
// A function that determines if the user exists, based on
// whether a password file for that user exists.
private int user_exists ( string name )
{
    return sizeof( stat( password_file( name ) ) ) > 0;
}

The efuns mkdir, sizeof, and stat were used here.  The above lines replace the comment that used to be at the top of this file saying that we needed to add password support.  Muahaha.  (Not that we’re done yet.)

After those functions, I just factored out of logon() those portions that actually create a user object and transfer control to it.  I did so because I will eventually need to use that functionality in more than one place, so it was handy to have it factored out.  This will become clearer below.


private void create_user_object ( string name )
{
    object user;

    user = new("/clone/user");

    user->set_name(name + "_" + getoid(user));
    exec(user, this_object());
    user->setup();
#ifndef __NO_ENVIRONMENT__
    user->move(VOID_OB);
#endif
    destruct(this_object());
}

The only change made when factoring this out was to take into account a username that we will be prompting the user for.  Note, however, that I still append the OID to keep names separate, since we will not be learning in this post how to handle the same user logging on more than once; that’s for another post.  So…how do we prompt for a username?

Ah, silly me to even ask that question.  I’m sure you took the hint in the previous post and already read up on the docs for input_to() and have already formed your own plan for how to do this!  Right?  Well, maybe you have.  If not, that’s what this post is for.

How input_to() works

To prompt the user for a response, wait for the user to type it and press enter, and then handle what they typed in is not something you can do in one function.  You need to provide the driver with a callback function, let the driver give the user time to type, and when they have pressed enter, the driver will notify your callback function.  In other words, code you might like to write like

10 REM BASIC IS MY COMFORT ZONE
20 PRINT "USERNAME: ";
30 INPUT N$
40 PRINT "PASSWORD: ";
50 PRINT P$
60 PRINT "OKAY, ";N$;", I'LL CHECK YOUR PASSWORD NOW..."

you end up having to write like this instead.


// this is just an example; real code comes below
void example ()
{
    // write a prompt with no newline at the end:
    write( "Username: " );
    // wait for their response, and send it to the handle_username() function:
    input_to( "handle_username" );
}

void handle_username ( string username )
{
    // we have their username, so prompt for their password:
    write( "Password: " );
    // wait for their response, and send it to the handle_password() function:
    input_to( "handle_password" );
}

// and so on...

In fact, knowing this, I challenge you as an intelligent reader to just go and do it yourself!  (There will be some gotchas, which I’ll warn about at the end of this post.)  Or you can read on…

Polished code

At this point, I’m just going to paste in some commented code.  You can read and understand it at your own pace.  I ain’t gonna baby-walk you through it.  Gotchas are linked to at the bottom.

// Called by the driver after this object has been made interactive
void
logon()
{
#ifdef __NO_ADD_ACTION__
    set_this_player(this_object());
#endif
#ifdef __PACKAGE_UIDS__
    seteuid(getuid(this_object()));
#endif
    write("Welcome to Mud!\n\n");
    cat("/etc/motd");
    // initiate the login process with a username prompt
    write("\nWhat is your name? ");
    input_to("accept_name",2); // 2 means no command escaping with !
}

// This function receives the username and does one of two things:
// If they're a returning user, then require authentication.
// If it's a brand new usersname, ask them to make up a password.
void
accept_name ( string name )
{
    if ( user_exists( name ) ) {
        write( "Returning user.\nPassword: " );
        input_to("accept_password",3,name);
        // the 3 in the above line means the same as the 2 from earlier,
        // plus the additional flag that the characters not be shown
    } else {
        write( "Welcome, new player!\nCreate a password: " );
        input_to("create_password",3,name); // ditto
    }
}

// This function accepts the password entered by a returning user
// who is trying to authenticate. We do one of two things.
// If it's incorrect, tell them so and start back at the username prompt.
// If it's correct, welcome them and complete the login process.
void
accept_password ( string password, string name )
{
    if ( read_file( password_file( name ) ) != crypt( password, name ) ) {
        write( "\nIncorrect password. Try again.\n"
             + "What is your name? " );
        input_to("accept_name",2);
        return;
    }
    write( "\n\nWelcome back!\n" );
    create_user_object( name );
}

// This function accepts a new password from someone creating an account.
// We just accept it and then ask them to enter it a second time,
// for verification that they typed what they think they typed.
void
create_password ( string password, string name )
{
    write( "\nRepeat password: " );
    input_to( "repeat_password",3,password,name );
}

// This function accepts the second repetition of the password
// from someone creating an account. If it wasn't a repeat,
// we refuse to create the account and start back at the username prompt.
// If it matched, we create the new account by saving the password,
// and then complete the login process.
void
repeat_password ( string password2, string password1, string name )
{
    if ( password1 != password2 ) {
        write( "\nPasswords did not match. Try again.\n"
             + "What is your name? " );
        input_to("accept_name",2);
        return;
    }

    ensure_passwords_folder();
    write_file( password_file( name ), crypt( password1, name ), 1 );
    create_user_object( name );
}

As you consider how you will implement this on your end, I have two gotchas to share with you.  First, learn to use kill -9.  Second, variable declarations must go first.

Leave a comment

Can I get docs with that?

More customization

I promised in the previous post that we would look into changing the message that prints when you log into your MUD.  It still had the word “Lil” in it, which we want to change.  The easy way to find that message is to grep for it from your lib directory.

$ grep -r Lil *
# later, if your greps get spammy, consider grep -r ___ * | grep -v ^doc | grep -v ^log

It gives results in several places, that fall into these categories:

  1. README files.  These are obviously just for your own benefit, and you can keep, change, or delete them as you see fit.
  2. Comments in code.  Same applies here; it’s probably best to change them when you end up improving the code to which they relate.
  3. clone/login.c and clone/user.c, both of which contain the message “Welcome to Lil.”
    Hmm, which of these could be the real culprit?

Turns out the code in clone/user.c is irrelevant, as the comments above it suggest it may be.  I determined this by removing it and relaunching the MUD, and nothing went wrong.  (Furthermore the welcome message remained, so it must be printed by the clone/login.c function instead.)

So let’s go ahead and change that to say the name of our MUD instead.

void
logon()
{
object user;

#ifdef __NO_ADD_ACTION__
    set_this_player(this_object());
#endif
    write("Welcome to Mud!\n\n");
    cat("/etc/motd");
    write("\n> ");

Now, relaunching our MUD and logging in produces the desired message.  Also, we learn the secret that the /etc/motd file is printed whenever the MUD is launched.  Feel free to change the contents of that file as you see fit also.

Digging deeper

So we found the code that printed the message, but what calls that code?  Where does control flow from that eventually leads to logon() getting called in clone/login.c?  Another grep (this time for login) will show us that the call comes from /single/master.c’s connect() function.

object
connect()
{
    object login_ob;
    mixed err;

    err = catch(login_ob = new(LOGIN_OB));

    if (err) {
        write("It looks like someone is working on the player object.\n");
        write(err);
        destruct(this_object());
    }
    return login_ob;
}

This is the only place where login or LOGIN_OB are mentioned, so they’re the only ones that can make the call.  But as we can see here, they don’t!  They create an instance of clone/login.c, but they don’t call the function.  What’s going on?

Applies

Functions that the MUD driver calls in your code are called “applies.”  This is an awful name.  They should be called “event handlers” or something far more descriptive of what they are.  (grumblefinewhatever)  How would I know this?  How can you find these things out?

Well, it turns out you need a good set of documentation!  The good news is that MudOS comes with one.  Not great, but good.  However, it’s a pain in the butt to navigate because it’s all text files.  (The web was a new thing.)  So I wrote a python script to pile it all together into one (enormous but) handy reference.  Enjoy!

From those docs, you can find the whole story of how logging into your MUD is handled.

  1. The driver looks to the master object (the one instance of single/master.c) and calls connect() on it.  (This is an apply, documented here.)
  2. The master object instantiates a new LOGIN_OB, as in the code above, and returns it.
  3. The driver then gives that object the magical blessing of “interactivity” (i.e., controlled by the newly connected user) and then calls logon() in it, to do any initialization on the newly interactive object.  (This comes from the documentation for the logon() apply.)

A little more complex than perhaps we might suspect logging in would be, but there it is.

Where to start?

That docs file might seem a liiiiittle overwhelming.  So where to start?  I’d suggest starting with the efuns.  No, start with knowing what an efun is, then read about the efuns.

  • An lfun is a “local function,” meaning one you write in your own code.  For obvious reasons, they don’t appear on that documentation page.
  • An efun is an “external function,” one not in your code, but external to it–in the MUD driver instead.  For this reason, you need to know what these functions are and what they do.  Hence the huge documentation page linked to above.  All efuns are available for you to call from anywhere in your code.  To add a new efun, you have to change the C code of the driver and rebuild it.  Don’t do that; there’s an easier way:
  • A simul_efun is a simulated efun.  This means you can write it in LPC inside your lib (specifically, in the single/simul_efun.c object) and the lib will treat it as if it were an efun, in the sense that it’s magically accessible to you everywhere.  Okay, not magically.

So what efuns are there?  Lots.  Check out the efuns heading on the big documentation page, and see how they’re conveniently broken into categories.

Since I’ll soon be writing posts about how to force users to log into your MUD with an actual username and password, you’ll probably want to start by reading up on input_to() and crypt().

Leave a comment