tmi2/
tmi2/bin/
tmi2/etc/
tmi2/lib/
tmi2/lib/adm/
tmi2/lib/adm/daemons/languages/
tmi2/lib/adm/daemons/network/I3/
tmi2/lib/adm/daemons/virtual/template/
tmi2/lib/adm/obj/master/
tmi2/lib/adm/priv/
tmi2/lib/adm/shell/
tmi2/lib/adm/tmp/
tmi2/lib/cmds/
tmi2/lib/d/
tmi2/lib/d/Conf/
tmi2/lib/d/Conf/adm/
tmi2/lib/d/Conf/boards/
tmi2/lib/d/Conf/cmds/
tmi2/lib/d/Conf/data/
tmi2/lib/d/Conf/logs/
tmi2/lib/d/Conf/obj/
tmi2/lib/d/Conf/text/help/
tmi2/lib/d/Fooland/adm/
tmi2/lib/d/Fooland/data/
tmi2/lib/d/Fooland/data/attic/
tmi2/lib/d/Fooland/items/
tmi2/lib/d/TMI/
tmi2/lib/d/TMI/adm/
tmi2/lib/d/TMI/boards/
tmi2/lib/d/TMI/data/
tmi2/lib/d/TMI/rooms/
tmi2/lib/d/grid/
tmi2/lib/d/grid/adm/
tmi2/lib/d/grid/data/
tmi2/lib/d/std/
tmi2/lib/d/std/adm/
tmi2/lib/data/adm/
tmi2/lib/data/adm/daemons/
tmi2/lib/data/adm/daemons/doc_d/
tmi2/lib/data/adm/daemons/emoted/
tmi2/lib/data/adm/daemons/network/http/
tmi2/lib/data/adm/daemons/network/services/mail_q/
tmi2/lib/data/adm/daemons/network/smtp/
tmi2/lib/data/adm/daemons/news/archives/
tmi2/lib/data/attic/connection/
tmi2/lib/data/attic/user/
tmi2/lib/data/std/connection/b/
tmi2/lib/data/std/connection/l/
tmi2/lib/data/std/user/a/
tmi2/lib/data/std/user/b/
tmi2/lib/data/std/user/d/
tmi2/lib/data/std/user/f/
tmi2/lib/data/std/user/l/
tmi2/lib/data/std/user/x/
tmi2/lib/data/u/d/dm/working/doc_d/
tmi2/lib/data/u/l/leto/doc_d/
tmi2/lib/data/u/l/leto/smtp/
tmi2/lib/doc/
tmi2/lib/doc/driverdoc/applies/
tmi2/lib/doc/driverdoc/concepts/
tmi2/lib/doc/driverdoc/driver/
tmi2/lib/doc/driverdoc/efuns/arrays/
tmi2/lib/doc/driverdoc/efuns/buffers/
tmi2/lib/doc/driverdoc/efuns/compile/
tmi2/lib/doc/driverdoc/efuns/ed/
tmi2/lib/doc/driverdoc/efuns/floats/
tmi2/lib/doc/driverdoc/efuns/functions/
tmi2/lib/doc/driverdoc/efuns/general/
tmi2/lib/doc/driverdoc/efuns/numbers/
tmi2/lib/doc/driverdoc/efuns/parsing/
tmi2/lib/doc/driverdoc/lpc/constructs/
tmi2/lib/doc/driverdoc/lpc/preprocessor/
tmi2/lib/doc/driverdoc/lpc/types/
tmi2/lib/doc/driverdoc/platforms/
tmi2/lib/doc/mudlib/
tmi2/lib/ftp/
tmi2/lib/log/
tmi2/lib/obj/net/
tmi2/lib/obj/shells/
tmi2/lib/std/board/
tmi2/lib/std/body/
tmi2/lib/std/fun/
tmi2/lib/std/living/
tmi2/lib/std/object/
tmi2/lib/std/shop/
tmi2/lib/std/socket/
tmi2/lib/std/virtual/
tmi2/lib/student/
tmi2/lib/student/kalypso/
tmi2/lib/student/kalypso/armor/
tmi2/lib/student/kalypso/rooms/
tmi2/lib/student/kalypso/weapons/
tmi2/lib/u/l/leto/
tmi2/lib/u/l/leto/cmds/
tmi2/lib/www/errors/
tmi2/lib/www/gateways/
tmi2/lib/www/images/
tmi2/old/
tmi2/v21.7a10/
tmi2/v21.7a10/ChangeLog.old/
tmi2/v21.7a10/compat/simuls/
tmi2/v21.7a10/include/
tmi2/v21.7a10/testsuite/
tmi2/v21.7a10/testsuite/clone/
tmi2/v21.7a10/testsuite/command/
tmi2/v21.7a10/testsuite/data/
tmi2/v21.7a10/testsuite/etc/
tmi2/v21.7a10/testsuite/include/
tmi2/v21.7a10/testsuite/inherit/
tmi2/v21.7a10/testsuite/inherit/master/
tmi2/v21.7a10/testsuite/log/
tmi2/v21.7a10/testsuite/u/
tmi2/v21.7a10/tmp/
#define SUPPRESS_COMPILER_INLINES
#include "std.h"
#include "lpc_incl.h"
#include "file_incl.h"
#include "backend.h"
#include "simul_efun.h"
#include "compiler.h"
#include "otable.h"
#include "comm.h"
#include "lex.h"
#include "binaries.h"
#include "swap.h"
#include "socket_efuns.h"
#include "md.h"
#include "eoperators.h"
#include "ed.h"
#include "file.h"
#include "packages/parser.h"

/*
 * 'inherit_file' is used as a flag. If it is set to a string
 * after yyparse(), this string should be loaded as an object,
 * and the original object must be loaded again.
 */
char *inherit_file;
#ifdef LPC_TO_C
int compile_to_c;
FILE *compilation_output_file;
char *compilation_ident;
#endif

/* prevents infinite inherit loops.
   No, mark-and-sweep solution won't work.  Exercise for reader.  */
static int num_objects_this_thread = 0;

static object_t *restrict_destruct;

char *last_verb = 0;

int illegal_sentence_action;
object_t *obj_list, *obj_list_destruct;
object_t *current_object;	/* The object interpreting a function. */
object_t *command_giver;	/* Where the current command came from. */
object_t *current_interactive;	/* The user who caused this execution */

#ifdef PRIVS
static void init_privs_for_object PROT((object_t *));
#endif
#ifdef PACKAGE_UIDS
static int give_uid_to_object PROT((object_t *));
#endif
static int init_object PROT((object_t *));
static svalue_t *load_virtual_object PROT((char *));
static char *make_new_name PROT((char *));
static void destruct_object_two PROT((object_t *));
#ifndef NO_ENVIRONMENT
static void send_say PROT((object_t *, char *, array_t *));
#endif
static sentence_t *alloc_sentence PROT((void));
#ifndef NO_ADD_ACTION
static void remove_sent PROT((object_t *, object_t *));
#endif

INLINE void check_legal_string P1(char *, s)
{
    if (strlen(s) >= LARGEST_PRINTABLE_STRING) {
	error("Printable strings limited to length of %d.\n",
	      LARGEST_PRINTABLE_STRING);
    }
}

#ifdef PRIVS
static char *privs_file_fname = (char *) 0;

static void
init_privs_for_object P1(object_t *, ob)
{
    object_t *tmp_ob;
    svalue_t *value;

    if (master_ob == (object_t *)-1) {
	tmp_ob = ob;
    } else {
	tmp_ob = master_ob;
    }
    if (!current_object
#ifdef PACKAGE_UIDS
	|| !current_object->uid
#endif
    ) {
	ob->privs = NULL;
	return;
    }
    push_string(ob->name, STRING_SHARED);
    if (!privs_file_fname)
	privs_file_fname = make_shared_string(APPLY_PRIVS_FILE);
    value = apply(privs_file_fname, tmp_ob, 1, ORIGIN_DRIVER);
    if (value == NULL || value->type != T_STRING)
	ob->privs = NULL;
    else
	ob->privs = make_shared_string(value->u.string);
}
#endif				/* PRIVS */

/*
 * Give the correct uid and euid to a created object.
 */
#ifdef PACKAGE_UIDS
static char *creator_file_fname = (char *) 0;

static int give_uid_to_object P1(object_t *, ob)
{
    svalue_t *ret;
    char *creator_name;

    if (master_ob == (object_t *)-1) {
	ob->uid = add_uid("NONAME");
#ifdef AUTO_SETEUID
	ob->euid = ob->uid;
#else	
	ob->euid = NULL;
#endif
	return 1;
    }
    /*
     * Ask master object who the creator of this object is.
     */
    push_string(ob->name, STRING_SHARED);
    if (!creator_file_fname)
	creator_file_fname = make_shared_string(APPLY_CREATOR_FILE);
    ret = apply_master_ob(creator_file_fname, 1);
    if (!ret)
	error("master object: No function " APPLY_CREATOR_FILE "() defined!\n");
    if (!ret || ret == (svalue_t *)-1 || ret->type != T_STRING) {
	svalue_t arg;

	arg.type = T_OBJECT;
	arg.u.ob = ob;
	destruct_object(&arg);
	if (!ret) error("Master object has no function " APPLY_CREATOR_FILE "().\n");
	if (ret == (svalue_t *)-1) error("Can't load objects without a master object.");
	error("Illegal object to load: return value of master::" APPLY_CREATOR_FILE "() was not a string.\n");
    }
    creator_name = ret->u.string;
    /*
     * Now we are sure that we have a creator name. Do not call apply()
     * again, because creator_name will be lost !
     */
    if (strcmp(current_object->uid->name, creator_name) == 0) {
	/*
	 * The loaded object has the same uid as the loader.
	 */
	ob->uid = current_object->uid;
	ob->euid = current_object->euid;	/* FIXME - is this right? */
	return 1;
    }
#ifdef AUTO_TRUST_BACKBONE
    if (strcmp(backbone_uid->name, creator_name) == 0) {
	/*
	 * The object is loaded from backbone. This is trusted, so we let it
	 * inherit the value of eff_user.
	 */
	ob->uid = current_object->euid;
	ob->euid = current_object->euid;
	return 1;
    }
#endif

    /*
     * The object is not loaded from backbone, nor from from the loading
     * objects path. That should be an object defined by another wizard. It
     * can't be trusted, so we give it the same uid as the creator. Also give
     * it eff_user 0, which means that user 'a' can't use objects from user
     * 'b' to load new objects nor modify files owned by user 'b'.
     * 
     * If this effect is wanted, user 'b' must let his object do 'seteuid()' to
     * himself. That is the case for most rooms.
     */
    ob->uid = add_uid(creator_name);
#ifdef AUTO_SETEUID
    ob->euid = ob->uid;
#else
    ob->euid = NULL;
#endif
    return 1;
}
#endif

