tinymush-2.2.4/conf/
tinymush-2.2.4/scripts/
tinymush-2.2.4/vms/
/* functions.c - MUSH function handlers */

#include "autoconf.h"
#include "copyright.h"
#ifndef	lint
static char RCSid[] = "$Id: functions.c,v 1.12 1995/03/29 02:55:13 ambar Exp $";
USE(RCSid);
#endif

#include <limits.h>
#include <math.h>

#include "externs.h"
#include "flags.h"
#include "attrs.h"
#include "match.h"
#include "command.h"
#include "functions.h"
#include "misc.h"
#include "alloc.h"
#include "ansi.h"

#ifdef FLOATING_POINTS
#ifndef linux			/* linux defines atof as a macro */
double atof();
#endif				/* ! linux */
#define aton atof
typedef double NVAL;
#else
#define aton atoi
typedef int NVAL;
#endif				/* FLOATING_POINTS */

UFUN *ufun_head;

extern void FDECL(cf_log_notfound, (dbref player, char *cmd,
				    const char *thingname, char *thing));

extern void FDECL(make_portlist,
		       (dbref player, dbref target, char *buff));

#ifdef TCL_INTERP_SUPPORT
extern void FDECL(fun_tclclear, (char *, dbref, dbref, char **, int,
				 char **, int));
extern void FDECL(fun_tcleval, (char *, dbref, dbref, char **, int,
				char **, int));
extern void FDECL(fun_tclparams, (char *, dbref, dbref, char **, int,
				  char **, int));
extern void FDECL(fun_tclregs, (char *, dbref, dbref, char **, int,
				char **, int));
#endif /* TCL_INTERP_SUPPORT */

/* This is the prototype for functions */

#define	FUNCTION(x)	\
static void x(buff, player, cause, fargs, nfargs, cargs, ncargs) \
char *buff; \
dbref player, cause; \
char *fargs[], *cargs[]; \
int nfargs, ncargs;

/* -------------------------------------------------------------------------
 * Macros for functions that take variable arguments, with or without
 * a delimiter.
 *
 * Call varargs_preamble("FUNCTION", max_args) for functions which
 * take either max_args - 1 args, or, with a delimiter, max_args args.
 *
 * Call mvarargs_preamble("FUNCTION", min_args, max_args) if there can
 * be more variable arguments than just the delimiter.
 *
 * Call evarargs_preamble("FUNCTION", min_args, max_args) if the delimiters
 * need to be evaluated.
 *
 * Call svarargs_preamble("FUNCTION", max_args) if the second to last and
 * last arguments are delimiters.
 *
 * Call xvarargs_preamble("FUNCTION", min_args, max_args) if this is varargs
 * but does not involve a delimiter.
 */

#define xvarargs_preamble(xname,xminargs,xnargs)	        \
if (!fn_range_check(xname, nfargs, xminargs, xnargs, buff))	\
return;							        

#define varargs_preamble(xname,xnargs)	                        \
if (!fn_range_check(xname, nfargs, xnargs-1, xnargs, buff))	\
return;							        \
if (!delim_check(fargs, nfargs, xnargs, &sep, buff, 0,		\
    player, cause, cargs, ncargs))                              \
return;

#define mvarargs_preamble(xname,xminargs,xnargs)	        \
if (!fn_range_check(xname, nfargs, xminargs, xnargs, buff))	\
return;							        \
if (!delim_check(fargs, nfargs, xnargs, &sep, buff, 0,          \
    player, cause, cargs, ncargs))                              \
return;

#define evarargs_preamble(xname, xminargs, xnargs)              \
if (!fn_range_check(xname, nfargs, xminargs, xnargs, buff))	\
return;							        \
if (!delim_check(fargs, nfargs, xnargs - 1, &sep, buff, 1,      \
    player, cause, cargs, ncargs))                              \
return;							        \
if (!delim_check(fargs, nfargs, xnargs, &osep, buff, 1,         \
    player, cause, cargs, ncargs))                              \
return;

#define svarargs_preamble(xname,xnargs)                         \
if (!fn_range_check(xname, nfargs, xnargs-2, xnargs, buff))	\
return;							        \
if (!delim_check(fargs, nfargs, xnargs-1, &sep, buff, 0,        \
    player, cause, cargs, ncargs))                              \
return;							        \
if (nfargs < xnargs)				                \
    osep = sep;				                        \
else if (!delim_check(fargs, nfargs, xnargs, &osep, buff, 0,    \
    player, cause, cargs, ncargs))                              \
return;

/* --------------------------------------------------------------------------
 * Auxiliary functions for stacks.
 */

typedef struct object_stack STACK;
  struct object_stack {
      char *data;
      STACK *next;
  };

#define stack_get(x)   ((STACK *) nhashfind(x, &mudstate.objstack_htab))

#define stack_object(p,x)				\
        x = match_thing(p, fargs[0]);			\
	if (!Good_obj(x)) {				\
	    return;					\
	}						\
	if (!Controls(p, x)) {				\
            notify_quiet(p, "Permission denied.");	\
	    return;					\
	}
	
/* --------------------------------------------------------------------------
 * Various utility functions.
 */

/* Trim off leading and trailing spaces if the separator char is a space */

static char *
trim_space_sep(str, sep)
    char *str, sep;
{
    char *p;

    if (sep != ' ')
	return str;
    while (*str && (*str == ' '))
	str++;
    for (p = str; *p; p++);
    for (p--; *p == ' ' && p > str; p--);
    p++;
    *p = '\0';
    return str;
}

/* next_token: Point at start of next token in string */

static char *
next_token(str, sep)
    char *str, sep;
{
    while (*str && (*str != sep))
	str++;
    if (!*str)
	return NULL;
    str++;
    if (sep == ' ') {
	while (*str == sep)
	    str++;
    }
    return str;
}

/* split_token: Get next token from string as null-term string.  String is
 * destructively modified.
 */

static char *
split_token(sp, sep)
    char **sp, sep;
{
    char *str, *save;

    save = str = *sp;
    if (!str) {
	*sp = NULL;
	return NULL;
    }
    while (*str && (*str != sep))
	str++;
    if (*str) {
	*str++ = '\0';
	if (sep == ' ') {
	    while (*str == sep)
		str++;
	}
    } else {
	str = NULL;
    }
    *sp = str;
    return save;
}

dbref 
match_thing(player, name)
    dbref player;
    char *name;
{
    init_match(player, name, NOTYPE);
    match_everything(MAT_EXIT_PARENTS);
    return (noisy_match_result());
}


/* ---------------------------------------------------------------------------
 * List management utilities.
 */

#define	ALPHANUM_LIST	1
#define	NUMERIC_LIST	2
#define	DBREF_LIST	3
#define	FLOAT_LIST	4

static int 
autodetect_list(ptrs, nitems)
    char *ptrs[];
    int nitems;
{
    int sort_type, i;
    char *p;

    sort_type = NUMERIC_LIST;
    for (i = 0; i < nitems; i++) {
	switch (sort_type) {
	case NUMERIC_LIST:
	    if (!is_number(ptrs[i])) {

		/* If non-numeric, switch to alphanum sort.
		 * Exception: if this is the first element and
		 * it is a good dbref, switch to a dbref sort.
		 * We're a little looser than the normal
		 * 'good dbref' rules, any number following
		 * the #-sign is accepted.
		 */

		if (i == 0) {
		    p = ptrs[i];
		    if (*p++ != NUMBER_TOKEN) {
			return ALPHANUM_LIST;
		    } else if (is_integer(p)) {
			sort_type = DBREF_LIST;
		    } else {
			return ALPHANUM_LIST;
		    }
		} else {
		    return ALPHANUM_LIST;
		}
	    } else if (index(ptrs[i], '.')) {
		sort_type = FLOAT_LIST;
	    }
	    break;
	case FLOAT_LIST:
	    if (!is_number(ptrs[i])) {
		sort_type = ALPHANUM_LIST;
		return ALPHANUM_LIST;
	    }
	    break;
	case DBREF_LIST:
	    p = ptrs[i];
	    if (*p++ != NUMBER_TOKEN)
		return ALPHANUM_LIST;
	    if (!is_integer(p))
		return ALPHANUM_LIST;
	    break;
	default:
	    return ALPHANUM_LIST;
	}
    }
    return sort_type;
}

static int 
get_list_type(fargs, nfargs, type_pos, ptrs, nitems)
    char *fargs[], *ptrs[];
    int nfargs, nitems, type_pos;
{
    if (nfargs >= type_pos) {
	switch (ToLower(*fargs[type_pos - 1])) {
	case 'd':
	    return DBREF_LIST;
	case 'n':
	    return NUMERIC_LIST;
	case 'f':
	    return FLOAT_LIST;
	case '\0':
	    return autodetect_list(ptrs, nitems);
	default:
	    return ALPHANUM_LIST;
	}
    }
    return autodetect_list(ptrs, nitems);
}

static int 
list2arr(arr, maxlen, list, sep)
    char *arr[], *list, sep;
    int maxlen;
{
    char *p;
    int i;

    list = trim_space_sep(list, sep);
    p = split_token(&list, sep);
    for (i = 0; p && i < maxlen; i++, p = split_token(&list, sep)) {
	arr[i] = p;
    }
    return i;
}

static void 
arr2list(arr, alen, list, sep)
    char *arr[], *list, sep;
    int alen;
{
    char *p;
    int i;

    p = list;
    for (i = 0; i < alen; i++) {
	safe_str(arr[i], list, &p);
	safe_chr(sep, list, &p);
    }
    if (p != list)
	p--;
    *p = '\0';
}

static int 
dbnum(dbr)
    char *dbr;
{
    if ((*dbr != '#') || (strlen(dbr) < 2))
	return 0;
    else
	return atoi(dbr + 1);
}

/* ---------------------------------------------------------------------------
 * nearby_or_control: Check if player is near or controls thing
 */

int 
nearby_or_control(player, thing)
    dbref player, thing;
{
    if (!Good_obj(player) || !Good_obj(thing))
	return 0;
    if (Controls(player, thing))
	return 1;
    if (!nearby(player, thing))
	return 0;
    return 1;
}
/* ---------------------------------------------------------------------------
 * fval: copy the floating point value into a buffer and make it presentable.
 *       alternatively, if we're not using floating points, then we just
 *       print the integer.
 */

#ifdef FLOATING_POINTS
static void fval(buff, result)
    char *buff;
    double result;
{
    char *p;

    sprintf(buff, "%.6f", result);	/* get double val into buffer */

    /* remove useless trailing 0's */
    if ((p = (char *) rindex(buff, '0')) == NULL) 
	return;
    else if (*(p + 1) == '\0') {
	while (*p == '0')
	    *p-- = '\0';
    }

    p = (char *) rindex(buff, '.');	/* take care of dangling '.' */
    if (*(p + 1) == '\0')
	*p = '\0';
}
#else
#define fval(b,n)  ltos(b, n)
#endif				/* FLOATING_POINTS */

/* ---------------------------------------------------------------------------
 * fn_range_check: Check # of args to a function with an optional argument
 * for validity.
 */

static int 
fn_range_check(fname, nfargs, minargs, maxargs, result)
    const char *fname;
    char *result;
    int nfargs, minargs, maxargs;
{
    if ((nfargs >= minargs) && (nfargs <= maxargs))
	return 1;

    if (maxargs == (minargs + 1))
	sprintf(result, "#-1 FUNCTION (%s) EXPECTS %d OR %d ARGUMENTS",
		fname, minargs, maxargs);
    else
	sprintf(result,
		"#-1 FUNCTION (%s) EXPECTS BETWEEN %d AND %d ARGUMENTS",
		fname, minargs, maxargs);
    return 0;
}

/* ---------------------------------------------------------------------------
 * delim_check: obtain delimiter
 */

static int 
delim_check(fargs, nfargs, sep_arg, sep, buff, eval, player, cause,
	    cargs, ncargs)
    char *fargs[], *cargs[], *sep, *buff;
    int nfargs, ncargs, sep_arg, eval;
    dbref player, cause;
{
    char *tstr;
    int tlen;

    if (nfargs >= sep_arg) {
	tlen = strlen(fargs[sep_arg - 1]);
	if (tlen <= 1)
	    eval = 0;
	if (eval) {
	    tstr = exec(player, cause, EV_EVAL | EV_FCHECK,
			fargs[sep_arg - 1], cargs, ncargs);
	    tlen = strlen(tstr);
	    *sep = *tstr;
	    free_lbuf(tstr);
	}

	if (tlen == 0) {
	    *sep = ' ';
	} else if (tlen != 1) {
	    strcpy(buff,
		   "#-1 SEPARATOR MUST BE ONE CHARACTER");
	    return 0;
	} else if (!eval) {
	    *sep = *fargs[sep_arg - 1];
	}
    } else {
	*sep = ' ';
    }
    return 1;
}

/* ---------------------------------------------------------------------------
 * fun_words: Returns number of words in a string.
 * Added 1/28/91 Philip D. Wasson
 */

static int 
countwords(str, sep)
    char *str, sep;
{
    int n;

    str = trim_space_sep(str, sep);
    if (!*str)
	return 0;
    for (n = 0; str; str = next_token(str, sep), n++);
    return n;
}

FUNCTION(fun_words)
{
    char sep;

    if (nfargs == 0) {
	strcpy(buff, "0");
	return;
    }
    varargs_preamble("WORDS", 2);
    ltos(buff, countwords(fargs[0], sep));
}

/* ---------------------------------------------------------------------------
 * fun_flags: Returns the flags on an object.
 * Because @switch is case-insensitive, not quite as useful as it could be.
 */

FUNCTION(fun_flags)
{
    dbref it;
    char *buff2;

    it = match_thing(player, fargs[0]);
    if ((it != NOTHING) &&
	(mudconf.pub_flags || Examinable(player, it) || (it == cause))) {
	buff2 = unparse_flags(player, it);
	strcpy(buff, buff2);
	free_sbuf(buff2);
    } else
	strcpy(buff, "#-1");
    return;
}

/* ---------------------------------------------------------------------------
 * fun_rand: Return a random number from 0 to arg1-1
 */

FUNCTION(fun_rand)
{
    int num;

    num = atoi(fargs[0]);
    if (num < 1)
	strcpy(buff, "0");
    else
	sprintf(buff, "%ld", (random() % num));
}

/* ---------------------------------------------------------------------------
 * fun_die: Roll an N-sided die a certain number of times.
 */

FUNCTION(fun_die)
{
    int n = atoi(fargs[0]);
    int die = atoi(fargs[1]);
    int count;
    int total = 0;

    if (n == 0) {
	strcpy(buff, "0");
	return;
    }

    if ((n < 1) || (n > 100)) {
	strcpy(buff, "#-1 NUMBER OUT OF RANGE");
	return;
    }

    for (count = 0; count < n; count++)
	total += random() % die;
    total += n;

    ltos(buff, total);
}

/* ---------------------------------------------------------------------------
 * fun_abs: Returns the absolute value of its argument.
 */

FUNCTION(fun_abs)
{
#ifdef FLOATING_POINTS
    double num;

    num = atof(fargs[0]);
    if (num == 0.0) {
	strcpy(buff, "0");
    } else if (num < 0.0) {
	fval(buff, -num);
    } else {
	fval(buff, num);
    }
#else
    ltos(buff, abs(atoi(fargs[0])));
#endif
}

/* ---------------------------------------------------------------------------
 * fun_sign: Returns -1, 0, or 1 based on the the sign of its argument.
 */

FUNCTION(fun_sign)
{
    NVAL num;

    num = aton(fargs[0]);
    if (num < 0)
	strcpy(buff, "-1");
    else if (num > 0)
	strcpy(buff, "1");
    else
	strcpy(buff, "0");
}

/* ---------------------------------------------------------------------------
 * fun_time: Returns nicely-formatted time.
 */

FUNCTION(fun_time)
{
    char *temp;

    temp = (char *) ctime(&mudstate.now);
    temp[strlen(temp) - 1] = '\0';
    strcpy(buff, temp);
}

/* ---------------------------------------------------------------------------
 * fun_time: Seconds since 0:00 1/1/70
 */

FUNCTION(fun_secs)
{
    ltos(buff, mudstate.now);
}

/* ---------------------------------------------------------------------------
 * fun_convsecs: converts seconds to time string, based off 0:00 1/1/70
 */

FUNCTION(fun_convsecs)
{
    char *temp;
    time_t tt;

    tt = atol(fargs[0]);
    temp = (char *) ctime(&tt);
    temp[strlen(temp) - 1] = '\0';
    strcpy(buff, temp);
}

/* ---------------------------------------------------------------------------
 * fun_convtime: converts time string to seconds, based off 0:00 1/1/70
 *    additional auxiliary function and table used to parse time string,
 *    since no ANSI standard function are available to do this.
 */

static const char *monthtab[] =
{"Jan", "Feb", "Mar", "Apr", "May", "Jun",
 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};

static const char daystab[] =
{31, 29, 31, 30, 31, 30,
 31, 31, 30, 31, 30, 31};

/* converts time string to a struct tm. Returns 1 on success, 0 on fail.
 * Time string format is always 24 characters long, in format
 * Ddd Mmm DD HH:MM:SS YYYY
 */

#define	get_substr(buf, p) { \
			     p = (char *)index(buf, ' '); \
			     if (p) { \
				      *p++ = '\0'; \
				      while (*p == ' ') p++; \
				  } \
			      }

static int 
do_convtime(str, ttm)
    char *str;
    struct tm *ttm;
{
    char *buf, *p, *q;
    int i;

    if (!str || !ttm)
	return 0;
    while (*str == ' ')
	str++;
    buf = p = alloc_sbuf("do_convtime");	/* make a temp copy of arg */
    safe_sb_str(str, buf, &p);

    get_substr(buf, p);		/* day-of-week or month */
    if (!p || strlen(buf) != 3) {
	free_sbuf(buf);
	return 0;
    }
    for (i = 0; (i < 12) && string_compare(monthtab[i], p); i++);
    if (i == 12) {
	get_substr(p, q);	/* month */
	if (!q || strlen(p) != 3) {
	    free_sbuf(buf);	/* bad length */
	    return 0;
	}
	for (i = 0; (i < 12) && string_compare(monthtab[i], p); i++);
	if (i == 12) {
	    free_sbuf(buf);	/* not found */
	    return 0;
	}
	p = q;
    }
    ttm->tm_mon = i;

    get_substr(p, q);		/* day of month */
    if (!q || (ttm->tm_mday = atoi(p)) < 1 || ttm->tm_mday > daystab[i]) {
	free_sbuf(buf);
	return 0;
    }
    p = (char *) index(q, ':');	/* hours */
    if (!p) {
	free_sbuf(buf);
	return 0;
    }
    *p++ = '\0';
    if ((ttm->tm_hour = atoi(q)) > 23 || ttm->tm_hour < 0) {
	free_sbuf(buf);
	return 0;
    }
    if (ttm->tm_hour == 0) {
	while (isspace(*q))
	    q++;
	if (*q != '0') {
	    free_sbuf(buf);
	    return 0;
	}
    }
    q = (char *) index(p, ':');	/* minutes */
    if (!q) {
	free_sbuf(buf);
	return 0;
    }
    *q++ = '\0';
    if ((ttm->tm_min = atoi(p)) > 59 || ttm->tm_min < 0) {
	free_sbuf(buf);
	return 0;
    }
    if (ttm->tm_min == 0) {
	while (isspace(*p))
	    p++;
	if (*p != '0') {
	    free_sbuf(buf);
	    return 0;
	}
    }
    get_substr(q, p);		/* seconds */
    if (!p || (ttm->tm_sec = atoi(q)) > 59 || ttm->tm_sec < 0) {
	free_sbuf(buf);
	return 0;
    }
    if (ttm->tm_sec == 0) {
	while (isspace(*q))
	    q++;
	if (*q != '0') {
	    free_sbuf(buf);
	    return 0;
	}
    }
    get_substr(p, q);		/* year */
    if ((ttm->tm_year = atoi(p)) == 0) {
	while (isspace(*p))
	    p++;
	if (*p != '0') {
	    free_sbuf(buf);
	    return 0;
	}
    }
    if (ttm->tm_year > 100)
	ttm->tm_year -= 1900;
    free_sbuf(buf);
    if (ttm->tm_year < 0)
	return 0;

    /* We don't whether or not it's daylight savings time. */
    ttm->tm_isdst = -1;

#define LEAPYEAR_1900(yr) ((yr)%400==100||((yr)%100!=0&&(yr)%4==0))
    return (ttm->tm_mday != 29 || i != 1 || LEAPYEAR_1900(ttm->tm_year));
#undef LEAPYEAR_1900
}

FUNCTION(fun_convtime)
{
    struct tm *ttm;

    ttm = localtime(&mudstate.now);
    if (do_convtime(fargs[0], ttm))
	ltos(buff, timelocal(ttm));
    else
	strcpy(buff, "-1");
}


/* ---------------------------------------------------------------------------
 * fun_starttime: What time did this system last reboot?
 */

FUNCTION(fun_starttime)
{
    char *temp;

    temp = (char *) ctime(&mudstate.start_time);
    temp[strlen(temp) - 1] = '\0';
    strcpy(buff, temp);
}

/* ---------------------------------------------------------------------------
 * fun_restarts: How many times have we restarted?
 */

FUNCTION(fun_restarts)
{
    ltos(buff, mudstate.reboot_nums);
}

/* ---------------------------------------------------------------------------
 * fun_restarttime: When did we last restart?
 */

FUNCTION(fun_restarttime)
{
    char *temp;

    temp = (char *) ctime(&mudstate.restart_time);
    temp[strlen(temp) - 1] = '\0';
    strcpy(buff, temp);
}

/* ---------------------------------------------------------------------------
 * fun_get, fun_get_eval: Get attribute from object.
 */

static int 
check_read_perms(player, thing, attr, aowner, aflags, buff)
    dbref player, thing;
    ATTR *attr;
    int aowner, aflags;
    char *buff;
{
    int see_it;

    /* If we have explicit read permission to the attr, return it */

    if (See_attr_explicit(player, thing, attr, aowner, aflags))
	return 1;

    /* If we are nearby or have examine privs to the attr and it is
     * visible to us, return it.
     */

    see_it = See_attr(player, thing, attr, aowner, aflags);
    if ((Examinable(player, thing) || nearby(player, thing)) && see_it)
	return 1;

    /* For any object, we can read its visible attributes, EXCEPT
     * for descs, which are only visible if read_rem_desc is on.
     */

    if (see_it) {
	if (!mudconf.read_rem_desc && (attr->number == A_DESC)) {
	    strcpy(buff, (char *) "#-1 TOO FAR AWAY TO SEE");
	    return 0;
	} else {
	    return 1;
	}
    }
    strcpy(buff, (char *) "#-1 PERMISSION DENIED");
    return 0;
}

FUNCTION(fun_get)
{
    dbref thing, aowner;
    int attrib, free_buffer, aflags;
    ATTR *attr;
    char *atr_gotten;
    struct boolexp *bool;

    if (!parse_attrib(player, fargs[0], &thing, &attrib)) {
	strcpy(buff, "#-1 NO MATCH");
	return;
    }
    if (attrib == NOTHING) {
	*buff = '\0';
	return;
    }
    free_buffer = 1;
    attr = atr_num(attrib);	/* We need the attr's flags for this: */
    if (!attr) {
	*buff = '\0';
	return;
    }
    if (attr->flags & AF_IS_LOCK) {
	atr_gotten = atr_get(thing, attrib, &aowner, &aflags);
	if (Read_attr(player, thing, attr, aowner, aflags)) {
	    bool = parse_boolexp(player, atr_gotten, 1);
	    free_lbuf(atr_gotten);
	    atr_gotten = unparse_boolexp(player, bool);
	    free_boolexp(bool);
	} else {
	    free_lbuf(atr_gotten);
	    atr_gotten = (char *) "#-1 PERMISSION DENIED";
	}
	free_buffer = 0;
    } else {
	atr_gotten = atr_pget(thing, attrib, &aowner, &aflags);
    }

    /* Perform access checks.  c_r_p fills buff with an error message
     * if needed.
     */

    if (check_read_perms(player, thing, attr, aowner, aflags, buff))
	strcpy(buff, atr_gotten);
    if (free_buffer)
	free_lbuf(atr_gotten);
    return;
}

FUNCTION(fun_get_eval)
{
    dbref thing, aowner;
    int attrib, free_buffer, aflags, eval_it;
    ATTR *attr;
    char *atr_gotten;
    struct boolexp *bool;

    if (!parse_attrib(player, fargs[0], &thing, &attrib)) {
	strcpy(buff, "#-1 NO MATCH");
	return;
    }
    if (attrib == NOTHING) {
	*buff = '\0';
	return;
    }
    free_buffer = 1;
    eval_it = 1;
    attr = atr_num(attrib);	/* We need the attr's flags for this: */
    if (!attr) {
	*buff = '\0';
	return;
    }
    if (attr->flags & AF_IS_LOCK) {
	atr_gotten = atr_get(thing, attrib, &aowner, &aflags);
	if (Read_attr(player, thing, attr, aowner, aflags)) {
	    bool = parse_boolexp(player, atr_gotten, 1);
	    free_lbuf(atr_gotten);
	    atr_gotten = unparse_boolexp(player, bool);
	    free_boolexp(bool);
	} else {
	    free_lbuf(atr_gotten);
	    atr_gotten = (char *) "#-1 PERMISSION DENIED";
	}
	free_buffer = 0;
	eval_it = 0;
    } else {
	atr_gotten = atr_pget(thing, attrib, &aowner, &aflags);
    }
    if (!check_read_perms(player, thing, attr, aowner, aflags, buff)) {
	if (free_buffer)
	    free_lbuf(atr_gotten);
	return;
    }
    strcpy(buff, atr_gotten);
    if (free_buffer)
	free_lbuf(atr_gotten);
    if (eval_it) {
	atr_gotten = exec(thing, player, EV_FIGNORE | EV_EVAL, buff,
			  (char **) NULL, 0);
	strcpy(buff, atr_gotten);
	free_lbuf(atr_gotten);
    }
    return;
}

