melville/
melville/cmds/
melville/cmds/admin/
melville/data/
melville/data/mail/
melville/data/player/
melville/data/system/
melville/data/user/
melville/doc/functions/
melville/doc/help/
melville/inherit/
melville/log/
melville/obj/
melville/system/auto/
melville/system/player/
melville/system/user/
melville/users/
melville/users/mobydick/
melville/world/
/*
   auto.c
   This object is automatically inherited by all other objects. It can
   be used to store interal data on objects, and it can also be used to
   define functions that all objects can access. Thus it can be used to
   make simul_efuns, particularly to mask sensitive efuns (eg, write_file)
   for security. See the DGD docs for more information.
   Not all the functions that are in this object necessarily need
   to be here; especially the heart beat ones, but also you might be
   be able to move the file handling and string mangling functions
   out and inherit them separately if you want to. It will improve
   performance (perhaps only slightly) to do so; it will also cause
   some confusion when you forget that you need to inherit those
   things :) By me it's not worth it, but you could do it if you
   thought it was, or if you wanted a slightly more C-like
   programming environment. But I don't like that environment :)
   Begun by Mobydick, 5-19-94
*/

/* config.h and options.h are included by overrides.c.
   admin.h is included by security.c */

/* You cannot inherit into the auto object, so these are included. */

#include "/system/auto/overrides.c"
#include "/system/auto/security.c"

/* Variables used in the heart_beat functions. */
private int heart_beat_time ;
private int heart_beat_handle ;

static nomask void write (string str) ;
static nomask object this_player() ;
static nomask int member_array (mixed elt, mixed *arr) ;

/* create() is called from init_object(), which is called when an object
   is created by the driver. It is here only so that we won't have
   problems with it being undefined. Most things will redefine it.
*/

void create() {
    return ;
}

/* init_object() is called by the driver when an object is loaded. It
   sets up the privileges and creator and calls create() in the object.*/

nomask void init_object() {
    set_privileges() ;
    set_creator() ;
    create() ;
}

/* input_to() makes a call into the user object. The next string
   passed by the user will be sent to this object to the named
   function, and will not be treated as a command. 
   If flag is not zero, the string will _not_ be echoed back to the
   user's terminal. Useful for password entry.
   It is static so one cannot call_other input_to(). Maybe we ought
   to permit such calls? It seems Dangerous to do so but I can imagine
   uses for it. But I think it's best not to unless you need to. */

varargs static nomask void input_to (string func,int flag) {

    object ob ;

/* The call should go to the user associated with this_player().
   If there isn't one, probably the best bet is the current user. */

    if (this_player()) ob = this_player()->query_user() ;
    if (!ob) ob=this_user() ;

    ob->set_input_to(this_object(),func,flag) ;
    return ;
}

/* This function capitalizes the first letter of a string. Handy.
   Taken from Dworkin's auto object. */

static nomask string capitalize(string str) {
    if (!str) return str ;
    if (str[0] >= 'a' && str[0] <= 'z') {
        str[0] -= 'a' - 'A';
    }
    return str;
}

/* This one lowercases all caps in the string. Also taken from Dworkin. */

static nomask string lowercase(string str) {

    int i ;

    for (i=0;i<strlen(str);i++) {
        if (str[i]>='A' && str[i]<='Z') str[i] += 'a' - 'A' ;
    }
    return str ;
}

/* This returns 1 if a file exists, -1 if a directory with that name
   exists, and 0 if no file exists. */

static nomask int file_exists (string str) {

    mixed *val ;
    int *sizes ;

    val=get_dir(str) ;
    sizes = val[1] ;
/* If there's no names at all, then the file doesn't exist. */
    if (!sizes || sizeof(sizes)==0) return 0 ;
/* It's not clear to me what the behavior should be in the case of
   more than one file returning (ie, if a wildcard is passed). For
   now, I'm going to return information for the first file in the
   list. Don't pass wildcards to this function, eh?  ;)    */
    if (sizes[0]==-2) return -1 ;
    return 1 ;
}

/* This one takes an object and returns the file name part of the
   object name. TMI-2 has the same thing. */