static int init_object P1(object_t *, ob)
{
#ifdef PACKAGE_MUDLIB_STATS
    init_stats_for_object(ob);
#endif
#ifdef PRIVS
    init_privs_for_object(ob);
#endif				/* PRIVS */
#ifdef PACKAGE_MUDLIB_STATS
    add_objects(&ob->stats, 1);
#endif
#ifdef NO_ADD_ACTION
    if (function_exists("catch_tell", ob))
	ob->flags |= O_LISTENER;
#endif
#ifdef PACKAGE_UIDS
    return give_uid_to_object(ob);
#else
    return 1;
#endif
}


static svalue_t *
       load_virtual_object P1(char *, name)
{
    svalue_t *v;

    if (master_ob == (object_t *)-1) return 0;
    push_string(name, STRING_MALLOC);
    v = apply_master_ob(APPLY_COMPILE_OBJECT, 1);
    if (!v || (v->type != T_OBJECT))
	return 0;
    return v;
}

void set_master P1(object_t *, ob) {
#if defined(PACKAGE_UIDS) || defined(PACKAGE_MUDLIB_STATS)
    int first_load = (master_ob == (object_t *)-1);
#endif
#ifdef PACKAGE_UIDS
    svalue_t *ret;
#endif

    master_ob = ob;
    /* Make sure master_ob is never made a dangling pointer. */
    add_ref(master_ob, "set_master");
#ifndef PACKAGE_UIDS
#  ifdef PACKAGE_MUDLIB_STATS
    if (first_load) {
      set_backbone_domain("BACKBONE");
      set_master_author("NONAME");
    }
#  endif
#else
    ret = apply_master_ob(APPLY_GET_ROOT_UID, 0);
    /* can't be -1 or we wouldn't be here */
    if (!ret) {
        debug_message("No function %s() in master object; possibly the mudlib doesn't want PACKAGE_UIDS to be defined.\n",
		    APPLY_GET_ROOT_UID);
	exit(-1);
    }
    if (ret->type != T_STRING) {
        debug_message("%s() in master object does not work.\n",
		    APPLY_GET_ROOT_UID);
	exit(-1);
    }
    if (first_load) {
      master_ob->uid = set_root_uid(ret->u.string);
      master_ob->euid = master_ob->uid;
#  ifdef PACKAGE_MUDLIB_STATS
      set_master_author(ret->u.string);
#  endif
      ret = apply_master_ob(APPLY_GET_BACKBONE_UID, 0);
      if (ret == 0 || ret->type != T_STRING) {
	  debug_message("%s() in the master file does not work\n",
		  APPLY_GET_BACKBONE_UID);
	  exit(-1);
      }
      set_backbone_uid(ret->u.string);
#  ifdef PACKAGE_MUDLIB_STATS
      set_backbone_domain(ret->u.string);
#  endif
    } else {
      master_ob->uid = add_uid(ret->u.string);
      master_ob->euid = master_ob->uid;
    }
#endif
 }

char *check_name P1(char *, src) {
    char *p;
    while (*src == '/') src++;
    p = src;
    while (*p) {
	if (*p == '/' && *(p + 1) == '/')
	    return 0;
	p++;
    }
    return src;
}

int strip_name P3(char *, src, char *, dest, int, size) {
    char last_c = 0;
    char *p = dest;
    char *end = dest + size - 1;

    while (*src == '/') src++;

    while (*src && p < end) {
	if (last_c == '/' && *src == '/') return 0;
	last_c = (*p++ = *src++);
    }

    /* In some cases, (for example, object loading) this currently gets
     * run twice, once in find_object, and once in load object.  The
     * net effect of this is:
     * /foo.c -> /foo [no such exists, try to load] -> /foo created
     * /foo.c.c -> /foo.c [no such exists, try to load] -> /foo created
     *
     * causing a duplicate object crash.  There are two ways to fix this:
     * (1) strip multiple .c's so that the output of this routine is something
     *     that doesn't change if this is run again.
     * (2) make sure this routine is only called once on any name.
     *
     * The first solution is the one currently in use.
     */
    while (p - dest > 2 && p[-1] == 'c' && p[-2] == '.') 
	p -= 2;

    *p = 0;
    return 1;
}

/*
 * Load an object definition from file. If the object wants to inherit
 * from an object that is not loaded, discard all, load the inherited object,
 * and reload again.
 *
 * In mudlib3.0 when loading inherited objects, their reset() is not called.
 * - why is this??  it makes no sense and causes a problem when a developer
 * inherits code from a real used item.  Say a room for example.  In this case
 * the room is loaded but is never set up properly, so when someone enters it
 * it's all messed up.  Realistically, I know that it's pretty bad style to
 * inherit from an object that's actually being used and isn't just a building
 * block, but I see no reason for this limitation.  It happens, and when it
 * does occur, produces mysterious results than can be hard to track down.
 * for now, I've reenabled resetting.  We'll see if anything breaks. -WF
 *
 * Save the command_giver, because reset() in the new object might change
 * it.
 *
 */
object_t *load_object P2(char *, lname, lpc_object_t *, lpc_obj)
{
    int f;

    object_t *ob, *save_command_giver = command_giver;
    svalue_t *mret;
    struct stat c_st;
    char real_name[200], name[200];
#ifdef LPC_TO_C
    char out_name[200];
    char *out_ptr = out_name;
    int save_compile_to_c = compile_to_c;
#endif

    if (++num_objects_this_thread > INHERIT_CHAIN_SIZE)
	error("Inherit chain too deep: > %d\n", INHERIT_CHAIN_SIZE);
#ifdef PACKAGE_UIDS
    if (current_object && current_object->euid == NULL)
	error("Can't load objects when no effective user.\n");
#endif
    if (!strip_name(lname, name, sizeof name))
	error("Filenames with consecutive /'s in them aren't allowed (%s).\n",
	      lname);

    /*
     * First check that the c-file exists.
     */
    (void) strcpy(real_name, name);
    (void) strcat(real_name, ".c");

    if (stat(real_name, &c_st) == -1) {
	svalue_t *v;

	if (!(v = load_virtual_object(name))) {
	    num_objects_this_thread--;
	    return 0;
	}
	/* Now set the file name of the specified object correctly... */
	ob = v->u.ob;
	remove_object_hash(ob);
	if (ob->name)
	    FREE(ob->name);
	ob->name = alloc_cstring(name, "load_object");
	SET_TAG(ob->name, TAG_OBJ_NAME);
	enter_object_hash(ob);
	ob->flags |= O_VIRTUAL;
	ob->load_time = current_time;
#ifdef LPC_TO_C
	compile_to_c = save_compile_to_c;
#endif
#ifdef PRIVS
	if (ob->privs) free_string(ob->privs);
	init_privs_for_object(ob);
#endif
	num_objects_this_thread--;
	return ob;
    }
    /*
     * Check if it's a legal name.
     */
    if (!legal_path(real_name)) {
	debug_message("Illegal pathname: /%s\n", real_name);
	error("Illegal path name '/%s'.\n", real_name);
	return 0;
    }
#ifdef BINARIES
    if (!load_binary(real_name, lpc_obj)) {
#endif
	/* maybe move this section into compile_file? */
	if (comp_flag) {
	    debug_message(" compiling /%s ...", real_name);
#ifdef LATTICE
	    fflush(stderr);
#endif
	}
	f = open(real_name, O_RDONLY);
	if (f == -1) {
	    debug_perror("compile_file", real_name);
	    error("Could not read the file '/%s'.\n", real_name);
	}
#ifdef LPC_TO_C
	if (compile_to_c) {
	    compilation_output_file = crdir_fopen(out_ptr);
	    if (compilation_output_file == 0) {
		debug_perror("compile_to_c", out_ptr);
		error("Could not open output file '/%s'.\n", out_ptr);
	    }
	    compilation_ident = 0;
	    compile_file(f, real_name);
	    fclose(compilation_output_file);
	    if (prog) {
		if (prog)
		    free_prog(prog, 1);
		prog = 0;
	    }
	} else {
#endif
	    compile_file(f, real_name);
#ifdef LPC_TO_C
	}
#endif
	if (comp_flag)
	    debug_message(" done\n");
	update_compile_av(total_lines);
	total_lines = 0;
	close(f);
#ifdef BINARIES
    }
#endif

    /* Sorry, can't handle objects without programs yet. */
    if (inherit_file == 0 && (num_parse_error > 0 || prog == 0)) {
	if (prog)
	    free_prog(prog, 1);
	if (num_parse_error == 0 && prog == 0)
	    error("No program in object '/%s'!\n", name);
	error("Error in loading object '/%s'\n", name);
    }
    /*
     * This is an iterative process. If this object wants to inherit an
     * unloaded object, then discard current object, load the object to be
     * inherited and reload the current object again. The global variable
     * "inherit_file" will be set by lang.y to point to a file name.
     */
    if (inherit_file) {
	object_t *inh_obj;
	char inhbuf[MAX_OBJECT_NAME_SIZE];

	if (!strip_name(inherit_file, inhbuf, sizeof inhbuf))
	    strcpy(inhbuf, inherit_file);
	FREE(inherit_file);
	inherit_file = 0;
	
	if (prog) {
	    free_prog(prog, 1);
	    prog = 0;
	}
	if (strcmp(inhbuf, name) == 0) {
	    error("Illegal to inherit self.\n");
	}

	if ((inh_obj = lookup_object_hash(inhbuf))) {
#ifdef LPC_TO_C
	    DEBUG_CHECK(!(inh_obj->flags & O_COMPILED_PROGRAM), "Inherited object is already loaded!\n");
	    inh_obj = load_object(inhbuf, (lpc_object_t *)inh_obj);
#else
	    IF_DEBUG(fatal("Inherited object is already loaded!"));
#endif
	} else {
	    inh_obj = load_object(inhbuf, 0);
	}
	if (!inh_obj) error("Inherited file '/%s' does not exist!\n",
			    inhbuf);

	/*
	 * Yes, the following is necessary.  It is possible that when we
	 * loaded the inherited object, it loaded this object from it's
	 * create function. Without this check, that would crash the driver.
	 * -Beek
	 */
	if (!(ob = lookup_object_hash(name))) {
	    ob = load_object(name, 0);
	    /* sigh, loading the inherited file removed us */
	    if (!ob) { 
		num_objects_this_thread--;
		return 0;
	    }
	    ob->load_time = current_time;
	}
#ifdef LPC_TO_C
	else if (ob->flags & O_COMPILED_PROGRAM) {
	    /* I don't think this is possible, but ... */
	    ob = load_object(name, (lpc_object_t *)ob);
	    /* sigh, loading the inherited file removed us */
	    if (!ob) return 0;
	    ob->load_time = current_time;
	}
#endif
	num_objects_this_thread--;
#ifdef LPC_TO_C
	compile_to_c = save_compile_to_c;
#endif
	return ob;
    }
    ob = get_empty_object(prog->num_variables);
    /* Shared string is no good here */
    ob->name = alloc_cstring(name, "load_object");
    SET_TAG(ob->name, TAG_OBJ_NAME);
    ob->prog = prog;
    ob->flags |= O_WILL_RESET;	/* must be before reset is first called */
    ob->next_all = obj_list;
    obj_list = ob;
    enter_object_hash(ob);	/* add name to fast object lookup table */
    push_object(ob);
    mret = apply_master_ob(APPLY_VALID_OBJECT, 1);
    if (mret && !MASTER_APPROVED(mret)) {
	destruct_object_two(ob);
	error("master object: " APPLY_VALID_OBJECT "() denied permission to load '/%s'.\n", name);
    }

    if (init_object(ob))
	call_create(ob, 0);
    if (!(ob->flags & O_DESTRUCTED) &&
	function_exists(APPLY_CLEAN_UP, ob)) {
	ob->flags |= O_WILL_CLEAN_UP;
    }
    command_giver = save_command_giver;
#ifdef DEBUG
    if (d_flag > 1 && ob)
	debug_message("--/%s loaded\n", ob->name);
#endif
    ob->load_time = current_time;
    num_objects_this_thread--;
#ifdef LPC_TO_C
    compile_to_c = save_compile_to_c;
#endif
    return ob;
}

