/* 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"

#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));

/* 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;

/* This is for functions that take an optional delimiter character */

#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 evarargs_preamble(xname, xnargs)                        \
if (!fn_range_check(xname, nfargs, xnargs-1, xnargs, buff))     \
return;                                                         \
if (!delim_check(fargs, nfargs, xnargs, &sep, buff, 1,          \
    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;

/* 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_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;
#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_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_parse: Make list from evaluating arg3 with each member of arg2.
 * arg1 specifies a delimiter character to use in the parsing of arg2.
 * NOTE: This function expects that its arguments have not been evaluated.
 */

FUNCTION(fun_parse)
{
    char *curr, *objstring, *buff2, *result, *bp, *cp, sep;
    int first;

    evarargs_preamble("PARSE", 3);
    cp = curr = exec(player, cause, EV_STRIP | EV_FCHECK | EV_EVAL, fargs[0],
		     cargs, ncargs);
    bp = buff;
    cp = trim_space_sep(cp, sep);
    if (!*cp) {
	free_lbuf(curr);
	*buff = '\0';
	return;
    }
    first = 1;
    while (cp && (mudstate.func_invk_ctr < mudconf.func_invk_lim)) {
	objstring = split_token(&cp, sep);
	buff2 = replace_string(BOUND_VAR, objstring, fargs[1]);
	result = exec(player, cause,
		      EV_STRIP | EV_FCHECK | EV_EVAL, buff2, cargs, ncargs);
	free_lbuf(buff2);
	if (!first) {
	    safe_chr(' ', buff, &bp);
	}
	first = 0;
	safe_str(result, buff, &bp);
	free_lbuf(result);
    }
    free_lbuf(curr);
    *bp = '\0';
}

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

FUNCTION(fun_mid)
{
    int l, len;

    l = atoi(fargs[1]);
    len = atoi(fargs[2]);
    if ((l < 0) || (len < 0) || ((len + l) > LBUF_SIZE)) {
	strcpy(buff, "#-1 OUT OF RANGE");
	return;
    }
    if (l < strlen(fargs[0]))
	strcpy(buff, fargs[0] + 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, fargs[0]);
	if (len < LBUF_SIZE)
	    buff[len] = '\0';
    }
}

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

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

    if (len < 1) {
	*buff = '\0';
    } else {
	len = strlen(fargs[0]) - len;
	if (len < 1)
	    strcpy(buff, fargs[0]);
	else
	    strcpy(buff, fargs[0] + 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");
    }

    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_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);

    if (bp != buff)
	*bp = '\0';
    else
	*buff = '\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_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;

    varargs_preamble("ELEMENTS", 3);

    /* 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(sep, 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(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]));
}

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);
}



/* ---------------------------------------------------------------------------
 * 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 first, 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;
    first = 1;
    while (s) {
	sp = split_token(&s, sep);
	if (found || strcmp(sp, word)) {
	    if (!first) {
		safe_chr(sep, buff, &bp);
	    }
	    safe_str(sp, buff, &bp);
	    first = 0;
	} 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");
}

/* ---------------------------------------------------------------------------
 * fun_secure, fun_escape: escape [, ], %, \, and the beginning of the string.
 */

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';
}

/*---------------------------------------------------------------------------
 * 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("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, name, fstr, type)
     dbref player;
     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;

    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))
		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, fargs[0], fargs[1], 0));
}

FUNCTION(fun_andflags)
{
    ltos(buff, handle_flaglists(player, 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) {
	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 ((attr->number == A_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_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;
    int first;

    /* 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;
    first = 1;
    while (tp) {
	if (!first) {
	    safe_chr(sep, buff, &bp);
	}
	t1 = split_token(&tp, sep);
	do_reverse(t1, bp);
	while (*bp)
	    bp++;
	first = 0;
    }
    *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;
    int words, i, first;

    varargs_preamble("SPLICE", 4);

    /* 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];
    first = 1;
    for (i = 0; i < words; i++) {
	p2 = split_token(&p1, sep);
	q2 = split_token(&q1, sep);
	if (!first) {
	    safe_chr(sep, buff, &bp);
	}
	if (!strcmp(p2, fargs[2]))
	    safe_str(q2, buff, &bp);	/* replace */
	else
	    safe_str(p2, buff, &bp);	/* copy */
	first = 0;
    }
    *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;

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

    /* 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(sep, 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;
    char *bp;

    times = atoi(fargs[1]);
    if (times < 1) {
	*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; i < times; i++)
	    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;

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

    varargs_preamble("SHUFFLE", 2);
    
    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, sep);
}

/* ---------------------------------------------------------------------------
 * fun_iter: Make list from evaluating arg2 with each member of arg1.
 * NOTE: This function expects that its arguments have not been evaluated.
 */

