game/bin/
game/data/
/*
 * funceval.c - MUX function handlers 
 */
/*
 * $Id: funceval.c,v 1.4 1995/11/22 23:33:17 root Exp $ 
 */

#include "copyright.h"
#include "autoconf.h"

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

#include "mudconf.h"
#include "config.h"
#include "db.h"
#include "flags.h"
#include "powers.h"
#include "attrs.h"
#include "externs.h"
#include "match.h"
#include "command.h"
#include "functions.h"
#include "misc.h"
#include "alloc.h"
#include "ansi.h"
#include "comsys.h"

/*
 * Note: Many functions in this file have been taken, whole or in part, from
 * * PennMUSH 1.50, and TinyMUSH 2.2, for softcode compatibility. The
 * * maintainers of MUX would like to thank those responsible for PennMUSH 1.50
 * * and TinyMUSH 2.2, and hope we have adequately noted in the source where
 * * credit is due.
 */

extern NAMETAB indiv_attraccess_nametab[];
extern char *FDECL(trim_space_sep, (char *, char));
extern char *FDECL(next_token, (char *, char));
extern char *FDECL(split_token, (char **, char));
extern dbref FDECL(match_thing, (dbref, char *));
extern int FDECL(countwords, (char *, char));
extern int FDECL(check_read_perms, (dbref, dbref, ATTR *, int, int, char *, char **));
extern void FDECL(arr2list, (char **, int, char *, char **, char));
extern void FDECL(make_portlist, (dbref, dbref, char *, char **));
extern char *FDECL(get_mail_message, (int));
extern char *msgbuff;

/*
 * This is the prototype for functions 
 */

#define	FUNCTION(x)	\
	void x(buff, bufc, player, cause, fargs, nfargs, cargs, ncargs) \
	char *buff, **bufc; \
	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, bufc))	\
		return;							\
	if (!delim_check(fargs, nfargs, xnargs, &sep, buff, bufc, 0,	\
		player, cause, cargs, ncargs))				\
		return;

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

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

FUNCTION(fun_cwho)
{
	struct channel *ch;
	struct comuser *user;
	int len = 0;
	static char smbuf[SBUF_SIZE];

	if (!(ch = select_channel(fargs[0]))) {
		safe_str("#-1 CHANNEL NOT FOUND", buff, bufc);
		return;
	}
	if (!mudconf.have_comsys || (!Comm_All(player) && (player != ch->charge_who))) {
		safe_str("#-1 NO PERMISSION TO USE", buff, bufc);
		return;
	}
	*buff = '\0';
	for (user = ch->on_users; user; user = user->on_next) {
		if (Connected(user->who)) {
			if (len) {
				sprintf(smbuf, " #%d", user->who);
				if ((strlen(smbuf) + len) > 990) {
					safe_str(" #-1", buff, bufc);
					return;
				}
				safe_str(smbuf, buff, bufc);
				len += strlen(smbuf);
			} else {
				safe_tprintf_str(buff, bufc, "#%d", user->who);
				len = strlen(buff);
			}
		}
	}
}

FUNCTION(fun_beep)
{
	safe_chr(BEEP_CHAR, buff, bufc);
}

/*
 * This function was originally taken from PennMUSH 1.50 
 */

FUNCTION(fun_ansi)
{
	char *s = fargs[0];

	while (*s) {
		switch (*s) {
		case 'h':	/*
				 * hilite 
				 */
			safe_str(ANSI_HILITE, buff, bufc);
			break;
		case 'i':	/*
				 * inverse 
				 */
			safe_str(ANSI_INVERSE, buff, bufc);
			break;
		case 'f':	/*
				 * flash 
				 */
			safe_str(ANSI_BLINK, buff, bufc);
			break;
		case 'n':	/*
				 * normal 
				 */
			safe_str(ANSI_NORMAL, buff, bufc);
			break;
		case 'x':	/*
				 * black fg 
				 */
			safe_str(ANSI_BLACK, buff, bufc);
			break;
		case 'r':	/*
				 * red fg 
				 */
			safe_str(ANSI_RED, buff, bufc);
			break;
		case 'g':	/*
				 * green fg 
				 */
			safe_str(ANSI_GREEN, buff, bufc);
			break;
		case 'y':	/*
				 * yellow fg 
				 */
			safe_str(ANSI_YELLOW, buff, bufc);
			break;
		case 'b':	/*
				 * blue fg 
				 */
			safe_str(ANSI_BLUE, buff, bufc);
			break;
		case 'm':	/*
				 * magenta fg 
				 */
			safe_str(ANSI_MAGENTA, buff, bufc);
			break;
		case 'c':	/*
				 * cyan fg 
				 */
			safe_str(ANSI_CYAN, buff, bufc);
			break;
		case 'w':	/*
				 * white fg 
				 */
			safe_str(ANSI_WHITE, buff, bufc);
			break;
		case 'X':	/*
				 * black bg 
				 */
			safe_str(ANSI_BBLACK, buff, bufc);
			break;
		case 'R':	/*
				 * red bg 
				 */
			safe_str(ANSI_BRED, buff, bufc);
			break;
		case 'G':	/*
				 * green bg 
				 */
			safe_str(ANSI_BGREEN, buff, bufc);
			break;
		case 'Y':	/*
				 * yellow bg 
				 */
			safe_str(ANSI_BYELLOW, buff, bufc);
			break;
		case 'B':	/*
				 * blue bg 
				 */
			safe_str(ANSI_BBLUE, buff, bufc);
			break;
		case 'M':	/*
				 * magenta bg 
				 */
			safe_str(ANSI_BMAGENTA, buff, bufc);
			break;
		case 'C':	/*
				 * cyan bg 
				 */
			safe_str(ANSI_BCYAN, buff, bufc);
			break;
		case 'W':	/*
				 * white bg 
				 */
			safe_str(ANSI_BWHITE, buff, bufc);
			break;
		}
		s++;
	}
	safe_str(fargs[1], buff, bufc);
	safe_str(ANSI_NORMAL, buff, bufc);
}

FUNCTION(fun_zone)
{
	dbref it;

	if (!mudconf.have_zones) {
		return;
	}
	it = match_thing(player, fargs[0]);
	if (it == NOTHING || !Examinable(player, it)) {
		safe_str("#-1", buff, bufc);
		return;
	}
	safe_tprintf_str(buff, bufc, "#%d", Zone(it));
}

#ifdef SIDE_EFFECT_FUNCTIONS

FUNCTION(fun_link)
{
	do_link(player, cause, 0, fargs[0], fargs[1]);
}

FUNCTION(fun_tel)
{
	do_teleport(player, cause, 0, fargs[0], fargs[1]);
}

FUNCTION(fun_pemit)
{
	do_pemit_list(player, fargs[0], fargs[1]);
}

/*------------------------------------------------------------------------
 * fun_create: Creates a room, thing or exit
 */

static int check_command(player, name, buff, bufc)
dbref player;
char *name, *buff, **bufc;
{
	CMDENT *cmd;

	if ((cmd = (CMDENT *) hashfind(name, &mudstate.command_htab)))
		if (!check_access(player, cmd->perms)) {
			safe_str("#-1 PERMISSION DENIED", buff, bufc);
			return (1);
		}
	return (0);
}