static char *make_new_name P1(char *, str)
{
    static int i;
    char *p = DXALLOC(strlen(str) + 10, TAG_OBJ_NAME, "make_new_name");

    (void) sprintf(p, "%s#%d", str, i);
    i++;
    return p;
}


/*
 * Save the command_giver, because reset() in the new object might change
 * it.
 */
object_t *clone_object P2(char *, str1, int, num_arg)
{
    object_t *ob, *new_ob;
    object_t *save_command_giver = command_giver;

#ifdef PACKAGE_UIDS
    if (current_object && current_object->euid == 0) {
	error("Object must call seteuid() prior to calling clone_object().\n");
    }
#endif
    num_objects_this_thread = 0;
    ob = find_object(str1);
    if (ob && !object_visible(ob))
	ob = 0;
    /*
     * If the object self-destructed...
     */
    if (ob == 0) {		/* fix from 3.1.1 */
	pop_n_elems(num_arg);
	return (0);
    }
    if (ob->flags & O_CLONE)
	if (!(ob->flags & O_VIRTUAL) || strrchr(str1, '#'))
	    error("Cannot clone from a clone\n");
	else {
	    /*
	     * well... it's a virtual object.  So now we're going to "clone"
	     * it.
	     */
	    svalue_t *v;

	    pop_n_elems(num_arg); /* possibly this should be smarter */
	                          /* but then, this whole section is a
				     kludge and should be looked at.

				     Note that create() never gets called
				     in clones of virtual objects.
				     -Beek */

	    if (!(str1 = check_name(str1))) 
		error("Filenames with consecutive /'s in them aren't allowed (%s).\n", str1);

	    if (ob->ref == 1
#ifndef NO_ENVIRONMENT
		&& !ob->super && !ob->contains
#endif
		) {
		/*
		 * ob unused so reuse it instead to save space. (possibly
		 * loaded just for cloning)
		 */
		new_ob = ob;
	    } else {
		/* can't reuse, so load another */
		if (!(v = load_virtual_object(str1)))
		    return 0;
		new_ob = v->u.ob;
	    }
	    remove_object_hash(new_ob);
	    if (new_ob->name)
		FREE(new_ob->name);
	    /* Now set the file name of the specified object correctly... */
	    new_ob->name = make_new_name(str1);
	    enter_object_hash(new_ob);
	    new_ob->flags |= O_VIRTUAL;
	    new_ob->load_time = current_time;
	    command_giver = save_command_giver;
	    return (new_ob);
	    /*
	     * we can skip all of the stuff below since we were already
	     * cloned once to have gotten to this stage.
	     */
	}
    /* We do not want the heart beat to be running for unused copied objects */
    if (ob->flags & O_HEART_BEAT)
	(void) set_heart_beat(ob, 0);
    new_ob = get_empty_object(ob->prog->num_variables);
    new_ob->name = make_new_name(ob->name);
    new_ob->flags |= (O_CLONE | (ob->flags & (O_WILL_CLEAN_UP | O_WILL_RESET)));
    new_ob->load_time = ob->load_time;
    new_ob->prog = ob->prog;
    reference_prog(ob->prog, "clone_object");
    DEBUG_CHECK(!current_object, "clone_object() from no current_object !\n");

    init_object(new_ob);

    new_ob->next_all = obj_list;
    obj_list = new_ob;
    enter_object_hash(new_ob);	/* Add name to fast object lookup table */
    call_create(new_ob, num_arg);
    command_giver = save_command_giver;
    /* Never know what can happen ! :-( */
    if (new_ob->flags & O_DESTRUCTED)
	return (0);
    return (new_ob);
}

#ifndef NO_ENVIRONMENT
object_t *environment P1(svalue_t *, arg)
{
    object_t *ob = current_object;

    if (arg && arg->type == T_OBJECT)
	ob = arg->u.ob;
    if (ob == 0 || ob->super == 0 || (ob->flags & O_DESTRUCTED))
	return 0;
    if (ob->flags & O_DESTRUCTED)
	error("environment() of destructed object.\n");
    return ob->super;
}
#endif

/*
 * Execute a command for an object. Copy the command into a
 * new buffer, because 'parse_command()' can modify the command.
 * If the object is not current object, static functions will not
 * be executed. This will prevent forcing users to do illegal things.
 *
 * Return cost of the command executed if success (> 0).
 * When failure, return 0.
 */
#ifndef NO_ADD_ACTION
int command_for_object P1(char *, str)
{
    char buff[1000];
    int save_eval_cost = eval_cost;

    if (strlen(str) > sizeof(buff) - 1)
	error("Too long command.\n");
    else if (current_object->flags & O_DESTRUCTED)
	return 0;
    strncpy(buff, str, sizeof buff);
    buff[sizeof buff - 1] = '\0';
    if (parse_command(buff, current_object))
	return save_eval_cost - eval_cost;
    else
	return 0;
}
#endif

/*
 * With no argument, present() looks in the inventory of the current_object,
 * the inventory of our super, and our super.
 * If the second argument is nonzero, only the inventory of that object
 * is searched.
 */


#ifndef NO_ENVIRONMENT
static object_t *object_present2 PROT((char *, object_t *));

object_t *object_present P2(svalue_t *, v, object_t *, ob)
{
    svalue_t *ret;
    object_t *ret_ob;
    int specific = 0;

    if (ob == 0)
	ob = current_object;
    else
	specific = 1;
    if (ob->flags & O_DESTRUCTED)
	return 0;
    if (v->type == T_OBJECT) {
	if (specific) {
	    if (v->u.ob->super == ob)
		return v->u.ob;
	    else
		return 0;
	}
	if (v->u.ob->super == ob ||
	    (v->u.ob->super == ob->super && ob->super != 0))
	    return v->u.ob->super;
	return 0;
    }
    ret_ob = object_present2(v->u.string, ob->contains);
    if (ret_ob)
	return ret_ob;
    if (specific)
	return 0;
    if (ob->super) {
	push_svalue(v);
	ret = apply(APPLY_ID, ob->super, 1, ORIGIN_DRIVER);
	if (ob->super->flags & O_DESTRUCTED)
	    return 0;
	if (!IS_ZERO(ret)) {
#if 0
	    /*
	     * if id() returns a value of type object then query that object.
	     * this will allow container objects to allow objects inside them
	     * to be referred to (for attack or whatever).
	     */
	    if (ret->type == T_OBJECT)
		return ret->u.ob;
	    else
#endif				/* 0 */
		return ob->super;
	}
	return object_present2(v->u.string, ob->super->contains);
    }
    return 0;
}

