dmuck0.15-beta/docs/muf/
dmuck0.15-beta/game/
dmuck0.15-beta/game/logs/
dmuck0.15-beta/game/muf/
dmuck0.15-beta/game/muf/text/
#include "config.h"

/*  PLEASE go read the copyright notice contained in mush.h  --Howard      */

#ifdef MUSH

#include <ctype.h>       /*  PennMUSH 1.50 pl 7 MUSH functions and parser. */
                         /*  with MUSH 2.0.9 pl 9 enhancements             */
#include <math.h>        /*  Ported by Howard on 3-23-93.                  */
#include <sys/time.h>
#include <sys/types.h>
#include <string.h>

#include "copyright.h"
#include "params.h"
#include "externs.h"
#include "db.h"
#include "match.h"
#include "mush.h"
#include "money.h"
#include "interface.h"
#include "version.h"

extern time_t time_started;

/* Next 5 lines are idioicy */
unsigned hash_fn()
{ return 0; }

static void do_userfn(dbref player, dbref cause, dbref obj, char *attrib, char *args[10], char *buff, int flag);

/* Globals */
static int nest_level = 0;	
static int recurs_lev = 0;
static int invok_counter = 0;
static char *lwptr[10];
static int lwlen[10];
static char lwbuff[BUFFER_LEN];
char rptr[10][BUFFER_LEN];	/* local registers */

#define UPCASE(x)               ((isascii(x) && islower(x)) ? toupper(x) : (x))
#define DOWNCASE(x)             ((isascii(x) && isupper(x)) ? tolower(x) : (x))

#define varargs_preamble(xname,xnargs)	\
	if (!fn_range_check(xname, nargs, xnargs-1, xnargs, buff))	\
		return;							\
	if (!delim_check(args, nargs, xnargs, &sep, buff))		\
		return;

#define mvarargs_preamble(xname,xminargs,xnargs)	\
	if (!fn_range_check(xname, nargs, xminargs, xnargs, buff))	\
		return;							\
	if (!delim_check(args, nargs, xnargs, &sep, buff))		\
		return;

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