FUNCTION(fun_create)
{
	dbref thing;
	int cost;
	char sep, *name;

	varargs_preamble("CREATE", 3);
	name = fargs[0];

	if (!name || !*name) {
		safe_str("#-1 ILLEGAL NAME", buff, bufc);
		return;
	}
	if (fargs[2] && *fargs[2])
		sep = *fargs[2];
	else
		sep = 't';

	switch (sep) {
	case 'r':
		if (check_command(player, "@dig", buff))
			return;
		thing = create_obj(player, TYPE_ROOM, name, 0);
		break;
	case 'e':
		if (check_command(player, "@open", buff))
			return;
		thing = create_obj(player, TYPE_EXIT, name, 0);
		if (thing != NOTHING) {
			s_Exits(thing, player);
			s_Next(thing, Exits(player));
			s_Exits(player, thing);
		}
		break;
	default:
		if (check_command(player, "@create", buff))
			return;
		if (fargs[1] && *fargs[1])
			cost = atoi(fargs[1]);

		if (cost < mudconf.createmin || cost > mudconf.createmax) {
			safe_str("#-1 COST OUT OF RANGE", buff, bufc);
			return;
		} else {
			cost = mudconf.createmin;
		}
		thing = create_obj(player, TYPE_THING, name, cost);
		if (thing != NOTHING) {
			move_via_generic(thing, player, NOTHING, 0);
			s_Home(thing, new_home(player));
		}
		break;
	}
	safe_tprintf_str(buff, bufc, "#%d", thing);
}

/*---------------------------------------------------------------------------
 * fun_set: sets an attribute on an object
 */

static void set_attr_internal(player, thing, attrnum, attrtext, key)
dbref player, thing;
int attrnum, key;
char *attrtext;
{
	dbref aowner;
	int aflags, could_hear;
	ATTR *attr;

	attr = atr_num(attrnum);
	atr_pget_info(thing, attrnum, &aowner, &aflags);
	if (attr && Set_attr(player, thing, attr, aflags)) {
		if ((attr->check != NULL) &&
		    (!(*attr->check) (0, player, thing, attrnum, attrtext)))
			return;
		could_hear = Hearer(thing);
		atr_add(thing, attrnum, attrtext, Owner(player), aflags);
		handle_ears(thing, could_hear, Hearer(thing));
		if (!(key & SET_QUIET) && !Quiet(player) && !Quiet(thing))
			notify_quiet(player, "Set.");
	} else {
		notify_quiet(player, "Permission denied.");
	}
}

FUNCTION(fun_set)
{
	dbref thing, thing2, aowner;
	char *p, *buff2;
	int atr, atr2, aflags, clear, flagvalue, could_hear;
	ATTR *attr, *attr2;

	/*
	 * obj/attr form? 
	 */

	if (parse_attrib(player, fargs[0], &thing, &atr)) {
		if (atr != NOTHING) {

			/*
			 * must specify flag name 
			 */

			if (!fargs[1] || !*fargs[1]) {

				safe_str("#-1 UNSPECIFIED PARAMETER", buff, bufc);
			}
			/*
			 * are we clearing? 
			 */

			clear = 0;
			if (*fargs[0] == NOT_TOKEN) {
				fargs[0]++;
				clear = 1;
			}
			/*
			 * valid attribute flag? 
			 */

			flagvalue = search_nametab(player,
					indiv_attraccess_nametab, fargs[1]);
			if (flagvalue < 0) {
				safe_str("#-1 CAN NOT SET", buff, bufc);
				return;
			}
			/*
			 * make sure attribute is present 
			 */

			if (!atr_get_info(thing, atr, &aowner, &aflags)) {
				safe_str("#-1 ATTRIBUTE NOT PRESENT ON OBJECT", buff, bufc);
				return;
			}
			/*
			 * can we write to attribute? 
			 */

			attr = atr_num(atr);
			if (!attr || !Set_attr(player, thing, attr, aflags)) {
				safe_str("#-1 PERMISSION DENIED", buff, bufc);
				return;
			}
			/*
			 * just do it! 
			 */

			if (clear)
				aflags &= ~flagvalue;
			else
				aflags |= flagvalue;
			could_hear = Hearer(thing);
			atr_set_flags(thing, atr, aflags);

			return;
		}
	}
	/*
	 * find thing 
	 */

	if ((thing = match_controlled(player, fargs[0])) == NOTHING) {
		safe_str("#-1", buff, bufc);
		return;
	}
	/*
	 * check for attr set first 
	 */
	for (p = fargs[1]; *p && (*p != ':'); p++) ;

	if (*p) {
		*p++ = 0;
		atr = mkattr(fargs[1]);
		if (atr <= 0) {
			safe_str("#-1 UNABLE TO CREATE ATTRIBUTE", buff, bufc);
			return;
		}
		attr = atr_num(atr);
		if (!attr) {
			safe_str("#-1 PERMISSION DENIED", buff, bufc);
			return;
		}
		atr_get_info(thing, atr, &aowner, &aflags);
		if (!Set_attr(player, thing, attr, aflags)) {
			safe_str("#-1 PERMISSION DENIED", buff, bufc);
			return;
		}
		buff2 = alloc_lbuf("fun_set");

		/*
		 * check for _ 
		 */
		if (*p == '_') {
			StringCopy(buff2, p + 1);
			if (!parse_attrib(player, p + 1, &thing2, &atr2) ||
			    (atr == NOTHING)) {
				free_lbuf(buff2);
				safe_str("#-1 NO MATCH", buff, bufc);
				return;
			}
			attr2 = atr_num(atr);
			p = buff2;
			atr_pget_str(buff2, thing2, atr2, &aowner, &aflags);

			if (!attr2 ||
			 !See_attr(player, thing2, attr2, aowner, aflags)) {
				free_lbuf(buff2);
				safe_str("#-1 PERMISSION DENIED", buff, bufc);
				return;
			}
		}
		/*
		 * set it 
		 */

		set_attr_internal(player, thing, atr, p, 0);
		free_lbuf(buff2);
		return;
	}
	/*
	 * set/clear a flag 
	 */
	flag_set(thing, player, fargs[1], 0);
}
#endif

/*
 * Code for encrypt() and decrypt() was taken from the DarkZone server 
 */
/*
 * Copy over only alphanumeric chars 
 */
static char *crunch_code(code)
char *code;
{
	char *in;
	char *out;
	static char output[LBUF_SIZE];

	out = output;
	in = code;
	while (*in) {
		if ((*in >= 32) || (*in <= 126)) {
			printf("%c", *in);
			*out++ = *in;
		}
		in++;
	}
	*out = '\0';
	return (output);
}

static char *crypt_code(code, text, type)
char *code;
char *text;
int type;
{
	static char textbuff[LBUF_SIZE];
	char codebuff[LBUF_SIZE];
	int start = 32;
	int end = 126;
	int mod = end - start + 1;
	char *p, *q, *r;

	if (!text && !*text)
		return ((char *)"");
	StringCopy(codebuff, crunch_code(code));
	if (!code || !*code || !codebuff || !*codebuff)
		return (text);
	StringCopy(textbuff, "");

	p = text;
	q = codebuff;
	r = textbuff;
	/*
	 * Encryption: Simply go through each character of the text, get its
	 * * * * ascii value, subtract start, add the ascii value (less
	 * start) * of * * the code, mod the result, add start. Continue  
	 */
	while (*p) {
		if ((*p < start) || (*p > end)) {
			p++;
			continue;
		}
		if (type)
			*r++ = (((*p++ - start) + (*q++ - start)) % mod) + start;
		else
			*r++ = (((*p++ - *q++) + 2 * mod) % mod) + start;
		if (!*q)
			q = codebuff;
	}
	*r = '\0';
	return (textbuff);
}

/*
 * Borrowed from DarkZone 
 */
FUNCTION(fun_zwho)
{
	dbref it = match_thing(player, fargs[0]);
	dbref i;
	int len = 0;

	if (!mudconf.have_zones || !(Controls(player, it)) || !(WizRoy(player))) {
		safe_str("#-1 NO PERMISSION TO USE", buff, bufc);
		return;
	}
	for (i = 0; i < mudstate.db_top; i++)
		if (Typeof(i) == TYPE_PLAYER)
			if (Zone(i) == it)
				if (len) {
					static char smbuf[SBUF_SIZE];

					sprintf(smbuf, " #%d", i);
					if ((strlen(smbuf) + len) > 990) {
						safe_str(" #-1", buff, bufc);
						return;
					}
					safe_str(smbuf, buff, bufc);
					len += strlen(smbuf);
				} else {
					safe_tprintf_str(buff, bufc, "#%d", i);
					len = strlen(buff);
				}
}