static object_t *object_present2 P2(char *, str, object_t *, ob)
{
    svalue_t *ret;
    char *p;
    int count = 0, length;
    char *item;

    item = alloc_cstring(str, "object_present2");
    length = strlen(item);
    p = item + length - 1;
    if (*p >= '0' && *p <= '9') {
	while (p > item && *p >= '0' && *p <= '9')
	    p--;
	if (p > item && *p == ' ') {
	    count = atoi(p + 1) - 1;
	    *p = '\0';
	    length = p - item;	/* This is never used again ! */
	}
    }
    for (; ob; ob = ob->next_inv) {
	push_string(item, STRING_SHARED);
	ret = apply(APPLY_ID, ob, 1, ORIGIN_DRIVER);
	if (ob->flags & O_DESTRUCTED) {
	    FREE(item);
	    return 0;
	}
	if (IS_ZERO(ret))
	    continue;
	if (count-- > 0)
	    continue;
	FREE(item);
#if 0
	if (ret->type == T_OBJECT)
	    return ret->u.ob;
	else
#endif
	    return ob;
    }
    FREE(item);
    return 0;
}
#endif

void init_master P1(char *, file) {
    char buf[512];
    lpc_object_t *compiled_version;
    object_t *new_ob;
    
    if (!file || !file[0]) {
	fprintf(stderr, "No master object specified in config file\n");
	exit(-1);
    }
	
    if (!strip_name(file, buf, sizeof buf))
	error("Illegal simul_efun file name '%s'\n", file);
    
    compiled_version = (lpc_object_t *)lookup_object_hash(buf);

    if (file[strlen(file) - 2] != '.')
	strcat(buf, ".c");
    
    new_ob = load_object(file, compiled_version);
    if (new_ob == 0) {
	fprintf(stderr, "The master file %s was not loaded.\n",
		file);
	exit(-1);
    }
    set_master(new_ob);
}

/*
 * Remove an object. It is first moved into the destruct list, and
 * not really destructed until later. (see destruct2()).
 */
static void destruct_object_two P1(object_t *, ob)
{
    object_t **pp;
    int removed;
#ifndef NO_ENVIRONMENT
    object_t *super;
    object_t *save_restrict_destruct = restrict_destruct;
#endif

    if (restrict_destruct && restrict_destruct != ob)
	error("Only this_object() can be destructed from move_or_destruct.\n");

#ifdef PACKAGE_SOCKETS
    /*
     * check if object has an efun socket referencing it for a callback. if
     * so, close the efun socket.
     */
    if (ob->flags & O_EFUN_SOCKET) {
	close_referencing_sockets(ob);
    }
#endif
#ifdef PACKAGE_PARSER
    if (ob->pinfo) {
	parse_free(ob->pinfo);
	ob->pinfo = 0;
    }
#endif

    if (ob->flags & O_DESTRUCTED) {
	return;
    }
    if (ob->flags & O_SWAPPED)
	load_ob_from_swap(ob);
    remove_object_from_stack(ob);
    /*
     * If this is the first object being shadowed by another object, then
     * destruct the whole list of shadows.
     */
#ifndef NO_SHADOWS
    if (ob->shadowed && !ob->shadowing) {
	svalue_t svp;
	object_t *ob2;

	svp.type = T_OBJECT;
	/*
	 * move from bottom to top of shadow chain
	 */
	for (ob2 = ob->shadowed; ob2->shadowed; ob2 = ob2->shadowed);
	/*
	 * remove shadows top...down being careful to unlink shadow being
	 * destructed from chain
	 */
	for (; ob2;) {
	    svp.u.ob = ob2;
	    ob2 = ob2->shadowing;
	    if (ob2)
		ob2->shadowed = 0;
	    svp.u.ob->shadowing = 0;
	    destruct_object(&svp);
	}
	return;
    }
    /*
     * The chain of shadows is a double linked list. Take care to update it
     * correctly.
     */
    if (ob->shadowing)
	ob->shadowing->shadowed = ob->shadowed;
    if (ob->shadowed)
	ob->shadowed->shadowing = ob->shadowing;
    ob->shadowing = 0;
    ob->shadowed = 0;
#endif

#ifdef DEBUG
    if (d_flag > 1)
	debug_message("Deobject_t %s (ref %d)\n", ob->name, ob->ref);
#endif

#ifndef NO_ENVIRONMENT
    /* try to move our contents somewhere */
    super = ob->super;

    while (ob->contains) {
	svalue_t svp;
	svp.type = T_OBJECT;
	svp.u.ob = ob->contains;
	/*
	 * An error here will not leave destruct() in an inconsistent
	 * stage.
	 */
	if (super && !(super->flags & O_DESTRUCTED))
	    push_object(super);
	else
	    push_number(0);
	    
	restrict_destruct = ob->contains;
	(void)apply(APPLY_MOVE, ob->contains, 1, ORIGIN_DRIVER);
	restrict_destruct = save_restrict_destruct;
	/* OUCH! we could be dested by this. -Beek */
	if (ob->flags & O_DESTRUCTED) return;
	if (svp.u.ob == ob->contains)
	    destruct_object(&svp);
    }
#endif
    
#ifdef PACKAGE_MUDLIB_STATS
    add_objects(&ob->stats, -1);
#endif
#ifdef OLD_ED
    if (ob->interactive && ob->interactive->ed_buffer)
	save_ed_buffer(ob);
#else
    if (ob->flags & O_IN_EDIT) {
	object_save_ed_buffer(ob);
	ob->flags &= ~O_IN_EDIT;
    }
#endif
#ifndef NO_ENVIRONMENT
    /*
     * Remove us out of this current room (if any). Remove all sentences
     * defined by this object from all objects here.
     */
    if (ob->super) {
#ifndef NO_LIGHT
	add_light(ob->super, -ob->total_light);
#endif
#ifndef NO_ADD_ACTION
	if (ob->super->flags & O_ENABLE_COMMANDS)
	    remove_sent(ob, ob->super);
#endif
	for (pp = &ob->super->contains; *pp;) {
#ifndef NO_ADD_ACTION
	    if ((*pp)->flags & O_ENABLE_COMMANDS)
		remove_sent(ob, *pp);
#endif
	    if (*pp != ob)
		pp = &(*pp)->next_inv;
	    else
		*pp = (*pp)->next_inv;
	}
    }
#endif

    /* At this point, we can still back out, but this is the very last
     * minute we can do so.  Make sure we have a new object to replace
     * us if this is a vital object.
     */
    if (ob == master_ob || ob == simul_efun_ob) {
	object_t *new_ob;
	/* hack to make sure we don't find the precompiled tag 
	   and not ourself */
	lpc_object_t *compiled_version;
	char *tmp = ob->name;
	
	ob->name = "";
	compiled_version = (lpc_object_t *)lookup_object_hash(tmp);
	ob->name = tmp;

	/* handle these two carefully, since they are rather vital */
	new_ob = load_object(tmp, compiled_version);
	if (!new_ob)
	    error("Destruct on vital object failed: new copy failed to reload.");

	free_object(ob, "vital object reference");
	if (ob == master_ob)
	    set_master(new_ob);
	if (ob == simul_efun_ob)
	    set_simul_efun(new_ob);
	
	/* be careful not to remove the new hash */
	tmp = new_ob->name;
	new_ob->name = "";
	remove_object_hash(ob);
	new_ob->name = tmp;
    } else
	remove_object_hash(ob);

    /*
     * Now remove us out of the list of all objects. This must be done last,
     * because an error in the above code would halt execution.
     */
    removed = 0;
    for (pp = &obj_list; *pp; pp = &(*pp)->next_all) {
	if (*pp != ob)
	    continue;
	*pp = (*pp)->next_all;
	removed = 1;
	break;
    }
    DEBUG_CHECK(!removed, "Failed to delete object.\n");

#ifndef NO_ADD_ACTION
    if (ob->living_name)
	remove_living_name(ob);
    ob->flags &= ~O_ENABLE_COMMANDS;
#endif
#ifndef NO_ENVIRONMENT
    ob->super = 0;
    ob->next_inv = 0;
    ob->contains = 0;
#endif
    ob->next_all = obj_list_destruct;
    obj_list_destruct = ob;
    set_heart_beat(ob, 0);
    ob->flags |= O_DESTRUCTED;
    /* moved this here from destruct2() -- see comments in destruct2() */
    if (ob->interactive) {
	remove_interactive(ob);
    }
}

void destruct_object P1(svalue_t *, v)
{
    object_t *ob = (object_t *) NULL;

    if (v->type == T_OBJECT) {
	ob = v->u.ob;
	destruct_object_two(ob);
    } else {
	error("destruct_object: called without an object argument\n");
    }
}

/*
 * This one is called when no program is executing from the main loop.
 */
void destruct2 P1(object_t *, ob)
{
#ifdef DEBUG
    if (d_flag > 1) {
	debug_message("Destruct-2 object %s (ref %d)\n", ob->name, ob->ref);
    }
#endif
#if 0
    /*
     * moved this into destruct_object() to deal with the 0 in users() efun
     * output problem
     */
    if (ob->interactive)
	remove_interactive(ob);
#endif
    /*
     * We must deallocate variables here, not in 'free_object()'. That is
     * because one of the local variables may point to this object, and
     * deallocation of this pointer will also decrease the reference count of
     * this object. Otherwise, an object with a variable pointing to itself,
     * would never be freed. Just in case the program in this object would
     * continue to execute, change string and object variables into the
     * number 0.
     */
    if (ob->prog->num_variables > 0) {
	/*
	 * Deallocate variables in this object. The space of the variables
	 * are not deallocated until the object structure is freed in
	 * free_object().
	 */
	int i;

	for (i = 0; i < (int) ob->prog->num_variables; i++) {
	    free_svalue(&ob->variables[i], "destruct2");
	    ob->variables[i] = const0n;
	}
    }
    free_object(ob, "destruct_object");
}