/* ---------------------------------------------------------------------------
 * fun_u and fun_ulocal:  Call a user-defined function.
 */

static void do_ufun(buff, player, cause,
		    fargs, nfargs, 
		    cargs, ncargs,
		    is_local)
     char *buff;
     dbref player, cause;
     char *fargs[], *cargs[];
     int nfargs, ncargs, is_local;
{
    dbref aowner, thing;
    int aflags, anum;
    ATTR *ap;
    char *atext, *result, *preserve[MAX_GLOBAL_REGS];

    /* We need at least one argument */

    if (nfargs < 1) {
	strcpy(buff, "#-1 TOO FEW ARGUMENTS");
	return;
    }
    /* Two possibilities for the first arg: <obj>/<attr> and <attr>. */

    if (parse_attrib(player, fargs[0], &thing, &anum)) {
	if ((anum == NOTHING) || (!Good_obj(thing)))
	    ap = NULL;
	else
	    ap = atr_num(anum);
    } else {
	thing = player;
	ap = atr_str(fargs[0]);
    }

    /* Make sure we got a good attribute */

    if (!ap) {
	*buff = '\0';
	return;
    }
    /* Use it if we can access it, otherwise return an error. */

    atext = atr_pget(thing, ap->number, &aowner, &aflags);
    if (!atext) {
	*buff = '\0';
	return;
    }
    if (!*atext) {
	free_lbuf(atext);
	*buff = '\0';
	return;
    }
    if (!check_read_perms(player, thing, ap, aowner, aflags, buff)) {
	free_lbuf(atext);
	*buff = '\0';
	return;
    }

    /* If we're evaluating locally, preserve the global registers. */

    if (is_local)
	save_global_regs("fun_ulocal_save", preserve);

    /* Evaluate it using the rest of the passed function args */

    result = exec(thing, cause, EV_FCHECK | EV_EVAL, atext,
		  &(fargs[1]), nfargs - 1);
    free_lbuf(atext);
    strcpy(buff, result);
    free_lbuf(result);

    /* If we're evaluating locally, restore the preserved registers. */

    if (is_local)
	restore_global_regs("fun_ulocal_restore", preserve);
}    

FUNCTION(fun_u)
{
    do_ufun(buff, player, cause, fargs, nfargs, cargs, ncargs, 0);
}

FUNCTION(fun_ulocal)
{
    do_ufun(buff, player, cause, fargs, nfargs, cargs, ncargs, 1);
}

/* ---------------------------------------------------------------------------
 * fun_default, fun_edefault, and fun_udefault:
 * These check for the presence of an attribute. If it exists, then it
 * is gotten, via the equivalent of get(), get_eval(), or u(), respectively.
 * Otherwise, the default message is used.
 * In the case of udefault(), the remaining arguments to the function
 * are used as arguments to the u().
 */

FUNCTION(fun_default)
{
    dbref thing, aowner;
    int attrib, aflags;
    ATTR *attr;
    char *objname, *atr_gotten, *defcase;

    objname = exec(player, cause, EV_EVAL | EV_STRIP | EV_FCHECK, fargs[0],
		   cargs, ncargs);

    /* First we check to see that the attribute exists on the object.
     * If so, we grab it and use it.
     */

    if (objname != NULL) {
	if (parse_attrib(player, objname, &thing, &attrib) &&
	    (attrib != NOTHING)) {
	    attr = atr_num(attrib);
	    if (attr && !(attr->flags & AF_IS_LOCK)) {
		atr_gotten = atr_pget(thing, attrib, &aowner, &aflags);
		if (*atr_gotten &&
		    check_read_perms(player, thing, attr, aowner,
				     aflags, buff)) {
		    strcpy(buff, atr_gotten);
		    free_lbuf(atr_gotten);
		    free_lbuf(objname);
		    return;
		}
		free_lbuf(atr_gotten);
	    }
	}
	free_lbuf(objname);
    }

    /* If we've hit this point, we've not gotten anything useful, so
     * we go and evaluate the default.
     */

    defcase = exec(player, cause, EV_EVAL | EV_STRIP | EV_FCHECK, fargs[1],
		   cargs, ncargs);
    strcpy(buff, defcase);
    free_lbuf(defcase);
}

FUNCTION(fun_edefault)
{
    dbref thing, aowner;
    int attrib, aflags;
    ATTR  *attr;
    char *objname, *atr_gotten, *defcase;

    objname = exec(player, cause, EV_EVAL | EV_STRIP | EV_FCHECK, fargs[0],
		   cargs, ncargs);

    /* First we check to see that the attribute exists on the object.
     * If so, we grab it and use it.
     */

    if (objname != NULL) {
	if (parse_attrib(player, objname, &thing, &attrib) &&
	    (attrib != NOTHING)) {
	    attr = atr_num(attrib);
	    if (attr && !(attr->flags & AF_IS_LOCK)) {
		atr_gotten = atr_pget(thing, attrib, &aowner, &aflags);
		if (*atr_gotten &&
		    check_read_perms(player, thing, attr, aowner,
				     aflags, buff)) {
  		    strcpy(buff, atr_gotten);
		    free_lbuf(atr_gotten);
		    atr_gotten = exec(thing, player, EV_FIGNORE | EV_EVAL,
				      buff, (char **) NULL, 0);
		    strcpy(buff, atr_gotten);
		    free_lbuf(atr_gotten);
		    free_lbuf(objname);
		    return;
		}
		free_lbuf(atr_gotten);
	    }
	}
	free_lbuf(objname);
    }

    /* If we've hit this point, we've not gotten anything useful, so
     * we go and evaluate the default.
     */

    defcase = exec(player, cause, EV_EVAL | EV_STRIP | EV_FCHECK, fargs[1],
		   cargs, ncargs);
    strcpy(buff, defcase);
    free_lbuf(defcase);
}

FUNCTION(fun_udefault)
{
    dbref thing, aowner;
    int aflags, anum, i, j;
    ATTR  *ap;
    char *objname, *atext, *result, *defcase, *xargs[NUM_ENV_VARS];

    *buff = '\0';

    if (nfargs < 2)		/* must have at least two arguments */
	return;

    objname = exec(player, cause, EV_EVAL | EV_STRIP | EV_FCHECK, fargs[0],
		   cargs, ncargs);

    /* First we check to see that the attribute exists on the object.
     * If so, we grab it and use it.
     */

    if (objname != NULL) {
	if (parse_attrib(player, objname, &thing, &anum)) {
	    if ((anum == NOTHING) || (!Good_obj(thing)))
		ap = NULL;
	    else
		ap = atr_num(anum);
	} else {
	    thing = player;
	    ap = atr_str(objname);
	}
	if (ap) {
	    atext = atr_pget(thing, ap->number, &aowner, &aflags);
	    if (atext) {
		if (*atext &&
		    check_read_perms(player, thing, ap, aowner, aflags,
				     buff)) {

		    /* Now we have a problem -- we've got to go eval
		     * all of those arguments to the function.
		     */
		    for (i = 2, j = 0; j < NUM_ENV_VARS; i++, j++) {
			if ((i < NUM_ENV_VARS) && fargs[i])
			    xargs[j] = exec(player, cause, 
					    EV_STRIP | EV_FCHECK | EV_EVAL,
					    fargs[i], cargs, ncargs);
			else
			    xargs[j] = NULL;
		    }

		    /* Now we should do the evaluation. */
		    result = exec(thing, cause, EV_FCHECK | EV_EVAL, atext,
				  xargs, nfargs - 2);

		    /* Then clean up after ourselves. */
		    for (j = 0; j < NUM_ENV_VARS; j++)
			if (xargs[j])
			    free_lbuf(xargs[j]);

		    free_lbuf(atext);
		    strcpy(buff, result);
		    free_lbuf(result);
		    free_lbuf(objname);
		    return;
		}
		free_lbuf(atext);
	    }
	}
	free_lbuf(objname);
    }

    /* If we've hit this point, we've not gotten anything useful, so
     * we go and evaluate the default.
     */

    defcase = exec(player, cause, EV_EVAL | EV_STRIP | EV_FCHECK, fargs[1],
		   cargs, ncargs);
    strcpy(buff, defcase);
    free_lbuf(defcase);
}

/* ---------------------------------------------------------------------------
 * fun_hasattr: does object X have attribute Y.
 */

FUNCTION(fun_hasattr)
{
    dbref thing, aowner;
    int aflags;
    ATTR *attr;
    char *tbuf;

    thing = match_thing(player, fargs[0]);
    if (thing == NOTHING) {
	strcpy(buff, "#-1 NO MATCH");
	return;
    } else if (!Examinable(player, thing)) {
	strcpy(buff, "#-1 PERMISSION DENIED");
	return;
    }
    attr = atr_str(fargs[1]);
    if (!attr) {
	strcpy(buff, "0");
	return;
    }
    atr_pget_info(thing, attr->number, &aowner, &aflags);
    if (!See_attr(player, thing, attr, aowner, aflags))
	strcpy(buff, "0");
    else {
	tbuf = atr_pget(thing, attr->number, &aowner, &aflags);
	if (*tbuf)
	    strcpy(buff, "1");
	else
	    strcpy(buff, "0");
	free_lbuf(tbuf);
    }
}

/* ---------------------------------------------------------------------------
 * fun_objeval: Evaluate expression from perspective of another object.
 *              All args to this function are passed _unevaluated_.
 */

FUNCTION(fun_objeval)
{
    char *s, *name;
    dbref obj;

    /* Evaluate the first argument to get the object name. */

    name = exec(player, cause, EV_EVAL | EV_STRIP | EV_FCHECK, fargs[0],
		cargs, ncargs);
    if (name == NULL) {
	*buff = '\0';
	return;
    }

    /* In order to evaluate from something else's viewpoint, you must
     * have the same owner as it, or be a wizard. Otherwise, we default
     * to evaluating from our own viewpoint.
     */
    if (((obj = match_thing(player, name)) == NOTHING) ||
	((Owner(player) != Owner(obj)) && !Wizard(player)))
	obj = player;

    s = exec(obj, cause, EV_EVAL | EV_STRIP | EV_FCHECK, fargs[1],
	     cargs, ncargs);
    if (s != NULL) {
	strcpy(buff, s);
	free_lbuf(s);
    } else {
	*buff = '\0';
    }
    free_lbuf(name);
}

/* ---------------------------------------------------------------------------
 * fun_parent: Get parent of object.
 */

FUNCTION(fun_parent)
{
    dbref it;

    it = match_thing(player, fargs[0]);
    if (Good_obj(it) && (Examinable(player, it) || (it == cause))) {
	*buff = '#';
	ltos(&buff[1], Parent(it));
    } else {
	strcpy(buff, "#-1");
    }
    return;
}

/* ---------------------------------------------------------------------------
 * fun_mid: mid(foobar,2,3) returns oba
 */

FUNCTION(fun_mid)
{
    int l, len;
    char *bufc, *strp;

    bufc = buff;
    l = atoi(fargs[1]);
    len = atoi(fargs[2]);
    if ((l < 0) || (len < 0) || ((len + l) > LBUF_SIZE)) {
	strcpy(buff, "#-1 OUT OF RANGE");
	return;
    }

    /* Set a pointer to the beginning of buffer -- strip_ansi() returns
     * a static.
     */
    strp = (mudconf.ansi_colors ? strip_ansi(fargs[0]) : fargs[0]);
    if (l < strlen(strp))
	strcpy(buff, strp + l);
    else
	*buff = '\0';
    buff[len] = '\0';
}

/* ---------------------------------------------------------------------------
 * fun_first: Returns first word in a string
 */

FUNCTION(fun_first)
{
    char *s, *first, sep;

    /* If we are passed an empty arglist return a null string */

    if (nfargs == 0) {
	*buff = '\0';
	return;
    }
    varargs_preamble("FIRST", 2);
    s = trim_space_sep(fargs[0], sep);	/* leading spaces ... */
    first = split_token(&s, sep);
    if (!first) {
	*buff = '\0';
    } else {
	strcpy(buff, first);
    }
}

/* ---------------------------------------------------------------------------
 * fun_rest: Returns all but the first word in a string
 */


FUNCTION(fun_rest)
{
    char *s, *first, sep;

    /* If we are passed an empty arglist return a null string */

    if (nfargs == 0) {
	*buff = '\0';
	return;
    }
    varargs_preamble("REST", 2);
    s = trim_space_sep(fargs[0], sep);	/* leading spaces ... */
    first = split_token(&s, sep);
    if (!s) {
	*buff = '\0';
    } else {
	strcpy(buff, s);
    }
}

/* ---------------------------------------------------------------------------
 * fun_last: Returns last word in a string
 */

FUNCTION(fun_last)
{
    char *s, *last, sep;
    int len, i;

    /* If we are passed an empty arglist return a null string */

    if (nfargs == 0) {
	*buff = '\0';
	return;
    }

    varargs_preamble("LAST", 2);
    s = trim_space_sep(fargs[0], sep); /* trim leading spaces */

    /* If we're dealing with spaces, trim off the trailing stuff */

    if (sep == ' ') {
	len = strlen(s);
	for (i = len - 1; s[i] == ' '; i--)
	    ;
	if (i + 1 <= len)
	    s[i + 1] = '\0';
    }

    last = (char *) rindex(s, sep);
    if (last)
	strcpy(buff, ++last);
    else 
	strcpy(buff, s);
}

/* ---------------------------------------------------------------------------
 * fun_left: Returns first n characters in a string
 */

FUNCTION(fun_left)
{
    int len = atoi(fargs[1]);

    if (len < 1) {
	*buff = '\0';
    } else { 
	strcpy(buff, mudconf.ansi_colors ? strip_ansi(fargs[0]) : fargs[0]);
	if (len < LBUF_SIZE)
	    buff[len] = '\0';
    }
}

/* ---------------------------------------------------------------------------
 * fun_right: Returns last n characters in a string
 */

FUNCTION(fun_right)
{
    char *strp;
    int len = atoi(fargs[1]);

    if (len < 1) {
	*buff = '\0';
    } else {
	strp = (mudconf.ansi_colors ? strip_ansi(fargs[0]) : fargs[0]);
	len = strlen(strp) - len;
	if (len < 1)
	    strcpy(buff, strp);
	else
	    strcpy(buff, strp + len);
    }
}

/* ---------------------------------------------------------------------------
 * fun_v: Function form of %-substitution
 */

FUNCTION(fun_v)
{
    dbref aowner;
    int aflags;
    char *sbuf, *sbufc, *tbuf;
    ATTR *ap;

    tbuf = fargs[0];
    if (isalpha(tbuf[0]) && tbuf[1]) {

	/* Fetch an attribute from me.  First see if it exists,
	 * returning a null string if it does not. */

	ap = atr_str(fargs[0]);
	if (!ap) {
	    *buff = '\0';
	    return;
	}
	/* If we can access it, return it, otherwise return a
	 * null string */

	atr_pget_info(player, ap->number, &aowner, &aflags);
	if (See_attr(player, player, ap, aowner, aflags)) {
	    tbuf = atr_pget(player, ap->number, &aowner, &aflags);
	    strcpy(buff, tbuf);
	    free_lbuf(tbuf);
	} else {
	    *buff = '\0';
	}
	return;
    }
    /* Not an attribute, process as %<arg> */

    sbuf = alloc_sbuf("fun_v");
    sbufc = sbuf;
    safe_sb_chr('%', sbuf, &sbufc);
    safe_sb_str(fargs[0], sbuf, &sbufc);
    tbuf = exec(player, cause, EV_FIGNORE, sbuf, cargs, ncargs);
    strcpy(buff, tbuf);
    free_lbuf(tbuf);
    free_sbuf(sbuf);
}

/* ---------------------------------------------------------------------------
 * fun_s: Force substitution to occur.
 */

FUNCTION(fun_s)
{
    char *tbuf;

    tbuf = exec(player, cause, EV_FIGNORE | EV_EVAL, fargs[0],
		cargs, ncargs);
    strcpy(buff, tbuf);
    free_lbuf(tbuf);
}

/* ---------------------------------------------------------------------------
 * fun_con: Returns first item in contents list of object/room
 */

FUNCTION(fun_con)
{
    dbref it;

    it = match_thing(player, fargs[0]);

    if ((it != NOTHING) &&
	(Has_contents(it)) &&
	(Examinable(player, it) ||
	 (where_is(player) == it) ||
	 (it == cause))) {
	*buff = '#';
	ltos(&buff[1], Contents(it));
	return;
    }
    strcpy(buff, "#-1");
    return;
}

/* ---------------------------------------------------------------------------
 * fun_exit: Returns first exit in exits list of room.
 */

FUNCTION(fun_exit)
{
    dbref it, exam;

    it = match_thing(player, fargs[0]);
    if (Good_obj(it) && Has_exits(it) && Good_obj(Exits(it))) {
	exam = Examinable(player, it);
	if (exam || (where_is(player) == it) || (it == cause)) {
	    *buff = '#';
	    ltos(&buff[1], next_exit(player, Exits(it), exam));
	    return;
	}
    }
    strcpy(buff, "#-1");
    return;
}

/* ---------------------------------------------------------------------------
 * fun_next: return next thing in contents or exits chain
 */

FUNCTION(fun_next)
{
    dbref it, loc, exam_here;

    it = match_thing(player, fargs[0]);
    if (Good_obj(it)) {
	loc = where_is(it);
	if (loc != NOTHING) {
	    exam_here = Examinable(player, loc);
	    if (exam_here || (loc == player) ||
		(loc == where_is(player))) {
		*buff = '#';
		ltos(&buff[1], next_exit(player, Next(it), exam_here));
		return;
	    }
	}
    }
    strcpy(buff, "#-1");
    return;
}

/* ---------------------------------------------------------------------------
 * fun_lastcreate: Return the last object of type Y that X created.
 */

FUNCTION(fun_lastcreate)
{
    int i, aowner, aflags, obj_list[4], obj_type;
    char *obj_str, *p;
    dbref obj = match_thing(player, fargs[0]);

    if (!controls(player, obj)) {    /* Automatically checks for GoodObj */
	strcpy(buff, "#-1");
	return;
    }

    switch (*fargs[1]) {
      case 'R':
      case 'r':
	obj_type = 0;
	break;
      case 'E':
      case 'e':
	obj_type = 1;;
	break;
      case 'T':
      case 't':
	obj_type = 2;
	break;
      case 'P':
      case 'p':
	obj_type = 3;
	break;
      default:
	notify_quiet(player, "Invalid object type.");
	strcpy(buff, "#-1");
	return;
    }

    if ((obj_str = atr_get(obj, A_NEWOBJS, &aowner, &aflags)) == NULL) {
	strcpy(buff, "#-1");
	return;
    }

    if (!*obj_str) {
	free_lbuf(obj_str);
	strcpy(buff, "#-1");
	return;
    }

    for (p = (char *) strtok(obj_str, " "), i = 0;
	 p && (i < 4);
	 p = (char *) strtok(NULL, " "), i++) {
	obj_list[i] = atoi(p);
    }
    free_lbuf(obj_str);

    *buff = '#';
    ltos(&buff[1], obj_list[obj_type]);
}


/* ---------------------------------------------------------------------------
 * fun_findable: can X locate Y
 */

FUNCTION(fun_findable)
{
    dbref obj = match_thing(player, fargs[0]);
    dbref victim = match_thing(player, fargs[1]);

    if (obj == NOTHING)
	strcpy(buff, "#-1 ARG1 NOT FOUND");
    else if (victim == NOTHING)
	strcpy(buff, "#-1 ARG2 NOT FOUND");
    else
	ltos(buff, locatable(obj, victim, obj));
}

/* ---------------------------------------------------------------------------
 * fun_loc: Returns the location of something
 */

FUNCTION(fun_loc)
{
    dbref it;

    it = match_thing(player, fargs[0]);
    if (locatable(player, it, cause)) {
	*buff = '#';
	ltos(&buff[1], Location(it));
    } else
	strcpy(buff, "#-1");
    return;
}

/* ---------------------------------------------------------------------------
 * fun_where: Returns the "true" location of something
 */

FUNCTION(fun_where)
{
    dbref it;

    it = match_thing(player, fargs[0]);
    if (locatable(player, it, cause)) {
	*buff = '#';
	ltos(&buff[1], where_is(it));
    } else
	strcpy(buff, "#-1");
    return;
}

/* ---------------------------------------------------------------------------
 * fun_rloc: Returns the recursed location of something (specifying #levels)
 */

FUNCTION(fun_rloc)
{
    int i, levels;
    dbref it;

    levels = atoi(fargs[1]);
    if (levels > mudconf.ntfy_nest_lim)
	levels = mudconf.ntfy_nest_lim;

    it = match_thing(player, fargs[0]);
    if (locatable(player, it, cause)) {
	for (i = 0; i < levels; i++) {
	    if (!Good_obj(it) || !Has_location(it))
		break;
	    it = Location(it);
	}
	*buff = '#';
	ltos(&buff[1], it);
	return;
    }
    strcpy(buff, "#-1");
}

/* ---------------------------------------------------------------------------
 * fun_room: Find the room an object is ultimately in.
 */

FUNCTION(fun_room)
{
    dbref it;
    int count;

    it = match_thing(player, fargs[0]);
    if (locatable(player, it, cause)) {
	for (count = mudconf.ntfy_nest_lim; count > 0; count--) {
	    it = Location(it);
	    if (!Good_obj(it))
		break;
	    if (isRoom(it)) {
		*buff = '#';
		ltos(&buff[1], it);
		return;
	    }
	}
	strcpy(buff, "#-1");
    } else if (isRoom(it)) {
	*buff = '#';
	ltos(&buff[1], it);
    } else {
	strcpy(buff, "#-1");
    }
    return;
}

/* ---------------------------------------------------------------------------
 * fun_owner: Return the owner of an object.
 */

FUNCTION(fun_owner)
{
    dbref it, aowner;
    int atr, aflags;

    if (parse_attrib(player, fargs[0], &it, &atr)) {
	if (atr == NOTHING) {
	    it = NOTHING;
	} else {
	    atr_pget_info(it, atr, &aowner, &aflags);
	    it = aowner;
	}
    } else {
	it = match_thing(player, fargs[0]);
	if (it != NOTHING)
	    it = Owner(it);
    }
    *buff = '#';
    ltos(&buff[1],  it);
}

/* ---------------------------------------------------------------------------
 * fun_controls: Does x control y?
 */

FUNCTION(fun_controls)
{
    dbref x, y;

    x = match_thing(player, fargs[0]);
    if (x == NOTHING) {
	strcpy(buff, "#-1 ARG1 NOT FOUND");
	return;
    }
    y = match_thing(player, fargs[1]);
    if (y == NOTHING) {
	strcpy(buff, "#-1 ARG2 NOT FOUND");
	return;
    }
    ltos(buff, Controls(x, y));
}

/* ---------------------------------------------------------------------------
 * fun_sees: Can X see Y in the normal Contents list of the room. If X
 *           or Y do not exist, 0 is returned.
 */

FUNCTION(fun_sees)
{
    dbref it, thing;
    int can_see_loc;

    if ((it = match_thing(player, fargs[0])) == NOTHING) {
	strcpy(buff, "0");
	return;
    }

    thing = match_thing(player, fargs[1]);
    if (!Good_obj(thing)) {
	strcpy(buff, "0");
	return;
    }

    can_see_loc = (!Dark(Location(thing)) ||
		   (mudconf.see_own_dark &&
		    Examinable(player, Location(thing))));
    ltos(buff, can_see(it, thing, can_see_loc));
}

/* ---------------------------------------------------------------------------
 * fun_visible:  Can X examine Y. If X does not exist, 0 is returned.
 *               If Y, the object, does not exist, 0 is returned. If
 *               Y the object exists, but the optional attribute does
 *               not, X's ability to return Y the object is returned.
 */

FUNCTION(fun_visible)
{
    dbref it, thing, aowner;
    int aflags, atr;
    ATTR *ap;

    if ((it = match_thing(player, fargs[0])) == NOTHING) {
	strcpy(buff, "0");
	return;
    }

    if (parse_attrib(player, fargs[1], &thing, &atr)) {
	if (atr == NOTHING) {
	    ltos(buff, Examinable(it, thing));
	    return;
	}
	ap = atr_num(atr);
	atr_pget_info(thing, atr, &aowner, &aflags);
	ltos(buff, See_attr(it, thing, ap, aowner, aflags));
	return;
    }

    thing = match_thing(player, fargs[1]);
    if (!Good_obj(thing)) {
	strcpy(buff, "0");
	return;
    }
    ltos(buff, Examinable(it, thing));
}