/*
 * Borrowed from DarkZone 
 */
FUNCTION(fun_inzone)
{
	dbref it = match_thing(player, fargs[0]);
	dbref i;
	int len = 0;

	if (!mudconf.have_zones || !(Controls(player, it)) || !(WizRoy(player))) {
		safe_str("#-1 NO PERMISSION TO USE", buff, bufc);
		return;
	}
	for (i = 0; i < mudstate.db_top; i++)
		if (Typeof(i) == TYPE_ROOM)
			if (db[i].zone == it)
				if (len) {
					static char smbuf[SBUF_SIZE];

					sprintf(smbuf, " #%d", i);
					if ((strlen(smbuf) + len) > 990) {
						safe_str(" #-1", buff, bufc);
						return;
					}
					safe_str(smbuf, buff, bufc);
					len += strlen(smbuf);
				} else {
					safe_tprintf_str(buff, bufc, "#%d", i);
					len = strlen(buff);
				}
}

/*
 * Borrowed from DarkZone 
 */
FUNCTION(fun_children)
{
	dbref it = match_thing(player, fargs[0]);
	dbref i;
	int len = 0;

	if (!(Controls(player, it)) || !(WizRoy(player))) {
		safe_str("#-1 NO PERMISSION TO USE", buff, bufc);
		return;
	}
	for (i = 0; i < mudstate.db_top; i++)
		if (Parent(i) == it)
			if (len) {
				static char smbuf[SBUF_SIZE];

				sprintf(smbuf, " #%d", i);
				if ((strlen(smbuf) + len) > 990) {
					safe_str(" #-1", buff, bufc);
					return;
				}
				safe_str(smbuf, buff, bufc);
				len += strlen(smbuf);
			} else {
				safe_tprintf_str(buff, bufc, "#%d", i);
				len = strlen(buff);
			}
}

FUNCTION(fun_encrypt)
{
	safe_str(crypt_code(fargs[1], fargs[0], 1), buff, bufc);
}

FUNCTION(fun_decrypt)
{
	safe_str(crypt_code(fargs[1], fargs[0], 0), buff, bufc);
}

static void noquotes(clean, dirty)
char *clean;
char *dirty;
{
	while (*dirty != '\0') {
		if (*dirty == '"')
			*clean++ = '\\';
		*clean++ = *dirty++;
	}
	*clean = '\0';
}

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

	if (!*fargs[0]) {
		return;
	}
	name = bp = alloc_lbuf("fun_objeval");
	str = fargs[0];
	exec(name, &bp, 0, player, cause, EV_FCHECK | EV_STRIP | EV_EVAL, &str,
	     cargs, ncargs);
	*bp = '\0';
	obj = match_thing(player, name);

	if ((obj == NOTHING) ||
	    ((Owner(obj) != player) && (!(WizRoy(player)))))
		obj = player;

	str = fargs[1];
	exec(buff, bufc, 0, obj, obj, EV_FCHECK | EV_STRIP | EV_EVAL, &str,
	     cargs, ncargs);
	free_lbuf(name);
}

FUNCTION(fun_squish)
{
	char *p, *q, *bp;

	bp = alloc_lbuf("fun_squish");
	StringCopy(bp, fargs[0]);
	p = q = bp;
	while (*p) {
		while (*p && (*p != ' '))
			*q++ = *p++;
		while (*p && (*p == ' '))
			p++;
		if (*p)
			*q++ = ' ';
	}
	*q = '\0';

	safe_str(bp, buff, bufc);
	free_lbuf(bp);
}

FUNCTION(fun_stripansi)
{
	safe_str((char *)strip_ansi(fargs[0]), buff, bufc);
}

/*
 * Borrowed from PennMUSH 1.50 
 */
FUNCTION(fun_zfun)
{
	dbref aowner;
	int a, aflags;
	int attrib;
	char *tbuf1, *str;

	dbref zone = Zone(player);

	if (!mudconf.have_zones) {
		safe_str("#-1 ZONES DISABLED", buff, bufc);
		return;
	}
	if (zone == NOTHING) {
		safe_str("#-1 INVALID ZONE", buff, bufc);
		return;
	}
	if (!fargs[0] || !*fargs[0])
		return;

	/*
	 * find the user function attribute 
	 */
	attrib = get_atr(upcasestr(fargs[0]));
	if (!attrib) {
		safe_str("#-1 NO SUCH USER FUNCTION", buff, bufc);
		return;
	}
	tbuf1 = atr_pget(zone, attrib, &aowner, &aflags);
	if (!See_attr(player, zone, (ATTR *) atr_num(attrib), aowner, aflags)) {
		safe_str("#-1 NO PERMISSION TO GET ATTRIBUTE", buff, bufc);
		free_lbuf(tbuf1);
		return;
	}
	str = tbuf1;
	exec(buff, bufc, 0, zone, player, EV_EVAL | EV_STRIP | EV_FCHECK, &str, &(fargs[1]),
	     nfargs - 1);
	free_lbuf(tbuf1);
}

FUNCTION(fun_columns)
{
	int spaces, number, ansinumber, count, i;
	static char buf[LBUF_SIZE];
	char *p, *q;
	int isansi = 0, rturn = 1;
	char *curr, *objstring, *bp, *cp, sep, *str;


	evarargs_preamble("COLUMNS", 3);

	number = atoi(fargs[1]);
	if (number < 1) {
		safe_str("#-1 OUT OF RANGE", buff, bufc);
		return;
	}
	cp = curr = bp = alloc_lbuf("fun_columns");
	str = fargs[0];
	exec(curr, &bp, 0, player, cause, EV_STRIP | EV_FCHECK | EV_EVAL, &str,
	     cargs, ncargs);
	*bp = '\0';
	cp = trim_space_sep(cp, sep);
	if (!*cp) {
		free_lbuf(curr);
		return;
	}
	safe_chr(' ', buff, bufc);

	while (cp) {
		objstring = split_token(&cp, sep);
		ansinumber = number;
		if (ansinumber > strlen((char *)strip_ansi(objstring)))
			ansinumber = strlen((char *)strip_ansi(objstring));

		p = objstring;
		q = buf;
		count = 0;
		while (p && *p) {
			if (count == number) {
				break;
			}
			if (*p == ESC_CHAR) {
				/*
				 * Start of ANSI code. Skip to end. 
				 */
				isansi = 1;
				while (*p && !isalpha(*p))
					*q++ = *p++;
				if (*p)
					*q++ = *p++;
			} else {
				*q++ = *p++;
				count++;
			}
		}
		if (isansi)
			safe_str(ANSI_NORMAL, buf, &q);
		*q = '\0';
		isansi = 0;

		spaces = number - strlen((char *)strip_ansi(objstring));

		/*
		 * Sanitize number of spaces 
		 */

		if (spaces == 0) {
			/*
			 * no padding needed, just return string 
			 */
			safe_str(buf, buff, bufc);
		} else if (spaces > LBUF_SIZE) {
			spaces = LBUF_SIZE;
		}
		safe_str(buf, buff, bufc);
		for (i = 0; i < spaces; i++)
			safe_chr(' ', buff, bufc);

		if (!(rturn % (int)(78 / number)))
			safe_str((char *)"\r\n ", buff, bufc);

		rturn++;
	}
	free_lbuf(curr);
}

/*
 * Code for objmem and playmem borrowed from PennMUSH 1.50 
 */