/*
 * say() efun - send a message to:
 *  all objects in the inventory of the source,
 *  all objects in the same environment as the source,
 *  and the object surrounding the source.
 *
 * when there is no command_giver, current_object is used as the source,
 *  otherwise, command_giver is used.
 *
 * message never goes to objects in the avoid array, or the source itself.
 *
 * rewritten, bobf@metronet.com (Blackthorn) 9/6/93
 */

#ifndef NO_ENVIRONMENT
static void send_say P3(object_t *, ob, char *, text, array_t *, avoid)
{
    int valid, j;

    for (valid = 1, j = 0; j < avoid->size; j++) {
	if (avoid->item[j].type != T_OBJECT)
	    continue;
	if (avoid->item[j].u.ob == ob) {
	    valid = 0;
	    break;
	}
    }

    if (!valid)
	return;

    tell_object(ob, text);
}
#endif

#ifndef NO_ENVIRONMENT
void say P2(svalue_t *, v, array_t *, avoid)
{
    object_t *ob, *origin, *save_command_giver = command_giver;
    char *buff;

    check_legal_string(v->u.string);
    buff = v->u.string;

    if (current_object->flags & O_LISTENER || current_object->interactive)
	command_giver = current_object;
    if (command_giver)
	origin = command_giver;
    else
	origin = current_object;

    /* To our surrounding object... */
    if ((ob = origin->super)) {
	if (ob->flags & O_LISTENER || ob->interactive)
	    send_say(ob, buff, avoid);

	/* And its inventory... */
	for (ob = origin->super->contains; ob; ob = ob->next_inv) {
	    if (ob != origin && (ob->flags & O_LISTENER || ob->interactive)) {
		send_say(ob, buff, avoid);
		if (ob->flags & O_DESTRUCTED)
		    break;
	    }
	}
    }
    /* Our inventory... */
    for (ob = origin->contains; ob; ob = ob->next_inv) {
	if (ob->flags & O_LISTENER || ob->interactive) {
	    send_say(ob, buff, avoid);
	    if (ob->flags & O_DESTRUCTED)
		break;
	}
    }

    command_giver = save_command_giver;
}

/*
 * Sends a string to all objects inside of a specific object.
 * Revised, bobf@metronet.com 9/6/93
 */

void tell_room P3(object_t *, room, svalue_t *, v, array_t *, avoid)
{
    object_t *ob;
    char *buff;
    int valid, j;
    char txt_buf[LARGEST_PRINTABLE_STRING];

    switch (v->type) {
    case T_STRING:
	check_legal_string(v->u.string);
	buff = v->u.string;
	break;
    case T_OBJECT:
	buff = v->u.ob->name;
	break;
    case T_NUMBER:
	buff = txt_buf;
	sprintf(buff, "%d", v->u.number);
	break;
    case T_REAL:
	buff = txt_buf;
	sprintf(buff, "%g", v->u.real);
	break;
    default:
	bad_argument(v, T_OBJECT | T_NUMBER | T_REAL | T_STRING,
		     2, F_TELL_ROOM);
	IF_DEBUG(buff = 0);
    }

    for (ob = room->contains; ob; ob = ob->next_inv) {
	if (!ob->interactive && !(ob->flags & O_LISTENER))
	    continue;

	for (valid = 1, j = 0; j < avoid->size; j++) {
	    if (avoid->item[j].type != T_OBJECT)
		continue;
	    if (avoid->item[j].u.ob == ob) {
		valid = 0;
		break;
	    }
	}

	if (!valid)
	    continue;

	if (!ob->interactive) {
	    tell_npc(ob, buff);
	    if (ob->flags & O_DESTRUCTED)
		break;
	} else {
	    tell_object(ob, buff);
	    if (ob->flags & O_DESTRUCTED)
		break;
	}
    }
}
#endif

void shout_string P1(char *, str)
{
    object_t *ob;

    check_legal_string(str);

    for (ob = obj_list; ob; ob = ob->next_all) {
	if (!(ob->flags & O_LISTENER) || (ob == command_giver)
#ifndef NO_ENVIRONMENT
	    || !ob->super
#endif
	    )
	    continue;
	tell_object(ob, str);
    }
}

/*
 * This will enable an object to use commands normally only
 * accessible by interactive users.
 * Also check if the user is a wizard. Wizards must not affect the
 * value of the wizlist ranking.
 */

#ifndef NO_ADD_ACTION
void enable_commands P1(int, num)
{
    object_t *pp;

    if (current_object->flags & O_DESTRUCTED)
	return;
#ifdef DEBUG
    if (d_flag > 1) {
	debug_message("Enable commands %s (ref %d)\n",
		      current_object->name, current_object->ref);
    }
#endif
    if (num) {
	current_object->flags |= O_ENABLE_COMMANDS;
	command_giver = current_object;
    } else {
	/*
	 * Remove all sentences defined by this object from all objects here.
	 */
#ifndef NO_ENVIRONMENT
	if (current_object->flags & O_ENABLE_COMMANDS) {
	    if (current_object->super) {
		if (current_object->flags & O_ENABLE_COMMANDS)
		    remove_sent(current_object, current_object->super);
		for (pp = current_object->super->contains; pp; pp = pp->next_inv) {
		    if (pp->flags & O_ENABLE_COMMANDS)
			remove_sent(current_object, pp);
		}
	    }
	}
#endif
	current_object->flags &= ~O_ENABLE_COMMANDS;
	command_giver = 0;
    }
}
#endif

/*
 * Set up a function in this object to be called with the next
 * user input string.
 */
int input_to P4(svalue_t *, fun, int, flag, int, num_arg, svalue_t *, args)
{
    sentence_t *s;
    svalue_t *x;
    int i;

    if (!command_giver || command_giver->flags & O_DESTRUCTED)
	return 0;
    s = alloc_sentence();
    if (set_call(command_giver, s, flag & ~I_SINGLE_CHAR)) {
	/*
	 * If we have args, we copy them, and adjust the stack automatically
	 * (elsewhere) to avoid double free_svalue()'s
	 */
	if (num_arg) {
	    i = num_arg * sizeof(svalue_t);
	    if ((x = (svalue_t *)
		 DMALLOC(i, TAG_TEMPORARY, "input_to: 1")) == NULL)
		fatal("Out of memory!\n");
	    memcpy(x, args, i);
	} else
	    x = NULL;

	command_giver->interactive->carryover = x;
	command_giver->interactive->num_carry = num_arg;
	if (fun->type == T_STRING) {
	    s->function.s = make_shared_string(fun->u.string);
	    s->flags = 0;
	} else {
	    s->function.f = fun->u.fp;
	    fun->u.fp->hdr.ref++;
	    s->flags = V_FUNCTION;
	}
	s->ob = current_object;
	add_ref(current_object, "input_to");
	return 1;
    }
    free_sentence(s);
    return 0;
}

/*
 * Set up a function in this object to be called with the next
 * user input character.
 */
int get_char P4(svalue_t *, fun, int, flag, int, num_arg, svalue_t *, args)
{
    sentence_t *s;
    svalue_t *x;
    int i;

    if (!command_giver || command_giver->flags & O_DESTRUCTED)
	return 0;
    s = alloc_sentence();
    if (set_call(command_giver, s, flag | I_SINGLE_CHAR)) {
	/*
	 * If we have args, we copy them, and adjust the stack automatically
	 * (elsewhere) to avoid double free_svalue()'s
	 */
	if (num_arg) {
	    i = num_arg * sizeof(svalue_t);
	    if ((x = (svalue_t *)
		 DMALLOC(i, TAG_TEMPORARY, "get_char: 1")) == NULL)
		fatal("Out of memory!\n");
	    memcpy(x, args, i);
	} else
	    x = NULL;

	command_giver->interactive->carryover = x;
	command_giver->interactive->num_carry = num_arg;
	if (fun->type == T_STRING) {
           s->function.s = make_shared_string(fun->u.string);
           s->flags = 0;
	} else {
	    s->function.f = fun->u.fp;
	    fun->u.fp->hdr.ref++;
	    s->flags = V_FUNCTION;
	}
	s->ob = current_object;
	add_ref(current_object, "get_char");
	return 1;
    }
    free_sentence(s);
    return 0;
}

void print_svalue P1(svalue_t *, arg)
{
    char tbuf[2048];

    if (arg == 0) {
	tell_object(command_giver, "<NULL>");
    } else
	switch (arg->type) {
	case T_STRING:
	    check_legal_string(arg->u.string);
	    tell_object(command_giver, arg->u.string);
	    break;
	case T_OBJECT:
	    sprintf(tbuf, "OBJ(/%s)", arg->u.ob->name);
	    tell_object(command_giver, tbuf);
	    break;
	case T_NUMBER:
	    sprintf(tbuf, "%d", arg->u.number);
	    tell_object(command_giver, tbuf);
	    break;
	case T_REAL:
	    sprintf(tbuf, "%g", arg->u.real);
	    tell_object(command_giver, tbuf);
	    break;
	case T_ARRAY:
	    tell_object(command_giver, "<ARRAY>");
	    break;
	case T_MAPPING:
	    tell_object(command_giver, "<MAPPING>");
	    break;
	case T_FUNCTION:
	    tell_object(command_giver, "<FUNCTION>");
	    break;
	case T_BUFFER:
	    tell_object(command_giver, "<BUFFER>");
	    break;
	default:
	    tell_object(command_giver, "<UNKNOWN>");
	    break;
	}
    return;
}

void do_write P1(svalue_t *, arg)
{
    object_t *save_command_giver = command_giver;

#ifndef NO_SHADOWS
    if (command_giver == 0 && current_object->shadowing)
	command_giver = current_object;
    if (command_giver) {
	/* Send the message to the first object in the shadow list */
	while (command_giver->shadowing)
	    command_giver = command_giver->shadowing;
    }
#else
    if (!command_giver)
	command_giver = current_object;
#endif				/* NO_SHADOWS */
    print_svalue(arg);
    command_giver = save_command_giver;
}

/* Find an object. If not loaded, load it !
 * The object may selfdestruct, which is the only case when 0 will be
 * returned.
 */