static nomask string base_name(object ob) {

    string str,obname ;
    int clone ;

    if (!ob) ob=this_object() ;
    obname = object_name(ob) ;
    if (sscanf(obname,"%s#%d",str,clone)==2) return str ;
    return obname ;
}

/* This one returns the clone number. Dunno if anyone has this but
   it seems like an obvious thing to have. If the object is a master
    copy, it returns 0. */

static nomask int clone_num(object ob) {

    string str ;
    int clone ;

    if (!ob) ob=this_object() ;
/* I'm getting a very weird bug where this_object() is returning 0.
   Surely if this_object() is 0, clone_num is 0 also?
*/
   if (!ob) return 0 ;
    if (sscanf(object_name(ob),"%s#%d",str,clone)==2) {
        return clone ;
    }
    return 0 ;
}

/* This one either finds or loads an object and returns it. It is
   basically a version of the kfun find_object() that Knows What To
   Do if it doesn't find the object. I don't want to override
   find_object() because there might be times when you want to know
   whether an object is or is not loaded and find_object() is useful
   for that.
*/

static nomask object get_object (string name) {

   object obj ;

/* If name has a trailing .c, let's get rid of it. */
    if (strlen(name)>2 && name[strlen(name)-2..strlen(name)-1]==".c") {
	name = name[0..strlen(name)-3] ;
    }
   obj = find_object(name) ;
   if (!obj) obj = compile_object(name) ;
   return obj ;
}

/* Send a message to this_player() if there is one, and this_user()
   if there isn't one. If there's no this_user() either, toss it
   over to this_object() and hope for the best. This means write()
   and say() can continue to be used inside the user object during
   call_outs (when this_user() is 0 because DGD handles it that way.)
   If you need to send messages in call_out'd functions, you can pass
   the player to the function and use catch_tell(). */

static nomask void write (string str) {
    if (this_player()) {
	this_player()->catch_tell(str) ;
	return ;
    }
/* If there's no working player, pop it to the user who started the
   call. That might be important in login, for instance. */
    if (this_user()) {
        this_user()->catch_tell(str) ;
	return ;
    }
/* No user either? Then pop it to this_object(). Probably we're
   in a call_out, and sending it to this_object() is better than
   letting it go nowhere. */
   this_object()->catch_tell(str) ;
}

/* Send a message to all players in the same room as the current one.
   Note that it calls catch_tell() in the room, to pass the string along
   to the room object. This might be handy sometimes. Right now the room
   can return 0 to prevent the say if it wants to. You might want to let
   the room alter the say and return a new value if you think that'd be
   cool.
*/

static nomask varargs void say (string str, object *excluded) {

    object ob, env ;
    object *contents ;
    int i ;

/* Find the environment of the current user. */

    ob = this_player() ;
    if (!ob) ob=this_object() ;
    if (!ob) return ;
    env = ob->query_environment() ;
/* Make sure we're not in the void. */
    if (!env) return ;
/* Notify the room of what's being said. If the room returns 0 we
   abort the say attempt. */
    i = env->catch_tell(str) ;
    if (!i) return ;
/* Get every object in the room, and if it's living, call catch_tell. */
    contents = env->query_inventory() ;
    if (!contents || sizeof(contents)==0) return ; /* should be impossible */
    for (i=0;i<sizeof(contents);i++) {
        if (!contents[i]->query_living()) continue ;
	if (contents[i]==ob) continue ;
	if (excluded && member_array(contents[i],excluded)>-1) continue ;
	contents[i]->catch_tell(str) ;
    }
}

/* Dump a file to the user, line by line. Don't exceed MAX_CAT_LINES. */

static nomask int cat (string file) {

    int i ;
    string *lines ;

    i=file_exists(file) ;
    if (i==0) {
        write ("No such file.\n") ;
	return 0 ;
    }
    if (i==-1) {
        write ("That file is a directory.\n") ;
        return 0 ;
    }
    lines = explode(read_file(file),"\n") ;
    if (sizeof(lines)>MAX_CAT_LINES) lines = lines[0..MAX_CAT_LINES-1] ;
    for (i=0;i<sizeof(lines);i++) {
        write (lines[i]+"\n") ;
    }
    return 1 ;
}