FUNCTION(fun_iter)
{
    char *curr, *objstring, *buff2, *result, *bp, *cp, sep;
    int first;

    evarargs_preamble("ITER", 3);
    cp = curr = exec(player, cause, EV_STRIP | EV_FCHECK | EV_EVAL, fargs[0],
		     cargs, ncargs);
    cp = trim_space_sep(cp, sep);
    if (!*cp) {
	free_lbuf(curr);
	*buff = '\0';
	return;
    }
    bp = buff;
    first = 1;
    while (cp && (mudstate.func_invk_ctr < mudconf.func_invk_lim)) {
	objstring = split_token(&cp, sep);
	buff2 = replace_string(BOUND_VAR, objstring, fargs[1]);
	result = exec(player, cause,
		      EV_STRIP | EV_FCHECK | EV_EVAL, buff2, cargs, ncargs);
	free_lbuf(buff2);
	if (!first) {
	    safe_chr(' ', buff, &bp);
	}
	first = 0;
	safe_str(result, buff, &bp);
	free_lbuf(result);
    }
    free_lbuf(curr);
    *bp = '\0';
}

/* ---------------------------------------------------------------------------
 * 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, first;
    ATTR *ap;
    char *atext, *result, *curr, *objstring, *bp, *cp, *atextbuf, sep;

    varargs_preamble("FILTER", 3);

    /* 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");
    first = 1;
    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 (!first && *result == '1') {
	    safe_chr(sep, buff, &bp);
	}
	first = 0;
	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, first;
    ATTR *ap;
    char *atext, *result, *objstring, *bp, *cp, *atextbuf, sep;

    varargs_preamble("MAP", 3);

    *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");
    first = 1;
    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 (!first) {
	    safe_chr(sep, buff, &bp);
	} else {
	    first = 0;
	}
	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 simultaneously, passing
 * the elements as %0 as %1.
 */