object_t *find_object P1(char *, str)
{
    object_t *ob;
    char tmpbuf[MAX_OBJECT_NAME_SIZE];

    if (!strip_name(str, tmpbuf, sizeof tmpbuf))
	return 0;

    if ((ob = lookup_object_hash(tmpbuf))) {
#ifdef LPC_TO_C
	if (ob->flags & O_COMPILED_PROGRAM) {
	    ob = load_object(tmpbuf, (lpc_object_t *)ob);
	    if (!ob || (ob->flags & O_DESTRUCTED))	/* *sigh* */
		return 0;
	}
#endif
	if (ob->flags & O_SWAPPED)
	    load_ob_from_swap(ob);
	return ob;
    }
    ob = load_object(tmpbuf, 0);
    if (!ob || (ob->flags & O_DESTRUCTED))	/* *sigh* */
	return 0;
    if (ob && ob->flags & O_SWAPPED)
	load_ob_from_swap(ob);
    return ob;
}

/* Look for a loaded object. Return 0 if non found. */
object_t *find_object2 P1(char *, str)
{
    register object_t *ob;
    char p[MAX_OBJECT_NAME_SIZE];

    if (!strip_name(str, p, sizeof p))
	return 0;

    if ((ob = lookup_object_hash(p))) {
#ifdef LPC_TO_C
	if (ob->flags & O_COMPILED_PROGRAM)
	    return 0;
#endif
	if (ob->flags & O_SWAPPED)
	    load_ob_from_swap(ob);
	return ob;
    }
    return 0;
}

#ifndef NO_ENVIRONMENT
/*
 * Transfer an object.
 * The object has to be taken from one inventory list and added to another.
 * The main work is to update all command definitions, depending on what is
 * living or not. Note that all objects in the same inventory are affected.
 */
void move_object P2(object_t *, item, object_t *, dest)
{
    object_t **pp, *ob;
#ifndef NO_ADD_ACTION
    object_t *next_ob;
    object_t *save_cmd = command_giver;
#endif

    /* Recursive moves are not allowed. */
    for (ob = dest; ob; ob = ob->super)
	if (ob == item)
	    error("Can't move object inside itself.\n");
#ifndef NO_SHADOWS
    if (item->shadowing)
	error("Can't move an object that is shadowing.\n");
#endif

#ifdef LAZY_RESETS
    try_reset(dest);
#endif
#ifndef NO_LIGHT
    add_light(dest, item->total_light);
#endif
    if (item->super) {
	int okey = 0;

#ifndef NO_ADD_ACTION
	if (item->flags & O_ENABLE_COMMANDS) {
	    remove_sent(item->super, item);
	}
	if (item->super->flags & O_ENABLE_COMMANDS)
	    remove_sent(item, item->super);
#endif
#ifndef NO_LIGHT
	add_light(item->super, -item->total_light);
#endif
	for (pp = &item->super->contains; *pp;) {
	    if (*pp != item) {
#ifndef NO_ADD_ACTION
		if ((*pp)->flags & O_ENABLE_COMMANDS)
		    remove_sent(item, *pp);
		if (item->flags & O_ENABLE_COMMANDS)
		    remove_sent(*pp, item);
#endif
		pp = &(*pp)->next_inv;
		continue;
	    }
	    /*
	     * unlink object from original inventory list
	     */
	    *pp = item->next_inv;
	    okey = 1;
	}
#ifdef DEBUG
	if (!okey)
	    fatal("Failed to find object %s in super list of %s.\n",
		  item->name, item->super->name);
#endif
    }
    /*
     * link object into target's inventory list
     */
    item->next_inv = dest->contains;
    dest->contains = item;
    item->super = dest;

#ifndef NO_ADD_ACTION
    /*
     * Setup the new commands. The order is very important, as commands in
     * the room should override commands defined by the room. Beware that
     * init() in the room may have moved 'item' !
     * 
     * The call of init() should really be done by the object itself (except in
     * the -o mode). It might be too slow, though :-(
     */
    if (item->flags & O_ENABLE_COMMANDS) {
	command_giver = item;
	(void) apply(APPLY_INIT, dest, 0, ORIGIN_DRIVER);
	if ((dest->flags & O_DESTRUCTED) || item->super != dest) {
	    command_giver = save_cmd;	/* marion */
	    return;
	}
    }
    /*
     * Run init of the item once for every present user, and for the
     * environment (which can be a user).
     */
    for (ob = dest->contains; ob; ob = next_ob) {
	next_ob = ob->next_inv;
	if (ob == item)
	    continue;
	if (ob->flags & O_DESTRUCTED)
	    error("An object was destructed at call of " APPLY_INIT "()\n");
	if (ob->flags & O_ENABLE_COMMANDS) {
	    command_giver = ob;
	    (void) apply(APPLY_INIT, item, 0, ORIGIN_DRIVER);
	    if (dest != item->super) {
		command_giver = save_cmd;	/* marion */
		return;
	    }
	}
	if (item->flags & O_DESTRUCTED)	/* marion */
	    error("The object to be moved was destructed at call of " APPLY_INIT "()\n");
	if (item->flags & O_ENABLE_COMMANDS) {
	    command_giver = item;
	    (void) apply(APPLY_INIT, ob, 0, ORIGIN_DRIVER);
	    if (dest != item->super) {
		command_giver = save_cmd;	/* marion */
		return;
	    }
	}
    }
    if (dest->flags & O_DESTRUCTED)	/* marion */
	error("The destination to move to was destructed at call of " APPLY_INIT "()\n");
    if (dest->flags & O_ENABLE_COMMANDS) {
	command_giver = dest;
	(void) apply(APPLY_INIT, item, 0, ORIGIN_DRIVER);
    }
    command_giver = save_cmd;
#endif
}
#endif

#ifndef NO_LIGHT
/*
 * Every object has a count of the number of light sources it contains.
 * Update this.
 */

void add_light P2(object_t *, p, int, n)
{
    if (n == 0)
	return;
    p->total_light += n;
    while ((p = p->super))
	p->total_light += n;
}
#endif

static sentence_t *sent_free = 0;
int tot_alloc_sentence;

static sentence_t *alloc_sentence()
{
    sentence_t *p;

    if (sent_free == 0) {
	p = ALLOCATE(sentence_t, TAG_SENTENCE, "alloc_sentence");
	tot_alloc_sentence++;
    } else {
	p = sent_free;
	sent_free = sent_free->next;
    }
#ifndef NO_ADD_ACTION
    p->verb = 0;
#endif
    p->function.s = 0;
    p->next = 0;
    return p;
}

#ifdef DEBUGMALLOC_EXTENSIONS
void mark_free_sentences() {
    sentence_t *sent = sent_free;

#ifdef PACKAGE_UIDS
    if (creator_file_fname)
	EXTRA_REF(BLOCK(creator_file_fname))++;
#endif
#ifdef PRIVS
    if (privs_file_fname)
	EXTRA_REF(BLOCK(privs_file_fname))++;
#endif

    while (sent) {
	DO_MARK(sent, TAG_SENTENCE);
/* Freed sentences should have been freed.  right?
	if (sent->function)
	    EXTRA_REF(BLOCK(sent->function))++;
	if (sent->verb)
	    EXTRA_REF(BLOCK(sent->verb))++;
*/
	sent = sent->next;
    }
}
#endif

void free_sentence P1(sentence_t *, p)
{
    if (p->flags & V_FUNCTION) {
      if (p->function.f)
          free_funp(p->function.f);
      else p->function.f = 0;
    } else {
      if (p->function.s)
          free_string(p->function.s);
      else p->function.s = 0;
    }
#ifndef NO_ADD_ACTION
    if (p->verb)
	free_string(p->verb);
    p->verb = 0;
#endif
    p->next = sent_free;
    sent_free = p;
}

/*
 * Find the sentence for a command from the user.
 * Return success status.
 */

#define MAX_VERB_BUFF 100