/* ---------------------------------------------------------------------------
 * fun_fullname: Return the fullname of an object (good for exits)
 */

FUNCTION(fun_fullname)
{
    dbref it;

    it = match_thing(player, fargs[0]);
    if (it == NOTHING) {
	buff[0] = '\0';
	return;
    }
    if (!mudconf.read_rem_name) {
	if (!nearby_or_control(player, it) &&
	    (!isPlayer(it))) {
	    strcpy(buff, "#-1 TOO FAR AWAY TO SEE");
	    return;
	}
    }
    strcpy(buff, Name(it));
}

/* ---------------------------------------------------------------------------
 * fun_name: Return the name of an object
 */

FUNCTION(fun_name)
{
    dbref it;
    char *s;

    it = match_thing(player, fargs[0]);
    if (it == NOTHING) {
	buff[0] = '\0';
	return;
    }
    if (!mudconf.read_rem_name) {
	if (!nearby_or_control(player, it) && !isPlayer(it)) {
	    strcpy(buff, "#-1 TOO FAR AWAY TO SEE");
	    return;
	}
    }
    strcpy(buff, Name(it));
    if (isExit(it)) {
	for (s = buff; *s && (*s != ';'); s++);
	*s = '\0';
    }
}

/* ---------------------------------------------------------------------------
 * fun_match, fun_matchall, fun_strmatch: Match arg2 against each word of
 * arg1 returning index of first match (or index of all matches), or against
 * the whole string.
 */

FUNCTION(fun_match)
{
    int wcount;
    char *r, *s, sep;

    varargs_preamble("MATCH", 3);

    /* Check each word individually, returning the word number of the
     * first one that matches.  If none match, return 0.
     */

    wcount = 1;
    s = trim_space_sep(fargs[0], sep);
    do {
	r = split_token(&s, sep);
	if (quick_wild(fargs[1], r)) {
	    ltos(buff, wcount);
	    return;
	}
	wcount++;
    } while (s);
    strcpy(buff, "0");
}

FUNCTION(fun_matchall)
{
    int wcount;
    char *r, *s, *bp, sep, tbuf[8];

    varargs_preamble("MATCHALL", 3);

    /* Check each word individually, returning the word number of all
     * that match. If none match, return an empty string.
     */

    wcount = 1;
    bp = buff;
    s = trim_space_sep(fargs[0], sep);
    do {
	r = split_token(&s, sep);
	if (quick_wild(fargs[1], r)) {
	    ltos(tbuf, wcount);
	    if (bp != buff) {
		safe_chr(' ', buff, &bp);
	    }
	    safe_str(tbuf, buff, &bp);
	}
	wcount++;
    } while (s);

    *bp = '\0';
}

FUNCTION(fun_strmatch)
{
    /* Check if we match the whole string.  If so, return 1 */

    if (quick_wild(fargs[1], fargs[0]))
	strcpy(buff, "1");
    else
	strcpy(buff, "0");
    return;
}

/* ---------------------------------------------------------------------------
 * fun_regmatch: Return 0 or 1 depending on whether or not a regular
 * expression matches a string. If a third argument is specified, dump
 * the results of a regexp pattern match into a set of arbitrary r()-registers.
 *
 * regmatch(string, pattern, list of registers)
 * If the number of matches exceeds the registers, those bits are tossed
 * out.
 * If -1 is specified as a register number, the matching bit is tossed.
 * Therefore, if the list is "-1 0 3 5", the regexp $0 is tossed, and
 * the regexp $1, $2, and $3 become r(0), r(3), and r(5), respectively.
 *
 */

FUNCTION(fun_regmatch)
{
    int i, nqregs, curq, len;
    char *qregs[10];
    int qnums[10];
    regexp *re;

    if (!fn_range_check("REGMATCH", nfargs, 2, 3, buff))
	return;

    if ((re = regcomp(fargs[1])) == NULL) {
	/* Matching error. */
	notify_quiet(player, (const char *) regexp_errbuf);
	strcpy(buff, "0");
	return;
    }

    ltos(buff, (int) regexec(re, fargs[0]));

    /* If we don't have a third argument, we're done. */
    if (nfargs != 3) {
	free(re);
	return;
    }

    /* We need to parse the list of registers. Anything that we don't get is
     * assumed to be -1.
     */
    nqregs = list2arr(qregs, 10, fargs[2], ' ');
    for (i = 0; i < 10; i++) {
	if ((i < nqregs) && qregs[i] && *qregs[i])
	    qnums[i] = atoi(qregs[i]);
	else
	    qnums[i] = -1;
    }

    /* Now we run a copy. */
    for (i = 0;
	 (i < NSUBEXP) && (re->startp[i]) && (re->endp[i]);
	 i++) {
	curq = qnums[i];
	if ((curq >= 0) && (curq < MAX_GLOBAL_REGS)) {
	    if (!mudstate.global_regs[curq]) {
		mudstate.global_regs[curq] = alloc_lbuf("fun_regmatch");
	    }
	    len = re->endp[i] - re->startp[i];
	    strncpy(mudstate.global_regs[curq], re->startp[i], len);
	    mudstate.global_regs[curq][len] = '\0'; /* must null-terminate */
	}
    }

    free(re);
}

/* ---------------------------------------------------------------------------
 * fun_extract: extract words from string:
 * extract(foo bar baz,1,2) returns 'foo bar'
 * extract(foo bar baz,2,1) returns 'bar'
 * extract(foo bar baz,2,2) returns 'bar baz'
 *
 * Now takes optional separator extract(foo-bar-baz,1,2,-) returns 'foo-bar'
 */

FUNCTION(fun_extract)
{
    int start, len;
    char *r, *s, *t, sep;

    varargs_preamble("EXTRACT", 4);

    s = fargs[0];
    start = atoi(fargs[1]);
    len = atoi(fargs[2]);

    if ((start < 1) || (len < 1)) {
	*buff = '\0';
	return;
    }
    /* Skip to the start of the string to save */

    start--;
    s = trim_space_sep(s, sep);
    while (start && s) {
	s = next_token(s, sep);
	start--;
    }

    /* If we ran of the end of the string, return nothing */

    if (!s || !*s) {
	*buff = '\0';
	return;
    }
    /* Count off the words in the string to save */

    r = s;
    len--;
    while (len && s) {
	s = next_token(s, sep);
	len--;
    }

    /* Chop off the rest of the string, if needed */

    if (s && *s)
	t = split_token(&s, sep);
    strcpy(buff, r);
}

int 
xlate(arg)
    char *arg;
{
    /* Deals with either dbrefs or integers. This is not, mind you,
     * used by the boolean-determining functions such as or() and not();
     * it's used by the search routines, though.
     */

    int temp;
    char *temp2;

    if (arg[0] == '#') {
	arg++;
	if (is_integer(arg)) {
	    temp = atoi(arg);
	    if (temp == -1)
		temp = 0;
	    return temp;
	}
	return 0;
    }
    temp2 = trim_space_sep(arg, ' ');
    if (!*temp2)
	return 0;
    if (is_integer(temp2))
	return atoi(temp2);
    return 1;
}

/* ---------------------------------------------------------------------------
 * fun_elements: given a list of numbers, get corresponding elements from
 * the list.  elements(ack bar eep foof yay,2 4) ==> bar foof
 * The function takes a separator, but the separator only applies to the
 * first list.
 */

FUNCTION(fun_elements)
{
    int nwords, cur;
    char *ptrs[LBUF_SIZE / 2];
    char *wordlist, *s, *r, *bp, sep, osep;

    svarargs_preamble("ELEMENTS", 4);

    /* Turn the first list into an array. */

    wordlist = alloc_lbuf("fun_elements.wordlist");
    strcpy(wordlist, fargs[0]);
    nwords = list2arr(ptrs, LBUF_SIZE / 2, wordlist, sep);

    bp = buff;
    s = trim_space_sep(fargs[1], ' ');

    /* Go through the second list, grabbing the numbers and finding the
     * corresponding elements.
     */

    do {
	r = split_token(&s, ' ');
	cur = atoi(r) - 1;
	if ((cur >= 0) && (cur < nwords) && ptrs[cur]) {
	    if (bp != buff) {
		safe_chr(osep, buff, &bp);
	    }
	    safe_str(ptrs[cur], buff, &bp);
	}
    } while (s);

    *bp = '\0';
    free_lbuf(wordlist);
}

/* ---------------------------------------------------------------------------
 * fun_grab: a combination of extract() and match(), sortof. We grab the
 *           single element that we match.
 *
 *   grab(Test:1 Ack:2 Foof:3,*:2)    => Ack:2
 *   grab(Test-1+Ack-2+Foof-3,*o*,+)  => Ack:2
 */

FUNCTION(fun_grab)
{
    char *r, *s, sep;

    varargs_preamble("GRAB", 3);

    /* Walk the wordstring, until we find the word we want. */

    s = trim_space_sep(fargs[0], sep);
    do {
	r = split_token(&s, sep);
	if (quick_wild(fargs[1], r)) {
	    strcpy(buff, r);
	    return;
	}
    } while (s);
    *buff = '\0';
}

/* ---------------------------------------------------------------------------
 * fun_index:  like extract(), but it works with an arbitrary separator.
 * index(a b | c d e | f gh | ij k, |, 2, 1) => c d e
 * index(a b | c d e | f gh | ij k, |, 2, 2) => c d e | f g h
 */

FUNCTION(fun_index)
{
    int start, end;
    char c, *s, *p, *bp;

    s = fargs[0];
    c = *fargs[1];
    start = atoi(fargs[2]);
    end = atoi(fargs[3]);
    *buff = '\0';
    bp = buff;

    if ((start < 1) || (end < 1) || (*s == '\0'))
	return;
    if (c == '\0')
	c = ' ';

    /* move s to point to the start of the item we want */

    start--;
    while (start && s && *s) {
	if ((s = (char *) index(s, c)) != NULL)
	    s++;
	start--;
    }

    /* skip over just spaces, not tabs or newlines, since people may
     * MUSHcode strings like "%r%tAmberyl %r%tMoonchilde %r%tEvinar"
     */

    while (s && (*s == ' '))
	s++;
    if (!s || !*s)
	return;

    /* figure out where to end the string */

    p = s;
    while (end && p && *p) {
	if ((p = (char *) index(p, c)) != NULL) {
	    if (--end == 0) {
		do {
		    p--;
		} while ((*p == ' ') && (p > s));
		*(++p) = '\0';
		safe_str(s, buff, &bp);
		return;
	    } else {
		p++;
	    }
	}
    }

    /* if we've gotten this far, we've run off the end of the string */

    safe_str(s, buff, &bp);
}


FUNCTION(fun_cat)
{
    int i;
    char *bp;

    bp = buff;
    safe_str(fargs[0], buff, &bp);
    for (i = 1; i < nfargs; i++) {
	safe_chr(' ', buff, &bp);
	safe_str(fargs[i], buff, &bp);
    }
}

FUNCTION(fun_version)
{
    strcpy(buff, mudstate.version);
}
FUNCTION(fun_strlen)
{
    ltos(buff, (int) strlen(mudconf.ansi_colors ?
			    strip_ansi(fargs[0]) : fargs[0]));
}

FUNCTION(fun_num)
{
    *buff = '#';
    ltos(&buff[1], match_thing(player, fargs[0]));
}
FUNCTION(fun_gt)
{
    ltos(buff, (aton(fargs[0]) > aton(fargs[1])));
}
FUNCTION(fun_gte)
{
    ltos(buff, (aton(fargs[0]) >= aton(fargs[1])));
}
FUNCTION(fun_lt)
{
    ltos(buff, (aton(fargs[0]) < aton(fargs[1])));
}
FUNCTION(fun_lte)
{
    ltos(buff, (aton(fargs[0]) <= aton(fargs[1])));
}
FUNCTION(fun_eq)
{
    ltos(buff, (aton(fargs[0]) == aton(fargs[1])));
}
FUNCTION(fun_neq)
{
    ltos(buff, (aton(fargs[0]) != aton(fargs[1])));
}

FUNCTION(fun_and)
{
    int i;

    if (nfargs < 2) {
	strcpy(buff, "#-1 TOO FEW ARGUMENTS");
    } else {
	for (i = 0; (i < nfargs) && atoi(fargs[i]); i++)
	    ;
	ltos(buff, i == nfargs);
    }
    return;
}

FUNCTION(fun_or)
{
    int i;

    if (nfargs < 2) {
	strcpy(buff, "#-1 TOO FEW ARGUMENTS");
    } else {
	for (i = 0; (i < nfargs) && !atoi(fargs[i]); i++)
	    ;
	ltos(buff, i != nfargs);
    }
    return;
}

FUNCTION(fun_xor)
{
    int i, val;

    if (nfargs < 2) {
	strcpy(buff, "#-1 TOO FEW ARGUMENTS");
    } else {
	val = atoi(fargs[0]);
	for (i = 1; i < nfargs; i++) {
	    if (val) {
		val = !atoi(fargs[i]);
	    } else {
		val = atoi(fargs[i]);
	    }
	}
	ltos(buff, val ? 1 : 0);
    }
    return;
}

FUNCTION(fun_not)
{
    ltos(buff, !atoi(fargs[0]));
}

/*-------------------------------------------------------------------------
 *  True boolean functions.
 */

FUNCTION(fun_andbool)
{
    int i;

    if (nfargs < 2) {
	strcpy(buff, "#-1 TOO FEW ARGUMENTS");
    } else {
	for (i = 0; (i < nfargs) && xlate(fargs[i]); i++)
	    ;
	ltos(buff, i == nfargs);
    }
    return;
}

FUNCTION(fun_orbool)
{
    int i;

    if (nfargs < 2) {
	strcpy(buff, "#-1 TOO FEW ARGUMENTS");
    } else {
	for (i = 0; (i < nfargs) && !xlate(fargs[i]); i++)
	    ;
	ltos(buff, i != nfargs);
    }
    return;
}

FUNCTION(fun_xorbool)
{
    int i, val;

    if (nfargs < 2) {
	strcpy(buff, "#-1 TOO FEW ARGUMENTS");
    } else {
	val = xlate(fargs[0]);
	for (i = 1; i < nfargs; i++) {
	    if (val) {
		val = !xlate(fargs[i]);
	    } else {
		val = xlate(fargs[i]);
	    }
	}
	ltos(buff, val ? 1 : 0);
    }
    return;
}

FUNCTION(fun_notbool)
{
    ltos(buff, !xlate(fargs[0]));
}

/*-------------------------------------------------------------------------
 * Bitmask functions.
 */

FUNCTION(fun_shl)
{
    ltos(buff, atoi(fargs[0]) << atoi(fargs[1]));
}

FUNCTION(fun_shr)
{
    ltos(buff, atoi(fargs[0]) >> atoi(fargs[1]));
}

FUNCTION(fun_band)
{
    ltos(buff, atoi(fargs[0]) & atoi(fargs[1]));
}

FUNCTION(fun_bor)
{
    ltos(buff, atoi(fargs[0]) | atoi(fargs[1]));
}

FUNCTION(fun_bnand)
{
    ltos(buff, atoi(fargs[0]) & ~(atoi(fargs[1])));
}

/*-------------------------------------------------------------------------
 * More math functions.
 */

FUNCTION(fun_sqrt)
{
    double val;

    val = atof(fargs[0]);
    if (val < 0) {
	strcpy(buff, "#-1 SQUARE ROOT OF NEGATIVE");
    } else if (val == 0) {
	strcpy(buff, "0");
    } else {
	fval(buff, sqrt(val));
    }
}

FUNCTION(fun_add)
{
    NVAL sum;
    int i;

    if (nfargs < 2) {
	strcpy(buff, "#-1 TOO FEW ARGUMENTS");
    } else {
	sum = aton(fargs[0]);
	for (i = 1; i < nfargs; i++) {
	    sum += aton(fargs[i]);
	}
	fval(buff, sum);
    }
    return;
}

FUNCTION(fun_sub)
{
    fval(buff, aton(fargs[0]) - aton(fargs[1]));
}

FUNCTION(fun_mul)
{
    NVAL prod;
    int i;

    if (nfargs < 2) {
	strcpy(buff, "#-1 TOO FEW ARGUMENTS");
    } else {
	prod = aton(fargs[0]);
	for (i = 1; i < nfargs; i++) {
	    prod *= aton(fargs[i]);
	}
	fval(buff, prod);
    }
    return;
}

FUNCTION(fun_floor)
{
    sprintf(buff, "%.0f", floor(atof(fargs[0])));
}
FUNCTION(fun_ceil)
{
    sprintf(buff, "%.0f", ceil(atof(fargs[0])));
}
FUNCTION(fun_round)
{
    const char *fstr;

    switch (atoi(fargs[1])) {
    case 1:
	fstr = "%.1f";
	break;
    case 2:
	fstr = "%.2f";
	break;
    case 3:
	fstr = "%.3f";
	break;
    case 4:
	fstr = "%.4f";
	break;
    case 5:
	fstr = "%.5f";
	break;
    case 6:
	fstr = "%.6f";
	break;
    default:
	fstr = "%.0f";
	break;
    }
    sprintf(buff, fstr, atof(fargs[0]));

    /* Handle bogus result of "-0" from sprintf.  Yay, cclib. */

    if (!strcmp(buff, "-0")) {
	strcpy(buff, "0");
    }
}

FUNCTION(fun_trunc)
{
    int num;

    num = atoi(fargs[0]);
    ltos(buff, num);
}

FUNCTION(fun_div)
{
    int bot;

    bot = atoi(fargs[1]);
    if (bot == 0) {
	strcpy(buff, "#-1 DIVIDE BY ZERO");
    } else {
	ltos(buff, (atoi(fargs[0]) / bot));
    }
}

FUNCTION(fun_fdiv)
{
    double bot;

    bot = atof(fargs[1]);
    if (bot == 0) {
	strcpy(buff, "#-1 DIVIDE BY ZERO");
    } else {
	fval(buff, (atof(fargs[0]) / bot));
    }
}

FUNCTION(fun_mod)
{
    int bot;

    bot = atoi(fargs[1]);
    if (bot == 0)
	bot = 1;
    ltos(buff, atoi(fargs[0]) % bot);
}

FUNCTION(fun_pi)
{
    strcpy(buff, "3.141592654");
}
FUNCTION(fun_e)
{
    strcpy(buff, "2.718281828");
}

FUNCTION(fun_sin)
{
    fval(buff, sin(atof(fargs[0])));
}
FUNCTION(fun_cos)
{
    fval(buff, cos(atof(fargs[0])));
}
FUNCTION(fun_tan)
{
    fval(buff, tan(atof(fargs[0])));
}

FUNCTION(fun_exp)
{
    fval(buff, exp(atof(fargs[0])));
}

FUNCTION(fun_power)
{
    double val1, val2;

    val1 = atof(fargs[0]);
    val2 = atof(fargs[1]);
    if (val1 < 0) {
	strcpy(buff, "#-1 POWER OF NEGATIVE");
    } else {
	fval(buff, pow(val1, val2));
    }
}

FUNCTION(fun_ln)
{
    double val;

    val = atof(fargs[0]);
    if (val > 0)
	fval(buff, log(val));
    else
	strcpy(buff, "#-1 LN OF NEGATIVE OR ZERO");
}

FUNCTION(fun_log)
{
    double val;

    val = atof(fargs[0]);
    if (val > 0) {
	fval(buff, log10(val));
    } else {
	strcpy(buff, "#-1 LOG OF NEGATIVE OR ZERO");
    }
}

FUNCTION(fun_asin)
{
    double val;

    val = atof(fargs[0]);
    if ((val < -1) || (val > 1)) {
	strcpy(buff, "#-1 ASIN ARGUMENT OUT OF RANGE");
    } else {
	fval(buff, asin(val));
    }
}

FUNCTION(fun_acos)
{
    double val;

    val = atof(fargs[0]);
    if ((val < -1) || (val > 1)) {
	strcpy(buff, "#-1 ACOS ARGUMENT OUT OF RANGE");
    } else {
	fval(buff, acos(val));
    }
}

FUNCTION(fun_atan)
{
    fval(buff, atan(atof(fargs[0])));
}

FUNCTION(fun_dist2d)
{
    int d;
    double r;

    d = atoi(fargs[0]) - atoi(fargs[2]);
    r = (double) (d * d);
    d = atoi(fargs[1]) - atoi(fargs[3]);
    r += (double) (d * d);
    d = (int) (sqrt(r) + 0.5);
    ltos(buff, d);
}

FUNCTION(fun_dist3d)
{
    int d;
    double r;

    d = atoi(fargs[0]) - atoi(fargs[3]);
    r = (double) (d * d);
    d = atoi(fargs[1]) - atoi(fargs[4]);
    r += (double) (d * d);
    d = atoi(fargs[2]) - atoi(fargs[5]);
    r += (double) (d * d);
    d = (int) (sqrt(r) + 0.5);
    ltos(buff, d);
}

/* ---------------------------------------------------------------------------
 * Vector functions, where vectors are space-separated numbers.
 *
 * TinyMUX and PennMUSH-derived implementation.
 */

#define MAXDIM 20
#define VADD_F 0
#define VSUB_F 1
#define VMUL_F 2
#define VDOT_F 3
#define VCROSS_F 4

static void handle_vectors(vecarg1, vecarg2, buff, sep, osep, flag)
    char *vecarg1;
    char *vecarg2;
    char *buff;
    char sep;
    char osep;
    int flag;
{
    char *v1[LBUF_SIZE], *v2[LBUF_SIZE];
    char vres[MAXDIM][LBUF_SIZE];
    double scalar;
    int n, m, i;

    /*
     * split the list up, or return if the list is empty 
     */
    if (!vecarg1 || !*vecarg1 || !vecarg2 || !*vecarg2) {
	return;
    }
    n = list2arr(v1, LBUF_SIZE, vecarg1, sep);
    m = list2arr(v2, LBUF_SIZE, vecarg2, sep);

    if (n != m) {
	strcpy(buff, "#-1 VECTORS MUST BE SAME DIMENSIONS");
	return;
    }
    if (n > MAXDIM) {
	strcpy(buff, "#-1 TOO MANY DIMENSIONS ON VECTORS");
	return;
    }

    switch (flag) {
	case VADD_F:
	    for (i = 0; i < n; i++) {
		fval(vres[i], atof(v1[i]) + atof(v2[i]));
		v1[i] = (char *) vres[i];
	    }
	    arr2list(v1, n, buff, osep);
	    return;
	    /* NOTREACHED */
	case VSUB_F:
	    for (i = 0; i < n; i++) {
		fval(vres[i], atof(v1[i]) - atof(v2[i]));
		v1[i] = (char *) vres[i];
	    }
	    arr2list(v1, n, buff, osep);
	    return;
	    /* NOTREACHED */
	case VMUL_F:
	    /* if n or m is 1, this is scalar multiplication.
	     * otherwise, multiply elementwise.
	     */
	    if (n == 1) {
		scalar = atof(v1[0]);
		for (i = 0; i < m; i++) {
		    fval(vres[i], atof(v2[i]) * scalar);
		    v1[i] = (char *) vres[i];
		}
		n = m;
	    } else if (m == 1) {
		scalar = atof(v2[0]);
		for (i = 0; i < n; i++) {
		    fval(vres[i], atof(v1[i]) * scalar);
		    v1[i] = (char *) vres[i];
		}
	    } else {
		/* vector elementwise product.
		 *
		 * Note this is a departure from TinyMUX, but an imitation
		 * of the PennMUSH behavior: the documentation in Penn
		 * claims it's a dot product, but the actual behavior
		 * isn't. We implement dot product separately!
		 */
		for (i = 0; i < n; i++) {
		    fval(vres[i], atof(v1[i]) * atof(v2[i]));
		    v1[i] = (char *) vres[i];
		}
	    }
	    arr2list(v1, n, buff, osep);
	    return;
	    /* NOTREACHED */
	case VDOT_F:
	    scalar = 0;
	    for (i = 0; i < n; i++) {
		scalar += atof(v1[i]) * atof(v2[i]);
		v1[i] = (char *) vres[i];
	    }
	    fval(buff, scalar);
	    return;
	    /* NOTREACHED */
	default:
	    /* If we reached this, we're in trouble. */
	    strcpy(buff, "#-1 UNIMPLEMENTED");
    }
}

FUNCTION(fun_vadd)
{
    char sep, osep;

    svarargs_preamble("VADD", 4);
    handle_vectors(fargs[0], fargs[1], buff, sep, osep, VADD_F);
}

FUNCTION(fun_vsub)
{
    char sep, osep;

    svarargs_preamble("VSUB", 4);
    handle_vectors(fargs[0], fargs[1], buff, sep, osep, VSUB_F);
}

FUNCTION(fun_vmul)
{
    char sep, osep;

    svarargs_preamble("VMUL", 4);
    handle_vectors(fargs[0], fargs[1], buff, sep, osep, VMUL_F);
}