static int mem_usage(thing)
dbref thing;
{
	int k;
	int ca;
	char *as, *str;
	ATTR *attr;

	k = sizeof(struct object);

	k += strlen(Name(thing)) + 1;
	for (ca = atr_head(thing, &as); ca; ca = atr_next(&as)) {
		str = atr_get_raw(thing, ca);
		if (str && *str)
			k += strlen(str);
		attr = atr_num(ca);
		if (attr) {
			str = (char *)attr->name;
			if (str && *str)
				k += strlen(((ATTR *) atr_num(ca))->name);
		}
	}
	return k;
}

FUNCTION(fun_objmem)
{
	dbref thing;

	thing = match_thing(player, fargs[0]);
	if (thing == NOTHING || !Examinable(player, thing)) {
		safe_str("#-1 PERMISSION DENIED", buff, bufc);
		return;
	}
	safe_tprintf_str(buff, bufc, "%d", mem_usage(thing));
}

FUNCTION(fun_playmem)
{
	int tot = 0;
	dbref thing;
	dbref j;

	thing = match_thing(player, fargs[0]);
	if (thing == NOTHING || !Examinable(player, thing)) {
		safe_str("#-1 PERMISSION DENIED", buff, bufc);
		return;
	}
	DO_WHOLE_DB(j)
		if (Owner(j) == thing)
		tot += mem_usage(j);
	safe_tprintf_str(buff, bufc, "%d", tot);
}

/*
 * Code for andflags() and orflags() borrowed from PennMUSH 1.50 
 */
static int handle_flaglists(player, name, fstr, type)
dbref player;
char *name;
char *fstr;
int type;			/*

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

	negate = temp = 0;

	if (it == NOTHING)
		return 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 we 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. * 
				 * 
				 * *  * * We OR a * true with the current
				 * value. 
				 */

				ret |= 1;
			}
			/*
			 * Otherwise, we don't need to do anything. 
			 */
		}
	}
	return (ret);
}

FUNCTION(fun_orflags)
{
	safe_tprintf_str(buff, bufc, "%d", handle_flaglists(player, fargs[0], fargs[1], 0));
}

FUNCTION(fun_andflags)
{
	safe_tprintf_str(buff, bufc, "%d", handle_flaglists(player, fargs[0], fargs[1], 1));
}

FUNCTION(fun_strtrunc)
{
	int number, count = 0;
	static char buf[LBUF_SIZE];
	char *p = (char *)fargs[0];
	char *q = buf;
	int isansi = 0;

	number = atoi(fargs[1]);
	if (number > strlen((char *)strip_ansi(fargs[0])))
		number = strlen((char *)strip_ansi(fargs[0]));

	if (number < 0) {
		safe_str("#-1 OUT OF RANGE", buff, bufc);
		return;
	}
	while (p && *p) {
		if (count == number) {
			break;
		}
		if (*p == ESC_CHAR) {
			/*
			 * Start of ANSI code. Skip to end. 
			 */
			isansi = 1;
			while (*p && !isalpha(*p))
				*q++ = *p++;
			if (*p)
				*q++ = *p++;
		} else {
			*q++ = *p++;
			count++;
		}
	}
	if (isansi)
		safe_str(ANSI_NORMAL, buf, &q);
	*q = '\0';
	safe_str(buf, buff, bufc);
}

FUNCTION(fun_ifelse)
{
	if (!is_number(fargs[0]) || !*fargs[0]) {
		if (!*fargs[0])
			safe_str(fargs[2], buff, bufc);
		else
			safe_str(fargs[1], buff, bufc);
	} else {
		if (atoi(fargs[0]) == 0)
			safe_str(fargs[2], buff, bufc);
		else
			safe_str(fargs[1], buff, bufc);
	}
}

FUNCTION(fun_inc)
{
	int number;

	if (!is_number(fargs[0])) {
		safe_str("#-1 ARGUMENT MUST BE A NUMBER", buff, bufc);
		return;
	}
	number = atoi(fargs[0]);
	safe_tprintf_str(buff, bufc, "%d", (++number));
}

FUNCTION(fun_dec)
{
	int number;

	if (!is_number(fargs[0])) {
		safe_str("#-1 ARGUMENT MUST BE A NUMBER", buff, bufc);
		return;
	}
	number = atoi(fargs[0]);
	safe_tprintf_str(buff, bufc, "%d", (--number));
}

/*
 * Mail functions borrowed from DarkZone 
 */
FUNCTION(fun_mail)
{
	/*
	 * This function can take one of three formats: 1.  mail(num)  --> *
	 * * * returns * message <num> for privs. 2.  mail(player)  -->
	 * returns  * *  * number of * messages for <player>. 3.
	 * mail(player, num)  -->  * * * returns message <num> * for
	 * <player>. 
	 */
	/*
	 * It can now take one more format: 4.  mail() --> returns number of
	 * * * * * messages for executor 
	 */

	struct mail *mp;
	dbref playerask;
	int num, rc, uc, cc;

	/*
	 * make sure we have the right number of arguments 
	 */
	if ((nfargs != 0) && (nfargs != 1) && (nfargs != 2)) {
		safe_str("#-1 FUNCTION (MAIL) EXPECTS 0 OR 1 OR 2 ARGUMENTS", buff, bufc);
		return;
	}
	if ((nfargs == 0) || !fargs[0] || !fargs[0][0]) {
		count_mail(player, 0, &rc, &uc, &cc);
		safe_tprintf_str(buff, bufc, "%d", rc + uc);
		return;
	}
	if (nfargs == 1) {
		if (!is_number(fargs[0])) {
			/*
			 * handle the case of wanting to count the number of
			 * * * * messages 
			 */
			if ((playerask = lookup_player(player, fargs[0], 1)) == NOTHING) {
				safe_str("#-1 NO SUCH PLAYER", buff, bufc);
				return;
			} else if ((player != playerask) && !Wizard(player)) {
				safe_str("#-1 PERMISSION DENIED", buff, bufc);
				return;
			} else {
				count_mail(playerask, 0, &rc, &uc, &cc);
				safe_tprintf_str(buff, bufc, "%d %d %d", rc, uc, cc);
				return;
			}
		} else {
			playerask = player;
			num = atoi(fargs[0]);
		}
	} else {
		if ((playerask = lookup_player(player, fargs[0], 1)) == NOTHING) {
			safe_str("#-1 NO SUCH PLAYER", buff, bufc);
			return;
		} else if ((player != playerask) && !God(player)) {
			safe_str("#-1 PERMISSION DENIED", buff, bufc);
			return;
		}
		num = atoi(fargs[1]);
	}

	if ((num < 1) || (Typeof(playerask) != TYPE_PLAYER)) {
		safe_str("#-1 NO SUCH MESSAGE", buff, bufc);
		return;
	}
	mp = mail_fetch(playerask, num);
	if (mp != NULL) {
#ifdef RADIX_COMPRESSION
		string_decompress(get_mail_message(mp->number), msgbuff);
		safe_str(msgbuff, buff, bufc);
#else
		safe_str(get_mail_message(mp->number), buff, bufc);
#endif
		return;
	}
	/*
	 * ran off the end of the list without finding anything 
	 */
	safe_str("#-1 NO SUCH MESSAGE", buff, bufc);
}