static int fn_range_check(const char *fname, int nfargs,
	int minargs, int maxargs, char *result)
{
	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(char *fargs[], int nfargs, int sep_arg,
	char *sep, char *buff)
{
	if (nfargs >= sep_arg) {
		if (!*fargs[sep_arg-1]) {
			*sep = ' ';
		} else if (strlen(fargs[sep_arg-1]) != 1) {
			strcpy(buff,
				"#-1 SEPARATOR MUST BE ONE CHARACTER");
			return 0;
		} else {
			*sep = *fargs[sep_arg-1];
		}
	} else {
		*sep = ' ';
	}
	return 1;
}

/* Matching for the string expressions */
dbref match_thing(dbref player, char *name)
{
  match_data md;

  init_match(player, name, NOTYPE, &md);
  match_everything(&md);

  return (noisy_match_result(&md));
}

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

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

   All function declarations follow the format
 
   static void fun_<name>(char *buff, char *args[10], dbref privs, dbref doer)

   All results are returned in buff. Be careful not to overflow this buffer.

   args are the arguments passed to the function. There cannot be more than
   ten. They can be hacked up if necessary, but for ease in debugging, it's
   probably a better idea not to.

   privs is the object executing the function.
   doer is the enactor (the thing which triggered privs).

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

/*  XFUNCTIONS declared elsewhere in mush but put where they belong here */

XFUNCTION(fun_lsearch)
{
  /* takes arguments in the form of: player, class, restriction */

  int is_wiz;
  char *who; char *class; char *restrict;
  int flag;
  object_flag_type restrict_type;
  object_flag_type  power_mask,  flag_mask, toggle_mask;
  char *restrict_name;
  dbref restrict_owner = NOTHING;
  dbref restrict_zone = NOTHING;
  dbref restrict_parent = NOTHING;
  dbref thing;
  int destitute = 1;
  struct dbref_list *found = NULL, *ptr = NULL, *list = NULL;
  match_data md;
  
/*
  if (o_daytime) {
    notify(privs, "Function disabled.");
    strcpy(buff, "#-1");
    return;
  }
*/

  if (!payfor(privs, LOOKUP_COST)) {
    notify(privs, privs, "Not enough money to do search.");
    strcpy(buff, "#-1");
    return;
  }

  is_wiz = Wizard(privs);

  who = args[0];
  class = args[1];
  restrict = args[2];

  /* set limits on who we search */
  if (!strcasecmp(who, "all"))
    restrict_owner = is_wiz ? ANY_OWNER : privs;
  else if (!strcasecmp(who, "me"))
    restrict_owner = privs;
  else
    restrict_owner = lookup_player(who);

  if (restrict_owner == NOTHING) {
    notify(privs, privs, "No such player.");
    strcpy(buff, "#-1");
    return;
  }

  /* set limits on what we search for */
  flag = 0;
  flag_mask = 0;
  power_mask = 0;
  restrict_name = NULL;
  restrict_type = NOTYPE;
  switch (class[0]) {
  case 'e':
  case 'E':
    if (string_prefix("exits", class)) {
      restrict_name = (char *) restrict;
      restrict_type = TYPE_EXIT;
    } else
      flag = 1;
    break;
  case 'f':
  case 'F':
    if (string_prefix("flags", class)) {
      if (!convert_flags(privs, restrict, &flag_mask, &toggle_mask,
			 &restrict_type)) {
	notify(privs, privs, "Unknown flag.");
	strcpy(buff, "#-1");
	return;
      }
    } else
      flag = 1;
    break;
  case 'n':
  case 'N':
    if (string_prefix("name", class))
      restrict_name = (char *) restrict;
    else if (!string_prefix("none", class))
      flag = 1;
    break;
  case 'o':
  case 'O':
    if (string_prefix("objects", class)) {
      restrict_name = (char *) restrict;
      restrict_type = TYPE_THING;
    } else
      flag = 1;
    break;
  case 'p':
  case 'P':
    switch (class[1]) {
    case 'a':
    case 'A':
      if (string_prefix("parent", class)) {
	init_match(privs, restrict, NOTYPE, &md);
	match_absolute(&md);
	restrict_parent = match_result(&md);
	if (restrict_parent == NOTHING) {
	  notify(privs, privs, "Unknown parent.");
	  strcpy(buff, "#-1");
	  return;
	}
      } else
	flag = 1;
      break;
    case 'l':
    case 'L':
      if (string_prefix("players", class)) {
	restrict_name = (char *) restrict;
	restrict_type = TYPE_PLAYER;
      } else
	flag = 1;
      break;
    case 'o':
    case 'O':
      if (string_prefix("powers", class)) {
/*	power_mask = find_power(class); */
	power_mask = -1;
	if (power_mask == -1) {
	  notify(privs, privs, "Unknown power.");
	  strcpy(buff, "#-1");
	  return;
	}
      }
      else
	flag =1;
      break;
    default:
      flag = 1;
    }
    break;
  case 'r':
  case 'R':
    if (string_prefix("rooms", class)) {
      restrict_name = (char *) restrict;
      restrict_type = TYPE_ROOM;
    } else
      flag = 1;
    break;
  case 't':
  case 'T':
    if (string_prefix("type", class)) {
      if (!strcasecmp(restrict, "none"))
	break;
      if (string_prefix("rooms", restrict))
	restrict_type = TYPE_ROOM;
      else if (string_prefix("exits", restrict))
	restrict_type = TYPE_EXIT;
      else if (string_prefix("objects", restrict))
	restrict_type = TYPE_THING;
      else if (string_prefix("players", restrict))
	restrict_type = TYPE_PLAYER;
      else {
	notify(privs, privs, "Unknown type.");
	strcpy(buff, "#-1");
	return;
      }
    } else
      flag = 1;
    break;
  case 'z':
  case 'Z':
    if (string_prefix("zone", class)) {
      init_match(privs, restrict, TYPE_THING, &md);
      match_absolute(&md);
      restrict_zone = match_result(&md);
      if (restrict_zone == NOTHING) {
	notify(privs, privs, "Unknown zone.");
	strcpy(buff, "#-1");
	return;
      }
    } else
      flag = 1;
    break;
  default:
    flag = 1;
  }
  if (flag) {
    notify(privs, privs, "Unknown type.");
    strcpy(buff, "#-1");
    return;
  }
 
  /* check privs */
  if (!is_wiz && (restrict_type != TYPE_PLAYER) &&
      ((restrict_owner == ANY_OWNER) || (restrict_owner != privs))) {
    notify(privs, privs, "Permission denied.");
    strcpy(buff, "#-1");
    return;
  }

  /* search loop */
  flag = 1;
  for (thing = 0; thing < db_top; thing++) {
    found = list;
    if ((restrict_type != NOTYPE) && (restrict_type != Typeof(thing)))
      continue;
    if ((FLAGS(thing) & flag_mask) != flag_mask)
      continue;
    if (Typeof(thing) == TYPE_GARBAGE)
      continue;
/*
    if ((Toggles(thing) & toggle_mask) != toggle_mask)
      continue;
    if ((Powers(thing) & power_mask) != power_mask)
      continue;
*/
    if ((restrict_owner != ANY_OWNER) && (restrict_owner != OWNER(thing)))
      continue;
    if ((restrict_name != NULL) &&
	(!string_match(unparse_name(thing), restrict_name)))
      continue;
    if ((restrict_parent != NOTHING) && (restrict_parent != Parent(thing)))
      continue;
/*
    if ((restrict_zone != NOTHING) && (restrict_zone != getzone(thing)))
      continue;
*/
    if (!found) {
      flag = 0;
      destitute = 0;
      found = listcreate(thing);
    } else
      listadd(found, thing);

    list = found;
  }

  /* nothing found matching search criteria */
  if (destitute) {
    notify(privs, privs, "Nothing found.");
    strcpy(buff, "#-1");
    return;
  }

  /* go down the list and print into a buffer */
  *buff = '\0';
  for (ptr = list; ptr; ptr = ptr->next) {
    if (strlen(buff) + 10 < BUFFER_LEN)
      strcat(buff, tprintf("#%d ", ptr->object));
    else {
      notify(privs, privs, "Search list too long.");
      strcpy(buff, "#-1");
    }
  }

  if (list) listfree(list);
}

XFUNCTION(fun_lwho)
{
  descriptor_data *d;
  char tbuf1[SBUF_LEN];
  char *bp;

  *buff = '\0';
  bp = buff;

  DESC_ITER_CONN(d) {
    if (d->connected) {
      if (*buff)
	sprintf(tbuf1, " #%d", d->player);
      else
	sprintf(tbuf1, "#%d", d->player);
      safe_str(tbuf1, buff, &bp);
    }
  }

  *bp = '\0';
}

XFUNCTION(fun_idlesecs)
{
  /* returns the number of seconds a player has been idle */

  time_t now;
  descriptor_data *d;
  dbref target;
  match_data md;

  now = time((time_t *) 0);

  target = lookup_player(args[0]);
  if (target == NOTHING) {
    init_match(privs, args[0], TYPE_PLAYER, &md);
    match_absolute(&md);
    match_player(&md);
    target = match_result(&md);
  }

  /* non-connected players and dark wizards return error, -1 */
  if ((target == NOTHING) || !Connected(target) ||
      (Dark(target) && !Wizard(privs))) {
    strcpy(buff, "-1");
    return;
  }

  /* else walk the descriptor list looking for a match */
  for (d = descriptor_list; (d); d = d->next) {
    if (d->connected) {
      if (d->player == target) {
	sprintf(buff, "%d", (now - d->last_time));
	return;
      }
    }
  }

  /* if we hit this point we are in trouble */
  strcpy(buff, "-1");
  fprintf(stderr, 
	  "** WHOA ** idlesecs() can't find player #%d on call by #%d\n",
	  target, privs);
}

XFUNCTION(fun_conn)
{
  /* returns the number of seconds a player has been connected */

  time_t now;
  descriptor_data *d;
  dbref target;
  match_data md;

  now = time((time_t *) 0);

  target = lookup_player(args[0]);
  if (target == NOTHING) {
    init_match(privs, args[0], TYPE_PLAYER, &md);
    match_absolute(&md);
    match_player(&md);
    target = match_result(&md);
  }

  /* non-connected players and dark wizards return error, -1 */
  if ((target == NOTHING) || !Connected(target) ||
      (Dark(target) && !Wizard(privs))) {
    strcpy(buff, "-1");
    return;
  }

  /* else walk the descriptor list looking for a match */
  for (d = descriptor_list; (d); d = d->next) {
    if (d->connected) {
      if (d->player == target) {
	sprintf(buff, "%d", (now - d->connected_at));
	return;
      }
    }
  }

  /* if we hit this point we are in trouble */
  strcpy(buff, "-1");
  fprintf(stderr, 
	  "** WHOA ** conn() can't find player #%d on call by #%d\n",
	  target, privs);
}

XFUNCTION(fun_lstats)
{
  int total, rooms, exits, things, players, garbage, i;
  dbref owner;
  match_data md;

  if (!strcasecmp(args[0], "all"))
    owner = ANY_OWNER;
  else if (!strcasecmp(args[0], "me"))
    owner = privs;
  else
    owner = lookup_player(args[0]);

  /* no name matched, try to match by number */
  if (owner == NOTHING) {
    init_match(privs, args[0], TYPE_PLAYER, &md);
    match_absolute(&md);
    owner = match_result(&md);
  }

  if (owner == NOTHING) {
    strcpy(buff, "#-1 NO SUCH PLAYER");
    return;
  }

  if (!Wizard(privs))
    if (owner != ANY_OWNER && owner != privs) {
      strcpy(buff, "#-1 PERMISSION DENIED");
      return;
    }

  total = rooms = exits = things = players = garbage = 0;
  for (i = 0; i < db_top; i++) {
    if (owner == ANY_OWNER || owner == OWNER(i)) {
      if (Typeof(i) == TYPE_GARBAGE) {
	garbage++;
      } else {
	total++;
	switch (Typeof(i)) {
	case TYPE_ROOM:
	  rooms++;
	  break;
	case TYPE_EXIT:
	  exits++;
	  break;
	case TYPE_THING:
	  things++;
	  break;
	case TYPE_PLAYER:
	  players++;
	  break;
	default:
	  break;
	}
      }				
    }				
  }

  if (owner == ANY_OWNER)
    total = total + garbage;
  /* note: God owns garbage */
  strcpy(buff, tprintf("%d %d %d %d %d %d", 
		       total, rooms, exits, things, players, garbage));
}

int Connected(dbref target) 
{
 descriptor_data *d;

   for (d = descriptor_list; (d); d = d->next) {
     if (d->connected && d->player == target)
         return 1;
   }
  return 0;
}

XFUNCTION(fun_lattr)
{
  propdir *list;
  dbref thing = match_thing(privs, args[0]);
  if (thing == NOTHING)
    strcpy(buff, "#-1 NO MATCH");
  else {
    if (!controls(privs, thing))
      strcpy(buff, "#-1 PERMISSION DENIED");
    else {
      strcpy(buff, "");
      list = DBFETCHPROP(thing);
      while (list && (strlen(buff) + 20 < BUFFER_LEN)) {
      if (list && check_perms(list->perms, ACCESS_CO, PT_SEE))
	  strcat(buff, tprintf("%s ", list->name));
	list = list->next;
      }
    }
  }
}

/*------------------------------------------------------------------------
 * User-defined global function handlers 
 */

typedef struct userfn_entry USERFN_ENTRY;

struct userfn_entry {
  dbref thing;
  char *name;
};

static USERFN_ENTRY userfn_tab[MAX_GLOBAL_FNS];
static int userfn_count = 0;

GLOBAL_FUN(fun_gfun)
{
  /* this function can never be called directly. It is called by
   * user-defined functions.
   */

  char *attrib;

  /* do not allow evaluation on objects which are HALT. This also
   * helps to prevent recursion.
   */
  if ((FLAGS(privs) & HAVEN)) {    /* Don't bother to check if set haven. */
    strcpy(buff, "#-1 OBJECT HALTED");
    return;
  }

  /* prevent loops */
  if (nest_level++ > MAX_NEST_LEVEL) {
    nest_level--;
    fprintf(stderr, 
      "** EXCESSIVE RECURSION ** in global function on #%d called by #%d.\n",
	    privs, doer);
    FLAGS(privs) |= HAVEN;	/* stop function evaluation for sure */
    DBDIRTY(privs);
    strcpy(buff, "#-1 EXCESSIVE RECURSION ERROR");
    return;
  }

  attrib = atr_get(userfn_tab[fn_num].thing, userfn_tab[fn_num].name);
  do_userfn(privs, doer, userfn_tab[fn_num].thing, attrib, args, buff, 1);
}

#ifdef DO_RNUM_GLOBALS
FUNCTION(fun_rnum)
{
 match_data md;

  dbref place = match_thing(privs, args[0]);
  char *name = args[1];
  dbref thing;
  init_match_remote(place, name, NOTYPE, &md);
  match_remote(&md);
  switch (thing = match_result(&md)) {
  case NOTHING:
    strcpy(buff, "#-1 NO MATCH");
    break;
  case AMBIGUOUS:
    strcpy(buff, "#-1 AMBIGUOUS MATCH");
    break;
  default:
    sprintf(buff, "#%ld", thing);
  }
}
#endif

int parse_thing_slash (dbref player, char *thing, char *after, dbref it)
{
char	*str;
match_data md;

	/* get name up to / */
	for (str=thing; *str && (*str!='/'); str++) ;

	/* If no / in string, return failure */

	if (!*str) {
		after = NULL;
		it = NOTHING;
		return 0;
	}
	*str++ = '\0';
	after = str;

	/* Look for the object */

	init_match(player, thing, NOTYPE, &md);
	match_everything(&md);
	it = match_result(&md);

	/* Return status of search */
	return (GoodObject(it));
}

int parse_attrib(dbref player, char *str, dbref thing, char *atr)
{
char	buff[BUFFER_LEN];

	if (!str) return 0;
	/* Break apart string into obj and attr.  Return on failure */
	strcpy(buff, str);
	if (!parse_thing_slash(player, buff, str, thing)) {
		return 0;
	}
	/* Get the named attribute from the object if we can */
	atr = atr_get(thing, str);
	return 1;
}

/* ---------------------------------------------------------------------------
 * 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	thing;
char    *ap;
char	*result, *curr, *objstring, *bp, *cp;
char atextbuf[BUFFER_LEN], sep;

	varargs_preamble("MAP", 3);

	/* Two possibilities for the second arg: <obj>/<attr> and <attr>. */
	if (parse_attrib(doer, args[0], thing, ap)) {
		if (!ap || (!GoodObject(thing)))
			ap = NULL;
	} else {
		thing = doer;
		ap = atr_get(thing, args[0]);
	}

	/* Make sure we got a good attribute */
	if (!ap) {
		strcpy(buff, "#-1 No such attribute");
		return;
	}

	/* now process the list one element at a time */
	cp = curr = args[1];
	bp = buff;
	while (cp && *cp) {
		while (*cp == sep) cp++;
		if (*cp) {
			objstring = parse_to(&cp, sep, 0);
			strcpy(atextbuf, ap);
	                result = exec(doer, atextbuf, privs, 
                                      EV_STRIP|EV_FCHECK|EV_EVAL);
			if (buff != bp)
				safe_chr(sep, buff, &bp);
			safe_str(result, buff, &bp);
			free(result);
		}
	}
	*bp = '\0';
}

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

	varargs_preamble("CENTER", 3);

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

	if (len >= width)  {
		strcpy(buff, args[0]);
		return;
	}

	p = buff;
	lead_chrs = (width / 2) - (len / 2) + .5;
	for (i=0; i<lead_chrs; i++)  safe_chr(sep, p, &p);
	safe_str(args[0], p, &p);
	trail_chrs = width - lead_chrs - len;
	for (i=0; i<trail_chrs; i++)  safe_chr(sep, p, &p);
	*p = '\0';
}

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

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

	mvarargs_preamble("TRIM", 1, 3);
	if (nargs >= 1)  {
		switch(tolower(*args[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 = args[0];
		while(*p != '\0')  {
			if (*p != sep)  lastchar = p;
			p++;
		}
		*(lastchar+1) = '\0';
	}

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

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

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

	/* we can't remove anything before the first position */

	if (el < 1) {
		strcpy(buff, str);
		return;
	}

	sptr = str;
	count = 1;

	/* go to the correct item in the string */

	while (sptr && (count < el)) {
		sptr++;
		sptr = (char *)index(sptr, sep);
		count++;
	}

	/* Did we run off the end of the string? */

	if (!sptr) {
		strcpy(buff, str);
		return;
	}

	/* now find the end of that element */

	if (sptr != str)
		*sptr++ = '\0';
	eptr = (char *)index(sptr, sep);

	switch (flag) {
	case 0:					/* deletion */
		if (!eptr) {			/* last element */
			strcpy(buff, str);
		} else if (sptr == str) {	/* first element */
			*eptr++ = '\0';		/* chop leading separator */
			strcpy(buff, eptr);
		} else {
			sprintf(buff, "%s%s", str, eptr);
		}
		break;
	case 1:					/* replacing */
		bp = buff;
		if (!eptr) {			/* last element */
			safe_str(str, buff, &bp);
			safe_chr(sep, buff, &bp);
			safe_str(word, buff, &bp);
		} else if (sptr == str) {	/* first element */
			safe_str(word, buff, &bp);
			safe_str(eptr, buff, &bp);
		} else {
			safe_str(str, buff, &bp);
			safe_chr(sep, buff, &bp);
			safe_str(word, buff, &bp);
			safe_str(eptr, buff, &bp);
		}
		*bp = '\0';
		break;
	case 2:					/* insertion */
		bp = buff;
		if (sptr == str) {		/* first element in string */
			safe_str(word, buff, &bp);
			safe_chr(sep, buff, &bp);
			safe_str(str, buff, &bp);
		} else {
			safe_str(str, buff, &bp);
		 	safe_chr(sep, buff, &bp);
			safe_str(word, buff, &bp);
			safe_chr(sep, buff, &bp);
			safe_str(sptr, buff, &bp);
		}
		*bp = '\0';
		break;
	}
}


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

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

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

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

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

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


/* ---------------------------------------------------------------------------
 * isdbref: is the argument a valid dbref?
 */
FUNCTION(fun_isdbref)
{
char	*p;
dbref	dbitem;

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

FUNCTION(fun_grep)
{
  char *sptr[10];
  int i;
  char *tp;

  dbref it = match_thing(privs, args[0]);
  if (it == NOTHING) {
    strcpy(buff, "#-1 NO SUCH OBJECT VISIBLE");
    return;
  }

  /* make sure there's an attribute and a pattern */
  if (!args[1] || !*args[1]) {
    strcpy(buff, "#-1 NO SUCH ATTRIBUTE");
    return;
  }
  if (!args[2] || !*args[2]) {
    strcpy(buff, "#-1 INVALID GREP PATTERN");
    return;
  }

  /* save wptr */
  for (i = 0; i < 10; i++)
    sptr[i] = wptr[i];
  
  tp = grep_util(it, args[1], args[2], strlen(args[2]));
  strcpy(buff, tp);
  free(tp);

  /* restore wptr */
  for (i = 0; i < 10; i++)
    wptr[i] = sptr[i];
}

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

FUNCTION(fun_starttime)
{
char    temp_buff[100];

        sprintf(temp_buff,"%s", asctime(localtime(&time_started)));
        temp_buff[strlen(temp_buff)-1] = '\0';
        strcpy(buff, temp_buff);
}

/* ---------------------------------------------------------------------------
 * fval: copy the floating point value into a buffer and make it presentable
 */

void fval(char *buff, double result)
{
char    *p;
 
        sprintf(buff, "%.6f",result);   /* get double val into buffer */
        p = rindex(buff, '0'); 
        if (p == NULL) {                /* remove useless trailing 0's */
                return;
        } else if (*(p+1) == '\0')  {
                while (*p == '0')  {
                        *p-- = '\0';
                }
        }
        p = rindex(buff, '.');           /* take care of dangling '.' */
        if (*(p+1) == '\0')
        *p = '\0';
}

  
/* ---------------------------------------------------------------------------
 * fun_abs: Returns the absolute value of its argument.                        
 */                                                     
    
FUNCTION(fun_abs)
{                 
double  num;
 
        num = atof(args[0]);
        if (num == 0)
                strcpy(buff, "0");
        else
                fval(buff, abs(num));
}       
 
/* ---------------------------------------------------------------------------
 * fun_fabs: Returns the absolute value of its argument.                        
 */                                                     
    
FUNCTION(fun_fabs)
{                 
double  num;
 
        num = atof(args[0]);
        if (num == 0)
                strcpy(buff, "0");
        else
#ifdef HAVE_FABS
                fval(buff, fabs(num));
#else
                strcpy(buff, "No fabs function on this computer");
#endif
}       

FUNCTION(fun_sqrt)
{
double  val;

        val = atof(args[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_power)
{
double  val1, val2;

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

FUNCTION(fun_ln)
{
double  val;
        val = atof(args[0]);
        if (val > 0)
                fval(buff, log(val));
        else     
                sprintf(buff, "#-1 LN OF NEGATIVE OR ZERO");
}                
 
FUNCTION(fun_log)
{                
double  val;
        val = atof(args[0]);
        if (val > 0) {
                fval(buff, log10(val));
        } else { 
                sprintf(buff, "#-1 LOG OF NEGATIVE OR ZERO");
        }        
}        

FUNCTION(fun_asin)
{
double  val;

        val = atof(args[0]);
        if ((val < -1) || (val > 1)) {
                sprintf(buff, "#-1 ASIN ARGUMENT OUT OF RANGE");
        } else { 
                fval(buff, asin(val));
        }        
}        
 
FUNCTION(fun_acos)
{                
double  val;
            
        val = atof(args[0]);
        if ((val < -1) || (val > 1)) {
                sprintf(buff, "#-1 ACOS ARGUMENT OUT OF RANGE");
        } else { 
                fval(buff, acos(val));
        }        
}        
 
FUNCTION(fun_atan)      { fval(buff, atan(atof(args[0]))); }


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

	cp = curr = exec(doer, args[0], privs, EV_STRIP|EV_FCHECK|EV_EVAL);
	bp = buff;
	while (cp && *cp) {
		while (isspace(*cp)) cp++;
		if (*cp) {
			objstring = parse_to(&cp, *args[2], 0);
			buff2 = replace_string("##", objstring, args[1]);
			result = exec(doer, buff2, privs,
				EV_STRIP|EV_FCHECK|EV_EVAL);
			free(buff2);
			if (buff != bp)
				safe_chr(' ', buff, &bp);
			safe_str(result, buff, &bp);
			free(result);
		}
	}
	free(curr);
	*bp = '\0';
}


/* --------------------------------------------------------------------------
 * Utility functions: RAND, DIE, SECURE, SPACE, BEEP, SWITCH, EDIT,
 *     ITER, ESCAPE, SQUISH
 */

FUNCTION(fun_rand)
{
  /*
   * Uses Sh'dow's random number generator, found in utils.c.  Better
   * distribution than original, w/ minimal speed losses.
   */
  sprintf(buff, "%d", getrandom(atoi(args[0])));
  
}

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

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

  for (count = 0; count < n; count++)
    total += getrandom(die) + 1;

  sprintf(buff, "%d", total);
}

FUNCTION(fun_secure)
{
  /* this function smashes all occurences of "unsafe" characters in a string.
   * "unsafe" characters are ( ) [ ] { } $ % , ; \
   * these characters get replaced by spaces
   */

  strcpy(buff, args[0]);
  while (*buff) {
    switch (*buff) {
    case '(':
    case ')':
    case '[':
    case ']':
    case '{':
    case '}':
    case '$':
    case '%':
    case ',':
    case ';':
    case '\\':
      *buff = ' ';
      break;
    }
    buff++;
  }
}

FUNCTION(fun_escape)
{
  /* another function more or less right out of 2.0 code */

  char *s, *p;

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

FUNCTION(fun_squish)
{
  /* zaps leading and trailing spaces, and reduces other spaces to a single
   * space. This only applies to the literal space character, and not to
   * tabs, newlines, etc.
   * We do not need to check for buffer length overflows, since we're
   * never going to end up with a longer string.
   */

  char *bp, *tp;

  bp = buff;
  tp = args[0];

  while (*tp) {

    while (*tp && (*tp != ' '))		/* copy non-spaces */
      *bp++ = *tp++;

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

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

  /* we might have to get rid of trailing spaces. Just overwrite them with
   * nulls. Make sure we're not at the beginning of the string, though.
   */
  if ((bp != buff) && (*(tp -1) == ' ')) {
    bp--;
    while ((bp != buff) && (*bp == ' '))
      *bp-- = '\0';
  } else {
    *bp = '\0';
  }
}

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

	if (!args[0] || !(*args[0])) 
		num = 1;
	 else 
		num = atoi(args[0]);

	if (num < 1) {
		if (!is_number(args[0]) || (num != 0)) {
			num = 1;
		}
	} else if (num >= BUFFER_LEN) {
		num = BUFFER_LEN - 1;
	}
	for (cp= buff; num > 0; num--)
		*cp++ = ' ';
	*cp = '\0';
	return;
}

FUNCTION(fun_version)
{ 
  strcpy(buff, VERSION); 
}

FUNCTION(fun_mudname)
{
 char  *prop;

     prop = get_property_data((dbref) 0, RWHO_NAME, ACCESS_WI);
     if( prop && *prop)
         strcpy(buff, prop);
     else
         strcpy(buff, "#-1  MUDNAME NOT SET");
}

FUNCTION(fun_beep)
{
  char tbuf1[10];
  int i, k;

  /* this function prints 1 to 5 beeps. The alert character '\a' is
   * an ANSI C invention; non-ANSI-compliant implementations may ignore
   * the '\' character and just print an 'a', or do something else nasty.
   */

  k = atoi(args[0]);

  if (!Wizard(privs) || (k <= 0) || (k > 5)) {
    strcpy(buff, "#-1 PERMISSION DENIED");
    return;
  }

  for (i = 0; i < k; i++)
    tbuf1[i] = '\a';
  tbuf1[i] = '\0';
  strcpy(buff, tbuf1);
}


#define FreeArgs  \
  if (mstr) { free(mstr); mstr = NULL; } \
  if (curr) { free(curr); curr = NULL; } \
  if (rbuf) { free(rbuf); }


FUNCTION(fun_switch)
{
  /* this works a bit like the @switch command: it returns the string
   * appropriate to the match. It picks the first match, like @select
   * does, though.
   * Args to this function are passed unparsed. Args are not evaluated
   * until they are needed.
   */

  int i;
  char *extra[90];
  int sw_args;
  char *mstr, *curr, *rbuf;

  mstr = curr = rbuf = NULL;

  /* need at least two args */
  if (!args[1]) {
    strcpy(buff, "#-1 TOO FEW ARGUMENTS");
    return;
  }

  mstr = exec(doer, args[0], privs, EV_EVAL | EV_STRIP | EV_FCHECK);

  /* try matching, return match immediately when found */
  for (i = 1; (i < 9) && args[i] && args[i + 1]; i+= 2) {
    curr = exec(doer, args[i], privs, EV_EVAL | EV_STRIP | EV_FCHECK);
    if (local_wild_match(curr, mstr)) {
      rbuf = exec(doer, args[i+1], privs, EV_EVAL | EV_STRIP | EV_FCHECK);
      strcpy(buff, rbuf);
      FreeArgs;
      return;
    }
    if (curr) {
      free(curr);
      curr = NULL;
    }
  }

  /* if nothing's been matched, our counter is at the default. Or, we
   * could be at the tenth arg. If we are, we want to split up the
   * tenth argument. It's already been parsed.
   */
  if ((i == 9) && args[9]) {
    sw_args = extra_arglist(args[9], extra, 90);
    if (sw_args < 2) {
      /* we don't have an overflow. Just return the arg. */
      rbuf = exec(doer, args[9], privs, EV_EVAL | EV_STRIP | EV_FCHECK);
      strcpy(buff, rbuf);
      FreeArgs;
      return;
    }
    /* now we try matching just like before */
    for (i = 0; (i < sw_args) && extra[i] && extra[i + 1]; i += 2) {
      curr = exec(doer, extra[i], privs, EV_EVAL | EV_STRIP | EV_FCHECK);
      if (local_wild_match(curr, mstr)) {
	rbuf = exec(doer, extra[i+1], privs, EV_EVAL | EV_STRIP | EV_FCHECK);
	strcpy(buff, rbuf);
	FreeArgs;
	return;
      }
      if (curr) {
	free(curr);
	curr = NULL;
      }
    }
    /* our counter is at the default. If there is one, return it. */
    if ((i < 90) && extra[i]) {
      rbuf = exec(doer, extra[i], privs, EV_EVAL | EV_STRIP | EV_FCHECK);
      strcpy(buff, rbuf);
      FreeArgs;
      return;
    }
  } else if ((i < 10) && args[i]) {
    /* no extra arguments, just copy the default */
    rbuf = exec(doer, args[i], privs, EV_EVAL | EV_STRIP | EV_FCHECK);
    strcpy(buff, rbuf);
    FreeArgs;
    return;
  }

  /* otherwise, return nothing */
  FreeArgs;
  *buff = '\0';
}

#undef FreeArgs

FUNCTION(fun_edit)
{
  int i, len;
  char *str, *f, *r;

  str = args[0];		/* complete string */ 
  f = args[1];			/* find this */  
  r = args[2];			/* replace it with this */

  if (!*f && !*r) {		/* check for nothing, or we'll infinite loop */
    strcpy(buff, str);
    return;
  }

  if (!strcmp(f, "$")) {
    /* append */
    if (strlen(str) + strlen(r) < BUFFER_LEN) {
      strcpy(buff, str);
      strcat(buff, r);
    } else {
      strcpy(buff, "#-1 STRING TOO LONG");
    }
  } else if (!strcmp(f, "^")) {
    /* prepend */
    if (strlen(str) + strlen(r) < BUFFER_LEN) {
      strcpy(buff, r);
      strcat(buff, str);
    } else {
      strcpy(buff, "#-1 STRING TOO LONG");
    }
  } else {
    /* find and replace */
    len = strlen(f);
    for (i = 0; (i < BUFFER_LEN) && *str; )
      if (strncmp(f, str, len) == 0) {
	if ((i + strlen(r)) < BUFFER_LEN) {
	  strcpy(buff + i, r);
	  i += strlen(r);
	  str += len;
	} else
	  buff[i++] = *str++;
      } else
	buff[i++] = *str++;
    buff[i++] = '\0';
  }
}

FUNCTION(fun_iter)
{
  /* more or less straight from the TinyMUSH 2.0 code.
   * There is one big difference -- arguments to this function are UNPARSED.
   */

  char *curr, *objstr, *tbuf, *result, *bp;
  char *s = NULL;

  s = curr = exec(doer, args[0], privs, EV_EVAL | EV_STRIP | EV_FCHECK);
  bp = buff;

  while (curr && *curr) {
    while (isspace(*curr))
      curr++;
    if (*curr) {
      objstr = parse_to(&curr, ' ', EV_STRIP);
      tbuf = replace_string("##", objstr, args[1]);
      result = exec(doer, tbuf, privs, EV_FCHECK);
      free(tbuf);
      if (buff != bp)
	safe_chr(' ', buff, &bp);
      safe_str(result, buff, &bp);
      free(result);
    }
  }
  *bp = '\0';
  if (s)
    free(s);
}

/* --------------------------------------------------------------------------
 * Time functions: TIME, SECS, CONVTIME, CONVSECS
 */

FUNCTION(fun_time)
{
  time_t tt;
  tt = time((time_t *) 0);
  sprintf(buff, "%s", ctime(&tt));
  buff[strlen(buff)-1] = '\0';
}

FUNCTION(fun_secs)
{
  time_t tt;
  time(&tt);
  strcpy(buff, tprintf("%d", tt));
}

FUNCTION(fun_convsecs)
{
  /* converts seconds to a time string */

  time_t tt;
  tt = atol(args[0]);
  sprintf(buff, "%s", ctime(&tt));
  buff[strlen(buff)-1] = '\0';
}
    
FUNCTION(fun_strftime)
{
 static struct tm *tm;
 long input;
 char p_buf[BUFFER_LEN];

  /* need at least two args */
  if (!args[1]) {
    strcpy(buff, "#-1 TOO FEW ARGUMENTS");
    return;
  }
    input = atol(args[0]);
    tm = localtime((time_t *) &input);

    if (!format_time(p_buf, BUFFER_LEN, args[1], tm))
	strcpy(buff, "#-1 Error.");
    else
	strcpy(buff, p_buf);
}

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

int do_convtime(str, ttm)
     char *str;
     struct tm *ttm;
{
  /* 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
   */

  char buf[32];
  char *p, *q;
  int i;

  if (strlen(str) != 24)
    return 0;

  /* do not hack up the string we were passed */
  strcpy(buf, str);

  /* move over the day of week and truncate. Will always be 3 chars.
   * we don't need this, so we can ignore it.
   */
  p = (char *) index(buf, ' ');
  if (p)
    *p++ = '\0';
  if (strlen(buf) != 3)
    return 0;

  /* get the month (3 chars), and convert it to a number */
  q = (char *) index(p, ' ');
  if (q)
    *q++ = '\0';
  if (strlen(p) != 3)
    return 0;
  else {
    for (i = 0; (i < 12) && strcmp(month_table[i], p); i++)
      ;
    if (i == 12)		/* not found */
      return 0;
    else
      ttm->tm_mon = i;
  }

  /* get the day of month */
  p = q;
  while (isspace(*p))		/* skip leading space */
    p++;
  q = (char *) index(p, ' ');
  if (q)
    *q++ = '\0';
  ttm->tm_mday = atoi(p);

  /* get hours */
  p = (char *) index(q, ':');
  if (p)
    *p++ = '\0';
  ttm->tm_hour = atoi(q);

  /* get minutes */
  q = (char *) index(p, ':');
  if (q)
    *q++ = '\0';
  ttm->tm_min = atoi(p);

  /* get seconds */
  p = (char *) index(q, ' ');
  if (p)
    *p++ = '\0';
  ttm->tm_sec = atoi(q);

  /* get year */
  ttm->tm_year = atoi(p) - 1900;

  return 1;
}

FUNCTION(fun_convtime)
{
  /* converts time string to seconds */
  
  struct tm *ttm;

  ttm = (struct tm *) malloc(sizeof(struct tm));
  if (do_convtime(args[0], ttm)) {
#ifdef HAVE_TIMELOCAL
    sprintf(buff, "%d", timelocal(ttm));
#else
#ifdef HAVE_MKTIME
    sprintf(buff, "%d", mktime(ttm));
#else
    strcpy(buff, "-1");
#endif
#endif
  } else {
    strcpy(buff, "-1");
  }
  free(ttm);
}

/* --------------------------------------------------------------------------
 * Attribute functions: GET, XGET, V, S, UFUN, ZFUN
 */

char *do_get_attrib(dbref player, dbref thing, char *attrib)
{
 char tbuf1[BUFFER_LEN];
 char *str;

 if(!GoodObject(thing))
    return ((char *) "");
    
 if (!(find_property(thing, attrib, ACCESS_WI))) 
    return ((char *) "");     /* used to be #-1 NO SUCH ATTRIBUTE */
 else {
 str = get_property_data(thing, attrib, access_rights(player, thing, NOTHING));
       if(!str)
          return ((char *) "#-1 NO PERMISSION TO GET ATTRIBUTE");
       else
          strcpy(tbuf1, str);
  }
  return tbuf1;
}

FUNCTION(fun_get)
{
  dbref thing;
  char *s;
  char tbuf1[BUFFER_LEN];

  strcpy(tbuf1, args[0]);
  for(s = tbuf1; *s && (*s != '/'); s++);
  if(!*s) {
    strcpy(buff, "#-1 BAD ARGUMENT FORMAT TO GET");
    return;
  }
  *s++ = 0;
  thing = match_thing(privs, tbuf1);
  if(thing == NOTHING) {
    strcpy(buff, "#-1 NO SUCH OBJECT VISIBLE");
    return;
  }

  if(*s == '_') s++;
  if(!*s) {
    strcpy(buff, "#-1 BAD ARGUMENT FORMAT TO GET");
    return;
  }

  strcpy(buff, do_get_attrib(privs, thing, s));
}

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

	s = args[0];
	start = atoi(args[1]);
	nchars = atoi(args[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';
	}
}

/* Functions like get, but uses the standard way of passing arguments */
/* to a function, and thus doesn't choke on nested functions within.  */

FUNCTION(fun_xget)
{
  dbref thing;

  thing = match_thing(privs, args[0]);
  if (thing == NOTHING) {
    strcpy(buff, "#-1 NO SUCH OBJECT VISIBLE");
    return;
  }

  strcpy(buff, do_get_attrib(privs, thing, args[1]));
}

FUNCTION(fun_eval)
{
  /* like xget, except pronoun substitution is done */

  dbref thing;
  char tbuf1[BUFFER_LEN];
  char *tbuf2;

  thing = match_thing(privs, args[0]);
  if (thing == NOTHING) {
    strcpy(buff, "#-1 NO SUCH OBJECT VISIBLE");
    return;
  }

  strcpy(tbuf1, do_get_attrib(privs, thing, args[1]));
  tbuf2 = exec(privs, tbuf1, thing, 0);
  strcpy(buff, tbuf2);
  free(tbuf2);
}

FUNCTION(fun_v)
{
  /* handle 0-9, va-vz, n, l, # */

  int c;

  switch (c = args[0][0]) {
    case '0':
    case '1':
    case '2':
    case '3':
    case '4':
    case '5':
    case '6':
    case '7':
    case '8':
    case '9':
      if (!wptr[c - '0']) {
	buff[0] = '\0';
	return;
      }
      if(strlen(wptr[c - '0']) < BUFFER_LEN)
        strcpy(buff, wptr[c - '0']);
      else
	buff[0] = '\0';
      break;
    case 'n':
    case 'N':
      if(args[0][1]) {
	strcpy(buff, do_get_attrib(privs, privs, args[0]));
      } else
        strcpy(buff, unparse_name(doer));
      break;
    case '#':
      sprintf(buff, "#%d", doer);
      break;
    case 'l': case 'L':
      if(args[0][1]) {
	strcpy(buff, do_get_attrib(privs, privs, args[0]));
      } else
	/*
	 * giving the location does not violate security, since the object
	 * is the enactor.
	 */
	sprintf(buff, "#%d", Location(doer));
      break;
      /* objects # */
    case '!':
      sprintf(buff, "#%d", privs);
      break;
    default:
      strcpy(buff, do_get_attrib(privs, privs, args[0]));
    }
}


FUNCTION(fun_s)
{
  char *s;

  s = exec(doer, args[0], privs, 0);
  strcpy(buff, s);
  free(s);
}

FUNCTION(fun_ufun)
{
  char *attrib, *name;
  char string[BUFFER_LEN];
  dbref obj;
  match_data md;

  if ((FLAGS(privs) & HAVEN)) {    /* Don't bother to check if set haven. */
    strcpy(buff, "#-1 OBJECT HALTED");
    return;
  }

  /* prevent loops */
  if (nest_level++ > MAX_NEST_LEVEL) {
    nest_level--;
    fprintf(stderr, 
      "** EXCESSIVE RECURSION ** in user function on #%d called by #%d.\n",
	    privs, doer);
    FLAGS(privs) |= HAVEN;
    DBDIRTY(privs);
    strcpy(buff, "#-1 EXCESSIVE RECURSION ERROR");
    return;
  }

  /* find the user function attribute */

  if (!args[0] || !*args[0]) {
    strcpy(buff, "#-1 NOT FOUND");
    nest_level--;
    return;
  }

  strcpy(string, args[0]);
  if ((name = (char *) index(string, '/')) != NULL) {
    *name++ = '\0';
    init_match(privs, string, NOTYPE, &md);
    match_everything(&md);
    obj = noisy_match_result(&md);
  } else {
    name = string;
    obj = privs;
  }
  attrib = get_property_data(obj, name, access_rights(privs, obj, NOTHING));
  do_userfn(privs, doer, obj, attrib, args, buff, 0);
}

/* --------------------------------------------------------------------------
 * Local registers: SETQ, R
 */


FUNCTION(fun_setq)
{
  /* sets a variable into a local register */

  int r = atoi(args[0]);
  if ((r < 0) || (r > 9)) {
    strcpy(buff, "#-1 REGISTER OUT OF RANGE");
    return;
  }

  strcpy(rptr[r], args[1]);
  *buff = '\0';
}

FUNCTION(fun_r)
{
  /* returns a local register */

  int r = atoi(args[0]);
  if ((r < 0) || (r > 9)) {
    strcpy(buff, "#-1 REGISTER OUT OF RANGE");
    return;
  }

  strcpy(buff, rptr[r]);
}

/* --------------------------------------------------------------------------
 * High-order functions: FILTER, FOLD
 */

FUNCTION(fun_fold)
{
  /* iteratively evaluates an attribute with a list of arguments and
   * optional base case. With no base case, the first list element is
   * passed as 50, and the second as %1. The attribute is then evaluated
   * with these args. The result is then used as %0, and the next arg as
   * %1. Repeat until no elements are left in the list. The base case 
   * can provide a starting point.
   */

  dbref thing;
  char *attrib, *name;
  char abuf[BUFFER_LEN], rstore[BUFFER_LEN], string[BUFFER_LEN];
  char *asave, *result, *bp, *cp;
  char *tptr[10];
  match_data md;
  int i;

  /* check our argument count */
  if ((nargs != 2) && (nargs != 3)) {
    strcpy(buff, "#-1 FUNCTION (FOLD) EXPECTS 2 OR 3 ARGUMENTS");
    return;
  }

  /* find our object and attribute */
  strcpy(string, args[0]);
  if ((name = (char *) index(string, '/')) != NULL) {
    *name++ = '\0';
    init_match(privs, string, NOTYPE, &md);
    match_everything(&md);
    thing = noisy_match_result(&md);
  } else {
    name = string;
    thing = privs;
  }

  if (!GoodObject(thing)) {
    strcpy(buff, "#-1 OBJECT NOT FOUND");
    return;
  }

  attrib = get_property_data(thing, name, access_rights(privs, thing, NOTHING));
  if (!attrib || !*attrib) {
    strcpy(buff, "#-1 PROPERTY NOT FOUND");
    return;
  }
  asave = attrib;
  
  cp = args[1];
  bp = buff;
  strcpy(abuf, asave);		/* save attribute text */

  /* save our stack */
  for (i = 0; i < 10; i++)
    tptr[i] = wptr[i];

  /* handle the first case */
  if (nargs == 3) {
    wptr[0] = args[2];
    wptr[1] = parse_to(&cp, ' ', 0);
  } else {
    wptr[0] = parse_to(&cp, ' ', 0);
    wptr[1] = parse_to(&cp, ' ', 0);
  }
  result = exec(doer, abuf, privs, EV_STRIP|EV_FCHECK|EV_EVAL);

  strcpy(rstore, result);
  free(result);
  
  /* handle the rest of the cases */
  while (cp && *cp) {
    while (*cp == ' ')		/* do not clobber tabs and newlines */
      cp++;
    if (*cp) {
      wptr[0] = rstore;
      wptr[1] = parse_to(&cp, ' ', 0);
      strcpy(abuf, asave);
      result = exec(doer, abuf, privs, EV_STRIP|EV_FCHECK|EV_EVAL);
      strcpy(rstore, result);
      free(result);
    }
  }
  safe_str(rstore, buff, &bp);
  *bp = '\0';

  for (i = 0; i < 10; i++)
    wptr[i] = tptr[i];
}

FUNCTION(fun_filter)
{
  /* take a user-def function and a list, and return only those elements
   * of the list for which the function evaluates to 1.
   */

  dbref thing;
  char *attrib, *name;
  char *result, *bp, *cp, *asave;
  char *tptr[10];
  match_data md;
  char abuf[BUFFER_LEN], string[BUFFER_LEN];
  int i;

  /* find our object and attribute */
  strcpy(string, args[0]);
  if ((name = (char *) index(string, '/')) != NULL) {
    *name++ = '\0';
    init_match(privs, string, NOTYPE, &md);
    match_everything(&md);
    thing = noisy_match_result(&md);
  } else {
    name = string;
    thing = privs;
  }

  if (!GoodObject(thing)) {
    strcpy(buff, "#-1 OBJECT NOT FOUND");
    return;
  }

  attrib = get_property_data(thing, name, access_rights(privs, thing, NOTHING));
  if (!attrib || !*attrib) {
    strcpy(buff, "#-1 PROPERTY NOT FOUND");
    return;
  }

  asave = attrib;

  for (i = 0; i < 10; i++)
    tptr[i] = wptr[i];

  cp = args[1];
  bp = buff;
  while (cp && *cp) {
    while (*cp == ' ')
      cp++;
    if (*cp) {
      wptr[0] = parse_to(&cp, ' ', 0);
      strcpy(abuf, asave);
      result = exec(doer, abuf, privs, EV_STRIP|EV_FCHECK|EV_EVAL);
      if (*result == '1') {
	if (bp != buff)
	  safe_chr(' ', buff, &bp);
	safe_str(wptr[0], buff, &bp);
      }
      free(result);
    }
  }
  *bp = '\0';

  for (i = 0; i < 10; i++)
    wptr[i] = tptr[i];
}

/* --------------------------------------------------------------------------
 * Arithmetic functions: ADD, SUB, MUL, DIV, MOD, GT, GTE, LT, LTE,
 *   EQ, NEQ, MAX, MIN, DIST2D, DIST3D, LNUM, SIGN, ABS, ISNUM, VAL
 */

FUNCTION(fun_isnum)
{
  /* trivial little function, returns 0 or 1 depending on whether
   * or not the string is a number. No overflow checks needed.
   */
  sprintf(buff, "%d", is_number(args[0]));
}

FUNCTION(fun_val)
{
  /* returns the value of the longest numeric prefix of a string */

  char *p;

  p = skip_space(args[0]);

  if (!isdigit(*p)) {
    strcpy(buff, "0");
  } else {
    do {
      *buff++ = *p++;
    } while (isdigit(*p));
    *buff = '\0';
  }
}

FUNCTION(fun_add)
{
double  sum;
int     i, got_one;

        sum = 0;
        for (i=0,got_one=0; i<nargs; i++) {
                sum = sum + atof(args[i]);
                if (i>0)
                        got_one = 1;
        }
        if (!got_one)
                strcpy(buff, "#-1 TOO FEW ARGUMENTS");
        else
                fval(buff, sum);
        return;
}

FUNCTION(fun_div)
{
int     bot;

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

FUNCTION(fun_sub)       { fval(buff, atof(args[0])-atof(args[1])); }

FUNCTION(fun_mul)
{
int     i, got_one;
double  prod;
 
        prod = 1;
        for (i=0,got_one=0; i<nargs; i++) {
                prod = prod * atof(args[i]);
                if (i>0)
                        got_one = 1;
        }
        if (!got_one)
                strcpy(buff, "#-1 TOO FEW ARGUMENTS");
        else
                fval(buff, prod);
        return;
}
 
FUNCTION(fun_floor)     { sprintf(buff, "%.0f", floor(atof(args[0]))); }
FUNCTION(fun_ceil)      { sprintf(buff, "%.0f", ceil(atof(args[0]))); }

FUNCTION(fun_round)
{
const char *fstr;
 
        switch (atoi(args[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(args[0]));
 
        /* Handle bogus result of "-0" from sprintf.  Yay, cclib. */
 
        if (!strcmp(buff, "-0")) {
                strcpy(buff, "0");
        }
}


FUNCTION(fun_trunc)
{
int     num;

        num = atoi(args[0]);
        sprintf(buff, "%d", num);
}

FUNCTION(fun_fdiv)
{
double  bot;
 
        bot = atof(args[1]);
        if (bot == 0) {
                strcpy(buff, "#-1 DIVIDE BY ZERO");
        } else {
                fval(buff, (atof(args[0]) / bot));
        }
}
 
FUNCTION(fun_mod)
{
int     bot;
 
        bot = atoi(args[1]);
        if (bot == 0) bot = 1;
        sprintf(buff, "%d", atoi(args[0]) % bot);
}       
 
FUNCTION(fun_pi)        { strcpy(buff, "3.141592654"); }
FUNCTION(fun_e)         { strcpy(buff, "2.718281828"); }
 
FUNCTION(fun_sin)       { fval(buff, sin(atof(args[0]))); }
FUNCTION(fun_cos)       { fval(buff, cos(atof(args[0]))); }
FUNCTION(fun_tan)       { fval(buff, tan(atof(args[0]))); }
FUNCTION(fun_exp)       { fval(buff, exp(atof(args[0]))); }
 
FUNCTION(fun_gt)    { sprintf(buff, "%d", (atof(args[0])> atof(args[1]))); }
FUNCTION(fun_gte)   { sprintf(buff, "%d", (atof(args[0])>=atof(args[1]))); }
FUNCTION(fun_lt)    { sprintf(buff, "%d", (atof(args[0])< atof(args[1]))); }
FUNCTION(fun_lte)   { sprintf(buff, "%d", (atof(args[0])<=atof(args[1]))); }
FUNCTION(fun_eq)    { sprintf(buff, "%d", (atof(args[0])==atof(args[1]))); }
FUNCTION(fun_neq)   { sprintf(buff, "%d", (atof(args[0])!=atof(args[1]))); }

FUNCTION(fun_min)
{        
int     i, j, got_one;
double  min;
         
        min = 0;
        for (i=0,got_one=0; i<nargs; i++) {
                if (args[i]) {
                        j = atof(args[i]);
                        if (!got_one || (j < min)) {
                                got_one = 1;
                                min = j;
                        }
                }
        }       
         
        if (!got_one) {
                strcpy(buff, "#-1 TOO FEW ARGUMENTS");
        } else {
                fval(buff, min);
        }       
        return;
}        

FUNCTION(fun_max)
{
int     i, j, got_one;
double  max;

        max = 0;
        for (i=0,got_one=0; i<nargs; i++) {
                if (args[i]) {
                        j = atof(args[i]);
                        if (!got_one || (j > max)) {
                                got_one = 1;
                                max = j;
                        }
                }
        }
 
        if (!got_one)
                strcpy(buff, "#-1 TOO FEW ARGUMENTS");
        else
                fval(buff, max);
        return; 
}        

/* this function and dist3d are taken from the 2.0 code */

FUNCTION(fun_dist2d)
{
  int d;
  double r;
  d = atoi(args[0]) - atoi(args[2]);
  r = (double) (d * d);
  d = atoi(args[1]) - atoi(args[3]);
  r += (double) (d * d);
  d = (int) (sqrt(r) + 0.5);
  sprintf(buff, "%d", d);
}

FUNCTION(fun_dist3d)
{
  int d;
  double r;
  d = atoi(args[0]) - atoi(args[3]);
  r = (double) (d * d);
  d = atoi(args[1]) - atoi(args[4]);
  r += (double) (d * d);
  d = atoi(args[2]) - atoi(args[5]);
  r += (double) (d * d);
  d = (int) (sqrt(r) + 0.5);
  sprintf(buff, "%d", d);
}

FUNCTION(fun_lnum)
{
  char *bp;
  char tbuf1[8];
  int i, x;
  int done = 0;

  x = atoi(args[0]);

  if (x < 0) {
    strcpy(buff, "#-1 NUMBER OUT OF RANGE");
    return;
  }

  bp = buff;
  safe_chr('0', buff, &bp);
  for (i = 1; i < x && !done; i++) {
    sprintf(tbuf1, " %d", i);
    done = safe_str(tbuf1, buff, &bp);
  }
  *bp = '\0';
}

/* ---------------------------------------------------------------------------
 * fun_sign: Returns -1, 0, or 1 based on the the sign of its argument.        
 */                                                                     
    
FUNCTION(fun_sign)
{                  
double  num;
 
        num = atof(args[0]);
        if (num < 0)
                strcpy(buff, "-1");
        else if (num > 0)
                strcpy(buff, "1");
        else
                strcpy(buff, "0");
}       


/* --------------------------------------------------------------------------
 * String functions: FIRST, REST, STRLEN, COMP, POS, MID, EXTRACT, WORDPOS,
 *   MATCH, CAT, REMOVE, MEMBER, FLIP, UCSTR, LCSTR, WORDS, BEFORE, AFTER,
 *   STRCAT, REVWORDS, MERGE, SPLICE, REPEAT, GREP, LJUST, RJUST
 */

FUNCTION(fun_first)
{
/* read first word from a string */

  char *s, *b;

  b = skip_space(args[0]);
  s = seek_char(b, ' ');
  if (s)
    *s = '\0';

  if(strlen(b) < BUFFER_LEN)
    strcpy(buff, b);
  else
    buff[0] = '\0';
}

FUNCTION(fun_rest)
{
  char *s;

  /* skip leading space */
  s = skip_space(args[0]);

  /* skip first word */
  s = seek_char(s, ' ');

  /* skip leading space */
  s = skip_space(s);

  if(strlen(s) < BUFFER_LEN)
    strcpy(buff, s);
  else
    buff[0] = '\0';
}

FUNCTION(fun_strlen)
{
  sprintf(buff, "%d", strlen(args[0]));
}

FUNCTION(fun_mid)
{
  int l = atoi(args[1]), len = atoi(args[2]);
  if ((l < 0) || (len < 0) || ((len + l) > BUFFER_LEN)) {
    strcpy(buff, "#-1 OUT OF RANGE");
    return;
  }
  if (l < strlen(args[0]))
    strcpy(buff, args[0] + l);
  else
    *buff = 0;
  buff[len] = 0;
}

FUNCTION(fun_comp)
{
  int x;
  x = strcmp(args[0], args[1]);
  if (x > 0)
    strcpy(buff, "1");
  else if (x < 0)
    strcpy(buff, "-1");
  else
    strcpy(buff, "0");
}

FUNCTION(fun_pos)
{
  int i = 1;
  char *t, *u, *s = args[1];
  while (*s) {
    u = s;
    t = args[0];
    while (*t && *t == *u)
      ++t, ++u;
    if (*t == '\0') {
      sprintf(buff, "%d", i);
      return;
    }
    ++i, ++s;
  }
  strcpy(buff, "#-1");
  return;
}

FUNCTION(fun_match)
{
  /* compares two strings with possible wildcards, returns the
   * word position of the match. Based on the 2.0 version of this
   * function.
   */

  int wcount = 1;
  char *s = args[0];
  char *r;

  do {

    r = skip_space(s);
    s = seek_char(r, ' ');
    if (*s)
      *s++ = '\0';

    if (local_wild_match(args[1], r)) {
      /* found it */
      sprintf(buff, "%d", wcount);
      return;
    }

    wcount++;

  } while (*s);

  strcpy(buff, "0");		/* no match */

}

FUNCTION(fun_strmatch)
{
  /* matches a wildcard pattern for an _entire_ string */

  if (local_wild_match(args[1], args[0]))
    strcpy(buff, "1");
  else
    strcpy(buff, "0");

}

/*  taken from the 2.0 code  */
FUNCTION(fun_wordpos)
{
  char *cp;
  char done, inspace;
  int charpos = atoi(args[1]);
  int word = 1;

  for (inspace = 0, done = 0, cp = args[0]; cp && *cp && !done; cp++) {
    if ((*cp == ' ') && (!inspace)) {
      word++;
      inspace = 1;
    }
    if ((*cp != ' ') && (inspace))
      inspace = 0;
    if ((cp - args[0] + 1) == charpos)
      done = 1;
  }
  if (!done)
    strcpy(buff, "#-1");
  else
    sprintf(buff, "%d", word);
}

FUNCTION(fun_extract)
{
  int start = atoi(args[1]), len = atoi(args[2]);
  char *s = args[0], *r;
  if ((start < 1) || (len < 1)) {
    *buff = 0;
    return;
  }
  start--;
  while (start && *s) {
    while (*s && (*s == ' '))
      s++;
    while (*s && (*s != ' '))
      s++;
    start--;
  }
  while (*s && (*s == ' '))
    s++;
  r = s;
  while (len && *s) {
    while (*s && (*s == ' '))
      s++;
    while (*s && (*s != ' '))
      s++;
    len--;
  }
  *s = 0;
  if(strlen(r) < BUFFER_LEN)
    strcpy(buff, r);
  else
    buff[0] = '\0';
}

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

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

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

  /* move s to the start of the item we want */
  start--;
  while (start && s && *s) {
    if ((s = (char *) index(s, c)) != NULL)   s++;
    start--;
  }

 /* skip just spaces, not tabs or newlines, since people may MUSHcode things
  * like "%r%tPolgara %r%tDurnik %r%tJavelin"
  */
  while (s && *s && (*s == ' '))   s++;
  if (!s || !*s)   return;

  /* now figure out where to end the string */
  p = s;
  while (end && p && *p) {
    if ((p = (char *) index(p, c)) != NULL) {
      if (--end == 0) {
	/* trim trailing spaces (just true spaces) */
	do {
	  p--;
	} while (*p == ' ');
	*(++p) = '\0';
	safe_str(s, buff, &bp);
	*bp = '\0';
	return;
      } else
	p++;
    }
  }

  /* if we've hit this point, we've run off the end of the string */
  safe_str(s, buff, &bp);
  *bp = '\0';
}

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

  bp = buff;
  safe_str(args[0], buff, &bp);
  for (i = 1; i < nargs; i++) {
    safe_chr(' ', buff, &bp);
    safe_str(args[i], buff, &bp);
  }
  *bp = '\0';
}

FUNCTION(fun_strcat)
{
  char *bp;

  bp = buff;
  safe_str(args[0], buff, &bp);
  safe_str(args[1], buff, &bp);
  *bp = '\0';
}

FUNCTION(fun_remove)
{
  char *s, *t, *p;
  int done = 0;

  /*
   * This function is 'safe' since it is impossible that any of the arg[]
   * elements will be larger than BUFFER_LEN, and in the worst case, this
   * does nothing to the the length, so still < BUFFER_LEN
   */
  if(index(args[1],' ')) {
    strcpy(buff,"#-1 CAN ONLY DELETE ONE ELEMENT");
    return;
  }
  s = p = args[0];
  t = args[1];

  while (*s && !done) {
    if (*t)
      if (*s == *t)
	t++;
      else {
	t = args[1];
	while (*(s+1) && *s != ' ')
	  s++;
        p = s;
      }
    else
      done = 1;
    s++;
  }
  if ((*t == '\0') && ((*(s-1) == ' ') || (*s == '\0'))) {
    if (p == args[0])
        strcpy(buff,s);
    else {
      *p = '\0';
      if (*s == '\0')
        strcpy(buff,args[0]);
      else
        sprintf(buff,"%s %s",args[0],s);
    }
    return;
  }
  strcpy(buff,args[0]);
}

FUNCTION(fun_member)
{
  char *s, *t;
  int el;

  if (index(args[1],' ')) {
    strcpy(buff,"#-1 CAN ONLY TEST ONE ELEMENT");
    return;
  }

  s = args[0];
  el = 1;

  do {
    t = skip_space(s);
    s = seek_char(t, ' ');
    if (*s)
      *s++ = '\0';
    if (!strcmp(args[1], t)) {
      sprintf(buff, "%d", el);
      return;
    }
    el++;
  } while (*s);

  strcpy(buff, "0");		/* not found */
}

FUNCTION(fun_before)
{
  int n = strlen(args[1]);
  char *s = args[0];
  char *word = args[1];
  char *p;

  if (!*word) {
    /* second argument is a space. Act like "first" function */
    p = strchr(s, ' ');
    if (p != NULL)
      *p = '\0';		/* cut off after first word */
    strcpy(buff, s);
    return;
  }

  while (*s) {
    /* find the first character in the string that matches the first
     * character of the target word
     */
    p = strchr(s, *word);

    if (p == NULL) {
      /* target string not found, return original string */
      strcpy(buff, args[0]);
      return;
    }

    /* check to see if the rest of the word matches */
    if (!strncmp(p, word, n)) {
      /* exact match. Don't need to worry about overflowing buffers,
       * so just truncate and return string.
       */
      *p = '\0';
      strcpy(buff, args[0]);
      return;
    }

    /* rest of the word didn't match, keep looking */
    s = p + 1;
  }

  /* didn't find anything, just return original string */
  strcpy(buff, args[0]);
}

FUNCTION(fun_after)
{
  int n = strlen(args[1]);
  char *s = args[0];
  char *word = args[1];
  char *p;

  if (!*word) {
    /* second argument is a space. Act like "rest" function */
    p = strchr(s, ' ');
    if (p == NULL)
      *buff = '\0';
    else
      strcpy(buff, p + 1);
    return;
  }

  while (*s) {
    /* find the first character in the string that matches the first
     * character of the target word
     */
    p = strchr(s, *word);

    if (p == NULL) {
      /* target string not found, return nothing */
      *buff = '\0';
      return;
    }
    
    /* check to see if the rest of the word matches */
    if (!strncmp(p, word, n)) {
      /* exact match, copy and return. No need to check to see if we
       * overflowed the buffer, since the string will be shorter.
       */
      s = p + n;
      strcpy(buff, s);
      return;
    }

    /* rest of the word didn't match, keep looking */
    s = p + 1;
  }

  /* didn't find anything */
  *buff = '\0';
}

void do_flip(char *s, char *r)
{
  char *p;      /* utility function to reverse a string */

  p = strlen(s) + r;
  *p-- = '\0';
  while (*s) 
    *p-- = *s++;
}

FUNCTION(fun_flip)
{
  do_flip(args[0], buff);
}

FUNCTION(fun_revwords)
{
  /* based on the 2.0 code */

  char tbuf1[BUFFER_LEN];
  char *bp, *tp, *lp;
  char c;

  /* reverse the string */
  do_flip(args[0], tbuf1);

  /* reverse each word in the string. The words themselves will then be
   * forwards again, with their order reversed (from the prior flip).
   */

  tp = tbuf1;
  bp = buff;

  while (*tp) {
    lp = tp;

    while (!isspace(*tp))	/* skip spaces */
      tp++;

    if (tp != lp) {
      c = *tp;
      *tp = '\0';		/* split off a word */
      do_flip(lp, bp);		/* reverse word */
      bp += tp - lp;		/* move pointer past stuff we've added */
      *tp = c;			/* put back the original char */
    }
    if (!*tp)
      break;
    *bp++ = *tp++;
  }
  *bp = '\0';			/* terminate */
}

static int do_wordcount(str)
     char *str;
{
  /* count the number of words in a string */

  char *s = str;
  int count = 0;

  while (*s && isspace(*s))
    s++;

  while (*s) {
    count++;
    while (*s && !isspace(*s))
      s++;
    while (*s && isspace(*s))
      s++;
  }

  return (count);
}

FUNCTION(fun_words)
{
  sprintf(buff, "%d", do_wordcount(args[0]));
}

FUNCTION(fun_merge)
{
  /* given s1, s2, and a char, for each character in s1, if the char
   * is the same as the given char, replace it with the corresponding
   * char in s2.
   */

  char *str, *rep, *bp;
  char c;

  /* do length checks first */
  if (strlen(args[0]) != strlen(args[1])) {
    strcpy(buff, "#-1 STRING LENGTHS MUST BE EQUAL");
    return;
  }
  if (strlen(args[2]) > 1) {
    strcpy(buff, "#-1 TOO MANY CHARACTERS");
    return;
  }

  /* find the character to look for */
  if (!*args[2])
    c = ' ';
  else
    c = *args[2];

  /* walk strings, copy from the appropriate string */
  for (str = args[0], rep = args[1], bp = buff;
       *str && *rep;
       str++, rep++, bp++) {
    if (*str == c)
      *bp = *rep;
    else
      *bp = *str;
  }

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

  /* there is no error checking necessary since everything passed in
   * will be smaller than BUFFER_LEN, and the string cannot be any
   * larger, since we're just doing a copy.
   */
}

FUNCTION(fun_splice)
{
  /* like MERGE(), but does it for a word */

  char *bp;
  char *p1, *p2;
  char *q1, *q2;
  int words;
  int i;

  /* length checks */
  if (!*args[2]) {
    strcpy(buff, "#-1 NEED A WORD");
    return;
  }
  if (do_wordcount(args[2]) != 1) {
    strcpy(buff, "#-1 TOO MANY WORDS");
    return;
  }

  words = do_wordcount(args[0]);
  if (words != do_wordcount(args[1])) {
    strcpy(buff, "#-1 NUMBER OF WORDS MUST BE EQUAL");
    return;
  }

  /* loop through the two lists */
  for (bp = buff, i = 0, p1 = args[0], q1 = args[1];
       i < words; 
       i++, p1 = p2, q1 = q2) {
    if ((p2 = (char *) index(p1, ' ')) != NULL)
      *p2++ = '\0';
    if ((q2 = (char *) index(q1, ' ')) != NULL)
      *q2++ = '\0';
    if (bp != buff)
      safe_chr(' ', buff, &bp);
    if (!strcmp(p1, args[2]))
      safe_str(q1, buff, &bp);	     /* replace */
    else
      safe_str(p1, buff, &bp);	     /* copy */
  }
  *bp = '\0';
}

FUNCTION(fun_lcstr)
{
  char *ap;
  ap = args[0];
  *buff = '\0';
  while (*ap) {
    if (isupper(*ap))
      *buff++ = tolower(*ap++);
    else
      *buff++ = *ap++;
  }
  *buff++ = '\0';
  /*  No need to check buffer length  */
}

FUNCTION(fun_ucstr)
{
  char *ap;
  ap = args[0];
  *buff = '\0';
  while (*ap) {
    if (islower(*ap))
      *buff++ = toupper(*ap++);
    else
      *buff++ = *ap++;
  }
  *buff++ = '\0';
  /*  No need to check buffer length */
}

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

  times = atoi(args[1]);
  if (times < 1) {
    *buff = '\0';
    return;
  }
  if (times == 1) {
    strcpy(buff, args[0]);
    return;
  }
  if (strlen(args[0]) * times >= BUFFER_LEN) {
    strcpy(buff, "#-1 STRING TOO LONG");
    return;
  }

  bp = buff;
  for (i = 0; i < times; i++)
    safe_str(args[0], buff, &bp);
  *bp = '\0';
}

FUNCTION(fun_ljust)
{
  /* pads a string with trailing blanks (or other fill character) */

  int spaces, i;
  char c;
  char *bp;

  if ((nargs != 2) && (nargs != 3)) {
    strcpy(buff, "#-1 FUNCTION (LJUST) EXPECTS 2 OR 3 ARGUMENTS");
    return;
  }

  spaces = atoi(args[1]) - strlen(args[0]);
  if (nargs == 3)
    c = *args[2];
  else
    c = ' ';

  if (spaces <= 0) {
    /* no padding needed, just return string */
    strcpy(buff, args[0]);
    return;
  }
  if (spaces > BUFFER_LEN)
    spaces = BUFFER_LEN;

  bp = buff;
  safe_str(args[0], buff, &bp);
  for (i = 0; i < spaces; i++)
    safe_chr(c, buff, &bp);
  *bp = '\0';
}

FUNCTION(fun_rjust)
{
  /* pads a string with leading blanks */

  int spaces, i;
  char c;
  char *bp;

  if ((nargs != 2) && (nargs != 3)) {
    strcpy(buff, "#-1 FUNCTION (RJUST) EXPECTS 2 OR 3 ARGUMENTS");
    return;
  }

  spaces = atoi(args[1]) - strlen(args[0]);
  if (nargs == 3)
    c = *args[2];
  else
    c = ' ';

  if (spaces <= 0) {
    /* no padding needed, just return string */
    strcpy(buff, args[0]);
    return;
  }
  if (spaces > BUFFER_LEN)
    spaces = BUFFER_LEN;

  bp = buff;
  for (i = 0; i < spaces; i++)
    safe_chr(c, buff, &bp);
  safe_str(args[0], buff, &bp);
  *bp = '\0';
}
  

/* --------------------------------------------------------------------------
 * Word functions: CAPSTR, ART, SUBJ, OBJ, POSS, ALPHAMAX, ALPHAMIN, ISWORD
 */

FUNCTION(fun_isword)
{
  /* is every character a letter? */

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

FUNCTION(fun_capstr)
{
  strcpy(buff, args[0]);
  if (islower(*buff))
    *buff = toupper(*buff);
  /*  No need to check buffer length  */
}

FUNCTION(fun_art)
{
  /* checks a word and returns the appropriate article, "a" or "an" */

  char c = tolower(*args[0]);
  if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u')
    strcpy(buff, "an");
  else
    strcpy(buff, "a");
}

/* utility function to find sex of object, used by SUBJ, OBJ, and POSS.
 * Returns 0 for none, 1 for female, 2 for male */
int find_sex(dbref player, dbref thing)
{
  int gender;
  char *a;

  a = do_get_attrib(player, thing, "sex");
  if (!a)
    gender = 0;
  else {
    switch (a[1]) {
    case 'M': case 'm':
      gender = 2;
      break;
    case 'W': case 'w': case 'F': case 'f':
      gender = 1;
      break;
    default:
      gender = 0;
    }
  }
  return gender;
}

FUNCTION(fun_subj)
{
  dbref thing;
  int gender;

  thing = match_thing(privs, args[0]);

  if (thing == NOTHING) {
    strcpy(buff, "#-1 NO MATCH");
    return;
  }

  gender = find_sex(privs, thing);
  switch (gender) {
  case 1:
    strcpy(buff, "she");
    break;
  case 2:
    strcpy(buff, "he");
    break;
  default:
    strcpy(buff, "it");
  }
}

FUNCTION(fun_obj)
{
  dbref thing;
  int gender;

  thing = match_thing(privs, args[0]);

  if (thing == NOTHING) {
    strcpy(buff, "#-1 NO MATCH");
    return;
  }

  gender = find_sex(privs, thing);
  switch (gender) {
  case 1:
    strcpy(buff, "her");
    break;
  case 2:
    strcpy(buff, "him");
    break;
  default:
    strcpy(buff, "it");
  }
}

FUNCTION(fun_poss)
{
  dbref thing;
  int gender;

  thing = match_thing(privs, args[0]);

  if (thing == NOTHING) {
    strcpy(buff, "#-1 NO MATCH");
    return;
  }

  gender = find_sex(privs, thing);
  switch (gender) {
  case 1:
    strcpy(buff, "her");
    break;
  case 2:
    strcpy(buff, "his");
    break;
  default:
    strcpy(buff, "its");
  }
}

FUNCTION(fun_alphamax)
{
  char *amax;
  int i = 1;
  if (!args[0]) {
    strcpy(buff, "#-1 TOO FEW ARGUMENTS");
    return;
  } else
    amax = args[0];
  while ((i < 10) && args[i]) {
    amax = (strcmp(amax, args[i]) > 0) ? amax : args[i];
    i++;
  }
  sprintf(buff, "%s", amax);
}

FUNCTION(fun_alphamin)
{
  char *amin;
  int i = 1;
  if (!args[0]) {
    strcpy(buff, "#-1 TOO FEW ARGUMENTS");
    return;
  } else
    amin = args[0];
  while ((i < 10) && args[i]) {
    amin = (strcmp(amin, args[i]) < 0) ? amin : args[i];
    i++;
  }
  sprintf(buff, "%s", amin);
}


/* --------------------------------------------------------------------------
 * MUSH utilities: FLAGS, NUM, RNUM, LEXITS, EXITS, LCON, CON, NEXT, MAIL,
 *   NEARBY, TYPE, HASFLAG, LOCK, ELOCK, LOC, HOME, OWNER, NAME, PMATCH
 *   LOCATE, ROOM, WHERE, CONTROLS
 */

FUNCTION(fun_flags)
{
  dbref thing;
  thing = match_thing(privs, args[0]);

  if (thing == NOTHING) {
    strcpy(buff,"#-1");
    return;
  }
  strcpy(buff, unparse_flags(thing));
}

FUNCTION(fun_num)
{
  sprintf(buff, "#%d", match_thing(privs, args[0]));
}

/*
 * fun_lcon, fun_lexits, fun_con, fun_exit, fun_next, and next_exit were all
 * re-coded by d'mike, 7/12/91.  next_con was added at this time as well.
 *
 * The function behavior was changed by Amberyl, to remove what she saw
 * as a security problem, since mortals could get the contents of rooms
 * they didn't control, thus (if they were willing to go through the trouble)
 * they could build a scanner to locate anything they wanted.
 *
 * You can get the contents of any room you control, regardless of whether
 * or not the object is dark. You can get the contents of your current
 * location, _except_ for dark objects (and DARK/OPAQUE rooms). You CANNOT 
 * get the contents of anything else, regardless of whether or not you have 
 * objects in it. This latter behavior is exhibited by 2.0.
 *
 * The same behavior is true for exits, except OPAQUE doesn't apply.
 */

FUNCTION(fun_lcon)
{
  dbref it = match_thing(privs, args[0]);
  dbref thing;

  buff[0] = '\0';

  if (it != NOTHING) {
    DOLIST(thing, DBFETCH(it)->contents) {
      if (controls(privs, thing) || controls(privs, it) ||
	  (!Dark(thing) && !Dark(it))) {
        if (buff[0] == '\0')
	  strcpy(buff, tprintf("#%d", thing));
	else {
	  if ((strlen(buff)+15) < BUFFER_LEN) {
		  strcpy(buff,tprintf("%s #%d", buff, thing));
          }
        }
      } 
    }
  } else
    strcpy(buff, "#-1");
}

FUNCTION(fun_con)
{
  dbref it=match_thing(privs,args[0]);
  if ((it!=NOTHING) && (controls(privs,it) ||
			(DBFETCH(privs)->location==it) || (it==doer)))
    {
      sprintf(buff,"#%d",DBFETCH(it)->contents);
      return;
    }
  strcpy(buff,"#-1");
  return;
}

/* return next contents that is ok to see */
dbref next_con(dbref player, dbref this)
{
  while((this !=NOTHING) && controls(player, this) 
         || controls(player, DBFETCH(this)->location)) {
    this = DBFETCH(this)->next;
  }
  return(this);
}

/* return next exit that is ok to see */
dbref next_exit(dbref player, dbref this)
{
  while((this!=NOTHING) && (FLAGS(this) & DARK) && !controls(player,this))
    this=DBFETCH(this)->next;
  return(this);
}

FUNCTION(fun_lexits)
{
  dbref it = match_thing(privs, args[0]);
  dbref thing;
  char tbuf1[BUFFER_LEN];

  buff[0] = '\0';

  if (it != NOTHING) { 
    DOLIST(thing,DBFETCH(it)->exits) {
      if (!controls(privs, thing) ||
	  !controls(privs, it) ||
	  (!Dark(thing) && !Dark(it))) {
        if (buff[0] == '\0')
          sprintf(buff,"#%d",thing);
        else {
	  if ((strlen(buff)+15) < BUFFER_LEN) {
            sprintf(tbuf1,"%s #%d",buff,thing);
            strcpy(buff,tbuf1);
	  }
        }
      }
    }
  } else
    strcpy(buff,"#-1");
}

/* fun_exit is really just a wrapper for next_exit now... */
FUNCTION(fun_exit)
{
  dbref it=match_thing(privs,args[0]);
  if ((it!=NOTHING) && (controls(privs,it) ||
			(DBFETCH(privs)->location==it) || (it==doer)))
    {
      sprintf(buff,"#%d",next_exit(privs,DBFETCH(it)->exits));
      return;
    }
  strcpy(buff,"#-1");
  return;
}

FUNCTION(fun_next)
{
  dbref it=match_thing(privs,args[0]);
  if (it!=NOTHING)                       
    {
      if (Typeof(it)!=TYPE_EXIT)
	{
	  if (controls(privs,DBFETCH(it)->location) || 
              (DBFETCH(it)->location==doer) ||
	      (DBFETCH(it)->location==DBFETCH(privs)->location))
	    { 
	      sprintf(buff,"#%d",DBFETCH(it)->next);
	      return;
	    }
	}
      else
	{
	  sprintf(buff,"#%d",next_exit(privs,DBFETCH(it)->next));
	  return;
	}
    }
  strcpy(buff,"#-1");
  return; 
} 

FUNCTION(fun_nearby)
{
  dbref obj1 = match_thing(privs, args[0]);
  dbref obj2 = match_thing(privs, args[1]);

  if (!controls(privs, obj1) && !controls(privs, obj2)
      && !nearby(privs, obj1) && !nearby(privs, obj2)) {
    strcpy(buff, "#-1 NO OBJECTS CONTROLLED");
    return;
  }

  if (!GoodObject(obj1) || !GoodObject(obj2)) {
    strcpy(buff, "#-1");
    return;
  } else
    sprintf(buff, "%d", nearby(obj1, obj2));
}

FUNCTION(fun_controls)
{
  dbref it = match_thing(privs, args[0]);
  dbref thing = match_thing(privs, args[1]);

  sprintf(buff, "%d", controls(it, thing));
}

FUNCTION(fun_type)
{
  dbref it = match_thing(privs, args[0]);
  if (it == NOTHING) {
    strcpy(buff, "#-1");
    return;
  }
  switch (Typeof(it)) {
  case TYPE_PLAYER:
    strcpy(buff, "PLAYER");
    break;
  case TYPE_THING:
    strcpy(buff, "THING");
    break;
  case TYPE_EXIT:
    strcpy(buff, "EXIT");
    break;
  case TYPE_ROOM:
    strcpy(buff, "ROOM");
    break;
  default:
    strcpy(buff, "WEIRD OBJECT");
    fprintf(stderr, "WARNING: Weird object #%d (type %d)\n", it, Typeof(it));
  }
}

char *mush_awake(dbref thing, dbref privs)
{
 descriptor_data *dd;

   for (dd = descriptor_list; dd; dd = dd->next) {
     if (dd->connected &&
       (dd->player == thing) && (!(FLAGS(dd->player) & DARK) || 
           Wizard(privs)))
        return "1";
  }
  return "0";
}

FUNCTION(fun_hasflag)
{
  int tmp = 0;
  FLAG *fst;
  dbref it = match_thing(privs, args[0]);
  char *flag = args[1];

  if (it == NOTHING) {
    strcpy(buff, "#-1");
    return;
  }
  if (string_prefix("connect", flag)) {
           strcpy(buff, mush_awake(it, privs));
           return;
 } else {
  if ((fst = flag_lookup(flag, it)) != NULL)
     tmp = fst->flag;
   }
  if (tmp && ((FLAGS(it) & tmp) != 0))
      strcpy(buff, "1");
  else
      strcpy(buff, "0");
}

FUNCTION(fun_lock)
{
  dbref it = match_thing(privs, args[0]);
  char *s;

  if((it != NOTHING) && controls(privs, it)) {
    s = unparse_boolexp(privs, DBFETCH(it)->key);
    if(strlen(s) < BUFFER_LEN) {
      sprintf(buff, "%s", s);
      return;
    }
  }
  strcpy(buff, "#-1");
  return;
}

FUNCTION(fun_elock)
{
  dbref it = match_thing(privs, args[0]);
  dbref victim = match_thing(privs, args[1]);

  if (it != NOTHING) {
    sprintf(buff, "%d", eval_boolexp(victim, DBFETCH(it)->key, it));
    return;
  }
  strcpy(buff, "#-1");
  return;
}

FUNCTION(fun_loc)
{
  dbref it = match_thing(privs, args[0]);

  if (it != NOTHING) {
    if(Unfindable(it))
       strcpy(buff, "#-1");
    else
       sprintf(buff, "#%d", Location(it));
    return;
  }

  strcpy(buff, "#-1");
  return;
}

FUNCTION(fun_where)
{
  /* finds the "real" location of an object */

  dbref it = match_thing(privs, args[0]);
  if ((it != NOTHING) && controls(privs, it)) {
    sprintf(buff, "#%d", where_is(it));
    return;
  }
  strcpy(buff, "#-1");
  return;
}

FUNCTION(fun_room)
{
  dbref room;
  int rec = 0;
  dbref it = match_thing(privs, args[0]);

  if ((it != NOTHING) && controls(privs, it)) {
    room = Location(it);
    if (!GoodObject(room)) {
      strcpy(buff, "#-1");
      return;
    }
    while ((Typeof(room) != TYPE_ROOM) && (rec < 15)) {
      room = Location(room);
      rec++;
    }
    if (rec > 15) {
      notify(privs, privs, "Too many containers.");
      strcpy(buff, "#-1");
      return;
    } else {
      sprintf(buff, "#%d", room);
    }
  } else {
    notify(privs, privs, "Permission denied.");
    strcpy(buff, "#-1");
  }
}

FUNCTION(fun_parent)
{
  dbref it = match_thing(privs, args[0]);
  if ((it == NOTHING) || !controls(privs, it)) {
    strcpy(buff, "#-1");
    return;
  }
  sprintf(buff, "#%ld", Parent(it));
}

FUNCTION(fun_home)
{
  dbref it = match_thing(privs, args[0]);
  if ((it == NOTHING) || !controls(privs, it) ||
     (Typeof(it) == TYPE_ROOM)) {
    strcpy(buff,"#-1");
    return;
  }
  sprintf(buff, "#%ld", DBFETCH(it)->link);
}

FUNCTION(fun_money)
{
  dbref it = match_thing(privs, args[0]);
  if (it == NOTHING) {
    strcpy(buff,"#-1");
    return;
  }
  sprintf(buff,"%ld", (DBFETCH(it)->pennies));
}

FUNCTION(fun_owner)
{
  dbref it = match_thing(privs, args[0]);
  if (it != NOTHING)
    it = OWNER(it);
  sprintf(buff, "#%d", it);
}

FUNCTION(fun_name)
{
  dbref it = match_thing(privs, args[0]);
  if (it == NOTHING)
    *buff = '\0';
   else {
    if(strlen(unparse_name(it)) < BUFFER_LEN)
      strcpy(buff, unparse_name(it));
    else
      buff[0] = '\0';
  }
}

FUNCTION(fun_fullname)
{
  dbref it = match_thing(privs, args[0]);
  if (it == NOTHING)
    *buff = '\0';
   else {
    if(strlen(NAME(it)) < BUFFER_LEN)
      strcpy(buff, NAME(it));
    else
      buff[0] = '\0';
  }       
}

FUNCTION(fun_pmatch)
{
  dbref target = lookup_player(args[0]);
  if (target == NOTHING)
    target = short_page(args[0]);
  switch (target) {
  case NOTHING:
    notify(privs, privs, "No match.");
    strcpy(buff, "#-1");
    break;
  case AMBIGUOUS:
    notify(privs, privs, "I'm not sure who you mean.");
    strcpy(buff, "#-2");
    break;
  default:
    sprintf(buff, "#%d", target);
  }
}

FUNCTION(fun_locate)
{
  dbref looker;
  object_flag_type pref_type;
  dbref item;
  char *p;
  char mbuff[BUFFER_LEN];
  int keys = 0;
  int done = 0;
  match_data md;
  
  /* find out what we're matching in relation to */
  looker = match_thing(privs, args[0]);
  if (looker == NOTHING) {
    notify(privs, privs, "I don't see that here.");
    strcpy(buff, "#-1");
    return;
  }
  if (!Wizard(privs) && !controls(privs, looker)) {
    notify(privs, privs, "Permission denied.");
    strcpy(buff, "#-1");
    return;
  }

  /* find out our preferred match type */
  pref_type = NOTYPE;
  for (p = args[2]; *p; p++) {
    switch (*p) {
    case 'N':
      pref_type = NOTYPE;
      break;
    case 'E':
      pref_type = TYPE_EXIT;
      break;
    case 'P':
      pref_type = TYPE_PLAYER;
      break;
    case 'R':
      pref_type = TYPE_ROOM;
      break;
    case 'T':
      pref_type = TYPE_THING;
      break;
    case 'L':
      keys = 1;
      break;
    }
  }

  if (keys)
    init_match_check_keys(looker, args[1], pref_type, &md);
  else
    init_match(looker, args[1], pref_type, &md);

  /* now figure out where what kinds of matches we want done */
  for (p = args[2]; *p && !done; p++) {
    switch (*p) {
    case '*':
      match_everything(&md);
      done = 1;
      break;
    case 'a':
      match_absolute(&md);
      break;
    case 'e':
      match_room_exits(Location(doer), &md);
      break;
    case 'h':
      match_here(&md);
      break;
    case 'i':
      match_possession(&md);
      break;
    case 'm':
      match_me(&md);
      break;
    case 'n':
      match_neighbor(&md);
      break;
    case 'p':
      match_player(&md);
      break;
    case 'N': case 'E' : case 'P' : case 'R' : case 'T' : case 'L':
      /* these are from previous type switch check. ignore. */
      break;
    default:
      sprintf(mbuff, "I don't understand switch '%c'.", *p);
      notify(privs, privs, mbuff);
      break;
    }
  }

  /* report the results */
  item = match_result(&md);

  if (item == NOTHING)
    notify(privs, privs, "Nothing found.");
  else if (item == AMBIGUOUS)
    notify(privs, privs, "More than one match found.");
  strcpy(buff, tprintf("#%d", item));
}


/* --------------------------------------------------------------------------
 * Booleans: AND, OR, XOR, NOT
 */

int xlate(char *arg)
{
int	temp;
char	*temp2;

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

FUNCTION(fun_and)
{
int	i, val, tval, got_one;

	val = 0;
	for (i=0,got_one=0; i<nargs; i++) {
		tval = atoi(args[i]);
		if (i>0) {
			got_one = 1;
			val = val && atoi(args[i]);
		} else {
			val = tval;
		}
	}
	if (!got_one)
		strcpy(buff, "#-1 TOO FEW ARGUMENTS");
	else
		sprintf(buff, "%d", val);
	return;
}

FUNCTION(fun_or)
{
int	i, val, tval, got_one;

	val = 0;
	for (i=0,got_one=0; i<nargs; i++) {
		tval = atoi(args[i]);
		if (i>0) {
			got_one = 1;
			val = val || atoi(args[i]);
		} else {
			val = tval;
		}
	}
	if (!got_one)
		strcpy(buff, "#-1 TOO FEW ARGUMENTS");
	else
		sprintf(buff, "%d", val);
	return;
}

FUNCTION(fun_xor)
{
int	i, val, tval, got_one;

	val = 0;
	for (i=0,got_one=0; i<nargs; i++) {
		tval = atoi(args[i]);
		if (i>0) {
			got_one = 1;
			val = (val && !tval) || (!val && tval);
		} else {
			val = tval;
		}
	}
	if (!got_one)
		strcpy(buff, "#-1 TOO FEW ARGUMENTS");
	else
		sprintf(buff, "%d", val);
	return;
}

FUNCTION(fun_not)   { sprintf(buff, "%d", !xlate(args[0])); }


/* --------------------------------------------------------------------------
 * Complex list handling functions: NSORT, SORT, SETUNION, SETINTER, SETDIFF,
 *   and auxiliary functions
 */

static void nswap(int *a, int *b)
{
  int c;      /* swaps two numbers */

  c = *a;
  *a = *b;
  *b = c;
}

FUNCTION(fun_scramble)
{
  int n, i, j;
  char c;

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

  n = strlen(buff);		/* just the length of the string */
  for (i = 0; i < n; i++) {
    j = getrandom(n - i) + i;
    c = buff[i];
    buff[i] = buff[j];
    buff[j] = c;
  }
}

static void do_nsort(x, n)
     int x[];
     int n;
{
  /* transposition sort an array of integers */

  int i, j;                     /* utility */

  for (i = 0; i < n; i++)
    for (j = i + 1; j < n; j++)
      if (x[i] > x[j])
	nswap(&x[i], &x[j]);
}

FUNCTION(fun_nsort)
{
  int i = 0;
  int num_args = 0;
  char *p;
  char list1[BUFFER_LEN];
  int a1[MAX_MUSH_ARG];
  char *bp = buff;

  /* ignore an empty list */
  if (!args[0] || !*args[0]) {
    *buff = '\0';
    return;
  }
  
  /* make an array out of the list */
  strcpy(list1, args[0]);
  p = (char *) strtok(list1, " ");
  for (i = 0; (i < MAX_MUSH_ARG) && p; i++, p = (char *) strtok(NULL, " "))
    a1[i] = atoi(p);

  num_args = i;

  do_nsort(a1, num_args);

  /* copy the sort into buffer, checking to make sure that it doesn't */
  /* overflow the buffer */

  safe_str(tprintf("%d", a1[0]), buff, &bp);
  for (i = 1; i < num_args; i++) {
    safe_chr(' ', buff, &bp);
    safe_str(tprintf("%d", a1[i]), buff, &bp);
  }
  *bp = '\0';
}

static int list2arr(r, max, list)
     char *r[];
     int max;
     char *list;
{
  /* chops up a list of words into an array of words. The list is
   * destructively modified.
   */

  char *p;
  int i;

  for (i = 0, p = (char *) strtok(list, " ");
       (p != NULL) && (i < max);
       i++, p = (char *) strtok(NULL, " "))
    r[i] = p;
  return i;
}

static void arr2list(r, max, list)
     char *r[];
     int max;
     char *list;
{
  int i;
  char *bp = list;

  safe_str(r[0], list, &bp);
  for (i = 1; i < max; i++) {
    safe_chr(' ', list, &bp);
    safe_str(r[i], list, &bp);
  }
  *bp = '\0';
}

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

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

FUNCTION(fun_shuffle)
{
  /* given a list of words, randomize the order of words. 
   * We do this by taking each element, and swapping it with another
   * element with a greater array index (thus, words[0] can be swapped
   * with anything up to words[n], words[5] with anything between
   * itself and words[n], etc.
   * This is relatively fast - linear time - and reasonably random.
   */

  char tbuf1[BUFFER_LEN];
  char *words[BUFFER_LEN];
  int n, i, j;

  /* split the list up, or return if the list is empty */
  if (!args[0] || !*args[0]) {
    *buff = '\0';
    return;
  }
  strcpy(tbuf1, args[0]);
  n = list2arr(words, BUFFER_LEN, tbuf1);

  /* shuffle it */
  for (i = 0; i < n; i++) {
    j = getrandom(n - i) + i;
    swap(&words[i], &words[j]);
  }

  arr2list(words, n, buff);
}

static void do_sort(s, n)
     char *s[];
     int n;
{
  /* uses a transposition sort to sort an array of words */

  int i, j;                     /* utility */

  for (i = 0; i < n; i++)
    for (j = i + 1; j < n; j++)
      if (strcmp(*(s + i), *(s + j)) > 0)
        swap(s + i, s + j);
}

FUNCTION(fun_sort)
{
  int num_args = 0;
  char list1[BUFFER_LEN];
  char *a1[MAX_MUSH_ARG];

  /* ignore an empty list */
  if (!args[0] || !*args[0]) {
    *buff = '\0';
    return;
  }
  
  /* make an array out of the list, and do the sort */
  strcpy(list1, args[0]);
  num_args = list2arr(a1, BUFFER_LEN, list1);
  do_sort(a1, num_args);
  arr2list(a1, num_args, buff);
}

char *do_setfun(str1, str2, flag)
     char *str1;
     char *str2;
     int flag;
{
  /* auxiliary functions to handle list sets */

  char list1[BUFFER_LEN];
  char list2[BUFFER_LEN];
  char *a1[MAX_MUSH_ARG];
  char *a2[MAX_MUSH_ARG];
  char *p, *tmp;
  int n1, n2;
  int i, j;
  static char tbuf1[BUFFER_LEN];
  int val;
 
  /* copy the two lists */
  strcpy(list1, str1);
  strcpy(list2, str2);
 
  /* make arrays out of them */
  n1 = list2arr(a1, BUFFER_LEN, list1);
  n2 = list2arr(a2, BUFFER_LEN, list2);
 
  /* sort each array */
  do_sort(a1, n1);
  do_sort(a2, n2);
 
  *tbuf1 = '\0';
 
  /* now do the requested operations */
  switch (flag) {
  case 1:
    /* set union */
    p = (char *) "";
    i = j = 0;
    /* handle single-word lists that are the same first */
    if ((n1 == 1) && (n2 == 1) && !strcmp(a1[0], a2[0])) {
      sprintf(tbuf1, "%s", a1[0]);
      break;
    }
    /* loop until done with at least one list */
    while ((i < n1) && (j < n2)) {
      /* skip duplicates */
      while ((i < n1) && !strcmp(a1[i], p))
        i++;
      while ((j < n2) && !strcmp(a2[j], p))
        j++;
      /* compare and copy */
      if ((i < n1) && (j < n2)) {
	if (strcmp(a1[i], a2[j]) < 0) {
	  if (*tbuf1 == '\0')
	    strcpy(tbuf1, a1[i]);
	  else
	    sprintf(tbuf1, "%s %s", tbuf1, a1[i]);
	  p = a1[i];
	  i++;
	} else {
	  if (*tbuf1 == '\0')
	    strcpy(tbuf1, a2[j]);
	  else
	    sprintf(tbuf1, "%s %s", tbuf1, a2[j]);
	  p = a2[j];
	  j++;
	}
      }
    }
    /* copy remainders */
    if (i < n1) {
      while ((i < n1) && !strcmp(p, a1[i]))
	i++;
      while (i < n1) {
	if (*tbuf1 == '\0')
	  strcpy(tbuf1, a1[i]);
	else
	  sprintf(tbuf1, "%s %s", tbuf1, a1[i]);
	i++;
      }
    } else {
      while ((j < n2) && !strcmp(p, a2[j]))
	j++;
      while ((j < n2) && (strcmp(p, a2[j]) != 0)) {
	if (*tbuf1 == '\0')
	  strcpy(tbuf1, a2[j]);
	else
	  sprintf(tbuf1, "%s %s", tbuf1, a2[j]);
	p = a2[j];
	j++;
      }
    }
    break;
  case 2:
    /* set intersection */
    p = (char *) "";
    i = j = 0;
    while ((i < n1) && (j < n2)) {
      val = strcmp(a1[i], a2[j]);
      if (!val) {
	/* got a match. copy it */
	if (*tbuf1 == '\0')
          strcpy(tbuf1, a1[i]);
        else
          sprintf(tbuf1, "%s %s", tbuf1, a1[i]);
        p = a1[i];
        while ((i < n1) && !strcmp(a1[i], p))
          i++;
        while ((j < n2) && !strcmp(a2[j], p))
          j++;
      } else if (val < 0) {
        i++;
      } else {
        j++;
      }
    }
    break;
  case 3:
    /* set difference */
    p = (char *) "";
    i = j = 0;
    while ((i < n1) && (j < n2)) {
      val = strcmp(a1[i], a2[j]);
      if (!val) {
	/* got a match. increment pointers */
        tmp = a1[i];
        while ((i < n1) && !strcmp(tmp, a1[i]))
          i++;
        while ((j < n2) && !strcmp(tmp, a2[j]))
          j++;
      } else if (val < 0) {
	/* found one in the first list which isn't in the second. copy it */
        while ((i < n1) && (strcmp(a1[i], a2[j]) < 0)) {
	  if (*tbuf1 == '\0')
            strcpy(tbuf1, a1[i]);
          else
            sprintf(tbuf1, "%s %s", tbuf1, a1[i]);
          p = a1[i];
          while ((i < n1) && !strcmp(p, a1[i]))
            i++;
        }
      } else {
	/* found one in second which isn't in first. ignore it */
        tmp = a2[j];
        while ((j < n2) && !strcmp(tmp, a2[j]))
          j++;
      }
    }
    /* copy over the elements of the first list that remain */
    while (i < n1) {
      if (*tbuf1 == '\0')
	strcpy(tbuf1, a1[i]);
      else
	sprintf(tbuf1, "%s %s", tbuf1, a1[i]);
      i++;
    }
    break;
  default:
    strcpy(tbuf1, "#-1 BAD ARGUMENT TO SETFUN");
  }
 
  return tbuf1;
}

FUNCTION(fun_setunion)
{
  if (!*args[0] && !*args[1])
    *buff = '\0';
  else
    strcpy(buff, do_setfun(args[0], args[1], 1));
}

FUNCTION(fun_setinter)
{
    strcpy(buff, do_setfun(args[0], args[1], 2));
}

FUNCTION(fun_setdiff)
{
  strcpy(buff, do_setfun(args[0], args[1], 3));
}

/* --------------------------------------------------------------------------
 * Creation functions: CREATE, OPEN, DIG
 */

FUNCTION(fun_create)
{
  sprintf(buff, "#%ld", do_mush_create(args[0], args[1], privs));
}

FUNCTION(fun_open) 
{    
  sprintf(buff, "#%ld", do_real_open(privs, args[0], args[1], NOTHING));
}

FUNCTION(fun_dig) 
{
  sprintf(buff, "#%ld", do_mush_dig(args[0], args[1], privs));
}

/* --------------------------------------------------------------------------
 * The actual function handlers
 */

typedef struct fun FUN;

struct fun {
  char *name;
  void (*fun) ();
  int nargs;
  int ftype;
};

FUN flist[] =
{
  {"ABS", fun_abs, 1, FN_REG},
  {"ACOS", fun_acos, 1, FN_REG},
  {"ADD", fun_add, 2, FN_REG},
  {"AFTER", fun_after, 2, FN_REG},
  {"ALPHAMAX", fun_alphamax, -1, FN_REG},
  {"ALPHAMIN", fun_alphamin, -1, FN_REG},
  {"AND",fun_and, -1, FN_REG},
  {"ART", fun_art, 1, FN_REG},
  {"ASIN", fun_asin, 1, FN_REG},
  {"ATAN", fun_atan, 1, FN_REG},
  {"BEEP", fun_beep, 1, FN_REG},
  {"BEFORE", fun_before, 2, FN_REG},
  {"CAPSTR", fun_capstr, 1, FN_REG},
  {"CAT", fun_cat, -1, FN_REG},
  {"CEIL", fun_ceil, 1, FN_REG},
  {"CENTER", fun_center, -1, FN_REG},
  {"COMP", fun_comp, 2, FN_REG},
  {"CON", fun_con, 1, FN_REG},
  {"CONN", fun_conn, 1, FN_REG},
  {"CONTROLS", fun_controls, 2, FN_REG},
  {"CONVSECS", fun_convsecs, 1, FN_REG},
  {"CONVTIME", fun_convtime, 1, FN_REG},
  {"COS", fun_cos, 1, FN_REG},
  {"CREATE", fun_create, 2, FN_REG},
  {"DELETE", fun_delete, 3, FN_REG},
  {"DIE", fun_die, 2, FN_REG},
  {"DIG", fun_dig, 3, FN_REG},
  {"DIST2D", fun_dist2d, 4, FN_REG},
  {"DIST3D", fun_dist3d, 6, FN_REG},
  {"DIV", fun_div, 2, FN_REG},
  {"E", fun_e, 0, FN_REG},
  {"EDIT", fun_edit, 3, FN_REG},
  {"ELOCK", fun_elock, 2, FN_REG},
  {"EQ", fun_eq, 2, FN_REG},
  {"EVAL", fun_eval, 2, FN_REG},
  {"ESCAPE", fun_escape, 1, FN_REG},
  {"EXIT", fun_exit, 1, FN_REG},
  {"EXP", fun_exp, 1, FN_REG},
  {"EXTRACT", fun_extract, 3, FN_REG},
  {"FABS", fun_fabs, 1, FN_REG},
  {"FDIV", fun_fdiv, 2, FN_REG},
  {"FILTER", fun_filter, 2, FN_REG},
  {"FIRST", fun_first, 1, FN_REG},
  {"FLAGS", fun_flags, 1, FN_REG},
  {"FLOOR", fun_floor, 1, FN_REG},
  {"FLIP", fun_flip, 1, FN_REG},
  {"FOLD", fun_fold, -1, FN_REG},
  {"FULLNAME", fun_fullname, 1, FN_REG},
  {"GET", fun_get, 1, FN_REG},
  {"GET_EVAL", fun_eval, 2, FN_REG},
  {"GREP", fun_grep, 3, FN_REG},
  {"GT", fun_gt, 2, FN_REG},
  {"GTE", fun_gte, 2, FN_REG},
  {"HASFLAG", fun_hasflag, 2, FN_REG},
  {"HOME", fun_home, 1, FN_REG},
  {"IDLE", fun_idlesecs, 1, FN_REG},
  {"IDLESECS", fun_idlesecs, 1, FN_REG},
  {"INDEX", fun_index, 4, FN_REG},
  {"INSERT", fun_insert, -1, FN_REG},
  {"ISDBREF", fun_isdbref, 1, FN_REG},
  {"ISNUM", fun_isnum, 1, FN_REG},
  {"ISWORD", fun_isword, 1, FN_REG},
  {"ITER", fun_iter, 2, FN_NOPARSE},
  {"LATTR", fun_lattr, 1, FN_REG},
  {"LCON", fun_lcon, 1, FN_REG},
  {"LCSTR", fun_lcstr, 1, FN_REG},
  {"LDELETE", fun_ldelete, -1, FN_REG},
  {"LEXITS", fun_lexits, 1, FN_REG},
  {"LJUST", fun_ljust, -1, FN_REG},
  {"LN", fun_ln, 1, FN_REG},
  {"LNUM", fun_lnum, 1, FN_REG},
  {"LOC", fun_loc, 1, FN_REG},
  {"LOG", fun_log, 1, FN_REG},
  {"LOCATE", fun_locate, 3, FN_REG},
  {"LOCK", fun_lock, 1, FN_REG},
  {"LSEARCH", fun_lsearch, 3, FN_REG},
  {"LSTATS", fun_lstats, 1, FN_REG},
  {"LT", fun_lt, 2, FN_REG},
  {"LTE", fun_lte, 2, FN_REG},
  {"LWHO", fun_lwho, 0, FN_REG},
  {"MATCH", fun_match, 2, FN_REG},
  {"MAP", fun_map, -1, FN_REG},
  {"MAX", fun_max, -1, FN_REG},
  {"MEMBER", fun_member, 2, FN_REG},
  {"MERGE", fun_merge, 3, FN_REG},
  {"MID", fun_mid, 3, FN_REG},
  {"MIN", fun_min, -1, FN_REG},
  {"MOD", fun_mod, 2, FN_REG},
  {"MONEY", fun_money, 1, FN_REG},
  {"MUDNAME", fun_mudname, 0, FN_REG},
  {"MUL", fun_mul, 2, FN_REG},
  {"NAME", fun_name, 1, FN_REG},
  {"NEARBY", fun_nearby, 2, FN_REG},
  {"NEQ", fun_neq, 2, FN_REG},
  {"NEXT", fun_next, 1, FN_REG},
  {"NOT", fun_not, 1, FN_REG},
  {"NSORT", fun_nsort, 1, FN_REG},
  {"NUM", fun_num, 1, FN_REG},
  {"OBJ", fun_obj, 1, FN_REG},
  {"OPEN", fun_open, 2, FN_REG},
  {"OR", fun_or, -1, FN_REG},
  {"OWNER", fun_owner, 1, FN_REG},
  {"PARENT", fun_parent, 1, FN_REG},
  {"PARSE", fun_parse, 3, FN_NOPARSE},
  {"PI", fun_pi, 0, FN_NOPARSE},
  {"PMATCH", fun_pmatch, 1, FN_REG},
  {"POS", fun_pos, 2, FN_REG},
  {"POSS", fun_poss, 1, FN_REG},
  {"POWER", fun_power, 2, FN_REG},
  {"R", fun_r, 1, FN_REG},
  {"RAND", fun_rand, 1, FN_REG},
  {"REMOVE", fun_remove, 2, FN_REG},
  {"REPLACE", fun_replace, -1, FN_REG},
  {"REPEAT", fun_repeat, 2, FN_REG},
  {"REST", fun_rest, 1, FN_REG},
  {"REVWORDS", fun_revwords, 1, FN_REG},
  {"RJUST", fun_rjust, -1, FN_REG},
#ifdef DO_RNUM_GLOBALS
  {"RNUM", fun_rnum, 1, FN_REG},
#endif /* DO_GLOBALS */
  {"ROOM", fun_room, 1, FN_REG},
  {"ROUND", fun_round, 2, FN_REG},
  {"S", fun_s, 1, FN_REG},
  {"SCRAMBLE", fun_scramble, 1, FN_REG},
  {"SECS", fun_secs, 0, FN_REG},
  {"SECURE", fun_secure, 1, FN_REG},
  {"SETQ", fun_setq, 2, FN_REG},
  {"SETDIFF", fun_setdiff, 2, FN_REG},
  {"SETINTER", fun_setinter, 2, FN_REG},
  {"SETUNION", fun_setunion, 2, FN_REG},
  {"SHUFFLE", fun_shuffle, 1, FN_REG},
  {"SIGN", fun_sign, 1, FN_REG},
  {"SIN", fun_sin, 1, FN_REG},
  {"SORT", fun_sort, 1, FN_REG},
  {"SQRT", fun_sqrt, 1, FN_REG},
  {"SPACE", fun_space, 1, FN_REG},
  {"SPLICE", fun_splice, 3, FN_REG},
  {"SQUISH", fun_squish, 1, FN_REG},
  {"STARTTIME", fun_starttime, 0, FN_REG},
  {"STRCAT", fun_strcat, 2, FN_REG},
  {"STRFTIME", fun_strftime, 2, FN_REG},
  {"STRLEN", fun_strlen, 1, FN_REG},
  {"STRMATCH", fun_strmatch, 2, FN_REG},
  {"SUB", fun_sub, 2, FN_REG},
  {"SUBJ", fun_subj, 1, FN_REG},
  {"SWITCH", fun_switch, -1, FN_NOPARSE},
  {"TAN", fun_tan, 1, FN_NOPARSE},
  {"TIME", fun_time, 0, FN_REG},
  {"TRIM", fun_trim, -1, FN_REG},
  {"TRUNC", fun_trunc, 1, FN_REG},
  {"TYPE", fun_type, 1, FN_REG},
  {"UCSTR", fun_ucstr, 1, FN_REG},
  {"UFUN", fun_ufun, -1, FN_REG},
  {"U", fun_ufun, -1, FN_REG},
  {"V", fun_v, 1, FN_REG},
  {"VAL", fun_val, 1, FN_REG},
  {"VERSION", fun_version, 0, FN_REG},
  {"WHERE", fun_where, 1, FN_REG},
  {"WORDPOS", fun_wordpos, 2, FN_REG},
  {"WORDS", fun_words, 1, FN_REG},
  {"XGET", fun_xget, 2, FN_REG},
  {"XOR", fun_xor, -1, FN_REG},
  {NULL, NULL, 0, 0}
};

void do_list_functions(dbref player)
{
  /* lists all built-in functions. */

  FUN *fp;
  char buff[BUFFER_LEN];
  char *bp;

  bp = buff;
  safe_str("Functions:", buff, &bp);

  for (fp = flist; fp->name != NULL; fp++) {
    safe_chr(' ', buff, &bp);
    safe_str(fp->name, buff, &bp);
  }
  *bp = '\0';

  notify(player, player, buff);
}

/*---------------------------------------------------------------------------
 * Hashed function table stuff
 */

#define FUNC_HASH_SIZE  128
#define FUNC_HASH_MASK  127

struct ftab_entry {
  struct ftab_entry *next;
  char *name;
  FUN *fp;
};

static struct ftab_entry *func_hashtab[FUNC_HASH_SIZE];
static FUN *null_htab;

FUN *func_hash_lookup(char *name)
{
  struct ftab_entry *p;

  for (p = func_hashtab[hash_fn(name, FUNC_HASH_MASK)];
       p != NULL; p = p->next) 
    if (!strcmp(name, p->name))
       return p->fp;

  return null_htab;		/* the notfound pointer */
}

void func_hash_insert(name, func)
     char *name;
     FUN *func;
{
  struct ftab_entry *newp;
  unsigned hashval;

  /* can assume no duplicates */
  newp = (struct ftab_entry *) malloc(sizeof(struct ftab_entry));
  newp->name = (char *) strdup(name);
  newp->fp = func;
  hashval = hash_fn(name, FUNC_HASH_MASK);
  newp->next = func_hashtab[hashval];
  func_hashtab[hashval] = newp;
}

void init_func_hashtab()
{
  FUN *fp;
  
  for (fp = flist; fp->name; fp++)
    func_hash_insert((char *)fp->name, (FUN *)fp);

  /* treat null function as a special case */
  null_htab = (FUN *) malloc(sizeof(FUN));
  null_htab->name = NULL;
  null_htab->fun = NULL;
  null_htab->nargs = 0;
}


/*-------------------------------------------------------------------------
 * Function handlers and the other good stuff. Almost all this code is
 * a modification of TinyMUSH 2.0 code.
 */

int extra_arglist(str, fargs, nargs)
     char *str;
     char *fargs[];
     int nargs;
{
  /* parses apart a comma-separated list, and returns the number of
   * arguments obtained. The list should already be pronoun-substituted.
   * str is destructively modified. No memory is allocated. No more than
   * nargs arguments are made. The list elements are placed in fargs.
   */

  int i;
  char *p;

  for (i = 0; i < nargs; i++)
    fargs[i] = NULL;
  if (!str || !*str)
    return 0;

  i = 0;
  while (((p = (char *) index(str, ',')) != NULL) && (i < nargs)) {
    /* chop it apart in-place */
    *p++ = '\0';
    fargs[i] = str;
    i++;
    str = p;
  }
  fargs[i] = str;
  return (i+1);
}

int get_gender(dbref player)
{
  char *a;    /* 0 for error, 1 for neuter, 2 for female, 3 for male */

  a = get_property_data(player, "sex", ACCESS_WI);
  if (!a) return 1;

  switch (*a) {
  case 'M':
  case 'm':
    return 3;
  case 'f':
  case 'F':
    return 2;
  default:
    return 1;
  }
}

#define NEXTCHAR \
   if (cstr == zstr) { \
     cstr++; \
     zstr++; \
   } else \
     *zstr++ = *cstr++

char *parse_to(char **dstr, char delim, int eflags)
{
  /* Split up a line at a character, but obey nesting. This will hack
   * up the string (null replaces the delimiter)
   * str will be modified to point to the character after the delimiter. 
   * The function will return a pointer to the found string.
   * If we don't find the delimiter, str is returned as NULL.
   */

#define MAX_STACK 32

  char stack[MAX_STACK];
  char *rstr, *cstr, *zstr;
  int sp, tp, first, brlev;

  if (!dstr || !*dstr)
    return NULL;

  if (**dstr == '\0') {
    rstr = *dstr;
    *dstr = NULL;
    return rstr;
  }

  sp = 0;
  first = 1;
  rstr = *dstr;
  while (*rstr && isspace(*rstr))
    rstr++;
  *dstr = rstr;
  zstr = cstr = rstr;

  while (*cstr) {
    switch (*cstr) {

    case '\\':			/* escape char */
    case '%':			/* can be used as an escape char */
      first = 0;
      NEXTCHAR;
      if (*cstr)
	NEXTCHAR;
      break;

    case ']':
    case ')':
      first = 0;
      for (tp = sp - 1; (tp >= 0) && (stack[tp] != *cstr); tp--)
	;
      /* If we've hit something on a stack, go back to it.
       * Otherwise, if it's our delimiter, we're done (replace
       * delim with null, and return a pointer to the char after
       * it). If it's not, then just move over it.
       */
      if (tp >= 0)
	sp = tp;
      else
	if (*cstr == delim) {
	  if (!first && (cstr[-1] == ' '))
	    zstr--;
	  *zstr = '\0';
	  *dstr = ++cstr;
	  return rstr;
	}
      NEXTCHAR;
      break;

    case '{':
      first = 0;
      brlev = 1;
      if (eflags & EV_STRIP) {
	cstr++;
      } else {
	NEXTCHAR;
      }
      while (*cstr && (brlev > 0)) {
	switch (*cstr) {
	case '\\':
	case '%':
	  if (cstr[1]) {
	    NEXTCHAR;
	  }
	  break;
	case '{':
	  brlev++;
	  break;
	case '}':
	  brlev--;
	  break;
	}
	if (brlev > 0) {
	  NEXTCHAR;
	}
      }
      if ((eflags & EV_STRIP) && (brlev == 0)) {
	cstr++;
      } else if (brlev == 0) {
	NEXTCHAR;
      }
      break;

    default:
      if ((*cstr == delim) && (sp == 0)) {
	if (!first && (cstr[-1] == ' '))
	  zstr--;
	*zstr = '\0';
	*dstr = ++cstr;
	return rstr;
      }
      switch (*cstr) {
      case ' ':			/* just a space. kill extra ones. */
	if (first)
	  rstr++;
	else if (cstr[-1] == ' ')
	  zstr--;
	break;
      case '[':
	first = 0;
	if (sp < MAX_STACK)
	  stack[sp++] = ']';
	break;
      case '(':
	first = 0;
	if (sp < MAX_STACK)
	  stack[sp++] = ')';
	break;
      default:
	first = 0;
      }
      NEXTCHAR;
    }
  }

  if (!first && (cstr[-1] == ' '))
    zstr--;
  *zstr = '\0';
  *dstr = NULL;
  return rstr;
}

char *parse_arglist(dbref player, dbref cause, char *dstr, char delim, int eflags, char *fargs[], int nfargs)
{
  /* Parses a line of comma-separated strings into an argument list.
   * A pointer is returned to whatever follows the final delimiter
   * (or NULL if the arglist is unterminated). 
   * The original arglist (in dstr) is hacked up.
   * This function allocates memory for the fargs, which must be freed.
   */

  char *rstr, *tstr;
  int arg, peval;

  for (arg = 0; arg < nfargs; arg++)
    fargs[arg] = NULL;

  if (!dstr)
    return NULL;

  rstr = parse_to(&dstr, delim, 0);
  arg = 0;

  if (eflags & EV_EVAL)
    peval = 0;
  else
    peval = eflags;

  while ((arg < nfargs) && rstr) {
    if (arg < (nfargs - 1))
      tstr = parse_to(&rstr, ',', peval);
    else 
      tstr = parse_to(&rstr, '\0', peval);
    if (eflags & EV_EVAL) {
      fargs[arg] = exec(cause, tstr, player, eflags | EV_FCHECK);
    } else {
      fargs[arg] = (char *) malloc(BUFFER_LEN + 1);
      strcpy(fargs[arg], tstr);
    }
    arg++;
  }
  
  return dstr;
}

char *exec(dbref cause, char *str, dbref player, int eflags)
{
  /* Function and other substitution evaluation.
   * This function mallocs memory, which must be freed.
   */

  char *fargs[10];
  char *buff, *bufc, *tstr, *tbuf, *tbufc, *savepos;
  char savec, ch;
  int at_space, nfargs, gender, i, j, done;
  FUN *fp;
  char *attrib;
  char temp[32];
  int a;
  char dbuf[BUFFER_LEN];
  char *dp, *dpexec;
  char dsave[BUFFER_LEN];
  char mbuff[BUFFER_LEN];

  static char *subj[4] = {"", "it", "she", "he"};
  static char *poss[4] = {"", "its", "her", "his"};
  static char *obj[4] = {"", "it", "her", "him"};

  if (str == NULL)
    return NULL;

  for (a = 0; a < 10; a++)
    fargs[a] = NULL;
  
  buff = (char *) malloc(BUFFER_LEN + 1);
  bufc = buff;

  at_space = 1;
  gender = -1;
  done = 0;

  if (Dark(player) && Typeof(player) == TYPE_THING) {
    dp = dbuf;
    safe_str(tprintf("#%d! %s =>", player, str), dbuf, &dp);
    safe_chr(' ', dbuf, &dp);
    FLAGS(player) &= ~DARK;
    strcpy(dsave, str);
    dpexec = exec(cause, dsave, player, eflags);
    safe_str(dpexec, dbuf, &dp);
    *dp = '\0';
    free(dpexec);
    FLAGS(player) |= DARK;
    notify_nolisten(OWNER(player), dbuf);
  }

  while (*str && !done) {
    switch (*str) {

    case ' ':
      /* Just a space. Add a space if previous char wasn't a space */
      if (!at_space) {
	safe_chr(' ', buff, &bufc);
	at_space = 1;
      }
      break;
    case '\\':
      /* The escape char. Add the next char without processing it. */
      at_space = 0;
      str++;
      if (*str)
	safe_chr(*str, buff, &bufc);
      else
	str--;
      break;

    case '[':
      /* Beginning of a function. Evaluate the contents of this
       * as a function. If we find no closing bracket, just add
       * the '[' and go on.
       */
      at_space = 0;
      tstr = str++;
      tbuf = parse_to(&str, ']', 0);
      if (str == NULL) {
	safe_chr('[', buff, &bufc);
	str = tstr;
      } else {
	tstr = exec(cause, tbuf, player, eflags | EV_FCHECK | EV_FMAND);
	safe_str(tstr, buff, &bufc);
	free(tstr);
	str--;
      }
      break;

    case '{':
      /* Start of something. Copy everything up to the terminating '}',
       * don't parse, unless there's no closing brace, in which case
       * we just add the '{'.
       */
      at_space = 0;
      tstr = str++;
      tbuf = parse_to(&str, '}', 0);
      if (str == NULL) {
	safe_chr('{', buff, &bufc);
	str = tstr;
      } else {
	if (!(eflags & EV_STRIP)) {
	  safe_chr('{', buff, &bufc);
	}
	tstr = exec(cause, tbuf, player, (eflags & ~(EV_STRIP | EV_FCHECK)));
	safe_str(tstr, buff, &bufc);
	if (!(eflags & EV_STRIP)) {
	  safe_chr('}', buff, &bufc);
	}
	free(tstr);
	str--;
      }
      break;

    case '%':
      /* Start of percent substitution. Evaluate the chars following,
       * doing the substitution.
       */
      at_space = 0;
      str++;
      savec = *str;
      savepos = bufc;
      switch (savec) {

      case '\0':		/* Null -- we're done */
	str--;
	break;

      case '%':			/* Sequence is '%%', so a real percent */
	safe_chr('%', buff, &bufc);
	break;

      case 'r':			/* Newline */
      case 'R':
	safe_str((char *)"\r\n", buff, &bufc);
	break;
      case 't':			/* Tab */
      case 'T':
	safe_chr('\t', buff, &bufc);
	break;
      case 'b':			/* Blank space */
      case 'B':
	safe_chr(' ', buff, &bufc);
	break;

      case '0':			/* stack */
      case '1':
      case '2':
      case '3':
      case '4':
      case '5':
      case '6':
      case '7':
      case '8':
      case '9':
	i = (*str - '0');
	if (wptr[i] != NULL)
	  safe_str(wptr[i], buff, &bufc);
	break;

      case 'v':			/* variable argument */
      case 'V':
      case 'w':
      case 'W':
      case 'x':
      case 'X':
	str++;
	if (!*str)
	  str--;
	ch = UPCASE(*str);
	if (!isalpha(ch))
	  break;
        sprintf(mbuff, "%c%c", UPCASE(*(str-1)), ch);
	attrib = atr_get(player, mbuff);
	if (attrib)
	  safe_str(attrib, buff, &bufc);
	break;

      case 'o':			/* objective pronoun */
      case 'O':
	if (gender < 0)
	  gender = get_gender(cause);
	tbuf = (char *) obj[gender];
	safe_str(tbuf, buff, &bufc);
	break;
      case 'p':
      case 'P':			/* possessive pronoun */
	if (gender < 0)
	  gender = get_gender(cause);
	tbuf = (char *) poss[gender];
	safe_str(tbuf, buff, &bufc);
	break;
      case 's':			/* subjective pronoun */
      case 'S':
	if (gender < 0)
	  gender = get_gender(cause);
	tbuf = (char *) subj[gender];
	safe_str(tbuf, buff, &bufc);
	break;

      case 'n':			/* name of enactor */
      case 'N':
	safe_str(unparse_name(cause), buff, &bufc);
	break;

      case '#':			/* enactor dbref */
	sprintf(temp, "#%d", cause);
	safe_str(temp, buff, &bufc);
	break;
      case '!':			/* executor ("me") dbref */
	sprintf(temp, "#%d", player);
	safe_str(temp, buff, &bufc);
	break;

      case 'l':			/* location of enactor */
      case 'L':
	/* this does not violate security because you have to trigger
         * something somehow (for example, by a $command) in order for
	 * it to find your location.
	 */
	sprintf(temp, "#%d", getloc(cause));
	safe_str(temp, buff, &bufc);
	break;

      default:			/* just copy */
	safe_chr(*str, buff, &bufc);
      }

      if (isupper(savec))
	*savepos = UPCASE(*savepos);
      break;

    case '(':
      /* start of argument list. Check to see if the stuff before is
       * a function. If so, execute it (if we should).
       */

      at_space = 0;
      if (!(eflags & EV_FCHECK)) {
	safe_chr('(', buff, &bufc);
	break;
      }
      eflags &= ~EV_FCHECK;

      /* get uppercase version of the function name, check to see if
       * it exists.
       */
      *bufc = '\0';
      tbuf = (char *) malloc(SBUF_LEN + 1);
      tbufc = tbuf;
      safe_short_str(buff, tbuf, &tbufc);
      *tbufc = '\0';
      while ((--tbufc >= tbuf) && isspace(*tbufc))
	;
      tbufc++;
      for (tbufc = tbuf; *tbufc; tbufc++)
	*tbufc = UPCASE(*tbufc);
      fp = func_hash_lookup(tbuf);

      /* give error message if function not found */
      if (!fp->name) {
	if (eflags & EV_FMAND) {
	  bufc = buff;
	  safe_str((char *) "#-1 FUNCTION (", buff, &bufc);
	  safe_str(tbuf, buff, &bufc);
	  safe_str((char *) ") NOT FOUND", buff, &bufc);
	  done = 1;
	} else {
	  safe_chr('(', buff, &bufc);
	}
	free(tbuf);
	break;
      }
      free(tbuf);

      /* get the arglist */
      nfargs = 10;
      tstr = (char *) str;
      str = parse_arglist(player, cause, str + 1, ')', 
			  (fp->ftype == FN_NOPARSE) ? 
			  (eflags & ~EV_EVAL) : (eflags | EV_EVAL), 
			  fargs, nfargs);

      /* if there's no closing delimiter, just add the '(' and continue */
      if (!str) {
	str = tstr;
	safe_chr(*str, buff, &bufc);
	for (i = 0; i < nfargs; i++)
	  if (fargs[i] != NULL) {
	    free(fargs[i]);
	  }
	break;
      }

      /* Count number of args returned */
      str--;
      j = 0;
      for (i = 0; i < nfargs; i++)
	if (fargs[i] != NULL)
	  j = i + 1;
      nfargs = j;

      /* If we have the right number of args, eval the function.
       * Otherwise, return an error message.
       * Special case: zero args is return by parse_arglist as
       * one null arg.
       */
      if ((fp->nargs == 0) && (nfargs == 1)) {
	if (!*fargs[0]) {
	  free(fargs[0]);
	  fargs[0] = NULL;
	  nfargs = 0;
	}
      }

      if ((nfargs == fp->nargs) || (fp->nargs == -1)) {
	/* Check recursion limit first */
	recurs_lev++;
	invok_counter++;
	if (recurs_lev >= MAX_NEST_LEVEL)
	  strcpy(buff, "#-1 FUNCTION RECURSION LIMIT EXCEEDED");
	else if (invok_counter > MAX_NEST_LEVEL * 100)
	  strcpy(buff, "#-1 FUNCTION INVOCATION LIMIT EXCEEDED");
	else if (invok_counter < MAX_NEST_LEVEL * 100) {
	  if (fp->fun != fun_gfun)
	    fp->fun(buff, fargs, nfargs, player, cause);
	  else
	    fp->fun(buff, fargs, nfargs, player, cause, GF_Index(fp->ftype));
	}
	else
	  *bufc = '\0';
	for (bufc = buff; *bufc; bufc++) /* fix bufc */
	  ;			
	recurs_lev--;
	invok_counter--;
      } else {
	bufc = buff;
	tstr = (char *) malloc(SBUF_LEN + 1);
	sprintf(tstr, "%d", fp->nargs);
	safe_str((char *) "#-1 FUNCTION (", buff, &bufc);
	safe_str((char *) fp->name, buff, &bufc);
	safe_str((char *) ") EXPECTS ", buff, &bufc);
	safe_str(tstr, buff, &bufc);
	safe_str((char *) " ARGUMENTS", buff, &bufc);
	free(tstr);
      }

      /* free up the space allocated for the args */
      for (i = 0; i < nfargs; i++)
	if (fargs[i] != NULL) {
	  free(fargs[i]);
	}
      break;

    default:
      /* just another character. copy it. */
      at_space = 0;
      safe_chr(*str, buff, &bufc);
    }

    str++;
  }
  *bufc = '\0';
  return buff;
}

char *replace_string(char *old, char *new, char *string)
{
  /* another 2.0 function: replaces string "old" with string "new".
   * The result returned by this must be freed.
   */

  char *result, *r, *s;
  int len;

  if (!string)
    return NULL;

  s = (char *) string;
  len = strlen(old);
  r = result = (char *) malloc(BUFFER_LEN + 1);
  while (*s) {

    /* copy up to the next occurence of first char of old */
    while (*s && *s != *old) {
      safe_chr(*s, result, &r);
      s++;
    }

    /* if we've really found  old, append new to the result and
     * move past the occurence of old. Else, copy the char and
     * continue.
     */
    if (*s) {
      if (!strncmp(old, s, len)) {
	safe_copy_str((char *) new, result, &r, BUFFER_LEN);
	s += len;
      } else {
	safe_chr(*s, result, &r);
	s++;
      }
    }
  }

  *r = '\0';
  return result;
}

char *skip_space(char *s)
{
  /* returns pointer to the next non-space char in s, or NULL if s == NULL
   * or *s == NULL or s has only spaces.
   */

  char *c = (char *) s;
  while (c && *c && isspace(*c))
    c++;
  return c;
}

char *seek_char(char *s, char c)
{
  /* similar to strchr(). returns a pointer to the next char in s which
   * matches c, or a pointer to the terminating null at the end of s.
   */

  char *p = (char *) s;
  while (p && *p && (*p != c))
    p++;
  return p;
}

char *upcasestr(char *s)
{
  /* modifies a string in-place to be upper-case */

  char *p;
  for (p = s; *p; p++)
    *p = UPCASE(*p);
  return s;
}

int do_mush_create(char *arg1, char *arg2, dbref player)
{
  dbref thing = NOTHING;
  int cost;
  char buf[BUFFER_LEN];

  if(!Builder(player)) {
    notify(player, player, "Command is restricted to authorized builders.");
    return -1;
  }
  
  if(*arg1 == '\0') {
    notify(player, player, "Create what?");
    return -1;
  }
  else if(!ok_name(arg1)) {
    notify(player, player, "That's a silly name for a thing!");
    return -1;
  }

  cost = atol(arg2);
  if(cost < OBJECT_COST) cost = OBJECT_COST;
  if(!payfor(player, cost)) {
     notify(player, player, tprintf(
           "Sorry, you don't have enough %s.", PL_MONEY));
     return -1;
  } else {
    /* create the object */
    thing = new_object();
    
    /* initialize everything */
    DBSTORE(thing, name, dup_string(arg1));
    DBSTORE(thing, location, player);
    DBSTORE(thing, owner, OWNER(player));
    add_ownerlist(thing);
    DBSTORE(thing, pennies, OBJECT_ENDOWMENT(cost));
    DBSTORE(thing, exits, NOTHING);
    DBSTORE(thing, link, player);
    FLAGS(thing) = TYPE_THING;
    add_backlinks(thing);
    
    /* endow the object */
    if(DBFETCH(thing)->pennies > MAX_OBJECT_ENDOWMENT)
      DBSTORE(thing, pennies, MAX_OBJECT_ENDOWMENT);
    
    DBSTORE(thing, pennies, cost);
    PUSH(thing, DBFETCH(player)->contents);
    DBDIRTY(player);
    sprintf(buf, "%s created with number %ld.", arg1, thing);
    notify(player, player, buf);
    DBDIRTY(thing);
  }
    return thing;
}

int do_mush_dig(char *arg1, char *arg2, dbref player)
{
  dbref room;
  dbref parent;
  char buf[BUFFER_LEN];
  match_data md;
  
  if(!Builder(player)) {
    notify(player, player, "Command is restricted to authorized builders.");
    return -1;
  }
  
  if(*arg1 == '\0') {
    notify(player, player, "You must specify a name for the room.");
    return -1;
  }

  if(!ok_name(arg1)) {
    notify(player, player, "That's a silly name for a room!");
    return -1;
  }

  if(!payfor(player, ROOM_COST)) {
    notify(player, player, tprintf(
    "Sorry, you don't have enough %s to dig a room.", PL_MONEY));
    return -1;
  }

  room = new_object();

  FLAGS(room) = TYPE_ROOM | (FLAGS(player) & JUMP_OK);

  parent = DBFETCH(DBFETCH(player)->location)->location;

  if (*arg2) {
    init_match(player, arg2, TYPE_ROOM, &md);
    match_absolute(&md);
    match_here(&md);
    parent = noisy_match_result(&md);
  }

  if ((parent == NOTHING) ||
    (parent == AMBIGUOUS) ||
    (!can_link_to(player, Typeof(room), parent)) ||
    (room == parent))
    parent = GLOBAL_ENVIRONMENT;

  /* Initialize everything */
  DBSTORE(room, name, dup_string(arg1));
  DBSTORE(room, location, parent);
  DBSTORE(room, owner, OWNER(player));
  add_ownerlist(room);
  DBSTORE(room, exits, NOTHING);
  DBSTORE(room, link, NOTHING);
  PUSH(room, DBFETCH(parent)->contents);
  DBDIRTY(room);
  DBDIRTY(parent);

  sprintf(buf, "%s created with room number %ld, parent %ld.",
    arg1, room, parent);
  notify(player, player, buf);
  return room;
}

int is_number(char *str)
{
  /* is a string a number? */

  while (*str && isspace(*str))	/* trim leading spaces */
    str++;

  if (*str == '-') {
    str++;
    if (!*str)
      return 0;			/* just a minus sign. no good. */
  }

  while (*str && isdigit(*str))	/* the number */
    str++;

  while (*str && isspace(*str))	/* trim trailing spaces */
    str++;

  return (*str ? 0 : 1);
}

char *atr_get(dbref thing, char *atr)
{
  char *attrib;
  int parent_depth = 0;	        /* excessive recursion prevention */
  dbref temp;

  if (thing == NOTHING || !atr) return NULL;
  temp = thing;
  while ((temp != NOTHING) && (parent_depth < MAX_PARENTS)) {
  attrib = get_property_data(temp, atr, ACCESS_CO);
    if (attrib) {
      parent_depth = 0;
      break;			/* found it, break out of loop */
    } else {
      parent_depth++;
      temp = Parent(temp);
    }
  }
  return attrib;
}

dbref where_is(dbref thing)
{
  /* returns "real" location of object. This is the location for players
   * and things, source for exits, and NOTHING for rooms.
   */

  if (!GoodObject(thing))
    return NOTHING;
  switch (Typeof(thing)) {
  case TYPE_ROOM:
    return NOTHING;
  case TYPE_EXIT:
    return (DBFETCH(thing)->link);
  default:
    return Location(thing);
  }
}

int GoodObject(dbref object1)
{
 if((object1 >= db_top) ||
    (Typeof(object1) == TYPE_GARBAGE) ||
    (object1 < 0) ) return 0;
 else
    return 1;
}

int Can_Read_Attr(dbref privs, dbref thing, char *attrib) 
{
 if (find_property(thing, attrib, access_rights(privs, thing, NOTHING)))
     return 1;
 return 0;
}

/* use this to create an exit */
int do_real_open(dbref player, char *direction, char *linkto, dbref pseudo)
{
  dbref loc = (pseudo != NOTHING) ? pseudo : Location(player);
  dbref exit; 
  dbref good_dest[MAX_LINKS];
  char buf[BUFFER_LEN];
  int i, ndest;

  if ((loc == NOTHING) || (Typeof(loc) != TYPE_ROOM)) {
    notify(player, player, "Sorry you can only make exits out of rooms.");
    return 0;
  }

  if (!Builder(player)) {
    notify(player, player, "That command is restricted to builders only.");
    return 0;
  }

  if(!*direction) {
    notify(player, player, "Open where?");
    return 0;
  } else if (!ok_name(direction)) {
    notify(player, player, "That's a strange name for an exit!");
    return 0;
  }
  if (!controls(player, loc)) {
    notify(player, player, "Permission denied.");
  } else if (payfor(player, EXIT_COST)) {
    /* create the exit */
    exit = new_object();

    /* initialize everything */
    DBSTORE(exit, name, dup_string(direction));
    DBSTORE(exit, location, loc);
    DBSTORE(exit, owner, OWNER(player));
    add_ownerlist(exit);
    FLAGS(exit) = TYPE_EXIT;
    DBFETCH(exit)->sp.exit.ndest = 0;
    DBFETCH(exit)->sp.exit.dest = NULL;
    
    /* link it in */
    PUSH(exit, DBFETCH(loc)->exits);
    DBDIRTY(loc);
    
    /* and we're done */
    sprintf(buf, "Exit opened with number %ld.", exit);
    notify(player, player, buf);
    
    /* check second arg to see if we should do a link */
    if(*linkto != '\0')
    {
      notify(player, player, "Trying to link...");
      ndest = link_exit(player, exit, linkto, good_dest);
      DBFETCH(exit)->sp.exit.ndest = ndest;
      DBFETCH(exit)->sp.exit.dest = (dbref *)malloc(sizeof(dbref) * ndest);
      for (i = 0; i < ndest; i++)
        (DBFETCH(exit)->sp.exit.dest)[i] = good_dest[i];
      add_backlinks(exit);
      DBDIRTY(exit);
      return exit;
    }
  }
  notify(player, player, "You don't have enough money.");
  return -1;
}

int local_wild_match(char *s, char *d)
{
  int a;
  for (a = 0; a < 10; a++)
    lwptr[a] = NULL;

  switch (*s) {
    case '>':
      s++;
      /* if both first letters are #'s then numeric compare */
      if ((isascii(s[0]) && isdigit(s[0])) || (*s == '-'))
	return (atoi(s) < atoi(d));
      else
	return (strcmp(s, d) < 0);
    case '<':
      s++;
      if ((isascii(s[0]) && isdigit(s[0])) || (*s == '-'))
	return (atoi(s) > atoi(d));
      else
	return (strcmp(s, d) > 0);
    default:
      if (local_wild(s, d, 0, 0)) {
	int b;
	char *e, *f = lwbuff;
	for (a = 0; a < 10; a++)
	  if ((e = lwptr[a]) != NULL) {
	    lwptr[a] = f;
	    for (b = lwlen[a]; b--; *f++ = *e++) ;
	    *f++ = 0;
	  }
	return (1);
      } else
	return (0);
  }
}

int local_wild(char *s, char *d, int p, int os)
{
  switch (*s) {
    case '?':			/* match any character in d, note end of
				 * string is considered a match */
      /* if just in nonwildcard state record location of change */
      if (!os && (p < 10))
	lwptr[p] = d;
      return (local_wild(s + 1, (*d) ? d + 1 : d, p, 1));
    case '*':			/* match a range of characters */
      if (!os && (p < 10)) {
	lwptr[p] = d;
      }
      return (local_wild(s + 1, d, p, 1) || 
	      ((*d) ? local_wild(s, d + 1, p, 1) : 0));
    default:
      if (os && (p < 10)) {
	lwlen[p] = d - lwptr[p];
	p++;
      }
      return ((DOWNCASE(*s) != DOWNCASE(*d)) ? 0 :
	      ((*s) ? local_wild(s + 1, d + 1, p, 0) : 1));
  }
}

static void do_userfn(player, cause, obj, attrib, args, buff, flag)
     dbref player, cause, obj;
     char *attrib;
     char *args[10];
     char *buff;
     int flag;			/* 0 if ufun, 1 if gfun */
{
  int a;
  char *result;
  char *tptr[10];
  char tbuf1[BUFFER_LEN];
  char *bp = tbuf1;

  if(obj == NOTHING) {
    strcpy(buff, "#-1 NO SUCH OBJECT");
    nest_level--;
    return;
  }

  if (!attrib) {
    strcpy(buff, "#-1 NO SUCH USER FUNCTION || NO PERMISSION TO GET ATTRIBUTE");
    nest_level--;
    return;
  }

  safe_str(attrib, tbuf1, &bp);
  *bp = '\0';

  /* save our stack */
  for (a = 0; a < 10; a++)
    tptr[a] = wptr[a];

  /* copy the appropriate args into the stack */
  if (flag == 0) {
    for (a = 1; a < 10; a++)
      wptr[a - 1] = args[a];
    wptr[9] = NULL;		/* sorry, can't have more than 9 args */
  } else {
    for (a = 0; a < 10; a++)
      wptr[a] = args[a];
  }

  /* now find the value of the function with the new stack */
  result = exec(cause, tbuf1, obj, 0);

  /* restore the stack */
  for (a = 0; a < 10; a++)
    wptr[a] = tptr[a];

  /* decrement the nested functions counter */
  nest_level--;

  /* copy and free */
  strcpy(buff, result);
  free(result);
}


void do_function(player, name, argv)
     dbref player;
     char *name;
     char *argv[];
{
  /* command of format: @function <function name>=<thing>,<attribute>
   * Adds a new user-defined function.
   */

  int i;
  struct ftab_entry *tabp;
  char tbuf1[BUFFER_LEN];
  char *bp = tbuf1;
  dbref thing;
  match_data md;
  FUN *fp;

  /* if no arguments, just give the list of user functions, by walking
   * the function hash table, and looking up all functions marked
   * as user-defined.
   */

  if (!name || !*name) {
    if (userfn_count == 0) {
      notify(player, player, "No global user-defined functions exist.");
      return;
    }
    if (Global_Funcs(player)) {
      /* if the player is privileged, display user-def'ed functions
       * with corresponding dbref number of thing and attribute name.
       */
      notify(player, player, "Function Name                   Dbref #    Attrib");
      for (i = 0; i < FUNC_HASH_SIZE; i++) {
	for (tabp = func_hashtab[i]; tabp != NULL; tabp = tabp->next) {
	  if (tabp->fp->ftype >= GLOBAL_OFFSET)
	    notify(player,player,  
		   tprintf("%-32s %6d    %s", tabp->fp->name,
			   userfn_tab[GF_Index(tabp->fp->ftype)].thing,
			   userfn_tab[GF_Index(tabp->fp->ftype)].name));
	}
      }
    } else {
      /* just print out the list of available functions */
      safe_str("User functions:", tbuf1, &bp);
      for (i = 0; i < FUNC_HASH_SIZE; i++) {
	for (tabp = func_hashtab[i]; tabp != NULL; tabp = tabp->next) {
	  if (tabp->fp->ftype >= GLOBAL_OFFSET) {
	    safe_chr(' ', tbuf1, &bp);
	    safe_str(tabp->fp->name, tbuf1, &bp);
	  }
	}
      }
      *bp = '\0';
      notify(player, player, tbuf1);
    }
    return;
  }

  /* otherwise, we are adding a user function. There is NO deletion
   * mechanism. Only those with the Global_Funcs power may add stuff.
   * If you add a function that is already a user-defined function,
   * the old function gets over-written.
   */
  
  if (!Global_Funcs(player)) {
    notify(player, player, "Permission denied.");
    return;
  }
  if (userfn_count >= MAX_GLOBAL_FNS) {
    notify(player, player, "Function table full.");
    return;
  }

  if (!argv[1] || !*argv[1] || !argv[2] || !*argv[2]) {
    notify(player, player, "You must specify an object and an attribute.");
    return;
  }

  /* make sure the function name length is okay */
  if (strlen(name) > 30) {
    notify(player, player, "Function name too long.");
    return;
  }

  /* find the object. For some measure of security, the player must
   * be able to examine it.
   */
  init_match(player, argv[1], NOTYPE, &md);
  match_everything(&md);
  if ((thing = noisy_match_result(&md)) == NOTHING)
    return;
  if (!controls(player, thing)) {
    notify(player, player, "No permission to examine object.");
    return;
  }

  /* we don't need to check if the attribute exists. If it doesn't,
   * it's not our problem - it's the user's responsibility to make
   * sure that the attribute exists (if it doesn't, invoking the
   * function will return a #-1 NO SUCH ATTRIBUTE error).
   */

  fp = func_hash_lookup(upcasestr(name));
  if (fp->name == NULL) {

    /* a completely new entry. First, insert it into general hash table */
    fp = (FUN *) malloc(sizeof(FUN));
    fp->name = (char *) strdup(name);
    fp->fun = fun_gfun;
    fp->nargs = -1;
    fp->ftype = userfn_count + GLOBAL_OFFSET;
    func_hash_insert(name, (FUN *)fp);

    /* now add it to the user function table */
    userfn_tab[userfn_count].thing = thing;
    userfn_tab[userfn_count].name = (char *) strdup(upcasestr(argv[2]));
    userfn_count++;
    
    notify(player, player, "Function added.");
    return;
  } else {

    /* we are modifying an old entry */
    userfn_tab[GF_Index(fp->ftype)].thing = thing;
    free(userfn_tab[GF_Index(fp->ftype)].name);
    userfn_tab[GF_Index(fp->ftype)].name = (char *) strdup(upcasestr(argv[2]));

    notify(player, player, "Function updated.");
  }
}

#endif  /* MUSH */