FUNCTION(fun_vdot)
{
    /* dot product: (a,b,c) . (d,e,f) = ad + be + cf
     * 
     * no cross product implementation yet: it would be
     * (a,b,c) x (d,e,f) = (bf - ce, cd - af, ae - bd)
     */

    char sep, osep;

    svarargs_preamble("VDOT", 4);
    handle_vectors(fargs[0], fargs[1], buff, sep, osep, VDOT_F);
}

FUNCTION(fun_vmag)
{
    char *v1[LBUF_SIZE];
    int n, i;
    double tmp, res = 0;
    char sep;

    varargs_preamble("VMAG", 2);

    /*
     * split the list up, or return if the list is empty 
     */
    if (!fargs[0] || !*fargs[0]) {
	return;
    }
    n = list2arr(v1, LBUF_SIZE, fargs[0], sep);

    if (n > MAXDIM) {
	strcpy(buff, "#-1 TOO MANY DIMENSIONS ON VECTORS");
	return;
    }
    /*
     * calculate the magnitude 
     */
    for (i = 0; i < n; i++) {
	tmp = atof(v1[i]);
	res += tmp * tmp;
    }

    if (res > 0)
	fval(buff, sqrt(res));
    else
	strcpy(buff, "0");
}

FUNCTION(fun_vunit)
{
    char *v1[LBUF_SIZE];
    char vres[MAXDIM][LBUF_SIZE];
    int n, i;
    double tmp, res = 0;
    char sep;

    varargs_preamble("VUNIT", 2);

    /*
     * split the list up, or return if the list is empty 
     */
    if (!fargs[0] || !*fargs[0]) {
	return;
    }
    n = list2arr(v1, LBUF_SIZE, fargs[0], sep);

    if (n > MAXDIM) {
	strcpy(buff, "#-1 TOO MANY DIMENSIONS ON VECTORS");
	return;
    }
    /*
     * calculate the magnitude 
     */
    for (i = 0; i < n; i++) {
	tmp = atof(v1[i]);
	res += tmp * tmp;
    }

    if (res <= 0) {
	strcpy(buff, "#-1 CAN'T MAKE UNIT VECTOR FROM ZERO-LENGTH VECTOR");
	return;
    }
    for (i = 0; i < n; i++) {
	fval(vres[i], atof(v1[i]) / sqrt(res));
	v1[i] = (char *) vres[i];
    }

    arr2list(v1, n, buff, sep);
}

FUNCTION(fun_vdim)
{
    char sep;

    if (fargs == 0)
	strcpy(buff, "0");
    else {
	varargs_preamble("VDIM", 2);
	ltos(buff, countwords(fargs[0], sep));
    }
}

/* ---------------------------------------------------------------------------
 * fun_comp: string compare.
 */

FUNCTION(fun_comp)
{
    int x;

    x = strcmp(fargs[0], fargs[1]);
    if (x > 0)
	strcpy(buff, "1");
    else if (x < 0)
	strcpy(buff, "-1");
    else
	strcpy(buff, "0");
}

/* ---------------------------------------------------------------------------
 * fun_xcon: Return a partial list of contents of an object, starting from
 *           a specified element in the list and copying a specified number
 *           of elements.
 */

FUNCTION(fun_xcon)
{
    dbref thing, it;
    char *bufp, *tbuf;
    int i, first, last;

    it = match_thing(player, fargs[0]);
    *buff = '\0';
    bufp = buff;
    if ((it != NOTHING) && (Has_contents(it)) &&
	(Examinable(player, it) || (Location(player) == it) ||
	 (it == cause))) {
	first = atoi(fargs[1]);
	last = atoi(fargs[2]);
	if ((first > 0) && (last > 0)) {

	    tbuf = alloc_sbuf("fun_xcon");

	    /* Move to the first object that we want */
	    for (thing = Contents(it), i = 1;
		 (i < first) && (thing != NOTHING) && (Next(thing) != thing);
		 thing = Next(thing), i++)
		;

	    /* Grab objects until we reach the last one we want */
	    for (i = 0;
		 (i < last) && (thing != NOTHING) && (Next(thing) != thing);
		 thing = Next(thing), i++) {
		if (*buff) {
		    tbuf[0] = ' ';
		    tbuf[1] = '#';
		    ltos(&tbuf[2], thing);
		} else {
		    *tbuf = '#';
		    ltos(&tbuf[1], thing);
		}
		safe_str(tbuf, buff, &bufp);
	    }

	    free_sbuf(tbuf);
	    *bufp = '\0';
	}
    } else
	strcpy(buff, "#-1");
}
		 

/* ---------------------------------------------------------------------------
 * fun_lcon: Return a list of contents.
 */

FUNCTION(fun_lcon)
{
    dbref thing, it;
    char *bufp, *tbuf;

    it = match_thing(player, fargs[0]);
    *buff = '\0';
    bufp = buff;
    if ((it != NOTHING) &&
	(Has_contents(it)) &&
	(Examinable(player, it) ||
	 (Location(player) == it) ||
	 (it == cause))) {
	tbuf = alloc_sbuf("fun_lcon");
	DOLIST(thing, Contents(it)) {
	    if (*buff) {
		tbuf[0] = ' ';
	        tbuf[1] = '#';
		ltos(&tbuf[2], thing);
	    } else {
		*tbuf = '#';
		ltos(&tbuf[1], thing);
	    }
	    safe_str(tbuf, buff, &bufp);
	}
	free_sbuf(tbuf);
	*bufp = '\0';
    } else
	strcpy(buff, "#-1");
}

/* ---------------------------------------------------------------------------
 * fun_lexits: Return a list of exits.
 */

FUNCTION(fun_lexits)
{
    dbref thing, it, parent;
    char *bufp, *tbuf;
    int exam, lev;

    *buff = '\0';
    bufp = buff;
    it = match_thing(player, fargs[0]);

    if (!Good_obj(it) || !Has_exits(it)) {
	strcpy(buff, "#-1");
	return;
    }
    exam = Examinable(player, it);
    if (!exam && (where_is(player) != it) && (it != cause)) {
	strcpy(buff, "#-1");
	return;
    }
    tbuf = alloc_sbuf("fun_lexits");

    /* Return info for all parent levels */

    ITER_PARENTS(it, parent, lev) {

	/* Look for exits at each level */

	if (!Has_exits(parent))
	    continue;
	for (thing = next_exit(player, Exits(parent), exam);
	     thing != NOTHING;
	     thing = next_exit(player, Next(thing), exam)) {
	    if (*buff) {
		tbuf[0] = ' ';
		tbuf[1] = '#';
		ltos(&tbuf[2], thing);
	    } else {
		*tbuf = '#';
		ltos(&tbuf[1], thing);
	    }
	    safe_str(tbuf, buff, &bufp);
	}
    }
    free_sbuf(tbuf);
    *bufp = '\0';
    return;
}

/* --------------------------------------------------------------------------
 * fun_home: Return an object's home
 */

FUNCTION(fun_home)
{
    dbref it;

    it = match_thing(player, fargs[0]);
    if (!Good_obj(it) || !Examinable(player, it))
	strcpy(buff, "#-1");
    else if (Has_home(it)) {
	*buff = '#';
	ltos(&buff[1], Home(it));
    } else if (Has_dropto(it)) {
	*buff = '#';
	ltos(&buff[1], Dropto(it));
    } else if (isExit(it)) {
	*buff = '#';
	ltos(&buff[1], where_is(it));
    } else
	strcpy(buff, "#-1");
    return;
}

/* ---------------------------------------------------------------------------
 * fun_money: Return an object's value
 */

FUNCTION(fun_money)
{
    dbref it;

    it = match_thing(player, fargs[0]);
    if ((it == NOTHING) || !Examinable(player, it))
	strcpy(buff, "#-1");
    else
	ltos(buff, Pennies(it));
}

/* ---------------------------------------------------------------------------
 * fun_pos: Find a word in a string */

FUNCTION(fun_pos)
{
    int i = 1;
    char *s, *t, *u;

    s = fargs[1];
    while (*s) {
	u = s;
	t = fargs[0];
	while (*t && *t == *u)
	    ++t, ++u;
	if (*t == '\0') {
	    ltos(buff, i);
	    return;
	}
	++i, ++s;
    }
    strcpy(buff, "#-1");
    return;
}

/* ---------------------------------------------------------------------------
 * fun_lpos: Find all occurrences of a character in a string, and return
 * a space-separated list of the positions, starting at 0. i.e.,
 * lpos(a-bc-def-g,-) ==> 1 4 8
 */

FUNCTION(fun_lpos)
{
    char *s, *bp;
    char c, tbuf[8];
    int i;

    *buff = '\0';

    if (!fargs[0] || !*fargs[0])
	return;

    c = (char) *(fargs[1]);
    if (!c)
	c = ' ';

    for (i = 0, s = fargs[0], bp = buff; *s; i++, s++) {
	if (*s == c) {
	    if (bp != buff) {
		safe_chr(' ', buff, &bp);
	    }
	    ltos(tbuf, i);
	    safe_str(tbuf, buff, &bp);
	}
    }

    *bp = '\0';
}


/* ---------------------------------------------------------------------------
 * ldelete: Remove a word from a string by place
 *  ldelete(<list>,<position>[,<separator>])
 *
 * insert: insert a word into a string by place
 *  insert(<list>,<position>,<new item> [,<separator>])
 *
 * replace: replace a word into a string by place
 *  replace(<list>,<position>,<new item>[,<separator>])
 */

#define	IF_DELETE	0
#define	IF_REPLACE	1
#define	IF_INSERT	2

static void 
do_itemfuns(buff, str, el, word, sep, flag)
    char *buff, *str, *word, sep;
    int el, flag;
{
    int ct, overrun;
    char *sptr, *iptr, *eptr, *bp;
    char nullb;

    /* If passed a null string return an empty string, except that we
	 * are allowed to append to a null string.
	 */

    if ((!str || !*str) && ((flag != IF_INSERT) || (el != 1))) {
	*buff = '\0';
	return;
    }
    /* we can't fiddle with anything before the first position */

    if (el < 1) {
	strcpy(buff, str);
	return;
    }
    /* Split the list up into 'before', 'target', and 'after' chunks
     * pointed to by sptr, iptr, and eptr respectively.
     */

    nullb = '\0';
    if (el == 1) {
	/* No 'before' portion, just split off element 1 */

	sptr = NULL;
	if (!str || !*str) {
	    eptr = NULL;
	    iptr = NULL;
	} else {
	    eptr = trim_space_sep(str, sep);
	    iptr = split_token(&eptr, sep);
	}
    } else {
	/* Break off 'before' portion */

	sptr = eptr = trim_space_sep(str, sep);
	overrun = 1;
	for (ct = el; ct > 2 && eptr; eptr = next_token(eptr, sep), ct--);
	if (eptr) {
	    overrun = 0;
	    iptr = split_token(&eptr, sep);
	}
	/* If we didn't make it to the target element, just return
		 * the string.  Insert is allowed to continue if we are
		 * exactly at the end of the string, but replace and delete
		 * are not.
	 */

	if (!(eptr || ((flag == IF_INSERT) && !overrun))) {
	    strcpy(buff, str);
	    return;
	}
	/* Split the 'target' word from the 'after' portion. */

	if (eptr)
	    iptr = split_token(&eptr, sep);
	else
	    iptr = NULL;
    }

    bp = buff;
    switch (flag) {
    case IF_DELETE:		/* deletion */
	if (sptr) {
	    safe_str(sptr, buff, &bp);
	    if (eptr) {
		safe_chr(sep, buff, &bp);
	    }
	}
	if (eptr) {
	    safe_str(eptr, buff, &bp);
	}
	break;
    case IF_REPLACE:		/* replacing */
	if (sptr) {
	    safe_str(sptr, buff, &bp);
	    safe_chr(sep, buff, &bp);
	}
	safe_str(word, buff, &bp);
	if (eptr) {
	    safe_chr(sep, buff, &bp);
	    safe_str(eptr, buff, &bp);
	}
	break;
    case IF_INSERT:		/* insertion */
	if (sptr) {
	    safe_str(sptr, buff, &bp);
	    safe_chr(sep, buff, &bp);
	}
	safe_str(word, buff, &bp);
	if (iptr) {
	    safe_chr(sep, buff, &bp);
	    safe_str(iptr, buff, &bp);
	}
	if (eptr) {
	    safe_chr(sep, buff, &bp);
	    safe_str(eptr, buff, &bp);
	}
	break;
    }
    *bp = '\0';
}


FUNCTION(fun_ldelete)
{				/* delete a word at position X of a list */
    char sep;

    varargs_preamble("LDELETE", 3);
    do_itemfuns(buff, fargs[0], atoi(fargs[1]), NULL, sep, IF_DELETE);
}

FUNCTION(fun_replace)
{				/* replace a word at position X of a list */
    char sep;

    varargs_preamble("REPLACE", 4);
    do_itemfuns(buff, fargs[0], atoi(fargs[1]), fargs[2], sep, IF_REPLACE);
}

FUNCTION(fun_insert)
{				/* insert a word at position X of a list */
    char sep;

    varargs_preamble("INSERT", 4);
    do_itemfuns(buff, fargs[0], atoi(fargs[1]), fargs[2], sep, IF_INSERT);
}

/* ---------------------------------------------------------------------------
 * fun_remove: Remove a word from a string
 */

FUNCTION(fun_remove)
{
    char *s, *sp, *word, *bp;
    char sep;
    int found;

    varargs_preamble("REMOVE", 3);
    if (index(fargs[1], sep)) {
	strcpy(buff, "#-1 CAN ONLY DELETE ONE ELEMENT");
	return;
    }
    s = fargs[0];
    word = fargs[1];

    /* Walk through the string copying words until (if ever) we get to
     * one that matches the target word.
     */

    bp = buff;
    sp = s;
    found = 0;
    while (s) {
	sp = split_token(&s, sep);
	if (found || strcmp(sp, word)) {
	    if (bp != buff)
		safe_chr(sep, buff, &bp);
	    safe_str(sp, buff, &bp);
	} else {
	    found = 1;
	}
    }
    *bp = '\0';
}

/* ---------------------------------------------------------------------------
 * fun_member: Is a word in a string
 */

FUNCTION(fun_member)
{
    int wcount;
    char *r, *s, sep;

    varargs_preamble("MEMBER", 3);
    wcount = 1;
    s = trim_space_sep(fargs[0], sep);
    do {
	r = split_token(&s, sep);
	if (!strcmp(fargs[1], r)) {
	    ltos(buff, wcount);
	    return;
	}
	wcount++;
    } while (s);
    strcpy(buff, "0");
}

/* ---------------------------------------------------------------------------
 * Functions that tinker with clobbering formatting.
 *
 * fun_secure, fun_escape: escape [, ], %, \, and the beginning of the string.
 * fun_lit: returns a literal string.
 * fun_translate: strips or converts raw sequences.
 */

FUNCTION(fun_secure)
{
    char *s, *d;

    s = fargs[0];
    d = buff;
    while (*s) {
	switch (*s) {
	case '%':
	case '$':
	case '\\':
	case '[':
	case ']':
	case '(':
	case ')':
	case '{':
	case '}':
	case ',':
	case ';':
	    safe_chr(' ', buff, &d);
	    break;
	default:
	    safe_chr(*s, buff, &d);
	}
	s++;
    }
    *d = '\0';
}

FUNCTION(fun_escape)
{
    char *s, *d;

    s = fargs[0];
    d = buff;
    while (*s) {
	switch (*s) {
	case '%':
	case '\\':
	case '[':
	case ']':
	case '{':
	case '}':
	case ';':
	    safe_chr('\\', buff, &d);
	default:
	    if (d == buff) {
		safe_chr('\\', buff, &d);
	    }
	    safe_chr(*s, buff, &d);
	}
	s++;
    }
    *d = '\0';
}

FUNCTION(fun_lit)
{
    strcpy(buff, fargs[0]);	/* just copy the unevaluated argument */
}


FUNCTION(fun_translate)
{
    int type = 0;

    if (fargs[0] && fargs[1]) {
	if (*fargs[1] && ((*fargs[1] == 's') || (*fargs[1] == '0')))
	    type = 0;
	else if (*fargs[1] && ((*fargs[1] == 'p') || (*fargs[1] == '1')))
	    type = 1;
	
	strcpy(buff, translate_string(fargs[0], type));
    }
}

/*---------------------------------------------------------------------------
 * Pueblo HTML-related functions.
 */

#ifdef PUEBLO_SUPPORT

FUNCTION(fun_html_escape)
{
    *buff = '\0';
    html_escape(fargs[0], buff, 0);
}

FUNCTION(fun_html_unescape)
{
    const char *msg_orig;
    char *temp;
    int ret = 0;
    char **destp;

    temp = buff;
    destp = &temp;

    *buff = '\0';
    for (msg_orig = fargs[0]; msg_orig && *msg_orig && !ret; msg_orig++) {
	switch (*msg_orig) {
	  case '&':
	    if (!strncmp(msg_orig, "&quot;", 6)) {
		ret = safe_chr_fn('\"', buff, destp);
		msg_orig += 5;
	    } else if (!strncmp(msg_orig, "&lt;", 4)) {
		ret = safe_chr_fn('<', buff, destp);
		msg_orig += 3;
	    } else if (!strncmp(msg_orig, "&gt;", 4)) {
		ret = safe_chr_fn('>', buff, destp);
		msg_orig += 3;
	    } else if (!strncmp(msg_orig, "&amp;", 5)) {
		ret = safe_chr_fn('&', buff, destp);
		msg_orig += 4;
	    }
	    break;
	  default:
	    ret = safe_chr_fn(*msg_orig, buff, destp);
	    break;
	}
    }
}

FUNCTION(fun_url_escape)
{
    /* These are the characters which are converted to %<hex> */
    char *escaped_chars = "<>#%{}|\\^~[]';/?:@=&\"+";
    const char *msg_orig;
    char *temp;
    int ret = 0;
    char **destp;
    char tbuf[10];

    temp = buff;
    destp = &temp;

    *buff = '\0';
    for (msg_orig = fargs[0]; msg_orig && *msg_orig && !ret; msg_orig++) {
	if (index(escaped_chars, *msg_orig)) {
	    sprintf(tbuf, "%%%2x", *msg_orig);
	    ret = safe_str(tbuf, buff, destp);
	} else if (*msg_orig == ' ') {
	    ret = safe_chr_fn('+', buff, destp);
	} else{
	    ret = safe_chr_fn(*msg_orig, buff, destp);
	}
    }
}

FUNCTION(fun_url_unescape)
{
    const char *msg_orig;
    char *temp;
    int ret = 0;
    char **destp;
    unsigned int tempchar;
    char tempstr[10];

    temp = buff;
    destp = &temp;

    *buff = '\0';
    for (msg_orig = fargs[0]; msg_orig && *msg_orig && !ret;) {
	switch (*msg_orig) {
	  case '+':
	    ret = safe_chr_fn(' ', buff, destp);
	    msg_orig++;
	    break;
	  case '%':
	    strncpy(tempstr, msg_orig+1, 2);
	    tempstr[2] = '\0';
	    if (sscanf(tempstr, "%x", &tempchar) == 1)
		ret = safe_chr_fn(tempchar, buff, destp);
	    if (*msg_orig)
		msg_orig++;	/* Skip the '%' */
	    if (*msg_orig) 	/* Skip the 1st hex character. */
		msg_orig++;
	    if (*msg_orig)	/* Skip the 2nd hex character. */
		msg_orig++;
	    break;
	  default:
	    ret = safe_chr_fn(*msg_orig, buff, destp);
	    msg_orig++;
	    break;
	}
    }
    return;
}
#endif /* PUEBLO_SUPPORT */

/*---------------------------------------------------------------------------
 * Take a character position and return which word that char is in.
 * wordpos(<string>, <charpos>)
 */

FUNCTION(fun_wordpos)
{
    int charpos, i;
    char *cp, *tp, *xp, sep;

    varargs_preamble("WORDPOS", 3);

    charpos = atoi(fargs[1]);
    cp = fargs[0];
    if ((charpos > 0) && (charpos <= strlen(cp))) {
	tp = &(cp[charpos - 1]);
	cp = trim_space_sep(cp, sep);
	xp = split_token(&cp, sep);
	for (i = 1; xp; i++) {
	    if (tp < (xp + strlen(xp)))
		break;
	    xp = split_token(&cp, sep);
	}
	ltos(buff, i);
	return;
    }
    strcpy(buff, "#-1");
    return;
}

FUNCTION(fun_type)
{
    dbref it;

    it = match_thing(player, fargs[0]);
    if (!Good_obj(it)) {
	strcpy(buff, "#-1 NOT FOUND");
	return;
    }
    switch (Typeof(it)) {
    case TYPE_ROOM:
	strcpy(buff, "ROOM");
	break;
    case TYPE_EXIT:
	strcpy(buff, "EXIT");
	break;
    case TYPE_PLAYER:
	strcpy(buff, "PLAYER");
	break;
    case TYPE_THING:
	strcpy(buff, "THING");
	break;
    default:
	strcpy(buff, "#-1 ILLEGAL TYPE");
    }
    return;
}

/*---------------------------------------------------------------------------
 * fun_hasflag:  plus auxiliary function atr_has_flag.
 */

static int 
atr_has_flag(player, thing, attr, aowner, aflags, flagname)
    dbref player, thing;
    ATTR *attr;
    int aowner, aflags;
    char *flagname;
{
    if (!See_attr(player, thing, attr, aowner, aflags))
	return 0;
    else {
	if (string_prefix("dark", flagname))
	    return (aflags & AF_DARK);
	else if (string_prefix("wizard", flagname))
	    return (aflags & AF_WIZARD);
	else if (string_prefix("hidden", flagname))
	    return (aflags & AF_MDARK);
	else if (string_prefix("html", flagname))
	    return (aflags & AF_HTML);
	else if (string_prefix("locked", flagname))
	    return (aflags & AF_LOCK);
	else if (string_prefix("no_command", flagname))
	    return (aflags & AF_NOPROG);
	else if (string_prefix("no_parse", flagname))
	    return (aflags & AF_NOPARSE);
	else if (string_prefix("regexp", flagname))
	    return (aflags & AF_REGEXP);
	else if (string_prefix("god", flagname))
	    return (aflags & AF_GOD);
	else if (string_prefix("visual", flagname))
	    return (aflags & AF_VISUAL);
	else if (string_prefix("no_inherit", flagname))
	    return (aflags & AF_PRIVATE);
	else
	    return 0;
    }
}

FUNCTION(fun_hasflag)
{
    dbref it, aowner;
    int atr, aflags;
    ATTR *ap;

    if (parse_attrib(player, fargs[0], &it, &atr)) {
	if (atr == NOTHING) {
	    strcpy(buff, "#-1 NOT FOUND");
	} else {
	    ap = atr_num(atr);
	    atr_pget_info(it, atr, &aowner, &aflags);
	    if (atr_has_flag(player, it, ap, aowner, aflags,
			     fargs[1]))
		strcpy(buff, "1");
	    else
		strcpy(buff, "0");
	}
    } else {
	it = match_thing(player, fargs[0]);
	if (!Good_obj(it)) {
	    strcpy(buff, "#-1 NOT FOUND");
	    return;
	}
	if (mudconf.pub_flags || Examinable(player, it) || (it == cause)) {
	    if (has_flag(player, it, fargs[1]))
		strcpy(buff, "1");
	    else
		strcpy(buff, "0");
	} else {
	    strcpy(buff, "#-1 PERMISSION DENIED");
	}
    }
}

/*---------------------------------------------------------------------------
 * fun_orflags, fun_andflags:  check multiple flags at the same time.
 * Based off the PennMUSH 1.50 code (and the TinyMUX derivative of it).
 */

static int handle_flaglists(player, cause, name, fstr, type)
     dbref player;
     dbref cause;
     char *name;
     char *fstr;
     int type;			/* 0 for orflags, 1 for andflags */
{
    char *s, flagletter[2];
    FLAGSET fset;
    FLAG p_type;
    int negate, temp, ret;

    dbref it = match_thing(player, name);

    if (it == NOTHING)
	return 0;
    if (! (mudconf.pub_flags || Examinable(player, it) || (it == cause)))
	return 0;

    ret = type;
    negate = temp = 0;

    for (s = fstr; *s; s++) {

	/* Check for a negation sign. If we find it, we note it and
	 * increment the pointer to the next character.
	 */

	if (*s == '!') {
	    negate = 1;
	    s++;
	} else {
	    negate = 0;
	}
	if (!*s)
	    return 0;

	flagletter[0] = *s;
	flagletter[1] = '\0';
	if (!convert_flags(player, flagletter, &fset, &p_type)) {

	    /* Either we got a '!' that wasn't followed by a letter, or
	     * we couldn't find that flag. For AND, since we've failed
	     * a check, we can return false. Otherwise we just go on.
	     */

	    if (type == 1)
		return 0;
	    else
		continue;
	    
	} else {

	    /* Does the object have this flag? */

	    if ((Flags(it) & fset.word1) || (Flags2(it) & fset.word2) ||
		(Typeof(it) == p_type)) {
		/* don't show Connected on Dark wizards to mortals. */
		if (isPlayer(it) &&
		    (fset.word2 == CONNECTED) &&
		    ((Flags(it) & (WIZARD | DARK)) == (WIZARD | DARK)) &&
		    !Wizard(player))
		    temp = 0;
		else 
		    temp = 1;
	    } else {
		temp = 0;
	    }

	    if ((type == 1) && ((negate && temp) || (!negate && !temp))) {

		/* Too bad there's no NXOR function...
		 * At this point we've either got a flag and we don't want
		 * it, or we don't have a flag and nwe want it. Since it's
		 * AND, we return false.
		 */
		return 0;

	    } else if ((type == 0) && 
		       ((!negate && temp) || (negate && !temp))) {

		/* We've found something we want, in an OR. Since we only
		 * need one thing to be true in order for the entire 
		 * expression to be true, we can return true now.
		 */
		return 1;
	    }
	    /* Otherwise we don't need to do anything. */
	}
    }
    return (ret);
}