FUNCTION(fun_mailfrom)
{
	/*
	 * This function can take these formats: 1) mailfrom(<num>) 2) * * *
	 * * mailfrom(<player>,<num>) It returns the dbref of the player the
	 * * * * mail is * from 
	 */
	struct mail *mp;
	dbref playerask;
	int num;

	/*
	 * make sure we have the right number of arguments 
	 */
	if ((nfargs != 1) && (nfargs != 2)) {
		safe_str("#-1 FUNCTION (MAILFROM) EXPECTS 1 OR 2 ARGUMENTS", buff, bufc);
		return;
	}
	if (nfargs == 1) {
		playerask = player;
		num = atoi(fargs[0]);
	} else {
		if ((playerask = lookup_player(player, fargs[0], 1)) == NOTHING) {
			safe_str("#-1 NO SUCH PLAYER", buff, bufc);
			return;
		} else if ((player != playerask) && !Wizard(player)) {
			safe_str("#-1 PERMISSION DENIED", buff, bufc);
			return;
		}
		num = atoi(fargs[1]);
	}

	if ((num < 1) || (Typeof(playerask) != TYPE_PLAYER)) {
		safe_str("#-1 NO SUCH MESSAGE", buff, bufc);
		return;
	}
	mp = mail_fetch(playerask, num);
	if (mp != NULL) {
		safe_tprintf_str(buff, bufc, "#%d", mp->from);
		return;
	}
	/*
	 * ran off the end of the list without finding anything 
	 */
	safe_str("#-1 NO SUCH MESSAGE", buff, bufc);
}

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

/*
 * Hasattr (and hasattrp, which is derived from hasattr) borrowed from
 * * TinyMUSH 2.2. 
 */

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

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

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

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

/*
 * ---------------------------------------------------------------------------
 * * 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().
 */

/*
 * default(), edefault(), and udefault() borrowed from TinyMUSH 2.2 
 */
FUNCTION(fun_default)
{
	dbref thing, aowner;
	int attrib, aflags;
	ATTR *attr;
	char *objname, *atr_gotten, *bp, *str;

	objname = bp = alloc_lbuf("fun_default");
	str = fargs[0];
	exec(objname, &bp, 0, player, cause, EV_EVAL | EV_STRIP | EV_FCHECK, &str,
	     cargs, ncargs);
	*bp = '\0';

	/*
	 * 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, bufc)) {
					safe_str(atr_gotten, buff, bufc);
					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. 
	 */

	str = fargs[1];
	exec(buff, bufc, 0, player, cause, EV_EVAL | EV_STRIP | EV_FCHECK, &str,
	     cargs, ncargs);
}

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

	objname = bp = alloc_lbuf("fun_edefault");
	str = fargs[0];
	exec(objname, &bp, 0, player, cause, EV_EVAL | EV_STRIP | EV_FCHECK, &str,
	     cargs, ncargs);
	*bp = '\0';

	/*
	 * 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, bufc)) {
					str = atr_gotten;
					exec(buff, bufc, 0, thing, player, EV_FIGNORE | EV_EVAL,
					     &str, (char **)NULL, 0);
					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. 
	 */

	str = fargs[1];
	exec(buff, bufc, 0, player, cause, EV_EVAL | EV_STRIP | EV_FCHECK, &str,
	     cargs, ncargs);
}

FUNCTION(fun_udefault)
{
	dbref thing, aowner;
	int aflags, anum;
	ATTR *ap;
	char *objname, *atext, *bp, *str;


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

	str = fargs[0];
	objname = bp = alloc_lbuf("fun_udefault");
	exec(objname, &bp, 0, player, cause, EV_EVAL | EV_STRIP | EV_FCHECK, &str,
	     cargs, ncargs);
	*bp = '\0';

	/*
	 * 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, bufc)) {
					str = atext;
					exec(buff, bufc, 0, thing, cause, EV_FCHECK | EV_EVAL, &str,
					     &(fargs[2]), nfargs - 1);
					free_lbuf(atext);
					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. 
	 */

	str = fargs[1];
	exec(buff, bufc, 0, player, cause, EV_EVAL | EV_STRIP | EV_FCHECK, &str,
	     cargs, ncargs);
}

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

/*
 * Borrowed from PennMUSH 1.50 
 */
FUNCTION(fun_findable)
{
	dbref obj = match_thing(player, fargs[0]);
	dbref victim = match_thing(player, fargs[1]);

	if (obj == NOTHING)
		safe_str("#-1 ARG1 NOT FOUND", buff, bufc);
	else if (victim == NOTHING)
		safe_str("#-1 ARG2 NOT FOUND", buff, bufc);
	else
		safe_tprintf_str(buff, bufc, "%d", locatable(obj, victim, obj));
}

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

/*
 * Borrowed from PennMUSH 1.50 
 */
FUNCTION(fun_isword)
{
	char *p;

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

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

/*
 * Borrowed from PennMUSH 1.50 
 */
FUNCTION(fun_visible)
{
	dbref it, thing, aowner;
	int aflags, atr;
	ATTR *ap;

	if ((it = match_thing(player, fargs[0])) == NOTHING) {
		safe_str("0", buff, bufc);
		return;
	}
	if (parse_attrib(player, fargs[1], &thing, &atr)) {
		if (atr == NOTHING) {
			safe_tprintf_str(buff, bufc, "%d", Examinable(it, thing));
			return;
		}
		ap = atr_num(atr);
		atr_pget_info(thing, atr, &aowner, &aflags);
		safe_tprintf_str(buff, bufc, "%d", See_attr(it, thing, ap, aowner, aflags));
		return;
	}
	thing = match_thing(player, fargs[1]);
	if (!Good_obj(thing)) {
		safe_str("0", buff, bufc);
		return;
	}
	safe_tprintf_str(buff, bufc, "%d", Examinable(it, thing));
}

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

/*
 * Borrowed from PennMUSH 1.50 
 */
FUNCTION(fun_elements)
{
	int nwords, cur;
	char *ptrs[LBUF_SIZE / 2];
	char *wordlist, *s, *r, sep, *oldp;

	varargs_preamble("ELEMENTS", 3);
	oldp = *bufc;

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

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

	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 (oldp != *bufc)
				safe_chr(sep, buff, bufc);
			safe_str(ptrs[cur], buff, bufc);
		}
	} while (s);
	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
 */

/*
 * Borrowed from PennMUSH 1.50 
 */
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)) {
			safe_str(r, buff, bufc);
			return;
		}
	} while (s);
}

/*
 * ---------------------------------------------------------------------------
 * * fun_scramble:  randomizes the letters in a string.
 */

/*
 * Borrowed from PennMUSH 1.50 
 */
FUNCTION(fun_scramble)
{
	int n, i, j;
	char c, *old;

	if (!fargs[0] || !*fargs[0]) {
		return;
	}
	old = *bufc;

	safe_str(fargs[0], buff, bufc);
	**bufc = '\0';

	n = strlen(old);

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

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

/*
 * Borrowed from PennMUSH 1.50 
 */
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]) {
		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, bufc, sep);
}

/*
 * sortby() code borrowed from TinyMUSH 2.2 
 */

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, *elems[2], *bp, *str;
	int n;

	if ((mudstate.func_invk_ctr > mudconf.func_invk_lim) ||
	    (mudstate.func_nest_lev > mudconf.func_nest_lim))
		return 0;

	tbuf = alloc_lbuf("u_comp");
	elems[0] = (char *)s1;
	elems[1] = (char *)s2;
	StringCopy(tbuf, ucomp_buff);
	result = bp = alloc_lbuf("u_comp");
	str = tbuf;
	exec(result, &bp, 0, ucomp_player, ucomp_cause, EV_STRIP | EV_FCHECK | EV_EVAL,
	     &str, &(elems[0]), 2);
	*bp = '\0';
	if (!result)
		n = 0;
	else {
		n = atoi(result);
		free_lbuf(result);
	}
	free_lbuf(tbuf);
	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]) {
		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) {
		return;
	}
	atext = atr_pget(thing, ap->number, &aowner, &aflags);
	if (!atext) {
		return;
	} else if (!*atext || !See_attr(player, thing, ap, aowner, aflags)) {
		free_lbuf(atext);
		return;
	}
	StringCopy(ucomp_buff, atext);
	ucomp_player = thing;
	ucomp_cause = cause;

	list = alloc_lbuf("fun_sortby");
	StringCopy(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, bufc, sep);
	free_lbuf(list);
	free_lbuf(atext);
}

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