#ifndef NO_ADD_ACTION
int user_parser P1(char *, buff)
{
    char verb_buff[MAX_VERB_BUFF];
#ifndef NO_ADD_ACTION
    object_t *super;
#endif
    sentence_t *s;
    char *p;
    int length;
    object_t *save_command_giver = command_giver;
    char *user_verb = 0;
    int where;

#ifdef DEBUG
    if (d_flag > 1)
	debug_message("cmd [%s]: %s\n", command_giver->name, buff);
#endif
    /* strip trailing spaces. */
    for (p = buff + strlen(buff) - 1; p >= buff; p--) {
	if (*p != ' ')
	    break;
	*p = '\0';
    }
    if (buff[0] == '\0')
	return 0;
    length = p - buff + 1;
    p = strchr(buff, ' ');
    if (p == 0) {
	user_verb = findstring(buff);
    } else {
	*p = '\0';
	user_verb = findstring(buff);
	*p = ' ';
	length = p - buff;
    }
    if (!user_verb) {
	/* either an xverb or a verb without a specific add_action */
	user_verb = buff;
    }
    /*
     * copy user_verb into a static character buffer to be pointed to by
     * last_verb.
     */
    strncpy(verb_buff, user_verb, MAX_VERB_BUFF - 1);
    if (p) {
	int pos;

	pos = p - buff;
	if (pos < MAX_VERB_BUFF) {
	    verb_buff[pos] = '\0';
	}
    }

    illegal_sentence_action = 0;
    for (s = save_command_giver->sent; s; s = s->next) {
	svalue_t *ret;
	object_t *command_object;
	    
	if (s->flags & (V_NOSPACE | V_SHORT)) {
	    if (strncmp(buff, s->verb, strlen(s->verb)) != 0)
		continue;
	} else {
	    /* note: if was add_action(blah, "") then accept it */
	    if (s->verb[0] && (user_verb != s->verb))
		continue;
	}
	/*
	 * Now we have found a special sentence !
	 */
#ifdef DEBUG
	if (d_flag > 1)
	    debug_message("Local command %s on %s\n",
			  s->function, s->ob->name);
#endif
	if (s->flags & V_NOSPACE) {
	    int l1 = strlen(s->verb);
	    int l2 = strlen(verb_buff);
	    
	    if (l1 < l2)
		last_verb = verb_buff + l1;
	    else
		last_verb = "";
	} else {
	    if (!s->verb[0] || (s->flags & V_SHORT))
		last_verb = verb_buff;
	    else
		last_verb = s->verb;
	}
	/*
	 * If the function is static and not defined by current object, then
	 * it will fail. If this is called directly from user input, then
	 * the origin is the driver and it will be allowed.
	 */
	where = (current_object ? ORIGIN_EFUN : ORIGIN_DRIVER);
	
	/*
	 * Remember the object, to update moves.
	 */
	command_object = s->ob;
#ifndef NO_ENVIRONMENT
	super = command_object->super;
#endif
	if (s->flags & V_NOSPACE) {
	    push_constant_string(&buff[strlen(s->verb)]);
	} else if (buff[length] == ' ') {
	    push_constant_string(&buff[length + 1]);
	} else {
	    push_null();
	}
	if (s->flags & V_FUNCTION) {
	    ret = call_function_pointer(s->function.f, 1);
	} else {
	    ret = apply(s->function.s, s->ob, 1, where);
	}
	/* s may be dangling at this point */
	
	command_giver = save_command_giver;
	
	last_verb = 0;
	
	/* was this the right verb? */
	if (ret == 0) {
	    /* is it still around?  Otherwise, ignore this ...
	       it moved somewhere or dested itself */
	    if (s == save_command_giver->sent) {
		char buf[256];
		if (s->flags & V_FUNCTION) {
		    sprintf(buf, "Verb '%s' bound to uncallable function pointer.\n", s->verb);
		    error(buf);
		} else {
		    sprintf(buf, "Function for verb '%s' not found.\n",
			    s->verb);
		    error(buf);
		}
	    }
	}
	
	if (ret && (ret->type != T_NUMBER || ret->u.number != 0)) {
#ifdef PACKAGE_MUDLIB_STATS
	    if (command_giver && command_giver->interactive
#ifndef NO_WIZARDS
		&& !(command_giver->flags & O_IS_WIZARD)
#endif
		)
		add_moves(&command_object->stats, 1);
#endif
	    
	    return 1;
	}
	if (illegal_sentence_action) {
	    switch (illegal_sentence_action) {
	    case 1:
		error("Illegal to call remove_action() from a verb returning zero.\n");
	    case 2:
		error("Illegal to move or destruct an object defining actions from a verb function which returns zero.\n");
	    }
	}
    }
    notify_no_command();
    
    return 0;
}

/*
 * Associate a command with function in this object.
 *
 * The optinal third argument is a flag that will state that the verb should
 * only match against leading characters.
 *
 * The object must be near the command giver, so that we ensure that the
 * sentence is removed when the command giver leaves.
 *
 * If the call is from a shadow, make it look like it is really from
 * the shadowed object.
 */
void add_action P3(svalue_t *, str, char *, cmd, int, flag)
{
    sentence_t *p;
    object_t *ob;

    if (current_object->flags & O_DESTRUCTED)
	return;
    ob = current_object;
#ifndef NO_SHADOWS
    while (ob->shadowing) {
	ob = ob->shadowing;
    }
    /* don't allow add_actions of a static function from a shadowing object */
    if ((ob != current_object) && str->type == T_STRING && is_static(str->u.string, ob)) {
	return;
    }
#endif
    if (command_giver == 0 || (command_giver->flags & O_DESTRUCTED))
	return;
    if (ob != command_giver
#ifndef NO_ENVIRONMENT
	&& ob->super != command_giver &&
	ob->super != command_giver->super && ob != command_giver->super
#endif
	)
	return;			/* No need for an error, they know what they
				 * did wrong. */
#ifdef DEBUG
    if (d_flag > 1)
	debug_message("--Add action %s\n", str);
#endif
    p = alloc_sentence();
    if (str->type == T_STRING) {
      p->function.s = make_shared_string(str->u.string);
      p->flags = flag;
    } else {
      p->function.f = str->u.fp;
      str->u.fp->hdr.ref++;
      p->flags = flag | V_FUNCTION;
    }
    p->ob = ob;
    p->verb = make_shared_string(cmd);
    /* This is ok; adding to the top of the list doesn't harm anything */
    p->next = command_giver->sent;
    command_giver->sent = p;
}

/*
 * Remove sentence with specified verb and action.  Return 1
 * if success.  If command_giver, remove his action, otherwise
 * remove current_object's action.
 */
int remove_action P2(char *, act, char *, verb)
{
    object_t *ob;
    sentence_t **s;

    if (command_giver)
	ob = command_giver;
    else
	ob = current_object;

    if (ob) {
	for (s = &ob->sent; *s; s = &((*s)->next)) {
	    sentence_t *tmp;
	    
	    if (((*s)->ob == current_object) && (!((*s)->flags & V_FUNCTION))
		&& !strcmp((*s)->function.s, act)
		&& !strcmp((*s)->verb, verb)) {
		tmp = *s;
		*s = tmp->next;
		free_sentence(tmp);
		illegal_sentence_action = 1;
		return 1;
	    }
	}
    }
    return 0;
}

/*
 * Remove all commands (sentences) defined by object 'ob' in object
 * 'user'
 */
static void remove_sent P2(object_t *, ob, object_t *, user)
{
    sentence_t **s;

    for (s = &user->sent; *s;) {
	sentence_t *tmp;

	if ((*s)->ob == ob) {
#ifdef DEBUG
	    if (d_flag > 1)
		debug_message("--Unlinking sentence %s\n", (*s)->function);
#endif
	    tmp = *s;
	    *s = tmp->next;
	    free_sentence(tmp);
	    illegal_sentence_action = 2;
	} else
	    s = &((*s)->next);
    }
}
#endif /* NO_ADD_ACTION */

void fatal P1V(char *, fmt)
{
    static int in_fatal = 0;
    char msg_buf[2049];
    va_list args;
    V_DCL(char *fmt);

    if (in_fatal) {
	debug_message("Fatal error while shutting down.  Aborting.\n");
    } else {
	in_fatal = 1;
	V_START(args, fmt);
	V_VAR(char *, fmt, args);
	vsprintf(msg_buf, fmt, args);
	va_end(args);
	debug_message("******** FATAL ERROR: %s\nMudOS driver attempting to exit gracefully.\n", msg_buf);
	if (current_file)
	    debug_message("(occured during compilation of %s at line %d)\n", current_file, current_line);
	if (current_object)
	    debug_message("(current object was /%s)\n", current_object->name);
	
	dump_trace(1);
	
#ifdef PACKAGE_MUDLIB_STATS
	save_stat_files();
#endif
	push_string(msg_buf, STRING_CONSTANT);
	if (command_giver) {
	    push_object(command_giver);
	} else {
	    push_undefined();
	}
	if (current_object) {
	    push_object(current_object);
	} else {
	    push_undefined();
	}
	apply_master_ob(APPLY_CRASH, 3);
	debug_message("crash() in master called successfully.  Aborting.\n");
    }
    /* Make sure we don't trap our abort() */
#ifdef SIGABRT
    signal(SIGABRT, SIG_DFL);
#endif
#ifdef SIGILL
    signal(SIGILL, SIG_DFL);
#endif
#ifdef SIGIOT
    signal(SIGIOT, SIG_DFL);
#endif
    
#if !defined(DEBUG_NON_FATAL) || !defined(DEBUG)
    abort();
#endif
    in_fatal = 0;
}

int num_error = 0;

#ifdef MUDLIB_ERROR_HANDLER
static int num_mudlib_error = 0;
#endif

/*
 * Error() has been "fixed" so that users can catch and throw them.
 * To catch them nicely, we really have to provide decent error information.
 * Hence, all errors that are to be caught
 * (error_recovery_context_exists == 2) construct a string containing
 * the error message, which is returned as the
 * thrown value.  Users can throw their own error values however they choose.
 */

/*
 * This is here because throw constructs its own return value; we dont
 * want to replace it with the system's error string.
 */

void throw_error()
{
    if (((current_error_context->save_csp + 1)->framekind & FRAME_MASK) == FRAME_CATCH) {
	LONGJMP(current_error_context->context, 1);
	fatal("Throw_error failed!");
    }
    error("Throw with no catch.\n");
}

static void debug_message_with_location P1(char *, err) {
    if (current_object && current_prog) {
	debug_message("%sprogram: /%s, object: /%s, file: %s\n",
		      err,
		      current_prog->name,
		      current_object->name,
		      get_line_number(pc, current_prog));
    } else if (current_object) {
	debug_message("%sprogram: (none), object: /%s, file: (none)\n",
		      err,
		      current_object->name);
    } else {
	debug_message("%sprogram: (none), object: (none), file: (none)\n",
		      err);
    }
}

#ifndef MUDLIB_ERROR_HANDLER
static void add_message_with_location P1(char *, err) {
    if (current_object && current_prog) {
	add_vmessage(command_giver, "%sprogram: /%s, object: /%s, file: %s\n",
		     err,
		     current_prog->name,
		     current_object->name,
		     get_line_number(pc, current_prog));
    } else if (current_object) {
	add_vmessage(command_giver, "%sprogram: (none), object: /%s, file: (none)\n",
		     err,
		     current_object->name);
    } else {
	add_vmessage(command_giver, "%sprogram: (none), object: (none), file: (none)\n",
		     err);
    }
}
#endif