/*
 * NAME:        member_array()
 * DESCRIPTION: return the index of the element in the array, or -1
 * Taken from Dworkin's auto object.
 */
static nomask int member_array(mixed elt, mixed *arr) {

    int i, sz;

    sz = sizeof(arr) ;
    for (i=0;i<sz;i++) {
        if (arr[i] == elt) {
            return i;
        }
    }
    return -1;
}

/* Return the appropriate article for a string. */

static nomask string article (string str) {
    if (member_array(str[0],({ 'a', 'e', 'i', 'o', 'u' }))==-1) return "a" ;
    return "an" ;
}

/* The all-important destruct call. You can call this from any object.
   It will decide whether it wants to be destructed or not. For most
   objects it will acquiese, but you can override this if you want
   other behavior. */

int destruct() {
    destruct_object(this_object()) ;
    return (this_object()==0) ;
}

/* The infamous resolve_path(). Used to eliminate any . and .. from
   a path before applying it. */

static nomask string resolve_path (string path) {

    int i,j ;
    string *dirs ;

    if (!path) return "" ;
    dirs = explode(path,"/") ;
/* First, remove any . from the array. */
    dirs -= ({ "." }) ;
/* Next, replace all occurences of ~ with the directory of this_user().
   It may sometimes be that this_user() is not right but we'll cross
   this bridge when we come to it.
   Also replace ~foo with the directory of foo.
*/
    for (i=0;i<sizeof(dirs);i++) {
	if (!dirs[i] || dirs[i]=="") continue ;
	if (dirs[i][0]=='~') {
	    if (strlen(dirs[i])==1) {
		dirs[i] = "users/"+this_user()->query_name() ;
	    } else {
		dirs[i] = "users/"+dirs[i][1..] ;
	    }
	}
    }
/* Now remove any .. and the preceding element. */
    i = member_array("..",dirs) ;
    while (i>-1) {
/* Can't start with a ..  */
        if (i==0) return "" ;
        j = sizeof(dirs) ;
/* Piece it back together, depending on if we remove the first two
   elements, the last two, or two from the middle. */
	if (i==1) {
	    if (j==2) return "" ;
	    dirs = dirs[i+1 .. j-1] ;
        } else {
	    if (i==j-1) {
	        dirs = dirs[0 .. i-2] ;
            } else {
	        dirs = dirs[0 .. i-2] + dirs[i+1 .. j-1] ;
	    }
	}
/* Look for another .. in the path. */
	i = member_array("..",dirs) ;
    }
    path = implode(dirs,"/") ;
    return path ;
}

/* absolute_path() returns 1 if the string is an absolute path (ie, it
   begins with ~ or /) and 0 if not. This is done in so many different
   commands that it seemed wisest to just put it in the auto object so
   you didn't have to change it in 30 places. The null string returns 0. */

static nomask int absolute_path (string str) {
    if (!str || str=="") return 0 ;
    if (str[0]=='/' || str[0]=='~') return 1 ;
    return 0 ;
}

/* log_file() is a front-end for write_file() but it has some special
   privileges that permit objects to write to /log when they don't
   ordinarily have permission to do so. See valid_write() for details.
*/

static nomask int log_file (string file, string str) {
    if (!valid_write(file)) return 0 ;
    return ::write_file(file,str) ;
}

/* pad takes a mixed arg, converts to string, and pads it to the desired
   length. Left-justify is the default: if the justify arg is 1, it will
   be right-justified. */

static nomask varargs string pad (string str, int size, int justify) {

    int i,len ;

    len = strlen(str) ;
    if (len>=size) return str[0..size-1] ;
    len = size-len ;
    for (i=0;i<len;i++) {
	if (justify) {
	    str = " "+str ;
	} else {
	    str += " " ;
	}
    }
    return str ;
}

/* A simple file-copying function. Read the file, write it to a new
   name. It will choke if the file has more than MAX_ARRAY_SIZE lines:
   in that case it should (but does not) break the file up into groups
   of MAX_ARRAY_SIZE lines and do it piece by piece. The extension is
   left as an exercise for the student. :P
   This implementation will not permit you to copy over an existing file,
   because I've wiped out too much stuff myself that way.  */