/*
 * Borrowed from TinyMUSH 2.2 
 */
FUNCTION(fun_last)
{
	char *s, *last, sep;
	int len, i;

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

	if (nfargs == 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)
		safe_str(++last, buff, bufc);
	else
		safe_str(s, buff, bufc);
}

/*
 * Borrowed from TinyMUSH 2.2 
 */
FUNCTION(fun_matchall)
{
	int wcount;
	char *r, *s, *old, sep, tbuf[8];

	varargs_preamble("MATCHALL", 3);
	old = *bufc;

	/*
	 * Check each word individually, returning the word number of all * * 
	 * 
	 * *  * * that match. 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)) {
			sprintf(tbuf, "%d", wcount);
			if (old != *bufc)
				safe_chr(' ', buff, bufc);
			safe_str(tbuf, buff, bufc);
		}
		wcount++;
	} while (s);

	if (*bufc == old)
		safe_str("0", buff, bufc);
}

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

/*
 * Borrowed from TinyMUSH 2.2 
 */
FUNCTION(fun_ports)
{
	dbref target;

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

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

/*
 * Borrowed from PennMUSH 1.50 
 */
FUNCTION(fun_mix)
{
	dbref aowner, thing;
	int aflags, anum;
	ATTR *ap;
	char *atext, *result, *os[2], *oldp, *bp, *str, *cp1, *cp2, *atextbuf,
	 sep;

	varargs_preamble("MIX", 4);
	oldp = *bufc;

	/*
	 * 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) {
		return;
	}
	atext = atr_pget(thing, ap->number, &aowner, &aflags);
	if (!atext) {
		return;
	} else if (!*atext || !See_attr(player, thing, ap, aowner, aflags)) {
		free_lbuf(atext);
		return;
	}
	/*
	 * 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);
		safe_str("#-1 LISTS MUST BE OF EQUAL SIZE", buff, bufc);
		return;
	}
	atextbuf = alloc_lbuf("fun_mix");

	while (cp1 && cp2) {
		os[0] = split_token(&cp1, sep);
		os[1] = split_token(&cp2, sep);
		StringCopy(atextbuf, atext);
		str = atextbuf;
		bp = result = alloc_lbuf("fun_mix");
		exec(result, &bp, 0, player, cause, EV_STRIP | EV_FCHECK | EV_EVAL,
		     &str, &(os[0]), 2);
		*bp = '\0';
		if (*bufc != oldp)
			safe_chr(sep, buff, bufc);
		safe_str(result, buff, bufc);
		free_lbuf(result);
	}
	free_lbuf(atext);
	free_lbuf(atextbuf);
}

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

/*
 * Borrowed from TinyMUSH 2.2 
 */
FUNCTION(fun_foreach)
{
	dbref aowner, thing;
	int aflags, anum;
	ATTR *ap;
	char *atext, *atextbuf, *result, *bp, *str, *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) {
		return;
	}
	atext = atr_pget(thing, ap->number, &aowner, &aflags);
	if (!atext) {
		return;
	} else if (!*atext || !See_attr(player, thing, ap, aowner, aflags)) {
		free_lbuf(atext);
		return;
	}
	atextbuf = alloc_lbuf("fun_foreach");
	cbuf = alloc_lbuf("fun_foreach.cbuf");
	cp = trim_space_sep(fargs[1], ' ');

	while (cp && *cp) {
		cbuf[0] = *cp++;
		cbuf[1] = '\0';
		StringCopy(atextbuf, atext);
		bp = result = alloc_lbuf("fun_foreach");
		str = atextbuf;
		exec(result, &bp, 0, player, cause, EV_STRIP | EV_FCHECK | EV_EVAL,
		     &str, &cbuf, 1);
		*bp = '\0';
		safe_str(result, buff, bufc);
		free_lbuf(result);
	}
	free_lbuf(atextbuf);
	free_lbuf(atext);
	free_lbuf(cbuf);
}

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

/*
 * Borrowed from TinyMUSH 2.2 
 */
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, *str, sep, *oldp;

	oldp = *bufc;
	if ((nfargs == 0) || !fargs[0] || !*fargs[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) {
		return;
	}
	atext = atr_pget(thing, ap->number, &aowner, &aflags);
	if (!atext) {
		return;
	} else if (!*atext || !See_attr(player, thing, ap, aowner, aflags)) {
		free_lbuf(atext);
		return;
	}
	/*
	 * Copy our lists and chop them up. 
	 */

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

	if (nptrs1 != nptrs2) {
		safe_str("#-1 LISTS MUST BE OF EQUAL SIZE", buff, bufc);
		free_lbuf(atext);
		free_lbuf(list1);
		free_lbuf(list2);
		return;
	}
	/*
	 * Call the u-function with the first list as %0. 
	 */

	bp = rlist = alloc_lbuf("fun_munge");
	str = atext;
	exec(rlist, &bp, 0, player, cause, EV_STRIP | EV_FCHECK | EV_EVAL, &str,
	     &fargs[1], 1);
	*bp = '\0';

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

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

/*
 * die() code borrowed from PennMUSH 1.50 
 */
int getrandom(x)
int x;
{
	/*
	 * In order to be perfectly anal about not introducing any further *
	 * * * sources * of statistical bias, we're going to call random() *
	 * until * * we get a number * less than the greatest representable * 
	 * multiple * of  * x. We'll then return * n mod x. 
	 */
	long n;

	if (x <= 0)
		return -1;

	do {
		n = random();
	} while (LONG_MAX - n < x);

/*
 * N.B. This loop happens in randomized constant time, and pretty damn
 * * fast randomized constant time too, since P(LONG_MAX - n < x) < 0.5
 * * for any x, so for any X, the average number of times we should
 * * have to call random() is less than 2.
 */
	return (n % x);
}

FUNCTION(fun_die)
{
	int n, die, count;
	int total = 0;

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

	n = atoi(fargs[0]);
	die = atoi(fargs[1]);

	if ((n < 1) || (n > 20)) {
		safe_str("#-1 NUMBER OUT OF RANGE", buff, bufc);
		return;
	}
	for (count = 0; count < n; count++)
		total += getrandom(die) + 1;

	safe_tprintf_str(buff, bufc, "%d", total);
}

/*
 * Borrowed from PennMUSH 1.50 
 */
FUNCTION(fun_lit)
{
	/*
	 * Just returns the argument, literally 
	 */
	safe_str(fargs[0], buff, bufc);
}

/*
 * shl() and shr() borrowed from PennMUSH 1.50 
 */
FUNCTION(fun_shl)
{
	if (is_number(fargs[0]) && is_number(fargs[1]))
		safe_tprintf_str(buff, bufc, "%d", atoi(fargs[0]) << atoi(fargs[1]));
	else
		safe_str("#-1 ARGUMENTS MUST BE NUMBERS", buff, bufc);
}

FUNCTION(fun_shr)
{
	if (is_number(fargs[0]) && is_number(fargs[1]))
		safe_tprintf_str(buff, bufc, "%d", atoi(fargs[0]) >> atoi(fargs[1]));
	else
		safe_str("#-1 ARGUMENTS MUST BE NUMBERS", buff, bufc);
}

/*
 * ------------------------------------------------------------------------
 * * Vector functions: VADD, VSUB, VMUL, VCROSS, VMAG, VUNIT, VDIM
 * * Vectors are space-separated numbers.
 */

/*
 * Vector functions borrowed from PennMUSH 1.50 
 */
#define MAXDIM	20

FUNCTION(fun_vadd)
{
	char *v1[LBUF_SIZE], *v2[LBUF_SIZE];
	char vres[MAXDIM][LBUF_SIZE];
	int n, m, i;
	char sep;

	varargs_preamble("VADD", 3);

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

	if (n != m) {
		safe_str("#-1 VECTORS MUST BE SAME DIMENSIONS", buff, bufc);
		return;
	}
	if (n > MAXDIM) {
		safe_str("#-1 TOO MANY DIMENSIONS ON VECTORS", buff, bufc);
		return;
	}
	/*
	 * add it 
	 */
	for (i = 0; i < n; i++) {
		sprintf(vres[i], "%f", atof(v1[i]) + atof(v2[i]));
		v1[i] = (char *)vres[i];
	}

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

FUNCTION(fun_vsub)
{
	char *v1[LBUF_SIZE], *v2[LBUF_SIZE];
	char vres[MAXDIM][LBUF_SIZE];
	int n, m, i;
	char sep;

	varargs_preamble("VSUB", 3);

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

	if (n != m) {
		safe_str("#-1 VECTORS MUST BE SAME DIMENSIONS", buff, bufc);
		return;
	}
	if (n > MAXDIM) {
		safe_str("#-1 TOO MANY DIMENSIONS ON VECTORS", buff, bufc);
		return;
	}
	/*
	 * sub it 
	 */
	for (i = 0; i < n; i++) {
		sprintf(vres[i], "%f", atof(v1[i]) - atof(v2[i]));
		v1[i] = (char *)vres[i];
	}

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

FUNCTION(fun_vmul)
{
	char *v1[LBUF_SIZE], *v2[LBUF_SIZE];
	char vres[MAXDIM][LBUF_SIZE];
	int n, m, i;
	float scalar;
	char sep;

	varargs_preamble("VMUL", 3);

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

	if ((n != 1) && (m != 1) && (n != m)) {
		safe_str("#-1 VECTORS MUST BE SAME DIMENSIONS", buff, bufc);
		return;
	}
	if (n > MAXDIM) {
		safe_str("#-1 TOO MANY DIMENSIONS ON VECTORS", buff, bufc);
		return;
	}
	/*
	 * multiply it - if n or m is 1, it's scalar multiplication by a * *
	 * * vector, otherwise it's a dot-product 
	 */

	if (n == 1) {
		scalar = atof(v1[0]);
		for (i = 0; i < m; i++) {
			sprintf(vres[i], "%f", atof(v2[i]) * scalar);
			v1[i] = (char *)vres[i];
		}
		n = m;
	} else if (m == 1) {
		scalar = atof(v2[0]);
		for (i = 0; i < n; i++) {
			sprintf(vres[i], "%f", atof(v1[i]) * scalar);
			v1[i] = (char *)vres[i];
		}
	} else {
		/*
		 * dot product 
		 */
		scalar = 0;
		for (i = 0; i < n; i++) {
			scalar += atof(v1[i]) * atof(v2[i]);
			v1[i] = (char *)vres[i];
		}

		safe_tprintf_str(buff, bufc, "%f", scalar);
		return;
	}

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

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

	varargs_preamble("VMAG", 2);

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

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

	if (res > 0)
		safe_tprintf_str(buff, bufc, "%f", sqrt(res));
	else
		safe_str("0", buff, bufc);
}

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

	varargs_preamble("VUNIT", 2);

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

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

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

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

FUNCTION(fun_vdim)
{
	char sep;

	if (fargs == 0)
		safe_str("0", buff, bufc);
	else {
		varargs_preamble("VDIM", 2);
		safe_tprintf_str(buff, bufc, "%d", countwords(fargs[0], sep));
	}
}

FUNCTION(fun_strcat)
{
	safe_str(fargs[0], buff, bufc);
	safe_str(fargs[1], buff, bufc);
}

/*
 * grep() and grepi() code borrowed from PennMUSH 1.50 
 */
char *grep_util(player, thing, pattern, lookfor, len, insensitive)
dbref player, thing;
char *pattern;
char *lookfor;
int len;
int insensitive;
{
	/*
	 * returns a list of attributes which match <pattern> on <thing> * *
	 * * * whose contents have <lookfor> 
	 */
	dbref aowner;
	char *tbuf1, *buf, *text, *attrib;
	char *bp;
	int found;
	int ca, aflags;

	tbuf1 = alloc_lbuf("grep_util");
	buf = alloc_lbuf("grep_util.parse_attrib");
	bp = tbuf1;
	sprintf(buf, "#%d/%s", thing, pattern);
	if (parse_attrib_wild(player, buf, &thing, 0, 0, 1)) {
		for (ca = olist_first(); ca != NOTHING; ca = olist_next()) {
			attrib = atr_get(player, ca, &aowner, &aflags);
			text = attrib;
			found = 0;
			while (*text && !found) {
				if ((!insensitive && !strncmp(lookfor, text, len)) ||
				    (insensitive && !strncasecmp(lookfor, text, len)))
					found = 1;
				else
					text++;
			}

			if (found) {
				if (bp != tbuf1)
					safe_chr(' ', tbuf1, &bp);

				safe_str((char *)(atr_num(ca))->name, tbuf1, &bp);
			}
			free_lbuf(attrib);
		}
	}
	free_lbuf(buf);
	*bp = '\0';
	return tbuf1;
}

FUNCTION(fun_grep)
{
	char *tp;

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

	if (it == NOTHING) {
		safe_str("#-1 NO MATCH", buff, bufc);
		return;
	} else if (!(Examinable(player, it))) {
		safe_str("#-1 PERMISSION DENIED", buff, bufc);
		return;
	}
	/*
	 * make sure there's an attribute and a pattern 
	 */
	if (!fargs[1] || !*fargs[1]) {
		safe_str("#-1 NO SUCH ATTRIBUTE", buff, bufc);
		return;
	}
	if (!fargs[2] || !*fargs[2]) {
		safe_str("#-1 INVALID GREP PATTERN", buff, bufc);
		return;
	}
	tp = grep_util(player, it, fargs[1], fargs[2], strlen(fargs[2]), 0);
	safe_str(tp, buff, bufc);
	free_lbuf(tp);
}

FUNCTION(fun_grepi)
{
	char *tp;

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

	if (it == NOTHING) {
		safe_str("#-1 NO MATCH", buff, bufc);
		return;
	} else if (!(Examinable(player, it))) {
		safe_str("#-1 PERMISSION DENIED", buff, bufc);
		return;
	}
	/*
	 * make sure there's an attribute and a pattern 
	 */
	if (!fargs[1] || !*fargs[1]) {
		safe_str("#-1 NO SUCH ATTRIBUTE", buff, bufc);
		return;
	}
	if (!fargs[2] || !*fargs[2]) {
		safe_str("#-1 INVALID GREP PATTERN", buff, bufc);
		return;
	}
	tp = grep_util(player, it, fargs[1], fargs[2], strlen(fargs[2]), 1);
	safe_str(tp, buff, bufc);
	free_lbuf(tp);
}

/*
 * Borrowed from PennMUSH 1.50 
 */
FUNCTION(fun_art)
{
/*
 * checks a word and returns the appropriate article, "a" or "an" 
 */
	char c = tolower(*fargs[0]);

	if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u')
		safe_str("an", buff, bufc);
	else
		safe_str("a", buff, bufc);
}

/*
 * Borrowed from PennMUSH 1.50 
 */
FUNCTION(fun_alphamax)
{
	char *amax;
	int i = 1;

	if (!fargs[0]) {
		safe_str("#-1 TOO FEW ARGUMENTS", buff, bufc);
		return;
	} else
		amax = fargs[0];

	while ((i < 10) && fargs[i]) {
		amax = (strcmp(amax, fargs[i]) > 0) ? amax : fargs[i];
		i++;
	}

	safe_tprintf_str(buff, bufc, "%s", amax);
}

/*
 * Borrowed from PennMUSH 1.50 
 */
FUNCTION(fun_alphamin)
{
	char *amin;
	int i = 1;

	if (!fargs[0]) {
		safe_str("#-1 TOO FEW ARGUMENTS", buff, bufc);
		return;
	} else
		amin = fargs[0];

	while ((i < 10) && fargs[i]) {
		amin = (strcmp(amin, fargs[i]) < 0) ? amin : fargs[i];
		i++;
	}

	safe_tprintf_str(buff, bufc, "%s", amin);
}

/*
 * Borrowed from PennMUSH 1.50 
 */

FUNCTION(fun_valid)
{
/*
 * Checks to see if a given <something> is valid as a parameter of a
 * * given type (such as an object name).
 */

	if (!fargs[0] || !*fargs[0] || !fargs[1] || !*fargs[1])
		safe_str("0", buff, bufc);
	else if (!strcasecmp(fargs[0], "name"))
		safe_tprintf_str(buff, bufc, "%d", ok_name(fargs[1]));
	else
		safe_str("#-1", buff, bufc);
}

/*
 * Borrowed from PennMUSH 1.50 
 */
FUNCTION(fun_hastype)
{
	dbref it = match_thing(player, fargs[0]);

	if (it == NOTHING) {
		safe_str("#-1 NO MATCH", buff, bufc);
		return;
	} else if (!(Examinable(player, it))) {
		safe_str("#-1 PERMISSION DENIED", buff, bufc);
		return;
	}
	if (!fargs[1] || !*fargs[1]) {
		safe_str("#-1 NO SUCH TYPE", buff, bufc);
		return;
	}
	switch (*fargs[1]) {
	case 'r':
	case 'R':
		safe_str((Typeof(it) == TYPE_ROOM) ? "1" : "0", buff, bufc);
		break;
	case 'e':
	case 'E':
		safe_str((Typeof(it) == TYPE_EXIT) ? "1" : "0", buff, bufc);
		break;
	case 'p':
	case 'P':
		safe_str((Typeof(it) == TYPE_PLAYER) ? "1" : "0", buff, bufc);
		break;
	case 't':
	case 'T':
		safe_str((Typeof(it) == TYPE_THING) ? "1" : "0", buff, bufc);
		break;
	default:
		safe_str("#-1 NO SUCH TYPE", buff, bufc);
		break;
	};
}

/*
 * Borrowed from PennMUSH 1.50 
 */
FUNCTION(fun_lparent)
{
	dbref it;
	dbref par;
	char tbuf1[20];

	it = match_thing(player, fargs[0]);
	if (!Good_obj(it)) {
		safe_str("#-1 NO MATCH", buff, bufc);
		return;
	} else if (!(Examinable(player, it))) {
		safe_str("#-1 PERMISSION DENIED", buff, bufc);
		return;
	}
	sprintf(tbuf1, "#%d", it);
	safe_str(tbuf1, buff, bufc);
	par = Parent(it);

	while (Good_obj(par) && Examinable(player, it)) {
		sprintf(tbuf1, " #%d", par);
		safe_str(tbuf1, buff, bufc);
		it = par;
		par = Parent(par);
	}
}

FUNCTION(fun_empty)
{
	STACK *sp, *next;
	dbref doer;

	if (nfargs > 1) {
		safe_str("#-1 FUNCTION (CSTACK) EXPECTS 0-1 ARGUMENTS", buff, bufc);
		return;
	}
	if (!fargs[0]) {
		doer = player;
	} else {
		doer = match_thing(player, fargs[0]);
	}

	if (!Controls(player, doer)) {
		safe_str("#-1 PERMISSION DENIED", buff, bufc);
		return;
	}
	for (sp = Stack(doer); sp != NULL; sp = next) {
		next = sp->next;
		free_lbuf(sp->data);
		free(sp);
	}

	s_Stack(doer, NULL);
	s_StackSize(doer, 0);
}

FUNCTION(fun_items)
{
	dbref doer;

	if (nfargs > 1) {
		safe_str("#-1 FUNCTION (NUMSTACK) EXPECTS 0-1 ARGUMENTS", buff, bufc);
		return;
	}
	if (!fargs[0]) {
		doer = player;
	} else {
		doer = match_thing(player, fargs[0]);
	}

	if (!Controls(player, doer)) {
		safe_str("#-1 PERMISSION DENIED", buff, bufc);
		return;
	}
	safe_tprintf_str(buff, bufc, "%d", StackSize(doer));
}

FUNCTION(fun_peek)
{
	STACK *sp;
	dbref doer;
	int count, pos;

	if (nfargs > 2) {
		safe_str("#-1 FUNCTION (PEEK) EXPECTS 0-2 ARGUMENTS", buff, bufc);
		return;
	}
	if (!fargs[0]) {
		doer = player;
	} else {
		doer = match_thing(player, fargs[0]);
	}

	if (!Controls(player, doer)) {
		safe_str("#-1 PERMISSION DENIED", buff, bufc);
		return;
	}
	if (!fargs[1] || !*fargs[1]) {
		pos = 0;
	} else {
		pos = atoi(fargs[1]);
	}

	if (StackSize(doer) == 0) {
		return;
	}
	if (pos > (StackSize(doer) - 1)) {
		safe_str("#-1 POSITION TOO LARGE", buff, bufc);
		return;
	}
	count = 0;
	sp = Stack(doer);
	while (count != pos) {
		if (sp == NULL) {
			return;
		}
		count++;
		sp = sp->next;
	}

	safe_str(sp->data, buff, bufc);
}

FUNCTION(fun_pop)
{
	STACK *sp, *prev;
	dbref doer;
	int count, pos;

	if (nfargs > 2) {
		safe_str("#-1 FUNCTION (POP) EXPECTS 0-2 ARGUMENTS", buff, bufc);
		return;
	}
	if (!fargs[0]) {
		doer = player;
	} else {
		doer = match_thing(player, fargs[0]);
	}

	if (!Controls(player, doer)) {
		safe_str("#-1 PERMISSION DENIED", buff, bufc);
		return;
	}
	if (!fargs[1] || !*fargs[1]) {
		pos = 0;
	} else {
		pos = atoi(fargs[1]);
	}

	sp = Stack(doer);
	count = 0;

	if (StackSize(doer) == 0) {
		return;
	}
	if (pos > (StackSize(doer) - 1)) {
		safe_str("#-1 POSITION TOO LARGE", buff, bufc);
		return;
	}
	while (count != pos) {
		if (sp == NULL) {
			return;
		}
		prev = sp;
		sp = sp->next;
		count++;
	}

	safe_str(sp->data, buff, bufc);
	if (count == 0) {
		s_Stack(doer, sp->next);
		s_StackSize(doer, StackSize(doer) - 1);
		free_lbuf(sp->data);
		free(sp);
	} else {
		prev->next = sp->next;
		s_StackSize(doer, StackSize(doer) - 1);
		free_lbuf(sp->data);
		free(sp);
	}
}

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

	if ((nfargs > 2) || (nfargs < 1)) {
		safe_str("#-1 FUNCTION (PUSH) EXPECTS 1-2 ARGUMENTS", buff, bufc);
		return;
	}
	if (!fargs[1]) {
		doer = player;
		data = fargs[0];
	} else {
		doer = match_thing(player, fargs[0]);
		data = fargs[1];
	}

	if (!Controls(player, doer)) {
		safe_str("#-1 PERMISSION DENIED", buff, bufc);
		return;
	}
	if (StackSize(doer) >= mudconf.stack_limit) {
		safe_str("#-1 STACK SIZE EXCEEDED", buff, bufc);
		return;
	}
	sp = (STACK *) malloc(sizeof(STACK));
	sp->next = Stack(doer);
	sp->data = alloc_lbuf("push");
	StringCopy(sp->data, data);
	s_Stack(doer, sp);
	s_StackSize(doer, StackSize(doer) + 1);
}