#ifdef MUDLIB_ERROR_HANDLER
static void mudlib_error_handler P2(char *, err, int, catch) {
    mapping_t *m;
    char *file;
    int line;
    svalue_t *mret;

    m = allocate_mapping(6);
    add_mapping_string(m, "error", err);
    if (current_prog)
	add_mapping_string(m, "program", current_prog->name);
    if (current_object)
	add_mapping_object(m, "object", current_object);
    add_mapping_array(m, "trace", get_svalue_trace());
    get_line_number_info(&file, &line);
    add_mapping_string(m, "file", file);
    add_mapping_pair(m, "line", line);

    push_refed_mapping(m);
    if (catch) {
	*(++sp) = const1;
	mret = apply_master_ob(APPLY_ERROR_HANDLER,2);
    } else {
	mret = apply_master_ob(APPLY_ERROR_HANDLER,1);
    }
    if ((mret == (svalue_t *)-1) || !mret) {
	debug_message("No error handler for error: ");
	debug_message_with_location(err);
	dump_trace(0);
    } else if (mret->type == T_STRING) {
	debug_message("%s", mret->u.string);
    }
}
#endif

void error_handler P1(char *, err)
{
#ifndef MUDLIB_ERROR_HANDLER
    char *object_name;
#endif

    /* in case we're going to jump out of load_object */
    restrict_destruct = 0;
    num_objects_this_thread = 0;/* reset the count */

    if (((current_error_context->save_csp + 1)->framekind & FRAME_MASK) == FRAME_CATCH) {
        /* user catches this error */
#ifdef LOG_CATCHES
	/* This is added so that catches generate messages in the log file. */
#ifdef MUDLIB_ERROR_HANDLER
	if (num_mudlib_error) {
	    debug_message("Error in error handler: ");
#endif
	    debug_message_with_location(err);
	    (void) dump_trace(0);
#ifdef MUDLIB_ERROR_HANDLER
	    num_mudlib_error = 0;
	} else {
	    num_mudlib_error++;
	    mudlib_error_handler(err, 1);
	    num_mudlib_error--;
	}
#endif
#endif
	free_svalue(&catch_value, "caught error");
	catch_value.type = T_STRING;
	catch_value.subtype = STRING_MALLOC;
	catch_value.u.string = string_copy(err, "caught error");
	LONGJMP(current_error_context->context, 1);
	fatal("Catch() longjump failed");
    }
    too_deep_error = max_eval_error = 0;
    num_error++;
    if (num_error > 1)
	fatal("Too many simultaneous errors.\n");
#ifdef MUDLIB_ERROR_HANDLER
    if (num_mudlib_error) {
	debug_message("Error in error handler: ");
	debug_message_with_location(err);
	(void) dump_trace(0);
	num_mudlib_error = 0;
    } else {
	num_mudlib_error++;
	num_error--;
	mudlib_error_handler(err, 0);
	num_mudlib_error--;
	num_error++;
    }
    if (current_heart_beat) {
	set_heart_beat(current_heart_beat, 0);
	debug_message("Heart beat in %s turned off.\n", current_heart_beat->name);
	if (current_heart_beat->interactive)
	    add_message(current_heart_beat, "MudOS driver tells you: You have no heart beat!\n");

	current_heart_beat = 0;
    }
    num_error--;
#else
    if (current_object) {
#ifdef PACKAGE_MUDLIB_STATS
	add_errors(&current_object->stats, 1);
#endif
    }
    debug_message_with_location(err + 1);
#if defined(DEBUG) && defined(TRACE_CODE)
    object_name = dump_trace(1);
#else
    object_name = dump_trace(0);
#endif
    if (object_name) {
	object_t *ob;

	ob = find_object2(object_name);
	if (!ob) {
	    if (command_giver)
		add_vmessage(command_giver,
			     "error when executing program in destroyed object /%s\n",
			    object_name);
	    debug_message("error when executing program in destroyed object /%s\n",
			  object_name);
	}
    }
    if (command_giver && command_giver->interactive) {
#ifndef NO_WIZARDS
	if ((command_giver->flags & O_IS_WIZARD) || !strlen(DEFAULT_ERROR_MESSAGE)) {
#endif
	    add_message_with_location(err + 1);
#ifndef NO_WIZARDS
	} else {
	    add_vmessage(command_giver, "%s\n", DEFAULT_ERROR_MESSAGE);
	}
#endif
    }
    if (current_heart_beat) {
	set_heart_beat(current_heart_beat, 0);
	debug_message("Heart beat in /%s turned off.\n", current_heart_beat->name);
	if (current_heart_beat->interactive)
	    add_message(current_heart_beat, "MudOS driver tells you: You have no heart beat!\n");

	current_heart_beat = 0;
    }
    num_error--;
#endif
    if (current_error_context)
	LONGJMP(current_error_context->context, 1);
    fatal("LONGJMP failed or no error context for error.\n");
}

void error_needs_free P1(char *, s)
{
    char err_buf[2048];
    strncpy(err_buf + 1, s, 2047);
    err_buf[0] = '*';		/* all system errors get a * at the start */
    err_buf[2047] = '\0';
    FREE_MSTR(s);

    error_handler(err_buf);
}

void error P1V(char *, fmt)
{
    char err_buf[2048];
    va_list args;
    V_DCL(char *fmt);

    V_START(args, fmt);
    V_VAR(char *, fmt, args);
    vsprintf(err_buf + 1, fmt, args);
    va_end(args);
    err_buf[0] = '*';		/* all system errors get a * at the start */

    error_handler(err_buf);
}

/*
 * This one is called from HUP.
 */
int MudOS_is_being_shut_down;

#ifdef SIGNAL_FUNC_TAKES_INT
void startshutdownMudOS P1(int, sig)
#else
void startshutdownMudOS()
#endif
{
    MudOS_is_being_shut_down = 1;
}

/*
 * This one is called from the command "shutdown".
 * We don't call it directly from HUP, because it is dangerous when being
 * in an interrupt.
 */
void shutdownMudOS P1(int, exit_code)
{
    int i;
    shout_string("MudOS driver shouts: shutting down immediately.\n");
#ifdef PACKAGE_MUDLIB_STATS
    save_stat_files();
#endif
    ipc_remove();
#ifdef PACKAGE_SOCKETS
    for (i = 0; i < MAX_EFUN_SOCKS; i++) {
	if (lpc_socks[i].state == CLOSED) continue;
	while (close(lpc_socks[i].fd) == -1 && errno == EINTR)
	    ;
    }
#endif
#ifdef LATTICE
    signal(SIGUSR1, SIG_IGN);
    signal(SIGTERM, SIG_IGN);
    signal(SIGINT, SIG_IGN);
    signal(SIGHUP, SIG_IGN);
    signal(SIGALRM, SIG_IGN);
#endif
    unlink_swap_file();
#ifdef PROFILING
    monitor(0, 0, 0, 0, 0);	/* cause gmon.out to be written */
#endif
    exit(exit_code);
}

/*
 * Call this one when there is only little memory left. It will start
 * Armageddon.
 */
void slow_shut_down P1(int, minutes)
{
    /*
     * Swap out objects, and free some memory.
     */
    svalue_t *amo;

    push_number(minutes);
    amo = apply_master_ob(APPLY_SLOW_SHUTDOWN, 1);
    /* in this case, approved means the mudlib will handle it */
    if (!MASTER_APPROVED(amo))
    {
	object_t *save_current = current_object, *save_command = command_giver;

	command_giver = 0;
	current_object = 0;
	shout_string("MudOS driver shouts: Out of memory.\n");
	command_giver = save_command;
	current_object = save_current;
#ifdef SIGNAL_FUNC_TAKES_INT
	startshutdownMudOS(1);
#else
	startshutdownMudOS();
#endif
	return;
    }
}

void do_message P5(svalue_t *, class, char *, msg, array_t *, scope, array_t *, exclude, int, recurse)
{
    int i, j, valid;
    object_t *ob;

    for (i = 0; i < scope->size; i++) {
	switch (scope->item[i].type) {
	case T_STRING:
	    ob = find_object(scope->item[i].u.string);
	    if (!ob || !object_visible(ob))
		continue;
	    break;
	case T_OBJECT:
	    ob = scope->item[i].u.ob;
	    break;
	default:
	    continue;
	}
	if (ob->flags & O_LISTENER || ob->interactive) {
	    for (valid = 1, j = 0; j < exclude->size; j++) {
		if (exclude->item[j].type != T_OBJECT)
		    continue;
		if (exclude->item[j].u.ob == ob) {
		    valid = 0;
		    break;
		}
	    }
	    if (valid) {
		push_svalue(class);
		push_string(msg, STRING_SHARED);
		apply(APPLY_RECEIVE_MESSAGE, ob, 2, ORIGIN_DRIVER);
	    }
	}
#ifndef NO_ENVIRONMENT
	else if (recurse) {
	    array_t *tmp;

	    tmp = all_inventory(ob, 1);
	    do_message(class, msg, tmp, exclude, 0);
	    free_array(tmp);
	}
#endif
    }
}

#ifdef LAZY_RESETS
void try_reset P1(object_t *, ob)
{
    if ((ob->next_reset < current_time) && !(ob->flags & O_RESET_STATE)) {
#ifdef DEBUG
	if (d_flag) {
	    debug_message("(lazy) RESET %s\n", ob->name);
	}
#endif
	/* need to set the flag here to prevent infinite loops in apply_low */
	ob->flags |= O_RESET_STATE;
	reset_object(ob);
    }
}
#endif

#ifndef NO_ENVIRONMENT
object_t *first_inventory P1(svalue_t *, arg)
{
    object_t *ob;

    if (arg->type == T_STRING) {
	ob = find_object(arg->u.string);
	if (ob && !object_visible(ob))
	    ob = 0;
    } else
	ob = arg->u.ob;
    if (ob == 0)
	bad_argument(arg, T_STRING | T_OBJECT, 1, F_FIRST_INVENTORY);
    ob = ob->contains;
    while (ob) {
	if (ob->flags & O_HIDDEN) {
	    if (object_visible(ob)) {
		return ob;
	    }
	} else
	    return ob;
	ob = ob->next_inv;
    }
    return 0;
}
#endif