FUNCTION(fun_orflags)
{
    ltos(buff, handle_flaglists(player, cause, fargs[0], fargs[1], 0));
}

FUNCTION(fun_andflags)
{
    ltos(buff, handle_flaglists(player, cause, fargs[0], fargs[1], 1));
}

/*---------------------------------------------------------------------------
 */

FUNCTION(fun_delete)
{
    char *s, *d;
    unsigned int i, start, nchars, len;

    s = fargs[0];
    start = atoi(fargs[1]);
    nchars = atoi(fargs[2]);
    len = strlen(s);
    if ((start >= len) || (nchars <= 0)) {
	strcpy(buff, s);
	return;
    }
    d = buff;
    for (i = 0; i < start; i++)
	*d++ = (*s++);
    if ( ((i + nchars) < len) && ((i + nchars) > 0) ) {
	s += nchars;
	while ((*d++ = *s++));
    } else {
	*d = '\0';
    }
}

FUNCTION(fun_lock)
{
    dbref it, aowner;
    int aflags;
    char *tbuf;
    ATTR *attr;
    struct boolexp *bool;

    /* Parse the argument into obj + lock */

    if (!get_obj_and_lock(player, fargs[0], &it, &attr, buff))
	return;

    /* Get the attribute and decode it if we can read it */

    tbuf = atr_get(it, attr->number, &aowner, &aflags);
    if (!Read_attr(player, it, attr, aowner, aflags)) {
	*buff = '\0';
	free_lbuf(tbuf);
    } else {
	bool = parse_boolexp(player, tbuf, 1);
	free_lbuf(tbuf);
	tbuf = (char *) unparse_boolexp_function(player, bool);
	free_boolexp(bool);
	strcpy(buff, tbuf);
    }
}

FUNCTION(fun_elock)
{
    dbref it, victim, aowner;
    int aflags;
    char *tbuf;
    ATTR *attr;
    struct boolexp *bool;

    /* Parse lock supplier into obj + lock */

    if (!get_obj_and_lock(player, fargs[0], &it, &attr, buff))
	return;

    /* Get the victim and ensure we can do it */

    victim = match_thing(player, fargs[1]);
    if (!Good_obj(victim)) {
	strcpy(buff, "#-1 NOT FOUND");
    } else if (!nearby_or_control(player, victim) &&
	       !nearby_or_control(player, it)) {
	strcpy(buff, "#-1 TOO FAR AWAY");
    } else {
	tbuf = atr_get(it, attr->number, &aowner, &aflags);
	if ((aflags & AF_LOCK) ||
	    Read_attr(player, it, attr, aowner, aflags)) {
	    bool = parse_boolexp(player, tbuf, 1);
	    ltos(buff, eval_boolexp(victim, it, it,
					     bool));
	    free_boolexp(bool);
	} else {
	    strcpy(buff, "0");
	}
	free_lbuf(tbuf);
    }
}

/* ---------------------------------------------------------------------------
 * fun_lwho: Return list of connected users.
 */

FUNCTION(fun_lwho)
{
    make_ulist(player, buff);
}

/* ---------------------------------------------------------------------------
 * fun_ports: Returns a list of ports for a user.
 */

FUNCTION(fun_ports)
{
    dbref target;

    if (!Wizard(player)) {
	*buff = '\0';
	return;
    }
    target = lookup_player(player, fargs[0], 1);
    if (!Good_obj(target) || !Connected(target)) {
	*buff = '\0';
	return;
    }
    make_portlist(player, target, buff);
}

/* ---------------------------------------------------------------------------
 * fun_programmer: Returns the dbref or #-1 of an object in a @program.
 */

FUNCTION(fun_programmer)
{
    dbref target;

    target = lookup_player(player, fargs[0], 1);
    if (!Good_obj(target) || !Connected(target) ||
	!Examinable(player, target)) {
	strcpy(buff, "#-1");
	return;
    }

    *buff = '#';
    ltos(&buff[1], get_programmer(target));
}

/* ---------------------------------------------------------------------------
 * fun_nearby: Return whether or not obj1 is near obj2.
 */

FUNCTION(fun_nearby)
{
    dbref obj1, obj2;

    obj1 = match_thing(player, fargs[0]);
    obj2 = match_thing(player, fargs[1]);
    if (!(nearby_or_control(player, obj1) ||
	  nearby_or_control(player, obj2)))
	strcpy(buff, "0");
    else if (nearby(obj1, obj2))
	strcpy(buff, "1");
    else
	strcpy(buff, "0");
}

/* ---------------------------------------------------------------------------
 * fun_obj, fun_poss, and fun_subj: perform pronoun sub for object.
 */

static void 
process_sex(player, what, token, buff)
    dbref player;
    char *what, *buff;
    const char *token;
{
    dbref it;
    char *tbuff;

    it = match_thing(player, what);
    if (!Good_obj(it) ||
	(!isPlayer(it) && !nearby_or_control(player, it))) {
	strcpy(buff, "#-1 NO MATCH");
    } else {
	tbuff = exec(it, it, 0, (char *) token, (char **) NULL, 0);
	strcpy(buff, tbuff);
	free_lbuf(tbuff);
    }
}

FUNCTION(fun_obj)
{
    process_sex(player, fargs[0], "%o", buff);
}
FUNCTION(fun_poss)
{
    process_sex(player, fargs[0], "%p", buff);
}
FUNCTION(fun_subj)
{
    process_sex(player, fargs[0], "%s", buff);
}
FUNCTION(fun_aposs)
{
    process_sex(player, fargs[0], "%a", buff);
}

/* ---------------------------------------------------------------------------
 * fun_mudname: Return the name of the mud.
 */

FUNCTION(fun_mudname)
{
    char *bp;

    bp = buff;
    safe_str(mudconf.mud_name, buff, &bp);
    return;
}

/* ---------------------------------------------------------------------------
 * fun_lcstr, fun_ucstr, fun_capstr: Lowercase, uppercase, or capitalize str.
 */

FUNCTION(fun_lcstr)
{
    char *ap, *bp;

    ap = fargs[0];
    bp = buff;
    while (*ap) {
	*bp = ToLower(*ap);
	ap++;
	bp++;
    }
    *bp = '\0';
}

FUNCTION(fun_ucstr)
{
    char *ap, *bp;

    ap = fargs[0];
    bp = buff;
    while (*ap) {
	*bp = ToUpper(*ap);
	ap++;
	bp++;
    }
    *bp = '\0';
}

FUNCTION(fun_capstr)
{
    strcpy(buff, fargs[0]);
    *buff = ToUpper(*buff);
}

/* ---------------------------------------------------------------------------
 * fun_lnum: Return a list of numbers.
 */

FUNCTION(fun_lnum)
{
    char *bp, tbuf[10], sep;
    int bot, top, over, i;

    *buff = '\0';
    
    if (nfargs == 0) {
	return;
    }

    mvarargs_preamble("LNUM", 1, 3);

    if (nfargs >= 2) {
	bot = atoi(fargs[0]);
	top = atoi(fargs[1]);
    } else {
	bot = 0;
	top = atoi(fargs[0]);
	if (top-- < 1)		/* still want to generate if arg is 1 */
	    return;
    }

    bp = buff;
    over = 0;

    if (top == bot) {
	ltos(buff, bot);
	return;
    } else if (top > bot) {
	for (i = bot; (i <= top) && !over; i++) {
	    if (bp != buff) {
		safe_chr(sep, buff, &bp);
	    }
	    ltos(tbuf, i);
	    over = safe_str(tbuf, buff, &bp);
	}
    } else {
	for (i = bot; (i >= top) && !over; i--) {
	    if (bp != buff) {
		safe_chr(sep, buff, &bp);
	    }
	    ltos(tbuf, i);
	    over = safe_str(tbuf, buff, &bp);
	}
    }
    *bp = '\0';
}

/* ---------------------------------------------------------------------------
 * fun_lattr: Return list of attributes I can see on the object.
 */

FUNCTION(fun_lattr)
{
    dbref thing;
    int ca;
    char *bp;
    ATTR *attr;

    bp = buff;

    /* Check for wildcard matching.  parse_attrib_wild checks for read
     * permission, so we don't have to.  Have p_a_w assume the slash-star
     * if it is missing.
     */

    if (parse_attrib_wild(player, fargs[0], &thing, 0, 0, 1)) {
	for (ca = olist_first(); ca != NOTHING; ca = olist_next()) {
	    attr = atr_num(ca);
	    if (attr) {
		if (bp != buff) {
		    safe_chr(' ', buff, &bp);
		}
		safe_str((char *) attr->name, buff, &bp);
	    }
	}
	*bp = '\0';
    } else {
	*buff = '\0';
    }
    return;
}

/* ---------------------------------------------------------------------------
 * fun_objmem: return the following totals for an object or obj/attr:
 * memory consumed by attribute text
 * memory consumed by attribute overhead (i.e. names, etc.)
 * memory consumed by other stuff
 * total memory consumed
 *
 * Current implementation: only the memory consumed by attribute text
 * is returned.
 */

FUNCTION(fun_objmem)
{
    dbref thing, aowner;
    int atr, aflags;
    char *abuf;
    ATTR *ap;
    int bytes_atext = 0;

    if (parse_attrib_wild(player, fargs[0], &thing, 0, 0, 1)) {
	for (atr = olist_first(); atr != NOTHING; atr = olist_next()) {
	    ap = atr_num(atr);
	    if (!ap)
		continue;
	    abuf = atr_get(thing, atr, &aowner, &aflags);
	    /* Player must be able to read attribute with 'examine' */
	    if (Examinable(player, thing) &&
		Read_attr(player, thing, ap, aowner, aflags))
		bytes_atext += strlen(abuf);
	    free_lbuf(abuf);
	}
    }
    ltos(buff, bytes_atext);
}

/* ---------------------------------------------------------------------------
 * do_reverse, fun_reverse, fun_revwords: Reverse things.
 */

static void 
do_reverse(from, to)
    char *from, *to;
{
    char *tp;

    tp = to + strlen(from);
    *tp-- = '\0';
    while (*from) {
	*tp-- = *from++;
    }
}

FUNCTION(fun_reverse)
{
    do_reverse(fargs[0], buff);
}

FUNCTION(fun_revwords)
{
    char *temp, *bp, *tp, *t1, sep;

    /* If we are passed an empty arglist return a null string */

    if (nfargs == 0) {
	*buff = '\0';
	return;
    }
    varargs_preamble("REVWORDS", 2);
    temp = alloc_lbuf("fun_revwords");

    /* Reverse the whole string */

    do_reverse(fargs[0], temp);

    /* Now individually reverse each word in the string.  This will
     * undo the reversing of the words (so the words themselves are
     * forwards again.
     */

    tp = temp;
    bp = buff;
    while (tp) {
	if (bp != buff)
	    safe_chr(sep, buff, &bp);
	t1 = split_token(&tp, sep);
	do_reverse(t1, bp);
	while (*bp)
	    bp++;
    }
    *bp = '\0';
    free_lbuf(temp);
}

/* ---------------------------------------------------------------------------
 * fun_after, fun_before: Return substring after or before a specified string.
 */

FUNCTION(fun_after)
{
    char *bp, *cp, *mp;
    int mlen;

    if (nfargs == 0) {
	*buff = '\0';
	return;
    }
    if (!fn_range_check("AFTER", nfargs, 1, 2, buff))
	return;
    bp = fargs[0];
    mp = fargs[1];

    /* Sanity-check arg1 and arg2 */

    if (bp == NULL)
	bp = "";
    if (mp == NULL)
	mp = " ";
    if (!mp || !*mp)
	mp = (char *) " ";
    mlen = strlen(mp);
    if ((mlen == 1) && (*mp == ' '))
	bp = trim_space_sep(bp, ' ');

    /* Look for the target string */

    while (*bp) {

	/* Search for the first character in the target string */

	cp = (char *) index(bp, *mp);
	if (cp == NULL) {

	    /* Not found, return empty string */

	    *buff = '\0';
	    return;
	}
	/* See if what follows is what we are looking for */

	if (!strncmp(cp, mp, mlen)) {

	    /* Yup, return what follows */

	    bp = cp + mlen;
	    strcpy(buff, bp);
	    return;
	}
	/* Continue search after found first character */

	bp = cp + 1;
    }

    /* Ran off the end without finding it */

    *buff = '\0';
    return;
}

FUNCTION(fun_before)
{
    char *bp, *cp, *mp, *ip;
    int mlen;

    if (nfargs == 0) {
	*buff = '\0';
	return;
    }
    if (!fn_range_check("BEFORE", nfargs, 1, 2, buff))
	return;

    bp = fargs[0];
    mp = fargs[1];

    /* Sanity-check arg1 and arg2 */

    if (bp == NULL)
	bp = "";
    if (mp == NULL)
	mp = " ";
    if (!mp || !*mp)
	mp = (char *) " ";
    mlen = strlen(mp);
    if ((mlen == 1) && (*mp == ' '))
	bp = trim_space_sep(bp, ' ');
    ip = bp;

    /* Look for the target string */

    while (*bp) {

	/* Search for the first character in the target string */

	cp = (char *) index(bp, *mp);
	if (cp == NULL) {

	    /* Not found, return entire string */

	    strcpy(buff, ip);
	    return;
	}
	/* See if what follows is what we are looking for */

	if (!strncmp(cp, mp, mlen)) {

	    /* Yup, return what follows */

	    *cp = '\0';
	    strcpy(buff, ip);
	    return;
	}
	/* Continue search after found first character */

	bp = cp + 1;
    }

    /* Ran off the end without finding it */

    strcpy(buff, ip);
    return;
}

/* ---------------------------------------------------------------------------
 * fun_max, fun_min: Return maximum (minimum) value.
 */

FUNCTION(fun_max)
{
    int i;
    NVAL max, val;

    if (nfargs < 1) {
	strcpy(buff, "#-1 TOO FEW ARGUMENTS");
    } else {
	max = aton(fargs[0]);
	for (i = 0; i < nfargs; i++) {
	    val = aton(fargs[i]);
	    if (max < val)
		max = val;
	}
	fval(buff, max);
    }
    return;
}

FUNCTION(fun_min)
{
    int i;
    NVAL min, val;

    if (nfargs < 1) {
	strcpy(buff, "#-1 TOO FEW ARGUMENTS");
    } else {
	min = aton(fargs[0]);
	for (i = 0; i < nfargs; i++) {
	    val = aton(fargs[i]);
	    if (min > val)
		min = val;
	}
	fval(buff, min);
    }
    return;
}

/* ---------------------------------------------------------------------------
 * fun_search: Search the db for things, returning a list of what matches
 */

FUNCTION(fun_search)
{
    dbref thing;
    char *bp, *nbuf;
    SEARCH searchparm;

    /* Set up for the search.  If any errors, abort. */

    if (!search_setup(player, fargs[0], &searchparm)) {
	strcpy(buff, "#-1 ERROR DURING SEARCH");
	return;
    }
    /* Do the search and report the results */

    search_perform(player, cause, &searchparm);
    bp = buff;
    nbuf = alloc_sbuf("fun_search");
    for (thing = olist_first(); thing != NOTHING; thing = olist_next()) {
	if (bp == buff) {
	    *nbuf = '#';
	    ltos(&nbuf[1], thing);
	} else {
	    nbuf[0] = ' ';
	    nbuf[1] = '#';
	    ltos(&nbuf[2], thing);
	}
	safe_str(nbuf, buff, &bp);
    }
    *bp = '\0';
    free_sbuf(nbuf);
    olist_init();
}

/* ---------------------------------------------------------------------------
 * fun_stats: Get database size statistics.
 */

FUNCTION(fun_stats)
{
    dbref who;
    STATS statinfo;

    if ((!fargs[0]) || !*fargs[0] || !string_compare(fargs[0], "all")) {
	who = NOTHING;
    } else {
	who = lookup_player(player, fargs[0], 1);
	if (who == NOTHING) {
	    strcpy(buff, "#-1 NOT FOUND");
	    return;
	}
    }
    if (!get_stats(player, who, &statinfo)) {
	strcpy(buff, "#-1 ERROR GETTING STATS");
	return;
    }
    sprintf(buff, "%d %d %d %d %d %d", statinfo.s_total, statinfo.s_rooms,
	    statinfo.s_exits, statinfo.s_things, statinfo.s_players,
	    statinfo.s_garbage);
}

/* ---------------------------------------------------------------------------
 * fun_merge:  given two strings and a character, merge the two strings
 *   by replacing characters in string1 that are the same as the given
 *   character by the corresponding character in string2 (by position).
 *   The strings must be of the same length.
 */

FUNCTION(fun_merge)
{
    char *str, *rep, *bp;
    char c;

    /* do length checks first */

    if (strlen(fargs[0]) != strlen(fargs[1])) {
	strcpy(buff, "#-1 STRING LENGTHS MUST BE EQUAL");
	return;
    }
    if (strlen(fargs[2]) > 1) {
	strcpy(buff, "#-1 TOO MANY CHARACTERS");
	return;
    }
    /* find the character to look for. null character is considered
     * a space
     */

    if (!*fargs[2])
	c = ' ';
    else
	c = *fargs[2];

    /* walk strings, copy from the appropriate string */

    for (str = fargs[0], rep = fargs[1], bp = buff;
	 *str && *rep;
	 str++, rep++, bp++) {
	if (*str == c)
	    *bp = *rep;
	else
	    *bp = *str;
    }

    *bp = '\0';			/* terminate */

    /* There is no need to check for overflowing the buffer since
     * both strings are LBUF_SIZE or less and the new string cannot be
     * any longer.
     */

    return;
}

/* ---------------------------------------------------------------------------
 * fun_splice: similar to MERGE(), replaces by word instead of by character.
 */

FUNCTION(fun_splice)
{
    char *bp, *p1, *p2, *q1, *q2, sep, osep;
    int words, i;

    svarargs_preamble("SPLICE", 5);

    /* length checks */

    if (countwords(fargs[2], sep) > 1) {
	strcpy(buff, "#-1 TOO MANY WORDS");
	return;
    }
    words = countwords(fargs[0], sep);
    if (words != countwords(fargs[1], sep)) {
	strcpy(buff, "#-1 NUMBER OF WORDS MUST BE EQUAL");
	return;
    }
    /* loop through the two lists */

    bp = buff;
    p1 = fargs[0];
    q1 = fargs[1];
    for (i = 0; i < words; i++) {
	p2 = split_token(&p1, sep);
	q2 = split_token(&q1, sep);
	if (bp != buff)
	    safe_chr(osep, buff, &bp);
	if (!strcmp(p2, fargs[2]))
	    safe_str(q2, buff, &bp);	/* replace */
	else
	    safe_str(p2, buff, &bp);	/* copy */
    }
    *bp = '\0';
}

/* ---------------------------------------------------------------------------
 * fun_munge: combines two lists in an arbitrary manner.
 */

FUNCTION(fun_munge)
{
    dbref aowner, thing;
    int aflags, anum, nptrs1, nptrs2, nresults, i, j;
    ATTR *ap;
    char *list1, *list2, *rlist;
    char *ptrs1[LBUF_SIZE / 2], *ptrs2[LBUF_SIZE / 2], *results[LBUF_SIZE / 2];
    char *atext, *bp, sep, osep;

    if ((nfargs == 0) || !fargs[0] || !*fargs[0]) {
	*buff = '\0';
	return;
    }
    svarargs_preamble("MUNGE", 5);

    /* Find our object and attribute */

    if (parse_attrib(player, fargs[0], &thing, &anum)) {
	if ((anum == NOTHING) || !Good_obj(thing))
	    ap = NULL;
	else
	    ap = atr_num(anum);
    } else {
	thing = player;
	ap = atr_str(fargs[0]);
    }

    if (!ap) {
	*buff = '\0';
	return;
    }
    atext = atr_pget(thing, ap->number, &aowner, &aflags);
    if (!atext) {
	*buff = '\0';
	return;
    } else if (!*atext || !See_attr(player, thing, ap, aowner, aflags)) {
	free_lbuf(atext);
	*buff = '\0';
	return;
    }
    /* Copy our lists and chop them up. */

    list1 = alloc_lbuf("fun_munge.list1");
    list2 = alloc_lbuf("fun_munge.list2");
    strcpy(list1, fargs[1]);
    strcpy(list2, fargs[2]);
    nptrs1 = list2arr(ptrs1, LBUF_SIZE / 2, list1, sep);
    nptrs2 = list2arr(ptrs2, LBUF_SIZE / 2, list2, sep);

    if (nptrs1 != nptrs2) {
	strcpy(buff, "#-1 LISTS MUST BE OF EQUAL SIZE");
	free_lbuf(atext);
	free_lbuf(list1);
	free_lbuf(list2);
	return;
    }

    /* Call the u-function with the first list as %0. */

    rlist = exec(player, cause, EV_STRIP | EV_FCHECK | EV_EVAL, atext,
		 &fargs[1], 1);


    /* Now that we have our result, put it back into array form. Search
     * through list1 until we find the element position, then copy the
     * corresponding element from list2.
     */

    nresults = list2arr(results, LBUF_SIZE / 2, rlist, sep);

    bp = buff;

    for (i = 0; i < nresults; i++) {
	for (j = 0; j < nptrs1; j++) {
	    if (!strcmp(results[i], ptrs1[j])) {
		if (bp != buff) {
		    safe_chr(osep, buff, &bp);
		}
		safe_str(ptrs2[j], buff, &bp);
		ptrs1[j][0] = '\0';
		break;
	    }
	}
    }
    *bp = '\0';
    free_lbuf(atext);
    free_lbuf(list1);
    free_lbuf(list2);
    free_lbuf(rlist);
}

/* ---------------------------------------------------------------------------
 * fun_repeat: repeats a string
 */

FUNCTION(fun_repeat)
{
    int times, i, over;
    char *bp;

    times = atoi(fargs[1]);
    if ((times < 1) || !*fargs[0]) {
	*buff = '\0';
    } else if (times == 1) {
	strcpy(buff, fargs[0]);
    } else if (strlen(fargs[0]) * times >= (LBUF_SIZE - 1)) {
	strcpy(buff, "#-1 STRING TOO LONG");
    } else {
	bp = buff;
	for (i = 0, over = 0; (i < times) && !over; i++)
	    over = safe_str(fargs[0], buff, &bp);
	*bp = '\0';
    }
}

/* ---------------------------------------------------------------------------
 * fun_scramble:  randomizes the letters in a string.
 */
FUNCTION(fun_scramble)
{
    int n, i, j;
    char c;

    if (!fargs[0] || !*fargs[0]) {
	*buff = '\0';
	return;
    }
    strcpy(buff, fargs[0]);

    n = strlen(buff);

    for (i = 0; i < n; i++) {
	j = (random() % (n - i)) + i;
	c = buff[i];
	buff[i] = buff[j];
	buff[j] = c;
    }
}

/* ---------------------------------------------------------------------------
 * fun_shuffle: randomize order of words in a list.
 */

static void swap(p, q)
     char **p;
     char **q;
{
    /* swaps two points to strings */

    char *temp;

    temp = *p;
    *p = *q;
    *q = temp;
}

FUNCTION(fun_shuffle)
{
    char *words[LBUF_SIZE];
    int n, i, j;
    char sep, osep;

    if (!nfargs || !fargs[0] || !*fargs[0]) {
	*buff = '\0';
	return;
    }

    svarargs_preamble("SHUFFLE", 3);
    
    n = list2arr(words, LBUF_SIZE, fargs[0], sep);

    for (i = 0; i < n; i++)  {
	j = (random() % (n - i)) + i;
	swap(&words[i], &words[j]);
    }
    arr2list(words, n, buff, osep);
}

/* ---------------------------------------------------------------------------
 * fun_iter: Make list from evaluating arg2 with each member of arg1.
 * NOTE: This function expects that its arguments have not been evaluated.
 *
 * Also note: parse() is equivalent to this.
 */