FUNCTION(fun_mix)
{
    dbref aowner, thing;
    int aflags, anum, first;
    ATTR *ap;
    char *atext, *result, *os[2], *bp, *cp1, *cp2, *atextbuf, sep;

    varargs_preamble("MIX", 4);

    /* 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;
    }
    /* process the two lists, one element at a time. */

    cp1 = trim_space_sep(fargs[1], sep);
    cp2 = trim_space_sep(fargs[2], sep);

    if (countwords(cp1, sep) != countwords(cp2, sep)) {
	free_lbuf(atext);
	strcpy(buff, "#-1 LISTS MUST BE OF EQUAL SIZE");
	return;
    }
    atextbuf = alloc_lbuf("fun_mix");
    bp = buff;
    first = 1;

    while (cp1 && cp2 && (mudstate.func_invk_ctr < mudconf.func_invk_lim)) {
	os[0] = split_token(&cp1, sep);
	os[1] = split_token(&cp2, sep);
	strcpy(atextbuf, atext);
	result = exec(player, cause, EV_STRIP | EV_FCHECK | EV_EVAL,
		      atextbuf, &(os[0]), 2);
	if (!first) {
	    safe_chr(sep, buff, &bp);
	} else {
	    first = 0;
	}
	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;

    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;
    }
    atextbuf = alloc_lbuf("fun_foreach");
    cbuf = alloc_lbuf("fun_foreach.cbuf");
    cp = trim_space_sep(fargs[1], ' ');
    bp = buff;

    while (cp && *cp && (mudstate.func_invk_ctr < mudconf.func_invk_lim)) {
	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(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, *tbuff;

    /* 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) {
	tbuff = exec(player, cause, EV_STRIP | EV_FCHECK | EV_EVAL,
		     fargs[i], cargs, ncargs);
	if (quick_wild(tbuff, mbuff)) {
	    free_lbuf(tbuff);
	    tbuff = exec(player, cause, EV_STRIP | EV_FCHECK | EV_EVAL,
			 fargs[i + 1], cargs, ncargs);
	    strcpy(buff, tbuff);
	    free_lbuf(mbuff);
	    free_lbuf(tbuff);
	    return;
	}
	free_lbuf(tbuff);
    }
    free_lbuf(mbuff);

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

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

/* ---------------------------------------------------------------------------
 * 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;
    char *ptrs[LBUF_SIZE / 2];

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

    if (nfargs == 0) {
	*buff = '\0';
	return;
    }
    mvarargs_preamble("SORT", 1, 3);

    /* 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, sep);
    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;
    int nptrs, aflags, anum;
    dbref thing, aowner;
    ATTR *ap;

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

    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, sep);
    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)
    char *fargs[], *buff, sep;
    int oper;
{
    char *list1, *list2, *p, *oldp;
    char *ptrs1[LBUF_SIZE], *ptrs2[LBUF_SIZE];
    int i1, i2, n1, n2, val, first;

    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;
    first = 1;
    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 (!first) {
		    safe_chr(sep, buff, &p);
		}
		first = 0;
		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 (!first) {
		    safe_chr(sep, buff, &p);
		}
		first = 0;
		oldp = p;
		safe_str(ptrs1[i1], buff, &p);
		*p = '\0';
	    }
	}
	for (; i2 < n2; i2++) {
	    if (strcmp(oldp, ptrs2[i2])) {
		if (!first) {
		    safe_chr(sep, buff, &p);
		}
		first = 0;
		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 (!first) {
		    safe_chr(sep, buff, &p);
		}
		first = 0;
		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 (!first) {
		    safe_chr(sep, buff, &p);
		}
		first = 0;
		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 (!first) {
		safe_chr(sep, buff, &p);
	    }
	    first = 0;
	    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;

    varargs_preamble("SETUNION", 3);
    handle_sets(fargs, buff, SET_UNION, sep);
    return;
}

FUNCTION(fun_setdiff)
{
    char sep;

    varargs_preamble("SETDIFF", 3);
    handle_sets(fargs, buff, SET_DIFF, sep);
    return;
}

FUNCTION(fun_setinter)
{
    char sep;

    varargs_preamble("SETINTER", 3);
    handle_sets(fargs, buff, SET_INTERSECT, sep);
    return;
}

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

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

    varargs_preamble("LJUST", 3);
    spaces = atoi(fargs[1]) - strlen(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;
    safe_str(fargs[0], buff, &bp);
    for (i = 0; i < spaces; i++) {
	safe_chr(sep, buff, &bp);
    }
}

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

    varargs_preamble("RJUST", 3);
    spaces = atoi(fargs[1]) - strlen(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; i < spaces; i++) {
	safe_chr(sep, buff, &bp);
    }
    safe_str(fargs[0], buff, &bp);
}

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

    varargs_preamble("CENTER", 3);

    width = atoi(fargs[1]);
    len = strlen(fargs[0]);

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

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

FUNCTION(fun_setq)
{
    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])
	    mudstate.global_regs[regnum] =
		alloc_lbuf("fun_setq");
	strcpy(mudstate.global_regs[regnum], fargs[1]);
	*buff = '\0';
    }
}

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';
}

/* ---------------------------------------------------------------------------
 * 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},
    {"ANDFLAGS", fun_andflags, 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},
    {"BEFORE", fun_before, 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},
    {"DIST2D", fun_dist2d, 4, 0, CA_PUBLIC},
    {"DIST3D", fun_dist3d, 6, 0, CA_PUBLIC},
    {"DIV", fun_div, 2, 0, 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},
    {"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, 2, 0, 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},
    {"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},
    {"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},
    {"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},
    {"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},
    {"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},
    {"ORFLAGS", fun_orflags, 2, 0, CA_PUBLIC},
    {"OWNER", fun_owner, 1, 0, CA_PUBLIC},
    {"PARENT", fun_parent, 1, 0, CA_PUBLIC},
    {"PARSE", fun_parse, 0, FN_VARARGS | FN_NO_EVAL, CA_PUBLIC},
    {"PI", fun_pi, 0, 0, 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},
    {"R", fun_r, 1, 0, CA_PUBLIC},
    {"RAND", fun_rand, 1, 0, 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},
    {"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},
    {"SETDIFF", fun_setdiff, 0, FN_VARARGS, CA_PUBLIC},
    {"SETINTER", fun_setinter, 0, FN_VARARGS, CA_PUBLIC},
    {"SETQ", fun_setq, 2, 0, CA_PUBLIC},
    {"SETUNION", fun_setunion, 0, FN_VARARGS, 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},
    {"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},
    {"SWITCH", fun_switch, 0, FN_VARARGS | FN_NO_EVAL,
     CA_PUBLIC},
    {"TAN", fun_tan, 1, 0, CA_PUBLIC},
    {"TIME", fun_time, 0, 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},
    {"VERSION", fun_version, 0, 0, CA_PUBLIC},
    {"VISIBLE", fun_visible, 2, 0, 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},
    {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;
}