static nomask int copy_file (string oldfile, string newfile) {

    string contents ;

    if (file_exists(oldfile)<1) return 0 ;
    if (file_exists(newfile)) return 0 ;
    if (!valid_read(oldfile)) return 0 ;
    if (!valid_write(newfile)) return 0 ;
    contents = read_file(oldfile) ;
    if (!contents || strlen(contents)==0) return 0 ;
    return write_file (newfile,contents) ;
}

/* The previous_function() function. It reports the name of the
   function in previous_object() that called the function which
   calls previous_function(). (Read that a couple times until it
   makes sense.)
   It's just a front end that extracts the particular information
   from the call_trace() kfun.
*/

static nomask string previous_function() {

    mixed *trace ;
    mixed *elem ;
    int size ;

    trace = call_trace() ;
    size = sizeof(trace) ;
/* The last element is this function. The second to last element is
   the function that requested previous_function(). The third to
   last element is the function before that: that's the one we want. */   
   if (size<3) return 0 ;
   elem = trace[size-3] ;
/* Element 2 is the function name. */
   return elem[2] ;
}

/* The this_player() function returns the working player body. This
   is tracked in the users daemon. It will normally be the player
   body associated with this_user(). If you used a force command
   then it'll be the body of the player you forced. Security is
   provided in the users daemon; see that file for details.
*/

static nomask object this_player() {
    return get_object(USERS_D)->query_this_player() ;
}

/* The fail_msg() autofun calls the set_fail_msg() in this_player().
   This allows you to set a failure message in the player to be printed
   instead of "What?" when a command doesn't recognize the input it was
   given.
   It is equivalent, and maybe better, to just call the function in the
   player.c object directly. It would be more properly object oriented
   to do so, it would be faster, and it would make for one less autofun.
   However, it is also much more typing, and I want it to be very easy
   for wizards to use this in their own add_commands, so I don't want
   people to have to type this_player()-> all the time. Also, doing it
   this way is analagous to the way MudOS handled it (the notify_fail()
   efun) and so I think people are more used to this.
*/

static nomask void fail_msg (string str) {
    this_player()->set_fail_msg(str) ;
}

/* The heartbeat function. There is really only one function, which is
   set_heart_beat(). It takes an integer arg. Setting it to zero turns
   off the heartbeat: setting it to an integer N provides a call_out to
   heart_beat() every N seconds.
   If you change the heartbeat time while the heartbeat is on, then the
   current callout will be executed and the time change will take effect
   for the following call. This is done because it's simplest and not
   for any deep philosophical reason.
   There is a second function, do_heart_beat(), which does the callout
   to heart_beat() and which should be left alone. */

static void set_heart_beat (int time) {

/* If we're turning it off, handle that case. */
    if (time==0) {
        heart_beat_time=0 ;
	remove_call_out (heart_beat_handle) ;
        return ;
    }

/* If it's not on now, then start it. */
    if (heart_beat_time==0) {
        heart_beat_handle=call_out("do_heart_beat",time) ;
    }

/* Set the new heartbeat time. */
    heart_beat_time = time ;
    return ;
}

/* This is called out. It sets up the next call to heart beat,
   then makes the call.
   It makes the heartbeat call before it sets up the next one.
   This way, if there's a runtime error in the heartbeat, the
   heartbeat won't be called again. Some fancy footwork has to
   be done to get things to Work Out Right. 
   It can only be called by the driver or within the same object.
   You probably -don't- want to call it within the same object -
   go through set_heart_beat - but if for some reason you need
   to, fine. It can't be static because the driver has to be
   able to reach it with call_out.
*/

nomask void do_heart_beat() {

    string err ;

    if (previous_object() && previous_object()!=this_object()) return ;
    err = catch(this_object()->heart_beat()) ;
    if (err) {
	heart_beat_time=0 ;
	return ;
    }
    heart_beat_handle = call_out("do_heart_beat",heart_beat_time) ;
}