static void do_iter(buff, player, cause, list, exprstr, cargs, ncargs,
		    sep, osep, flag)
    char *buff;
    dbref player, cause;
    char *list, *exprstr;
    char *cargs[];
    int ncargs;
    char sep, osep;
    int flag;			/* 0 is iter(), 1 is list() */
{
    char *curr, *objstring, *buff2, *buff3, *result, *bp, *cp;
    char tbuf[8];
    int number = 0;

    cp = curr = exec(player, cause, EV_STRIP | EV_FCHECK | EV_EVAL, list,
		     cargs, ncargs);
    cp = trim_space_sep(cp, sep);
    if (!*cp) {
	free_lbuf(curr);
	*buff = '\0';
	return;
    }
    bp = buff;
    while (cp && (mudstate.func_invk_ctr < mudconf.func_invk_lim)) {
	number++;
	objstring = split_token(&cp, sep);
	buff2 = replace_string(BOUND_VAR, objstring, exprstr);
	ltos(tbuf, number);
	buff3 = replace_string(LISTPLACE_VAR, tbuf, buff2);
	result = exec(player, cause,
		      EV_STRIP | EV_FCHECK | EV_EVAL, buff3, cargs, ncargs);
	free_lbuf(buff2);
	free_lbuf(buff3);
	if (flag) {
	    notify(cause, result);
	} else {
	    if (bp != buff)
		safe_chr(osep, buff, &bp);
	    safe_str(result, buff, &bp);
	}
	free_lbuf(result);
    }
    free_lbuf(curr);
    *bp = '\0';
}

FUNCTION(fun_iter)
{
    char sep, osep;

    evarargs_preamble("ITER", 2, 4);
    do_iter(buff, player, cause, fargs[0], fargs[1], cargs, ncargs,
	    sep, osep, 0);
}

FUNCTION(fun_list)
{
    char sep, osep;

    evarargs_preamble("LIST", 2, 4);
    do_iter(buff, player, cause, fargs[0], fargs[1], cargs, ncargs,
	    sep, osep, 1);
}

/* ---------------------------------------------------------------------------
 * fun_fold: iteratively eval an attrib with a list of arguments
 *	  and an optional base case.  With no base case, the first list element
 *    is passed as %0 and the second is %1.  The attrib is then evaluated
 *    with these args, the result is then used as %0 and the next arg is
 *    %1 and so it goes as there are elements left in the list.  The
 *    optinal base case gives the user a nice starting point.
 *
 *    > &REP_NUM object=[%0][repeat(%1,%1)]
 *    > say fold(OBJECT/REP_NUM,1 2 3 4 5,->)
 *    You say "->122333444455555"
 *
 *	NOTE: To use added list separator, you must use base case!
 */

FUNCTION(fun_fold)
{
    dbref aowner, thing;
    int aflags, anum;
    ATTR *ap;
    char *atext, *result, *curr, *bp, *cp, *atextbuf, *clist[2], *rstore,
         sep;

    /* We need two to four arguements only */

    mvarargs_preamble("FOLD", 2, 4);

    /* Two possibilities for the first arg: <obj>/<attr> and <attr>. */

    if (parse_attrib(player, fargs[0], &thing, &anum)) {
	if ((anum == NOTHING) || (!Good_obj(thing)))
	    ap = NULL;
	else
	    ap = atr_num(anum);
    } else {
	thing = player;
	ap = atr_str(fargs[0]);
    }

    /* Make sure we got a good attribute */

    if (!ap) {
	*buff = '\0';
	return;
    }
    /* Use it if we can access it, otherwise return an error. */

    atext = atr_pget(thing, ap->number, &aowner, &aflags);
    if (!atext) {
	*buff = '\0';
	return;
    } else if (!*atext || !See_attr(player, thing, ap, aowner, aflags)) {
	free_lbuf(atext);
	*buff = '\0';
	return;
    }
    /* Evaluate it using the rest of the passed function args */

    cp = curr = fargs[1];
    bp = buff;
    atextbuf = alloc_lbuf("fun_fold");
    strcpy(atextbuf, atext);

    /* may as well handle first case now */

    if ((nfargs >= 3) && (*fargs[2])) {
	clist[0] = fargs[2];
	clist[1] = split_token(&cp, sep);
	result = exec(player, cause, EV_STRIP | EV_FCHECK | EV_EVAL,
		      atextbuf, clist, 2);
    } else {
	clist[0] = split_token(&cp, sep);
	clist[1] = split_token(&cp, sep);
	result = exec(player, cause, EV_STRIP | EV_FCHECK | EV_EVAL,
		      atextbuf, clist, 2);
    }

    rstore = result;
    result = NULL;

    while (cp && (mudstate.func_invk_ctr < mudconf.func_invk_lim)) {
	clist[0] = rstore;
	clist[1] = split_token(&cp, sep);
	strcpy(atextbuf, atext);
	result = exec(player, cause,
		      EV_STRIP | EV_FCHECK | EV_EVAL, atextbuf, clist, 2);
	strcpy(rstore, result);
	free_lbuf(result);
    }
    safe_str(rstore, buff, &bp);
    free_lbuf(rstore);
    free_lbuf(atext);
    free_lbuf(atextbuf);
}

/* ---------------------------------------------------------------------------
 * fun_filter: iteratively perform a function with a list of arguments
 *		and return the arg, if the function evaluates to TRUE using the
 *      arg.
 *
 *      > &IS_ODD object=mod(%0,2)
 *      > say filter(object/is_odd,1 2 3 4 5)
 *      You say "1 3 5"
 *      > say filter(object/is_odd,1-2-3-4-5,-)
 *      You say "1-3-5"
 *
 *  NOTE:  If you specify a separator it is used to delimit returned list
 */

FUNCTION(fun_filter)
{
    dbref aowner, thing;
    int aflags, anum;
    ATTR *ap;
    char *atext, *result, *curr, *objstring, *bp, *cp, *atextbuf, sep, osep;

    svarargs_preamble("FILTER", 4);

    /* Two possibilities for the first arg: <obj>/<attr> and <attr>. */

    if (parse_attrib(player, fargs[0], &thing, &anum)) {
	if ((anum == NOTHING) || (!Good_obj(thing)))
	    ap = NULL;
	else
	    ap = atr_num(anum);
    } else {
	thing = player;
	ap = atr_str(fargs[0]);
    }

    /* Make sure we got a good attribute */

    if (!ap) {
	*buff = '\0';
	return;
    }
    /* Use it if we can access it, otherwise return an error. */

    atext = atr_pget(thing, ap->number, &aowner, &aflags);
    if (!atext) {
	*buff = '\0';
	return;
    } else if (!*atext || !See_attr(player, thing, ap, aowner, aflags)) {
	free_lbuf(atext);
	*buff = '\0';
	return;
    }
    /* Now iteratively eval the attrib with the argument list */

    cp = curr = trim_space_sep(fargs[1], sep);
    bp = buff;
    atextbuf = alloc_lbuf("fun_filter");
    while (cp && (mudstate.func_invk_ctr < mudconf.func_invk_lim)) {
	objstring = split_token(&cp, sep);
	strcpy(atextbuf, atext);
	result = exec(player, cause,
		   EV_STRIP | EV_FCHECK | EV_EVAL, atextbuf, &objstring, 1);
	if ((bp != buff) && (*result == '1')) {
	    safe_chr(osep, buff, &bp);
	}
	if (*result == '1')
	    safe_str(objstring, buff, &bp);
	free_lbuf(result);
    }
    free_lbuf(atext);
    free_lbuf(atextbuf);
    *bp = '\0';
}

/* ---------------------------------------------------------------------------
 * fun_map: iteratively evaluate an attribute with a list of arguments.
 *
 *  > &DIV_TWO object=fdiv(%0,2)
 *  > say map(1 2 3 4 5,object/div_two)
 *  You say "0.5 1 1.5 2 2.5"
 *  > say map(object/div_two,1-2-3-4-5,-)
 *  You say "0.5-1-1.5-2-2.5"
 *
 */

FUNCTION(fun_map)
{
    dbref aowner, thing;
    int aflags, anum;
    ATTR *ap;
    char *atext, *result, *objstring, *bp, *cp, *atextbuf, sep, osep;

    svarargs_preamble("MAP", 4);
    *buff = '\0';

    /* If we don't have anything for a second arg, don't bother. */
    if (!fargs[1] || !*fargs[1])
	return;

    /* Two possibilities for the second arg: <obj>/<attr> and <attr>. */

    if (parse_attrib(player, fargs[0], &thing, &anum)) {
	if ((anum == NOTHING) || (!Good_obj(thing)))
	    ap = NULL;
	else
	    ap = atr_num(anum);
    } else {
	thing = player;
	ap = atr_str(fargs[0]);
    }

    /* Make sure we got a good attribute */

    if (!ap) {
	return;
    }
    /* Use it if we can access it, otherwise return an error. */

    atext = atr_pget(thing, ap->number, &aowner, &aflags);
    if (!atext) {
	return;
    } else if (!*atext || !See_attr(player, thing, ap, aowner, aflags)) {
	free_lbuf(atext);
	return;
    }
    /* now process the list one element at a time */

    cp = trim_space_sep(fargs[1], sep);
    bp = buff;
    atextbuf = alloc_lbuf("fun_map");
    while (cp && (mudstate.func_invk_ctr < mudconf.func_invk_lim)) {
	objstring = split_token(&cp, sep);
	strcpy(atextbuf, atext);
	result = exec(player, cause,
		   EV_STRIP | EV_FCHECK | EV_EVAL, atextbuf, &objstring, 1);
	if (bp != buff)
	    safe_chr(osep, buff, &bp);
	safe_str(result, buff, &bp);
	free_lbuf(result);
    }
    free_lbuf(atext);
    free_lbuf(atextbuf);
    *bp = '\0';
}

/* ---------------------------------------------------------------------------
 * fun_mix: Like map, but operates on two lists or more lists simultaneously,
 * passing the elements as %0, %1, %2, etc.
 */

FUNCTION(fun_mix)
{
    dbref aowner, thing;
    int aflags, anum, i, lastn, nwords, wc;
    ATTR *ap;
    char *atext, *result, *os[10], *bp, *atextbuf, sep;
    char *cp[10];

    /* Check to see if we have an appropriate number of arguments.
     * If there are more than three arguments, the last argument is
     * ALWAYS assumed to be a delimiter.
     */

    if (!fn_range_check("MIX", nfargs, 3, 10, buff)) {
	return;
    }
    if (nfargs < 4) {
	sep = ' ';
	lastn = nfargs - 1;
    } else if (!delim_check(fargs, nfargs, nfargs, &sep, buff, 0,
			    player, cause, cargs, ncargs)) {
	return;
    } else {
	lastn = nfargs - 2;
    }

    /* Get the attribute, check the permissions. */

    if (parse_attrib(player, fargs[0], &thing, &anum)) {
	if ((anum == NOTHING) || !Good_obj(thing))
	    ap = NULL;
	else
	    ap = atr_num(anum);
    } else {
	thing = player;
	ap = atr_str(fargs[0]);
    }

    if (!ap) {
	*buff = '\0';
	return;
    }
    atext = atr_pget(thing, ap->number, &aowner, &aflags);
    if (!atext) {
	*buff = '\0';
	return;
    } else if (!*atext || !See_attr(player, thing, ap, aowner, aflags)) {
	free_lbuf(atext);
	*buff = '\0';
	return;
    }

    for (i = 0; i < 10; i++)
	cp[i] = NULL;

    /* process the lists, one element at a time. */

    for (i = 1; i <= lastn; i++) {
	cp[i] = trim_space_sep(fargs[i], sep);
    }
    nwords = countwords(cp[1], sep);
    for (i = 2; i<= lastn; i++) {
	if (countwords(cp[i], sep) != nwords) {
	    free_lbuf(atext);
	    strcpy(buff, "#-1 LISTS MUST BE OF EQUAL SIZE");
	    return;
	}
    }
    atextbuf = alloc_lbuf("fun_mix");
    bp = buff;

    for (wc = 0;
	 (wc < nwords) && (mudstate.func_invk_ctr < mudconf.func_invk_lim);
	 wc++) {
	for (i = 1; i <= lastn; i++) {
	    os[i - 1] = split_token(&cp[i], sep);
	}
	strcpy(atextbuf, atext);
	result = exec(player, cause, EV_STRIP | EV_FCHECK | EV_EVAL,
		      atextbuf, &(os[0]), lastn);
	if (bp != buff)
	    safe_chr(sep, buff, &bp);
	safe_str(result, buff, &bp);
	free_lbuf(result);
    }
    free_lbuf(atext);
    free_lbuf(atextbuf);
    *bp = '\0';
}

/* ---------------------------------------------------------------------------
 * fun_foreach: like map(), but it operates on a string, rather than on a list,
 * calling a user-defined function for each character in the string.
 * No delimiter is inserted between the results.
 */

FUNCTION(fun_foreach)
{
    dbref aowner, thing;
    int aflags, anum;
    ATTR *ap;
    char *atext, *atextbuf, *result, *bp, *cp, *cbuf;
    char start_token, end_token;
    int in_string = 1;

    if (!fn_range_check("FOREACH", nfargs, 2, 4, buff))
	return;

    if (parse_attrib(player, fargs[0], &thing, &anum)) {
	if ((anum == NOTHING) || !Good_obj(thing))
	    ap = NULL;
	else
	    ap = atr_num(anum);
    } else {
	thing = player;
	ap = atr_str(fargs[0]);
    }

    *buff = '\0';

    if (!ap) {
	return;
    }
    atext = atr_pget(thing, ap->number, &aowner, &aflags);
    if (!atext) {
	return;
    } else if (!*atext || !See_attr(player, thing, ap, aowner, aflags)) {
	free_lbuf(atext);
	return;
    }
    atextbuf = alloc_lbuf("fun_foreach");
    cbuf = alloc_lbuf("fun_foreach.cbuf");
    cp = trim_space_sep(fargs[1], ' ');
    bp = buff;

    start_token = '\0';
    end_token = '\0';

    if (nfargs > 2) {
	in_string = 0;
	start_token = *fargs[2];
    }
    if (nfargs > 3) {
	end_token = *fargs[3];
    }

    while (cp && *cp && (mudstate.func_invk_ctr < mudconf.func_invk_lim)) {

	if (!in_string) {
	    /* Look for a start token. */
	    while (*cp && (*cp != start_token)) {
		safe_chr(*cp, buff, &bp);
		cp++;
	    }
	    if (!*cp)
		break;
	    /* Skip to the next character. Don't copy the start token. */
	    cp++;
	    if (!*cp)
		break;
	    in_string = 1;
	}
	if (*cp == end_token) {
	    /* We've found an end token. Skip over it. Note that it's
	     * possible to have a start and end token next to one
	     * another.
	     */
	    cp++;
	    in_string = 0;
	    continue;
	}

	cbuf[0] = *cp++;
	cbuf[1] = '\0';
	strcpy(atextbuf, atext);
	result = exec(player, cause, EV_STRIP | EV_FCHECK | EV_EVAL,
		      atextbuf, &cbuf, 1);
	safe_str(result, buff, &bp);
	free_lbuf(result);
    }

    *bp = '\0';
    free_lbuf(atextbuf);
    free_lbuf(atext);
    free_lbuf(cbuf);
}

/* ---------------------------------------------------------------------------
 * fun_edit: Edit text.
 */

FUNCTION(fun_edit)
{
    char *tstr;

    edit_string(mudconf.ansi_colors ? strip_ansi(fargs[0]) : fargs[0],
		&tstr, fargs[1], fargs[2]);
    strcpy(buff, tstr);
    free_lbuf(tstr);
}

/* ---------------------------------------------------------------------------
 * fun_locate: Search for things with the perspective of another obj.
 */

FUNCTION(fun_locate)
{
    int pref_type, check_locks, verbose, multiple;
    dbref thing, what;
    char *cp;

    pref_type = NOTYPE;
    check_locks = verbose = multiple = 0;

    /* Find the thing to do the looking, make sure we control it. */

    if (Wizard(player))
	thing = match_thing(player, fargs[0]);
    else
	thing = match_controlled(player, fargs[0]);
    if (!Good_obj(thing)) {
	strcpy(buff, "#-1 PERMISSION DENIED");
	return;
    }
    /* Get pre- and post-conditions and modifiers */

    for (cp = fargs[2]; *cp; cp++) {
	switch (*cp) {
	case 'E':
	    pref_type = TYPE_EXIT;
	    break;
	case 'L':
	    check_locks = 1;
	    break;
	case 'P':
	    pref_type = TYPE_PLAYER;
	    break;
	case 'R':
	    pref_type = TYPE_ROOM;
	    break;
	case 'T':
	    pref_type = TYPE_THING;
	    break;
	case 'V':
	    verbose = 1;
	    break;
	case 'X':
	    multiple = 1;
	    break;
	}
    }

    /* Set up for the search */

    if (check_locks)
	init_match_check_keys(thing, fargs[1], pref_type);
    else
	init_match(thing, fargs[1], pref_type);

    /* Search for each requested thing */

    for (cp = fargs[2]; *cp; cp++) {
	switch (*cp) {
	case 'a':
	    match_absolute();
	    break;
	case 'c':
	    match_carried_exit_with_parents();
	    break;
	case 'e':
	    match_exit_with_parents();
	    break;
	case 'h':
	    match_here();
	    break;
	case 'i':
	    match_possession();
	    break;
	case 'm':
	    match_me();
	    break;
	case 'n':
	    match_neighbor();
	    break;
	case 'p':
	    match_player();
	    break;
	case '*':
	    match_everything(MAT_EXIT_PARENTS);
	    break;
	}
    }

    /* Get the result and return it to the caller */

    if (multiple)
	what = last_match_result();
    else
	what = match_result();

    if (verbose)
	(void) match_status(player, what);

    *buff = '#';
    ltos(&buff[1], what);
}

/* ---------------------------------------------------------------------------
 * fun_switch: Return value based on pattern matching (ala @switch)
 * NOTE: This function expects that its arguments have not been evaluated.
 */

FUNCTION(fun_switch)
{
    int i;
    char *mbuff, *tbuf1, *tbuf2;

    /* If we don't have at least 2 args, return nothing */

    if (nfargs < 2) {
	*buff = '\0';
	return;
    }
    /* Evaluate the target in fargs[0] */

    mbuff = exec(player, cause, EV_STRIP | EV_FCHECK | EV_EVAL,
		 fargs[0], cargs, ncargs);

    /* Loop through the patterns looking for a match */

    for (i = 1; (i < nfargs - 1) && fargs[i] && fargs[i + 1]; i += 2) {
	tbuf1 = exec(player, cause, EV_STRIP | EV_FCHECK | EV_EVAL,
		     fargs[i], cargs, ncargs);
	if (quick_wild(tbuf1, mbuff)) {
	    free_lbuf(tbuf1);
	    tbuf1 = replace_string(SWITCH_VAR, mbuff, fargs[i + 1]);
	    tbuf2 = exec(player, cause, EV_STRIP | EV_FCHECK | EV_EVAL,
			 tbuf1, cargs, ncargs);
	    strcpy(buff, tbuf2);
	    free_lbuf(mbuff);
	    free_lbuf(tbuf1);
	    free_lbuf(tbuf2);
	    return;
	}
	free_lbuf(tbuf1);
    }

    /* Nope, return the default if there is one */

    if ((i < nfargs) && fargs[i]) {
	tbuf1 = replace_string(SWITCH_VAR, mbuff, fargs[i]);
	tbuf2 = exec(player, cause, EV_STRIP | EV_FCHECK | EV_EVAL,
		     tbuf1, cargs, ncargs);
	strcpy(buff, tbuf2);
	free_lbuf(tbuf1);
	free_lbuf(tbuf2);
    } else {
	*buff = '\0';
    }
    free_lbuf(mbuff);

    return;
}

/* ---------------------------------------------------------------------------
 * fun_ifelse: Sort of like switch, but works on a true/false basis.
 * NOTE: This function expects that its arguments have not been evaluated.
 *
 * Derived from TinyMUX.
 */

FUNCTION(fun_ifelse)
{
    char *mbuff, *tbuff, *str;

    /* Evaluate our target. */

    mbuff = exec(player, cause, EV_STRIP | EV_FCHECK | EV_EVAL,
		 fargs[0], cargs, ncargs);

    if (!mbuff || !*mbuff || !xlate(mbuff)) {
	str = fargs[2];
    } else {
	str = fargs[1];
    }
    tbuff = exec(player, cause, EV_STRIP | EV_FCHECK | EV_EVAL,
		 str, cargs, ncargs);
    strcpy(buff, tbuff);
    free_lbuf(tbuff);
    free_lbuf(mbuff);
}


/* ---------------------------------------------------------------------------
 * fun_space: Make spaces.
 */

FUNCTION(fun_space)
{
    int num;
    char *cp;

    if (!fargs[0] || !(*fargs[0])) {
	num = 1;
    } else {
	num = atoi(fargs[0]);
    }

    if (num < 1) {

	/* If negative or zero spaces return a single space,
	 * -except- allow 'space(0)' to return "" for calculated
	 * padding
	 */

	if (!is_integer(fargs[0]) || (num != 0)) {
	    num = 1;
	}
    } else if (num >= LBUF_SIZE) {
	num = LBUF_SIZE - 1;
    }
    for (cp = buff; num > 0; num--)
	*cp++ = ' ';
    *cp = '\0';
    return;
}

/* ---------------------------------------------------------------------------
 * fun_idle, fun_conn: return seconds idle or connected.
 */

FUNCTION(fun_idle)
{
    dbref target;

    target = lookup_player(player, fargs[0], 1);
    if (Good_obj(target) && Dark(target) && !Wizard(player))
	target = NOTHING;
    ltos(buff, fetch_idle(target));
}

FUNCTION(fun_conn)
{
    dbref target;

    target = lookup_player(player, fargs[0], 1);
    if (Good_obj(target) && Dark(target) && !Wizard(player))
	target = NOTHING;
    ltos(buff, fetch_connect(target));
}

/* ---------------------------------------------------------------------------
 * fun_sort: Sort lists.
 */

typedef struct f_record f_rec;
typedef struct i_record i_rec;

  struct f_record {
      double data;
      char *str;
  };

  struct i_record {
      long data;
      char *str;
  };

static int 
a_comp(s1, s2)
    const void *s1, *s2;
{
    return strcmp(*(char **) s1, *(char **) s2);
}

static int 
f_comp(s1, s2)
    const void *s1, *s2;
{
    if (((f_rec *) s1)->data > ((f_rec *) s2)->data)
	return 1;
    if (((f_rec *) s1)->data < ((f_rec *) s2)->data)
	return -1;
    return 0;
}

static int 
i_comp(s1, s2)
    const void *s1, *s2;
{
    if (((i_rec *) s1)->data > ((i_rec *) s2)->data)
	return 1;
    if (((i_rec *) s1)->data < ((i_rec *) s2)->data)
	return -1;
    return 0;
}

static void 
do_asort(s, n, sort_type)
    char *s[];
    int n, sort_type;
{
    int i;
    f_rec *fp;
    i_rec *ip;

    switch (sort_type) {
    case ALPHANUM_LIST:
	qsort((void *) s, n, sizeof(char *), a_comp);

	break;
    case NUMERIC_LIST:
	ip = (i_rec *) XMALLOC(n * sizeof(i_rec), "do_asort");
	for (i = 0; i < n; i++) {
	    ip[i].str = s[i];
	    ip[i].data = atoi(s[i]);
	}
	qsort((void *) ip, n, sizeof(i_rec), i_comp);
	for (i = 0; i < n; i++) {
	    s[i] = ip[i].str;
	}
	free(ip);
	break;
    case DBREF_LIST:
	ip = (i_rec *) XMALLOC(n * sizeof(i_rec), "do_asort.2");
	for (i = 0; i < n; i++) {
	    ip[i].str = s[i];
	    ip[i].data = dbnum(s[i]);
	}
	qsort((void *) ip, n, sizeof(i_rec), i_comp);
	for (i = 0; i < n; i++) {
	    s[i] = ip[i].str;
	}
	free(ip);
	break;
    case FLOAT_LIST:
	fp = (f_rec *) XMALLOC(n * sizeof(f_rec), "do_asort.3");
	for (i = 0; i < n; i++) {
	    fp[i].str = s[i];
	    fp[i].data = atof(s[i]);
	}
	qsort((void *) fp, n, sizeof(f_rec), f_comp);
	for (i = 0; i < n; i++) {
	    s[i] = fp[i].str;
	}
	free(fp);
	break;
    }
}

FUNCTION(fun_sort)
{
    int nitems, sort_type;
    char *list, sep, osep;
    char *ptrs[LBUF_SIZE / 2];

    /* If we are passed an empty arglist return a null string */

    if (nfargs == 0) {
	*buff = '\0';
	return;
    }

    if (!fn_range_check("SORT", nfargs, 1, 4, buff))
	return;
    if (!delim_check(fargs, nfargs, 3, &sep, buff, 0,
		     player, cause, cargs, ncargs))
	return;
    if (nfargs < 4)
	osep = sep;
    else if (!delim_check(fargs, nfargs, 4, &osep, buff, 0,
			  player, cause, cargs, ncargs))
	return;

    /* Convert the list to an array */

    list = alloc_lbuf("fun_sort");
    strcpy(list, fargs[0]);
    nitems = list2arr(ptrs, LBUF_SIZE / 2, list, sep);
    sort_type = get_list_type(fargs, nfargs, 2, ptrs, nitems);
    do_asort(ptrs, nitems, sort_type);
    arr2list(ptrs, nitems, buff, osep);
    free_lbuf(list);
}

static char ucomp_buff[LBUF_SIZE];
static dbref ucomp_cause;
static dbref ucomp_player;

static int 
u_comp(s1, s2)
    const void *s1, *s2;
{
    /* Note that this function is for use in conjunction with our own
     * sane_qsort routine, NOT with the standard library qsort!
     */

    char *result, tbuf[LBUF_SIZE], *elems[2];
    int n;

    /* Prevent hideously large recursive sorts. */
    if ((mudstate.func_invk_ctr > mudconf.func_invk_lim) ||
	(mudstate.func_nest_lev > mudconf.func_nest_lim))
	return 0;

    elems[0] = (char *) s1;
    elems[1] = (char *) s2;
    strcpy(tbuf, ucomp_buff);
    result = exec(ucomp_player, ucomp_cause, EV_STRIP | EV_FCHECK | EV_EVAL,
		  tbuf, &(elems[0]), 2);
    if (!result)
	n = 0;
    else {
	n = atoi(result);
	free_lbuf(result);
    }
    return n;
}

static void sane_qsort(array, left, right, compare)
     void *array[];
     int left, right;
     int (*compare)();
{
    /* Andrew Molitor's qsort, which doesn't require transitivity between
     * comparisons (essential for preventing crashes due to boneheads
     * who write comparison functions where a > b doesn't mean b < a).
     */
    
    int	i, last;
    void *tmp;

  loop:
    if (left >= right)
	return;

    /* Pick something at random at swap it into the leftmost slot   */
    /* This is the pivot, we'll put it back in the right spot later */

    i = random() % (1 + (right - left));
    tmp = array[left + i];
    array[left + i] = array[left];
    array[left] = tmp;
    
    last = left;
    for(i = left+1; i <= right; i++) {

	/* Walk the array, looking for stuff that's less than our */
	/* pivot. If it is, swap it with the next thing along     */	

	if ((*compare)(array[i], array[left]) < 0) {
	    last++;
	    if (last == i)
		continue;

	    tmp = array[last];
	    array[last] = array[i];
	    array[i] = tmp;
	}
    }

    /* Now we put the pivot back, it's now in the right spot, we never */
    /* need to look at it again, trust me.                             */

    tmp = array[last];
    array[last] = array[left];
    array[left] = tmp;

    /* At this point everything underneath the 'last' index is < the */
    /* entry at 'last' and everything above it is not < it.          */

    if ((last - left) < (right - last)) {
	sane_qsort(array, left, last-1, compare);
	left = last+1;
	goto loop;
    } else {
	sane_qsort(array, last+1, right, compare);
	right = last - 1;
	goto loop;
    }
}


FUNCTION(fun_sortby)
{
    char *atext, *list, *ptrs[LBUF_SIZE / 2], sep, osep;
    int nptrs, aflags, anum;
    dbref thing, aowner;
    ATTR *ap;

    if ((nfargs == 0) || !fargs[0] || !*fargs[0]) {
	*buff = '\0';
	return;
    }
    svarargs_preamble("SORTBY", 4);

    if (parse_attrib(player, fargs[0], &thing, &anum)) {
	if ((anum == NOTHING) || !Good_obj(thing))
	    ap = NULL;
	else
	    ap = atr_num(anum);
    } else {
	thing = player;
	ap = atr_str(fargs[0]);
    }

    if (!ap) {
	*buff = '\0';
	return;
    }
    atext = atr_pget(thing, ap->number, &aowner, &aflags);
    if (!atext) {
	*buff = '\0';
	return;
    } else if (!*atext || !See_attr(player, thing, ap, aowner, aflags)) {
	free_lbuf(atext);
	*buff = '\0';
	return;
    }
    strcpy(ucomp_buff, atext);
    ucomp_player = thing;
    ucomp_cause = cause;

    list = alloc_lbuf("fun_sortby");
    strcpy(list, fargs[1]);
    nptrs = list2arr(ptrs, LBUF_SIZE / 2, list, sep);

    if (nptrs > 1) 		/* pointless to sort less than 2 elements */
	sane_qsort((void *) ptrs, 0, nptrs-1, u_comp);

    arr2list(ptrs, nptrs, buff, osep);
    free_lbuf(list);
    free_lbuf(atext);
}

/*---------------------------------------------------------------------------
 * fun_setunion, fun_setdiff, fun_setinter: Set management.
 */

#define	SET_UNION	1
#define	SET_INTERSECT	2
#define	SET_DIFF	3

static void 
handle_sets(fargs, buff, oper, sep, osep)
    char *fargs[], *buff, sep, osep;
    int oper;
{
    char *list1, *list2, *p, *oldp;
    char *ptrs1[LBUF_SIZE], *ptrs2[LBUF_SIZE];
    int i1, i2, n1, n2, val;

    list1 = alloc_lbuf("fun_setunion.1");
    strcpy(list1, fargs[0]);
    n1 = list2arr(ptrs1, LBUF_SIZE, list1, sep);
    do_asort(ptrs1, n1, ALPHANUM_LIST);

    list2 = alloc_lbuf("fun_setunion.2");
    strcpy(list2, fargs[1]);
    n2 = list2arr(ptrs2, LBUF_SIZE, list2, sep);
    do_asort(ptrs2, n2, ALPHANUM_LIST);

    i1 = i2 = 0;
    oldp = p = buff;
    *p = '\0';

    switch (oper) {
    case SET_UNION:		/* Copy elements common to both lists
							 */

	/* Handle case of two identical single-element lists
							  */

	if ((n1 == 1) && (n2 == 1) &&
	    (!strcmp(ptrs1[0], ptrs2[0]))) {
	    safe_str(ptrs1[0], buff, &p);
	    break;
	}
	/* Process until one list is empty */

	while ((i1 < n1) && (i2 < n2)) {

	    /* Skip over duplicates */

	    if ((i1 > 0) || (i2 > 0)) {
		while ((i1 < n1) && !strcmp(ptrs1[i1],
					    oldp))
		    i1++;
		while ((i2 < n2) && !strcmp(ptrs2[i2],
					    oldp))
		    i2++;
	    }
	    /* Compare and copy */

	    if ((i1 < n1) && (i2 < n2)) {
		if (p != buff) {
		    safe_chr(osep, buff, &p);
		}
		oldp = p;
		if (strcmp(ptrs1[i1], ptrs2[i2]) < 0) {
		    safe_str(ptrs1[i1], buff, &p);
		    i1++;
		} else {
		    safe_str(ptrs2[i2], buff, &p);
		    i2++;
		}
		*p = '\0';
	    }
	}

	/* Copy rest of remaining list, stripping duplicates
							  */

	for (; i1 < n1; i1++) {
	    if (strcmp(oldp, ptrs1[i1])) {
		if (p != buff) {
		    safe_chr(osep, buff, &p);
		}
		oldp = p;
		safe_str(ptrs1[i1], buff, &p);
		*p = '\0';
	    }
	}
	for (; i2 < n2; i2++) {
	    if (strcmp(oldp, ptrs2[i2])) {
		if (p != buff) {
		    safe_chr(osep, buff, &p);
		}
		oldp = p;
		safe_str(ptrs2[i2], buff, &p);
		*p = '\0';
	    }
	}
	break;
    case SET_INTERSECT:	/* Copy elements not in both lists */

	while ((i1 < n1) && (i2 < n2)) {
	    val = strcmp(ptrs1[i1], ptrs2[i2]);
	    if (!val) {

		/* Got a match, copy it */

		if (p != buff) {
		    safe_chr(osep, buff, &p);
		}
		oldp = p;
		safe_str(ptrs1[i1], buff, &p);
		i1++;
		i2++;
		while ((i1 < n1) && !strcmp(ptrs1[i1], oldp))
		    i1++;
		while ((i2 < n2) && !strcmp(ptrs2[i2], oldp))
		    i2++;
	    } else if (val < 0) {
		i1++;
	    } else {
		i2++;
	    }
	}
	break;
    case SET_DIFF:		/* Copy elements unique to list1 */

	while ((i1 < n1) && (i2 < n2)) {
	    val = strcmp(ptrs1[i1], ptrs2[i2]);
	    if (!val) {

		/* Got a match, increment pointers */

		oldp = ptrs1[i1];
		while ((i1 < n1) && !strcmp(ptrs1[i1], oldp))
		    i1++;
		while ((i2 < n2) && !strcmp(ptrs2[i2], oldp))
		    i2++;
	    } else if (val < 0) {

		/* Item in list1 not in list2, copy */

		if (p != buff) {
		    safe_chr(osep, buff, &p);
		}
		safe_str(ptrs1[i1], buff, &p);
		oldp = ptrs1[i1];
		i1++;
		while ((i1 < n1) && !strcmp(ptrs1[i1], oldp))
		    i1++;
	    } else {

		/* Item in list2 but not in list1, discard */

		oldp = ptrs2[i2];
		i2++;
		while ((i2 < n2) && !strcmp(ptrs2[i2], oldp))
		    i2++;
	    }
	}

	/* Copy remainder of list1 */

	while (i1 < n1) {
	    if (p != buff) {
		safe_chr(osep, buff, &p);
	    }
	    safe_str(ptrs1[i1], buff, &p);
	    oldp = ptrs1[i1];
	    i1++;
	    while ((i1 < n1) && !strcmp(ptrs1[i1], oldp))
		i1++;
	}
    }
    *p = '\0';
    free_lbuf(list1);
    free_lbuf(list2);
    return;
}

FUNCTION(fun_setunion)
{
    char sep, osep;

    svarargs_preamble("SETUNION", 4);
    handle_sets(fargs, buff, SET_UNION, sep, osep);
    return;
}

FUNCTION(fun_setdiff)
{
    char sep, osep;

    svarargs_preamble("SETDIFF", 4);
    handle_sets(fargs, buff, SET_DIFF, sep, osep);
    return;
}

FUNCTION(fun_setinter)
{
    char sep, osep;

    svarargs_preamble("SETINTER", 4);
    handle_sets(fargs, buff, SET_INTERSECT, sep, osep);
    return;
}

/* ---------------------------------------------------------------------------
 * rjust, ljust, center: Justify or center text, specifying fill character
 */

FUNCTION(fun_ljust)
{
    int spaces, i, over;
    char *bp, sep;

    varargs_preamble("LJUST", 3);
    spaces = atoi(fargs[1]) -
	strlen(mudconf.ansi_colors ? strip_ansi(fargs[0]) : fargs[0]);

    /* Sanitize number of spaces */

    if (spaces <= 0) {
	/* no padding needed, just return string */
	strcpy(buff, fargs[0]);
	return;
    } else if (spaces > LBUF_SIZE) {
	spaces = LBUF_SIZE;
    }
    bp = buff;
    over = safe_str(fargs[0], buff, &bp);
    for (i = 0; (i < spaces) && !over; i++) {
	over = safe_chr_fn(sep, buff, &bp);
    }
    *bp = '\0';
}

FUNCTION(fun_rjust)
{
    int spaces, i, over;
    char *bp, sep;

    varargs_preamble("RJUST", 3);
    spaces = atoi(fargs[1]) -
	strlen(mudconf.ansi_colors ? strip_ansi(fargs[0]) : fargs[0]);

    /* Sanitize number of spaces */

    if (spaces <= 0) {
	/* no padding needed, just return string */
	strcpy(buff, fargs[0]);
	return;
    } else if (spaces > LBUF_SIZE) {
	spaces = LBUF_SIZE;
    }
    bp = buff;
    for (i = 0, over = 0; (i < spaces) && !over; i++) {
	over = safe_chr_fn(sep, buff, &bp);
    }
    safe_str(fargs[0], buff, &bp);
    *bp = '\0';
}

FUNCTION(fun_center)
{
    char *p, sep;
    int i, len, lead_chrs, trail_chrs, width, over;

    varargs_preamble("CENTER", 3);

    width = atoi(fargs[1]);
    len = strlen(mudconf.ansi_colors ? strip_ansi(fargs[0]) : fargs[0]);

    if (len >= width) {
	strcpy(buff, fargs[0]);
	return;
    }
    p = buff;
    lead_chrs = (width / 2) - (len / 2) + .5;
    for (i = 0, over = 0; (i < lead_chrs) && !over; i++) {
	over = safe_chr_fn(sep, buff, &p);
    }
    over = safe_str(fargs[0], buff, &p);
    trail_chrs = width - lead_chrs - len;
    for (i = 0; (i < trail_chrs) && !over; i++) {
	safe_chr(sep, buff, &p);
    }
    *p = '\0';
}

/* ---------------------------------------------------------------------------
 * set: Side-effect function for setting attributes.
 */

FUNCTION(fun_set)
{
    do_set(player, cause, SET_QUIET, fargs[0], fargs[1]);
    *buff = '\0';
}

/* ---------------------------------------------------------------------------
 * setq, setr, r: set and read global registers.
 */

INLINE static int do_setq(numstr, qval, buff, flag)
    char *numstr;
    char *qval;
    char *buff;
    int flag;			/* 0 if setq, 1 if setr */
{
    int regnum;

    regnum = atoi(numstr);
    if ((regnum < 0) || (regnum >= MAX_GLOBAL_REGS)) {
	strcpy(buff, "#-1 INVALID GLOBAL REGISTER");
    } else {
	if (!mudstate.global_regs[regnum])
	    mudstate.global_regs[regnum] =
		alloc_lbuf("do_setq");
	strcpy(mudstate.global_regs[regnum], qval);
	if (flag) {
	    strcpy(buff, qval);
	} else {
	    *buff = '\0';
	}
    }
}

FUNCTION(fun_setq)
{
    do_setq(fargs[0], fargs[1], buff, 0);
}

FUNCTION(fun_setr)
{
    do_setq(fargs[0], fargs[1], buff, 1);
}

FUNCTION(fun_r)
{
    int regnum;

    regnum = atoi(fargs[0]);
    if ((regnum < 0) || (regnum >= MAX_GLOBAL_REGS)) {
	strcpy(buff, "#-1 INVALID GLOBAL REGISTER");
    } else if (mudstate.global_regs[regnum]) {
	strcpy(buff, mudstate.global_regs[regnum]);
    } else {
	*buff = '\0';
    }
}

/* ---------------------------------------------------------------------------
 * isword: is every character in the argument a letter?
 */

FUNCTION(fun_isword)
{
    char *p;

    for (p = fargs[0]; *p; p++) {
	if (!isalpha(*p)) {
	    strcpy(buff, "0");
	    return;
	}
    }
    strcpy(buff, "1");
}

/* ---------------------------------------------------------------------------
 * isnum: is the argument a number?
 */

FUNCTION(fun_isnum)
{
    strcpy(buff, (is_number(fargs[0]) ? "1" : "0"));
}

/* ---------------------------------------------------------------------------
 * isdbref: is the argument a valid dbref?
 */

FUNCTION(fun_isdbref)
{
    char *p;
    dbref dbitem;

    p = fargs[0];
    if (*p++ == NUMBER_TOKEN) {
	dbitem = parse_dbref(p);
	if (Good_obj(dbitem)) {
	    strcpy(buff, "1");
	    return;
	}
    }
    strcpy(buff, "0");
}

/* ---------------------------------------------------------------------------
 * trim: trim off unwanted white space.
 */

FUNCTION(fun_trim)
{
    char *p, *lastchar, *q, sep;
    int trim;

    if (nfargs == 0) {
	*buff = '\0';
	return;
    }
    mvarargs_preamble("TRIM", 1, 3);
    if (nfargs >= 2) {
	switch (ToLower(*fargs[1])) {
	case 'l':
	    trim = 1;
	    break;
	case 'r':
	    trim = 2;
	    break;
	default:
	    trim = 3;
	    break;
	}
    } else {
	trim = 3;
    }

    if (trim == 2 || trim == 3) {
	p = lastchar = fargs[0];
	while (*p != '\0') {
	    if (*p != sep)
		lastchar = p;
	    p++;
	}
	*(lastchar + 1) = '\0';
    }
    q = fargs[0];
    if (trim == 1 || trim == 3) {
	while (*q != '\0') {
	    if (*q == sep)
		q++;
	    else
		break;
	}
    }
    strcpy(buff, q);
}

/* ---------------------------------------------------------------------------
 * fun_squish: Squash occurrences of a given character down to 1.
 *             We do this both on leading and trailing chars, as well as
 *             internal ones; if the player wants to trim off the leading
 *             and trailing as well, they can always call trim().
 */

FUNCTION(fun_squish)
{
    char *bp, *tp, sep;

    if (nfargs == 0) {
	*buff = '\0';
	return;
    }

    varargs_preamble("SQUISH", 2);

    bp = buff;
    tp = fargs[0];

    while (*tp) {

	while (*tp && (*tp != sep))       /* copy non-seps */
	    *bp++ = *tp++;

	if (!*tp) {		/* end of string */
	    *bp = '\0';
	    return;
	}

	/* We've hit a sep char. Copy it, then skip to the next non-sep. */
	*bp++ = *tp++;
	while (*tp && (*tp == sep))
	    tp++;
    }

    *bp = '\0';
}

/* ---------------------------------------------------------------------------
 * Object stack functions.
 */

void stack_clr(thing)
    dbref thing;
{
    STACK *sp, *tp, *xp;

    sp = stack_get(thing);
    if (sp) {
	for (tp = sp; tp != NULL; ) {
	    XFREE(tp->data, "stack_clr_data");
	    xp = tp;
	    tp = tp->next;
	    XFREE(xp, "stack_clr");
	}
	nhashdelete(thing, &mudstate.objstack_htab);
    }
}

static void stack_set(thing, sp)
    dbref thing;
    STACK *sp;
{
    STACK *xsp;
    int stat;

    if (!sp) {
	nhashdelete(thing, &mudstate.objstack_htab);
	return;
    }

    xsp = stack_get(thing);
    if (xsp) {
	stat = nhashrepl(thing, (int *) sp, &mudstate.objstack_htab);
    } else {
	stat = nhashadd(thing, (int *) sp, &mudstate.objstack_htab);
    }
    if (stat < 0) {		/* failure for some reason */
	STARTLOG(LOG_BUGS, "STK", "SET")
	    log_name(thing);
	ENDLOG
	stack_clr(thing);
    }
}

FUNCTION(fun_empty)
{
    dbref it;

    xvarargs_preamble("EMPTY", 0, 1);

    *buff = '\0';

    if (!fargs[0]) {
	it = player;
    } else {
	stack_object(player, it);
    }

    stack_clr(it);
}

FUNCTION(fun_items)
{
    dbref it;
    int i;
    STACK *sp;

    *buff = '\0';

    if (!fargs[0]) {
	it = player;
    } else {
	stack_object(player, it);
    }

    for (i = 0, sp = stack_get(it); sp != NULL; sp = sp->next, i++)
	;
    ltos(buff, i);
}
    

FUNCTION(fun_push)
{
    dbref it;
    char *data;
    STACK *sp;

    xvarargs_preamble("PUSH", 1, 2);

    *buff = '\0';

    if (!fargs[1]) {
	it = player;
	data = fargs[0];
    } else {
	stack_object(player, it);
	data = fargs[1];
    }

    sp = (STACK *) XMALLOC(sizeof(STACK), "stack_push");
    if (!sp)			/* out of memory, ouch */
	return;
    sp->next = stack_get(it);
    sp->data = (char *) XMALLOC(sizeof(char *) * (strlen(data) + 1),
				"stack_push_data");
    if (! sp->data)
	return;
    strcpy(sp->data, data);
    stack_set(it, sp);
}

FUNCTION(fun_dup)
{
    dbref it;
    STACK *hp;			/* head of stack */
    STACK *tp;			/* temporary stack pointer */
    STACK *sp;			/* new stack element */
    int pos, count = 0;

    xvarargs_preamble("DUP", 0, 2);

    *buff = '\0';

    if (!fargs[0]) {
	it = player;
    } else {
	stack_object(player, it);
    }

    if (!fargs[1] || !*fargs[1]) {
	pos = 0;
    } else {
	pos = atoi(fargs[1]);
    }

    hp = stack_get(it);
    for (tp = hp; (count != pos) && (tp != NULL); count++, tp = tp->next)
	;
    if (!tp) {
	notify_quiet(player, "No such item on stack.");
	return;
    }

    sp = (STACK *) XMALLOC(sizeof(STACK), "stack_dup");
    if (!sp)
	return;
    sp->next = hp;
    sp->data = (char *) XMALLOC(sizeof(char *) * (strlen(tp->data) + 1),
				"stack_dup_data");
    if (!sp->data)
	return;
    strcpy(sp->data, tp->data);
    stack_set(it, sp);
}

FUNCTION(fun_swap)
{
    dbref it;
    STACK *sp, *tp;

    xvarargs_preamble("SWAP", 0, 1);

    *buff = '\0';

    if (!fargs[0]) {
	it = player;
    } else {
	stack_object(player, it);
    }

    sp = stack_get(it);
    if (sp->next == NULL) {
	notify_quiet(player, "Not enough items on stack.");
	return;
    }

    tp = sp->next;
    sp->next = tp->next;
    tp->next = sp;
    stack_set(it, tp);
}

FUNCTION(fun_pop)
{
    dbref it;
    int pos, count = 0;
    STACK *sp, *prev;

    xvarargs_preamble("POP", 0, 2);

    *buff = '\0';

    if (!fargs[0]) {
	it = player;
    } else {
	stack_object(player, it);
    }
    if (!fargs[1] || !*fargs[1]) {
	pos = 0;
    } else {
	pos = atoi(fargs[1]);
    }

    sp = stack_get(it);
    if (!sp)
	return;

    while (count != pos) {
	if (!sp)
	    return;
	prev = sp;
	sp = sp->next;
	count++;
    }
    if (!sp)
	return;

    strcpy(buff, sp->data);
    if (count == 0) {
	stack_set(it, sp->next);
    } else {
	prev->next = sp->next;
    }
    XFREE(sp->data, "stack_pop_data");
    XFREE(sp, "stack_pop");
}

FUNCTION(fun_popn)
{
    dbref it;
    int pos, nitems, i, count = 0, over = 0;
    STACK *sp, *prev, *tp, *xp;
    char sep, *bp;

    varargs_preamble("POPN", 4);

    *buff = '\0';

    stack_object(player, it);
    pos = atoi(fargs[1]);
    nitems = atoi(fargs[2]);

    sp = stack_get(it);
    if (!sp)
	return;

    while (count != pos) {
	if (!sp)
	    return;
	prev = sp;
	sp = sp->next;
	count++;
    }
    if (!sp)
	return;

    /* We've now hit the start item, the first item. Copy 'em off. */

    for (bp = buff, i = 0, tp = sp;
	 (i < nitems) && (tp != NULL);
	 i++) {
	if (!over) {
	    /* We have to pop off the items regardless of whether
	     * or not there's an overflow, but we can save ourselves
	     * some copying if so.
	     */
	    if (bp != buff)
		safe_chr(sep, buff, &bp);
	    over = safe_str(tp->data, buff, &bp);
	}
	xp = tp;
	tp = tp->next;
	XFREE(xp->data, "stack_popn_data");
	XFREE(xp, "stack_popn");
    }

    /* Relink the chain. */

    *bp = '\0';
    if (count == 0) {
	stack_set(it, tp);
    } else {
	prev->next = tp;
    }
}

FUNCTION(fun_peek)
{
    dbref it;
    int pos, count = 0;
    STACK *sp;

    xvarargs_preamble("POP", 0, 2);

    *buff = '\0';

    if (!fargs[0]) {
	it = player;
    } else {
	stack_object(player, it);
    }
    if (!fargs[1] || !*fargs[1]) {
	pos = 0;
    } else {
	pos = atoi(fargs[1]);
    }

    sp = stack_get(it);
    if (!sp)
	return;

    while (count != pos) {
	if (!sp)
	    return;
	sp = sp->next;
	count++;
    }
    if (!sp)
	return;

    strcpy(buff, sp->data);
}

FUNCTION(fun_lstack)
{
    char sep;
    dbref it;
    STACK *sp;
    char *bp;
    int over = 0;

    mvarargs_preamble("LSTACK", 0, 2);

    *buff = '\0';

    if (!fargs[0]) {
	it = player;
    } else {
	stack_object(player, it);
    }

    bp = buff;
    for (sp = stack_get(it); (sp != NULL) && !over; sp = sp->next) {
	if (bp != buff)
	    safe_chr(sep, buff, &bp);
	over = safe_str(sp->data, buff, &bp);
    }
    *bp = '\0';
}

/* ---------------------------------------------------------------------------
 * ANSI functions. Derived from PennMUSH/TinyMUX.
 */

FUNCTION(fun_ansi)
{
    char *bufc;
    char *s;

    if (!mudconf.ansi_colors) {
	strcpy(buff, fargs[1]);
	return;
    }

    s = fargs[0];
    bufc = buff;
    while (*s) {
	switch (*s) {
	    case 'h':
		safe_str(ANSI_HILITE, buff, &bufc);
		break;
	    case 'i':
		safe_str(ANSI_INVERSE, buff, &bufc);
		break;
	    case 'f':
		safe_str(ANSI_BLINK, buff, &bufc);
		break;
	    case 'n':
		safe_str(ANSI_NORMAL, buff, &bufc);
		break;
	    case 'x':
		safe_str(ANSI_BLACK, buff, &bufc);
		break;
	    case 'r':
		safe_str(ANSI_RED, buff, &bufc);
		break;
	    case 'g':
		safe_str(ANSI_GREEN, buff, &bufc);
		break;
	    case 'y':
		safe_str(ANSI_YELLOW, buff, &bufc);
		break;
	    case 'b':
		safe_str(ANSI_BLUE, buff, &bufc);
		break;
	    case 'm':
		safe_str(ANSI_MAGENTA, buff, &bufc);
		break;
	    case 'c':
		safe_str(ANSI_CYAN, buff, &bufc);
		break;
	    case 'w':
		safe_str(ANSI_WHITE, buff, &bufc);
		break;
	    case 'X':
		safe_str(ANSI_BBLACK, buff, &bufc);
		break;
	    case 'R':
		safe_str(ANSI_BRED, buff, &bufc);
		break;
	    case 'G':
		safe_str(ANSI_BGREEN, buff, &bufc);
		break;
	    case 'Y':
		safe_str(ANSI_BYELLOW, buff, &bufc);
		break;
	    case 'B':
		safe_str(ANSI_BBLUE, buff, &bufc);
		break;
	    case 'M':
		safe_str(ANSI_BMAGENTA, buff, &bufc);
		break;
	    case 'C':
		safe_str(ANSI_BCYAN, buff, &bufc);
		break;
	    case 'W':
		safe_str(ANSI_BWHITE, buff, &bufc);
		break;
	}
	s++;
    }
    safe_str(fargs[1], buff, &bufc);
    safe_str(ANSI_NORMAL, buff, &bufc);
    *bufc = '\0';
}

FUNCTION(fun_stripansi)
{
    /* strip ANSI codes regardless of whether or not ANSI is on */

    strcpy(buff, strip_ansi(fargs[0]));
}


/* ---------------------------------------------------------------------------
 * flist: List of existing functions in alphabetical order.
 */

FUN flist[] =
{
    {"ABS", fun_abs, 1, 0, CA_PUBLIC},
    {"ACOS", fun_acos, 1, 0, CA_PUBLIC},
    {"ADD", fun_add, 0, FN_VARARGS, CA_PUBLIC},
    {"AFTER", fun_after, 0, FN_VARARGS, CA_PUBLIC},
    {"AND", fun_and, 0, FN_VARARGS, CA_PUBLIC},
    {"ANDBOOL", fun_andbool, 0, FN_VARARGS, CA_PUBLIC},
    {"ANDFLAGS", fun_andflags, 2, 0, CA_PUBLIC},
    {"ANSI", fun_ansi, 2, 0, CA_PUBLIC},
    {"APOSS", fun_aposs, 1, 0, CA_PUBLIC},
    {"ASIN", fun_asin, 1, 0, CA_PUBLIC},
    {"ATAN", fun_atan, 1, 0, CA_PUBLIC},
    {"BAND", fun_band, 2, 0, CA_PUBLIC},
    {"BEFORE", fun_before, 0, FN_VARARGS, CA_PUBLIC},
    {"BNAND", fun_bnand, 2, 0, CA_PUBLIC},
    {"BOR", fun_bor, 0, FN_VARARGS, CA_PUBLIC},
    {"CAPSTR", fun_capstr, -1, 0, CA_PUBLIC},
    {"CAT", fun_cat, 0, FN_VARARGS, CA_PUBLIC},
    {"CEIL", fun_ceil, 1, 0, CA_PUBLIC},
    {"CENTER", fun_center, 0, FN_VARARGS, CA_PUBLIC},
    {"COMP", fun_comp, 2, 0, CA_PUBLIC},
    {"CON", fun_con, 1, 0, CA_PUBLIC},
    {"CONN", fun_conn, 1, 0, CA_PUBLIC},
    {"CONTROLS", fun_controls, 2, 0, CA_PUBLIC},
    {"CONVSECS", fun_convsecs, 1, 0, CA_PUBLIC},
    {"CONVTIME", fun_convtime, 1, 0, CA_PUBLIC},
    {"COS", fun_cos, 1, 0, CA_PUBLIC},
    {"DEFAULT", fun_default, 2, FN_NO_EVAL, CA_PUBLIC},
    {"DELETE", fun_delete, 3, 0, CA_PUBLIC},
    {"DIE", fun_die, 2, 0, CA_PUBLIC},
    {"DIST2D", fun_dist2d, 4, 0, CA_PUBLIC},
    {"DIST3D", fun_dist3d, 6, 0, CA_PUBLIC},
    {"DIV", fun_div, 2, 0, CA_PUBLIC},
    {"DUP", fun_dup, 0, FN_VARARGS, CA_PUBLIC},
    {"E", fun_e, 0, 0, CA_PUBLIC},
    {"EDEFAULT", fun_edefault, 2, FN_NO_EVAL, CA_PUBLIC},
    {"EDIT", fun_edit, 3, 0, CA_PUBLIC},
    {"ELEMENTS", fun_elements, 0, FN_VARARGS, CA_PUBLIC},
    {"ELOCK", fun_elock, 2, 0, CA_PUBLIC},
    {"EMPTY", fun_empty, 0, FN_VARARGS, CA_PUBLIC},
    {"EQ", fun_eq, 2, 0, CA_PUBLIC},
    {"ESCAPE", fun_escape, -1, 0, CA_PUBLIC},
    {"EXIT", fun_exit, 1, 0, CA_PUBLIC},
    {"EXP", fun_exp, 1, 0, CA_PUBLIC},
    {"EXTRACT", fun_extract, 0, FN_VARARGS, CA_PUBLIC},
    {"FDIV", fun_fdiv, 2, 0, CA_PUBLIC},
    {"FILTER", fun_filter, 0, FN_VARARGS, CA_PUBLIC},
    {"FIRST", fun_first, 0, FN_VARARGS, CA_PUBLIC},
    {"FLAGS", fun_flags, 1, 0, CA_PUBLIC},
    {"FLOOR", fun_floor, 1, 0, CA_PUBLIC},
    {"FOLD", fun_fold, 0, FN_VARARGS, CA_PUBLIC},
    {"FOREACH", fun_foreach, 0, FN_VARARGS, CA_PUBLIC},
    {"FINDABLE", fun_findable, 2, 0, CA_PUBLIC},
    {"FULLNAME", fun_fullname, 1, 0, CA_PUBLIC},
    {"GET", fun_get, 1, 0, CA_PUBLIC},
    {"GET_EVAL", fun_get_eval, 1, 0, CA_PUBLIC},
    {"GRAB", fun_grab, 0, FN_VARARGS, CA_PUBLIC},
    {"GT", fun_gt, 2, 0, CA_PUBLIC},
    {"GTE", fun_gte, 2, 0, CA_PUBLIC},
    {"HASATTR", fun_hasattr, 2, 0, CA_PUBLIC},
    {"HASFLAG", fun_hasflag, 2, 0, CA_PUBLIC},
    {"HOME", fun_home, 1, 0, CA_PUBLIC},
#ifdef PUEBLO_SUPPORT
    {"HTML_ESCAPE", fun_html_escape, -1, 0, CA_PUBLIC},
    {"HTML_UNESCAPE", fun_html_unescape, -1, 0, CA_PUBLIC},
#endif /* PUEBLO_SUPPORT */
    {"IDLE", fun_idle, 1, 0, CA_PUBLIC},
    {"IFELSE", fun_ifelse, 3, FN_NO_EVAL, CA_PUBLIC},
    {"INDEX", fun_index, 4, 0, CA_PUBLIC},
    {"INSERT", fun_insert, 0, FN_VARARGS, CA_PUBLIC},
    {"ISDBREF", fun_isdbref, 1, 0, CA_PUBLIC},
    {"ISNUM", fun_isnum, 1, 0, CA_PUBLIC},
    {"ISWORD", fun_isword, 1, 0, CA_PUBLIC},
    {"ITEMS", fun_items, 0, FN_VARARGS, CA_PUBLIC},
    {"ITER", fun_iter, 0, FN_VARARGS | FN_NO_EVAL, CA_PUBLIC},
    {"LAST", fun_last, 0, FN_VARARGS, CA_PUBLIC},
    {"LASTCREATE", fun_lastcreate, 2, 0, CA_PUBLIC},
    {"LATTR", fun_lattr, 1, 0, CA_PUBLIC},
    {"LCON", fun_lcon, 1, 0, CA_PUBLIC},
    {"LCSTR", fun_lcstr, -1, 0, CA_PUBLIC},
    {"LDELETE", fun_ldelete, 0, FN_VARARGS, CA_PUBLIC},
    {"LEFT", fun_left, 2, 0, CA_PUBLIC},
    {"LEXITS", fun_lexits, 1, 0, CA_PUBLIC},
    {"LIST", fun_list, 0, FN_VARARGS | FN_NO_EVAL, CA_PUBLIC},
    {"LIT", fun_lit, 1, FN_NO_EVAL, CA_PUBLIC},
    {"LJUST", fun_ljust, 0, FN_VARARGS, CA_PUBLIC},
    {"LN", fun_ln, 1, 0, CA_PUBLIC},
    {"LNUM", fun_lnum, 0, FN_VARARGS, CA_PUBLIC},
    {"LOC", fun_loc, 1, 0, CA_PUBLIC},
    {"LOCATE", fun_locate, 3, 0, CA_PUBLIC},
    {"LOCK", fun_lock, 1, 0, CA_PUBLIC},
    {"LOG", fun_log, 1, 0, CA_PUBLIC},
    {"LPOS", fun_lpos, 2, 0, CA_PUBLIC},
    {"LSTACK", fun_lstack, 0, FN_VARARGS, CA_PUBLIC},
    {"LT", fun_lt, 2, 0, CA_PUBLIC},
    {"LTE", fun_lte, 2, 0, CA_PUBLIC},
    {"LWHO", fun_lwho, 0, 0, CA_PUBLIC},
    {"MAP", fun_map, 0, FN_VARARGS, CA_PUBLIC},
    {"MATCH", fun_match, 0, FN_VARARGS, CA_PUBLIC},
    {"MATCHALL", fun_matchall, 0, FN_VARARGS, CA_PUBLIC},
    {"MAX", fun_max, 0, FN_VARARGS, CA_PUBLIC},
    {"MEMBER", fun_member, 0, FN_VARARGS, CA_PUBLIC},
    {"MERGE", fun_merge, 3, 0, CA_PUBLIC},
    {"MID", fun_mid, 3, 0, CA_PUBLIC},
    {"MIN", fun_min, 0, FN_VARARGS, CA_PUBLIC},
    {"MIX", fun_mix, 0, FN_VARARGS, CA_PUBLIC},
    {"MOD", fun_mod, 2, 0, CA_PUBLIC},
    {"MONEY", fun_money, 1, 0, CA_PUBLIC},
    {"MUDNAME", fun_mudname, 0, 0, CA_PUBLIC},
    {"MUL", fun_mul, 0, FN_VARARGS, CA_PUBLIC},
    {"MUNGE", fun_munge, 0, FN_VARARGS, CA_PUBLIC},
    {"NAME", fun_name, 1, 0, CA_PUBLIC},
    {"NEARBY", fun_nearby, 2, 0, CA_PUBLIC},
    {"NEQ", fun_neq, 2, 0, CA_PUBLIC},
    {"NEXT", fun_next, 1, 0, CA_PUBLIC},
    {"NOT", fun_not, 1, 0, CA_PUBLIC},
    {"NOTBOOL", fun_notbool, 1, 0, CA_PUBLIC},
    {"NUM", fun_num, 1, 0, CA_PUBLIC},
    {"OBJ", fun_obj, 1, 0, CA_PUBLIC},
    {"OBJEVAL", fun_objeval, 2, FN_NO_EVAL, CA_PUBLIC},
    {"OBJMEM", fun_objmem, 1, 0, CA_PUBLIC},
    {"OR", fun_or, 0, FN_VARARGS, CA_PUBLIC},
    {"ORBOOL", fun_orbool, 0, FN_VARARGS, CA_PUBLIC},
    {"ORFLAGS", fun_orflags, 2, 0, CA_PUBLIC},
    {"OWNER", fun_owner, 1, 0, CA_PUBLIC},
    {"PARENT", fun_parent, 1, 0, CA_PUBLIC},
    {"PARSE", fun_iter, 0, FN_VARARGS | FN_NO_EVAL, CA_PUBLIC},
    {"PEEK", fun_peek, 0, FN_VARARGS, CA_PUBLIC},
    {"PI", fun_pi, 0, 0, CA_PUBLIC},
    {"POP", fun_pop, 0, FN_VARARGS, CA_PUBLIC},
    {"POPN", fun_popn, 0, FN_VARARGS, CA_PUBLIC},
    {"PORTS", fun_ports, 1, 0, CA_PUBLIC},
    {"POS", fun_pos, 2, 0, CA_PUBLIC},
    {"POSS", fun_poss, 1, 0, CA_PUBLIC},
    {"POWER", fun_power, 2, 0, CA_PUBLIC},
    {"PROGRAMMER", fun_programmer, 1, 0, CA_PUBLIC},
    {"PUSH", fun_push, 0, FN_VARARGS, CA_PUBLIC},
    {"R", fun_r, 1, 0, CA_PUBLIC},
    {"RAND", fun_rand, 1, 0, CA_PUBLIC},
    {"REGMATCH", fun_regmatch, 0, FN_VARARGS, CA_PUBLIC},
    {"REMOVE", fun_remove, 0, FN_VARARGS, CA_PUBLIC},
    {"REPEAT", fun_repeat, 2, 0, CA_PUBLIC},
    {"REPLACE", fun_replace, 0, FN_VARARGS, CA_PUBLIC},
    {"REST", fun_rest, 0, FN_VARARGS, CA_PUBLIC},
    {"RESTARTS", fun_restarts, 0, 0, CA_PUBLIC},
    {"RESTARTTIME", fun_restarttime, 0, 0, CA_PUBLIC},
    {"REVERSE", fun_reverse, -1, 0, CA_PUBLIC},
    {"REVWORDS", fun_revwords, 0, FN_VARARGS, CA_PUBLIC},
    {"RIGHT", fun_right, 2, 0, CA_PUBLIC},
    {"RJUST", fun_rjust, 0, FN_VARARGS, CA_PUBLIC},
    {"RLOC", fun_rloc, 2, 0, CA_PUBLIC},
    {"ROOM", fun_room, 1, 0, CA_PUBLIC},
    {"ROUND", fun_round, 2, 0, CA_PUBLIC},
    {"S", fun_s, -1, 0, CA_PUBLIC},
    {"SCRAMBLE", fun_scramble, 1, 0, CA_PUBLIC},
    {"SEARCH", fun_search, -1, 0, CA_PUBLIC},
    {"SECS", fun_secs, 0, 0, CA_PUBLIC},
    {"SECURE", fun_secure, -1, 0, CA_PUBLIC},
    {"SEES", fun_sees, 2, 0, CA_PUBLIC},
    {"SET", fun_set, 2, 0, CA_PUBLIC},
    {"SETDIFF", fun_setdiff, 0, FN_VARARGS, CA_PUBLIC},
    {"SETINTER", fun_setinter, 0, FN_VARARGS, CA_PUBLIC},
    {"SETQ", fun_setq, 2, 0, CA_PUBLIC},
    {"SETR", fun_setr, 2, 0, CA_PUBLIC},
    {"SETUNION", fun_setunion, 0, FN_VARARGS, CA_PUBLIC},
    {"SHL", fun_shl, 2, 0, CA_PUBLIC},
    {"SHR", fun_shr, 2, 0, CA_PUBLIC},
    {"SHUFFLE", fun_shuffle, 0, FN_VARARGS, CA_PUBLIC},
    {"SIGN", fun_sign, 1, 0, CA_PUBLIC},
    {"SIN", fun_sin, 1, 0, CA_PUBLIC},
    {"SORT", fun_sort, 0, FN_VARARGS, CA_PUBLIC},
    {"SORTBY", fun_sortby, 0, FN_VARARGS, CA_PUBLIC},
    {"SPACE", fun_space, 1, 0, CA_PUBLIC},
    {"SPLICE", fun_splice, 0, FN_VARARGS, CA_PUBLIC},
    {"SQRT", fun_sqrt, 1, 0, CA_PUBLIC},
    {"SQUISH", fun_squish, 0, FN_VARARGS, CA_PUBLIC},
    {"STARTTIME", fun_starttime, 0, 0, CA_PUBLIC},
    {"STATS", fun_stats, 1, 0, CA_PUBLIC},
    {"STRIPANSI", fun_stripansi, 1, 0, CA_PUBLIC},
    {"STRLEN", fun_strlen, -1, 0, CA_PUBLIC},
    {"STRMATCH", fun_strmatch, 2, 0, CA_PUBLIC},
    {"SUB", fun_sub, 2, 0, CA_PUBLIC},
    {"SUBJ", fun_subj, 1, 0, CA_PUBLIC},
    {"SWAP", fun_swap, 0, FN_VARARGS, CA_PUBLIC},
    {"SWITCH", fun_switch, 0, FN_VARARGS | FN_NO_EVAL,
     CA_PUBLIC},
    {"TAN", fun_tan, 1, 0, CA_PUBLIC},
#ifdef TCL_INTERP_SUPPORT
    {"TCLCLEAR", fun_tclclear, 0, 0, CA_TICKLER},
    {"TCLEVAL", fun_tcleval, 0, FN_VARARGS, CA_TICKLER},
    {"TCLPARAMS", fun_tclparams, 0, FN_VARARGS, CA_TICKLER},
    {"TCLREGS", fun_tclregs, 0, 0, CA_TICKLER},
#endif /* TCL_INTERP_SUPPORT */
    {"TIME", fun_time, 0, 0, CA_PUBLIC},
    {"TRANSLATE", fun_translate, 2, 0, CA_PUBLIC},
    {"TRIM", fun_trim, 0, FN_VARARGS, CA_PUBLIC},
    {"TRUNC", fun_trunc, 1, 0, CA_PUBLIC},
    {"TYPE", fun_type, 1, 0, CA_PUBLIC},
    {"U", fun_u, 0, FN_VARARGS, CA_PUBLIC},
    {"UCSTR", fun_ucstr, -1, 0, CA_PUBLIC},
    {"UDEFAULT", fun_udefault, 0, FN_VARARGS | FN_NO_EVAL, CA_PUBLIC},
    {"ULOCAL", fun_ulocal, 0, FN_VARARGS, CA_PUBLIC},
#ifdef PUEBLO_SUPPORT
    {"URL_ESCAPE", fun_url_escape, -1, 0, CA_PUBLIC},
    {"URL_UNESCAPE", fun_url_unescape, -1, 0, CA_PUBLIC},
#endif /* PUEBLO_SUPPORT */
    {"V", fun_v, 1, 0, CA_PUBLIC},
    {"VADD", fun_vadd, 0, FN_VARARGS, CA_PUBLIC},
    {"VDIM", fun_vdim, 0, FN_VARARGS, CA_PUBLIC},
    {"VDOT", fun_vdot, 0, FN_VARARGS, CA_PUBLIC},
    {"VERSION", fun_version, 0, 0, CA_PUBLIC},
    {"VISIBLE", fun_visible, 2, 0, CA_PUBLIC},
    {"VMAG", fun_vmag, 0, FN_VARARGS, CA_PUBLIC},
    {"VMUL", fun_vmul, 0, FN_VARARGS, CA_PUBLIC},
    {"VSUB", fun_vsub, 0, FN_VARARGS, CA_PUBLIC},
    {"VUNIT", fun_vunit, 0, FN_VARARGS, CA_PUBLIC},
    {"WHERE", fun_where, 1, 0, CA_PUBLIC},
    {"WORDPOS", fun_wordpos, 0, FN_VARARGS, CA_PUBLIC},
    {"WORDS", fun_words, 0, FN_VARARGS, CA_PUBLIC},
    {"XCON", fun_xcon, 3, 0, CA_PUBLIC},
    {"XOR", fun_xor, 0, FN_VARARGS, CA_PUBLIC},
    {"XORBOOL", fun_xorbool, 0, FN_VARARGS, CA_PUBLIC},
    {NULL, NULL, 0, 0, 0}
};

void 
NDECL(init_functab)
{
    FUN *fp;
    char *buff, *cp, *dp;

    buff = alloc_sbuf("init_functab");
    hashinit(&mudstate.func_htab, 217);
    for (fp = flist; fp->name; fp++) {
	cp = (char *) fp->name;
	dp = buff;
	while (*cp) {
	    *dp = ToLower(*cp);
	    cp++;
	    dp++;
	}
	*dp = '\0';
	hashadd(buff, (int *) fp, &mudstate.func_htab);
    }
    free_sbuf(buff);

    ufun_head = NULL;
    hashinit(&mudstate.ufunc_htab, 11);
}

void 
do_function(player, cause, key, fname, target)
    dbref player, cause;
    int key;
    char *fname, *target;
{
    UFUN *ufp, *ufp2;
    ATTR *ap;
    char *np, *bp;
    int atr, aflags;
    dbref obj, aowner;

    /* Make a local uppercase copy of the function name */

    bp = np = alloc_sbuf("add_user_func");
    safe_sb_str(fname, np, &bp);
    for (bp = np; *bp; bp++)
	*bp = ToLower(*bp);

    /* Verify that the function doesn't exist in the builtin table */

    if (hashfind(np, &mudstate.func_htab) != NULL) {
	notify_quiet(player,
		     "Function already defined in builtin function table.");
	free_sbuf(np);
	return;
    }
    /* Make sure the target object exists */

    if (!parse_attrib(player, target, &obj, &atr)) {
	notify_quiet(player, "I don't see that here.");
	free_sbuf(np);
	return;
    }
    /* Make sure the attribute exists */

    if (atr == NOTHING) {
	notify_quiet(player, "No such attribute.");
	free_sbuf(np);
	return;
    }
    /* Make sure attribute is readably by me */

    ap = atr_num(atr);
    if (!ap) {
	notify_quiet(player, "No such attribute.");
	free_sbuf(np);
	return;
    }
    atr_get_info(obj, atr, &aowner, &aflags);
    if (!See_attr(player, obj, ap, aowner, aflags)) {
	notify_quiet(player, "Permission denied.");
	free_sbuf(np);
	return;
    }
    /* Privileged functions require you control the obj.  */

    if ((key & FN_PRIV) && !Controls(player, obj)) {
	notify_quiet(player, "Permission denied.");
	free_sbuf(np);
	return;
    }
    /* See if function already exists.  If so, redefine it */

    ufp = (UFUN *) hashfind(np, &mudstate.ufunc_htab);

    if (!ufp) {
	ufp = (UFUN *) XMALLOC(sizeof(UFUN), "do_function");
	ufp->name = strsave(np);
	for (bp = (char *) ufp->name; *bp; bp++)
	    *bp = ToUpper(*bp);
	ufp->obj = obj;
	ufp->atr = atr;
	ufp->perms = CA_PUBLIC;
	ufp->next = NULL;
	if (!ufun_head) {
	    ufun_head = ufp;
	} else {
	    for (ufp2 = ufun_head; ufp2->next; ufp2 = ufp2->next);
	    ufp2->next = ufp;
	}
	if (hashadd(np, (int *) ufp, &mudstate.ufunc_htab)) {
	    notify_quiet(player, tprintf("Function %s not defined.", fname));
	    XFREE((char *) ufp->name, "do_function");
	    XFREE(ufp, "do_function.2");
	    free_sbuf(np);
	    return;
	}
    }
    ufp->obj = obj;
    ufp->atr = atr;
    ufp->flags = key;
    free_sbuf(np);
    if (!Quiet(player))
	notify_quiet(player, tprintf("Function %s defined.", fname));
}

/* ---------------------------------------------------------------------------
 * list_functable: List available functions.
 */

void 
list_functable(player)
    dbref player;
{
    FUN *fp;
    UFUN *ufp;
    char *buf, *bp, *cp;

    buf = alloc_lbuf("list_functable");
    bp = buf;
    for (cp = (char *) "Functions:"; *cp; cp++)
	*bp++ = *cp;
    for (fp = flist; fp->name; fp++) {
	if (check_access(player, fp->perms)) {
	    *bp++ = ' ';
	    for (cp = (char *) (fp->name); *cp; cp++)
		*bp++ = *cp;
	}
    }
    for (ufp = ufun_head; ufp; ufp = ufp->next) {
	if (check_access(player, ufp->perms)) {
	    *bp++ = ' ';
	    for (cp = (char *) (ufp->name); *cp; cp++)
		*bp++ = *cp;
	}
    }
    *bp = '\0';
    notify(player, buf);
    free_lbuf(buf);
}

/* ---------------------------------------------------------------------------
 * cf_func_access: set access on functions
 */

CF_HAND(cf_func_access)
{
    FUN *fp;
    UFUN *ufp;
    char *ap;

    for (ap = str; *ap && !isspace(*ap); ap++);
    if (*ap)
	*ap++ = '\0';

    for (fp = flist; fp->name; fp++) {
	if (!string_compare(fp->name, str)) {
	    return (cf_modify_bits(&fp->perms, ap, extra,
				   player, cmd));
	}
    }
    for (ufp = ufun_head; ufp; ufp = ufp->next) {
	if (!string_compare(ufp->name, str)) {
	    return (cf_modify_bits(&ufp->perms, ap, extra,
				   player, cmd));
	}
    }
    cf_log_notfound(player, cmd, "Function", str);
    return -1;
}