pennmush/game/
pennmush/game/data/
pennmush/game/log/
pennmush/game/save/
pennmush/game/txt/evt/
pennmush/game/txt/nws/
pennmush/os2/
#include "copyrite.h"

#include "config.h"
#include <limits.h>
#ifdef I_STRING
#include <string.h>
#else
#include <strings.h>
#endif
#include <ctype.h>
#include "conf.h"
#include "attrib.h"
#include "dbdefs.h"
#include "externs.h"
#include "function.h"
#include "intrface.h"
#include "match.h"
#include "htab.h"
#include "parse.h"
#ifdef MEM_CHECK
#include "memcheck.h"
#endif
#include "mymalloc.h"
#include "confmagic.h"

void save_global_regs _((const char *funcname, char *preserve[]));
void restore_global_regs _((const char *funcname, char *preserve[]));
int delim_check _((char *buff, char **bp, int nfargs, char **fargs, int sep_arg, char *sep));
void do_list_functions _((dbref player));
FUN *func_hash_lookup _((char *name));
void func_hash_insert _((char *name, FUN *func));
void init_func_hashtab _((void));
char *strip_braces _((const char *str));
void do_function _((dbref player, char *name, char **argv));
void do_function_delete _((dbref player, char *name));
extern void local_functions _((void));

HASHTAB htab_function;

/* -------------------------------------------------------------------------*
 * Utilities.
 */

void
save_global_regs(funcname, preserve)
    const char *funcname;
    char *preserve[];
{
  int i;

  for (i = 0; i < 10; i++) {
    if (!renv[i][0])
      preserve[i] = NULL;
    else {
      preserve[i] = (char *) mush_malloc(BUFFER_LEN, funcname);
      strcpy(preserve[i], renv[i]);
    }
  }
}

void
restore_global_regs(funcname, preserve)
    const char *funcname;
    char *preserve[];
{
  int i;

  for (i = 0; i < 10; i++) {
    if (preserve[i]) {
      strcpy(renv[i], preserve[i]);
      mush_free(preserve[i], funcname);
    } else {
      renv[i][0] = '\0';
    }
  }
}

int
delim_check(buff, bp, nfargs, fargs, sep_arg, sep)
    char *buff;
    char **bp;
    int nfargs;
    char *fargs[];
    int sep_arg;
    char *sep;
{
  /* Find a delimiter. */

  if (nfargs >= sep_arg) {
    if (!*fargs[sep_arg - 1])
      *sep = ' ';
    else if (strlen(fargs[sep_arg - 1]) != 1) {
      safe_str("#-1 SEPARATOR MUST BE ONE CHARACTER", buff, bp);
      return 0;
    } else
      *sep = *fargs[sep_arg - 1];
  } else
    *sep = ' ';

  return 1;
}

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

FUN flist[] =
{
  {"ABS", fun_abs, 1, 1, FN_REG},
  {"ADD", fun_add, 2, INT_MAX, FN_REG},
  {"AFTER", fun_after, 2, 2, FN_REG},
  {"ALPHAMAX", fun_alphamax, 1, INT_MAX, FN_REG},
  {"ALPHAMIN", fun_alphamin, 1, INT_MAX, FN_REG},
  {"AND", fun_and, 2, INT_MAX, FN_REG},
  {"ANDFLAGS", fun_andflags, 2, 2, FN_REG},
  {"ANSI", fun_ansi, 2, -2, FN_REG},
  {"APOSS", fun_aposs, 1, 1, FN_REG},
  {"ART", fun_art, 1, 1, FN_REG},
  {"ATRLOCK", fun_atrlock, 1, 2, FN_REG},
  {"BEEP", fun_beep, 0, 1, FN_REG},
  {"BEFORE", fun_before, 2, 2, FN_REG},
  {"CAND", fun_cand, 2, INT_MAX, FN_NOPARSE},
  {"CAPSTR", fun_capstr, 1, -1, FN_REG},
  {"CAT", fun_cat, 1, INT_MAX, FN_REG},
#ifdef CHAT_SYSTEM
  {"CEMIT", fun_cemit, 2, -2, FN_REG},
#endif
  {"CENTER", fun_center, 2, 3, FN_REG},
#ifdef CHAT_SYSTEM
  {"CHANNELS", fun_channels, 0, 1, FN_REG},
#endif
  {"CLONE", fun_clone, 1, 1, FN_REG},
  {"COMP", fun_comp, 2, 2, FN_REG},
  {"CON", fun_con, 1, 1, FN_REG},
  {"CONN", fun_conn, 1, 1, FN_REG},
  {"CONTROLS", fun_controls, 2, 2, FN_REG},
  {"CONVSECS", fun_convsecs, 1, 1, FN_REG},
  {"CONVTIME", fun_convtime, 1, 1, FN_REG},
  {"COR", fun_cor, 2, INT_MAX, FN_NOPARSE},
  {"CREATE", fun_create, 1, 2, FN_REG},
#ifdef CREATION_TIMES
  {"CTIME", fun_ctime, 1, 1, FN_REG},
#endif
#ifdef CHAT_SYSTEM
  {"CWHO", fun_cwho, 1, 1, FN_REG},
#endif
  {"DEC", fun_dec, 1, 1, FN_REG},
  {"DECRYPT", fun_decrypt, 2, 2, FN_REG},
  {"DEFAULT", fun_default, 2, 2, FN_NOPARSE},
  {"DELETE", fun_delete, 3, 3, FN_REG},
  {"DIE", fun_die, 2, 2, FN_REG},
  {"DIG", fun_dig, 1, 3, FN_REG},
  {"DIST2D", fun_dist2d, 4, 4, FN_REG},
  {"DIST3D", fun_dist3d, 6, 6, FN_REG},
  {"DIV", fun_div, 2, 2, FN_REG},
  {"DOING", fun_doing, 1, 1, FN_REG},
  {"EDEFAULT", fun_edefault, 2, 2, FN_NOPARSE},
  {"EDIT", fun_edit, 3, 3, FN_REG},
  {"ELEMENT", fun_element, 3, 3, FN_REG},
  {"ELEMENTS", fun_elements, 2, 3, FN_REG},
  {"ELOCK", fun_elock, 2, 2, FN_REG},
  {"EMIT", fun_emit, 1, -1, FN_REG},
  {"ENCRYPT", fun_encrypt, 2, 2, FN_REG},
  {"ENTRANCES", fun_entrances, 0, 4, FN_REG},
  {"EQ", fun_eq, 2, 2, FN_REG},
  {"EVAL", fun_eval, 2, 2, FN_REG},
  {"ESCAPE", fun_escape, 1, -1, FN_REG},
  {"EXIT", fun_exit, 1, 1, FN_REG},
  {"EXTRACT", fun_extract, 3, 4, FN_REG},
  {"FILTER", fun_filter, 2, 3, FN_REG},
  {"FINDABLE", fun_findable, 2, 2, FN_REG},
  {"FIRST", fun_first, 1, 2, FN_REG},
  {"FLAGS", fun_flags, 1, 1, FN_REG},
  {"FLIP", fun_flip, 1, 1, FN_REG},
  {"FOLD", fun_fold, 2, 4, FN_REG},
#ifdef USE_MAILER
  {"FOLDERSTATS", fun_folderstats, 0, 2, FN_REG},
#endif
  {"FOREACH", fun_foreach, 2, 2, FN_REG},
  {"FUNCTIONS", fun_functions, 0, 0, FN_REG},
  {"FULLNAME", fun_fullname, 1, 1, FN_REG},
  {"GET", fun_get, 1, 1, FN_REG},
  {"GET_EVAL", fun_get_eval, 1, 1, FN_REG},
  {"GRAB", fun_grab, 2, 3, FN_REG},
  {"GRABALL", fun_graball, 2, 3, FN_REG},
  {"GREP", fun_grep, 3, 3, FN_REG},
  {"GREPI", fun_grep, 3, 3, FN_REG},
  {"GT", fun_gt, 2, 2, FN_REG},
  {"GTE", fun_gte, 2, 2, FN_REG},
  {"HASATTR", fun_hasattr, 2, 2, FN_REG},
  {"HASATTRP", fun_hasattr, 2, 2, FN_REG},
  {"HASATTRPVAL", fun_hasattr, 2, 2, FN_REG},
  {"HASATTRVAL", fun_hasattr, 2, 2, FN_REG},
  {"HASFLAG", fun_hasflag, 2, 2, FN_REG},
  {"HASPOWER", fun_haspower, 2, 2, FN_REG},
  {"HASTYPE", fun_hastype, 2, 2, FN_REG},
  {"HIDDEN", fun_hidden, 1, 1, FN_REG},
  {"HOME", fun_home, 1, 1, FN_REG},
  {"IDLE", fun_idlesecs, 1, 1, FN_REG},
  {"IDLESECS", fun_idlesecs, 1, 1, FN_REG},
  {"IF", fun_if, 2, 3, FN_NOPARSE},
  {"IFELSE", fun_if, 3, 3, FN_NOPARSE},
  {"INC", fun_inc, 1, 1, FN_REG},
  {"INDEX", fun_index, 4, 4, FN_REG},
  {"INSERT", fun_insert, 3, 4, FN_REG},
  {"ISDAYLIGHT", fun_isdaylight, 0, 0, FN_REG},
  {"ISDBREF", fun_isdbref, 1, 1, FN_REG},
  {"ISNUM", fun_isnum, 1, 1, FN_REG},
  {"ISWORD", fun_isword, 1, 1, FN_REG},
  {"ITER", fun_iter, 2, 4, FN_NOPARSE},
  {"ITEMS", fun_items, 2, 2, FN_REG},
  {"LAST", fun_last, 1, 2, FN_REG},
  {"LATTR", fun_lattr, 1, 1, FN_REG},
  {"LCON", fun_lcon, 1, 1, FN_REG},
  {"LCSTR", fun_lcstr, 1, -1, FN_REG},
  {"LDELETE", fun_ldelete, 2, 3, FN_REG},
  {"LEFT", fun_left, 2, 2, FN_REG},
  {"LEMIT", fun_lemit, 1, -1, FN_REG},
  {"LEXITS", fun_lexits, 1, 1, FN_REG},
  {"LINK", fun_link, 2, 2, FN_REG},
  {"LIT", fun_lit, 1, -1, FN_LITERAL},
  {"LJUST", fun_ljust, 2, 3, FN_REG},
  {"LNUM", fun_lnum, 1, 3, FN_REG},
  {"LOC", fun_loc, 1, 1, FN_REG},
  {"LOCATE", fun_locate, 3, 3, FN_REG},
  {"LOCK", fun_lock, 1, 2, FN_REG},
  {"LPARENT", fun_lparent, 1, 1, FN_REG},
  {"LSEARCH", fun_lsearch, 3, 5, FN_REG},
  {"LSEARCHR", fun_lsearch, 3, 5, FN_REG},
  {"LSTATS", fun_lstats, 0, 1, FN_REG},
  {"LT", fun_lt, 2, 2, FN_REG},
  {"LTE", fun_lte, 2, 2, FN_REG},
  {"LWHO", fun_lwho, 0, 0, FN_REG},
#ifdef USE_MAILER
  {"MAIL", fun_mail, 0, 2, FN_REG},
  {"MAILFROM", fun_mailfrom, 1, 2, FN_REG},
  {"MAILSTATUS", fun_mailstatus, 1, 2, FN_REG},
#ifdef MAIL_SUBJECTS
  {"MAILSUBJECT", fun_mailsubject, 1, 2, FN_REG},
#endif
  {"MAILTIME", fun_mailtime, 1, 2, FN_REG},
#endif
  {"MAP", fun_map, 2, 3, FN_REG},
  {"MATCH", fun_match, 2, 3, FN_REG},
  {"MATCHALL", fun_matchall, 2, 3, FN_REG},
  {"MAX", fun_max, 1, INT_MAX, FN_REG},
  {"MEMBER", fun_member, 2, 3, FN_REG},
  {"MERGE", fun_merge, 3, 3, FN_REG},
  {"MID", fun_mid, 3, 3, FN_REG},
  {"MIN", fun_min, 1, INT_MAX, FN_REG},
  {"MIX", fun_mix, 3, 4, FN_REG},
  {"MOD", fun_mod, 2, 2, FN_REG},
  {"MONEY", fun_money, 1, 1, FN_REG},
#ifdef CREATION_TIMES
  {"MTIME", fun_mtime, 1, 1, FN_REG},
#endif
  {"MUDNAME", fun_mudname, 0, 0, FN_REG},
  {"MUL", fun_mul, 2, INT_MAX, FN_REG},
  {"MUNGE", fun_munge, 3, 4, FN_REG},
  {"MWHO", fun_lwho, 0, 0, FN_REG},
  {"NAME", fun_name, 1, 2, FN_REG},
  {"NEARBY", fun_nearby, 2, 2, FN_REG},
  {"NEQ", fun_neq, 2, 2, FN_REG},
  {"NEXT", fun_next, 1, 1, FN_REG},
  {"NOT", fun_not, 1, 1, FN_REG},
  {"NUM", fun_num, 1, 1, FN_REG},
  {"OBJ", fun_obj, 1, 1, FN_REG},
  {"OBJEVAL", fun_objeval, 2, -2, FN_NOPARSE},
  {"OBJMEM", fun_objmem, 1, 1, FN_REG},
  {"OEMIT", fun_oemit, 2, -2, FN_REG},
  {"OPEN", fun_open, 2, 2, FN_REG},
  {"OR", fun_or, 2, INT_MAX, FN_REG},
  {"ORFLAGS", fun_orflags, 2, 2, FN_REG},
  {"OWNER", fun_owner, 1, 1, FN_REG},
  {"PARENT", fun_parent, 1, 2, FN_REG},
  {"PARSE", fun_iter, 2, 4, FN_NOPARSE},
  {"PEMIT", fun_pemit, 2, -2, FN_REG},
  {"PLAYERMEM", fun_playermem, 1, 1, FN_REG},
  {"PMATCH", fun_pmatch, 1, 1, FN_REG},
  {"POLL", fun_poll, 0, 0, FN_REG},
  {"PORTS", fun_ports, 1, 1, FN_REG},
  {"POS", fun_pos, 2, 2, FN_REG},
  {"POSS", fun_poss, 1, 1, FN_REG},
  {"POWERS", fun_powers, 1, 1, FN_REG},
  {"PUEBLO", fun_pueblo, 1, 1, FN_REG},
#ifdef QUOTA
  {"QUOTA", fun_quota, 1, 1, FN_REG},
#endif
  {"R", fun_r, 1, 1, FN_REG},
  {"RAND", fun_rand, 1, 1, FN_REG},
  {"REGMATCH", fun_regmatch, 2, 3, FN_REG},
  {"REMIT", fun_remit, 2, -2, FN_REG},
  {"REMOVE", fun_remove, 2, 3, FN_REG},
  {"REPEAT", fun_repeat, 2, 2, FN_REG},
  {"REPLACE", fun_replace, 3, 4, FN_REG},
  {"REST", fun_rest, 1, 2, FN_REG},
  {"REVERSE", fun_flip, 1, 1, FN_REG},
  {"REVWORDS", fun_revwords, 1, 2, FN_REG},
  {"RIGHT", fun_right, 2, 2, FN_REG},
  {"RJUST", fun_rjust, 2, 3, FN_REG},
  {"RLOC", fun_rloc, 2, 2, FN_REG},
  {"RNUM", fun_rnum, 2, 2, FN_REG},
  {"ROOM", fun_room, 1, 1, FN_REG},
  {"S", fun_s, 1, -1, FN_REG},
  {"SCRAMBLE", fun_scramble, 1, -1, FN_REG},
  {"SEARCH", fun_lsearch, 3, 5, FN_REG},
  {"SECS", fun_secs, 0, 0, FN_REG},
  {"SECURE", fun_secure, 1, -1, FN_REG},
  {"SET", fun_set, 2, 2, FN_REG},
  {"SETQ", fun_setq, 2, 2, FN_REG},
  {"SETR", fun_setq, 2, 2, FN_REG},
  {"SETDIFF", fun_setdiff, 2, 3, FN_REG},
  {"SETINTER", fun_setinter, 2, 3, FN_REG},
  {"SETUNION", fun_setunion, 2, 3, FN_REG},
  {"SHL", fun_shl, 2, 2, FN_REG},
  {"SHR", fun_shr, 2, 2, FN_REG},
  {"SHUFFLE", fun_shuffle, 1, 2, FN_REG},
  {"SIGN", fun_sign, 1, 1, FN_REG},
  {"SORT", fun_sort, 1, 3, FN_REG},
  {"SORTBY", fun_sortby, 2, 3, FN_REG},
  {"SOUNDEX", fun_soundex, 1, 1, FN_REG},
  {"SOUNDLIKE", fun_soundlike, 2, 2, FN_REG},
  {"SOUNDSLIKE", fun_soundlike, 2, 2, FN_REG},
  {"SPACE", fun_space, 1, 1, FN_REG},
  {"SPLICE", fun_splice, 3, 4, FN_REG},
  {"SQUISH", fun_squish, 1, 1, FN_REG},
  {"STARTTIME", fun_starttime, 0, 0, FN_REG},
  {"STATS", fun_lstats, 0, 1, FN_REG},
  {"STRCAT", fun_strcat, 1, INT_MAX, FN_REG},
  {"STRIPANSI", fun_stripansi, 1, -1, FN_REG},
  {"STRLEN", fun_strlen, 1, -1, FN_REG},
  {"STRMATCH", fun_strmatch, 2, 2, FN_REG},
  {"SUB", fun_sub, 2, 2, FN_REG},
  {"SUBJ", fun_subj, 1, 1, FN_REG},
  {"SWITCH", fun_switch, 3, INT_MAX, FN_NOPARSE},
  {"T", fun_t, 1, 1, FN_REG},
  {"TABLE", fun_table, 1, 5, FN_REG},
  {"TEL", fun_tel, 2, 2, FN_REG},
  {"TIME", fun_time, 0, 0, FN_REG},
  {"TIMESTRING", fun_timestring, 1, 2, FN_REG},
  {"TRIM", fun_trim, 1, 3, FN_REG},
  {"TRUNC", fun_trunc, 1, 1, FN_REG},
  {"TYPE", fun_type, 1, 1, FN_REG},
  {"UCSTR", fun_ucstr, 1, -1, FN_REG},
  {"UDEFAULT", fun_udefault, 2, 12, FN_NOPARSE},
  {"UFUN", fun_ufun, 1, 11, FN_REG},
  {"ULOCAL", fun_ulocal, 1, 11, FN_REG},
  {"U", fun_ufun, 1, 11, FN_REG},
  {"V", fun_v, 1, 1, FN_REG},
  {"VAL", fun_trunc, 1, 1, FN_REG},
  {"VALID", fun_valid, 2, 2, FN_REG},
  {"VERSION", fun_version, 0, 0, FN_REG},
  {"VISIBLE", fun_visible, 2, 2, FN_REG},
  {"WHERE", fun_where, 1, 1, FN_REG},
  {"WIPE", fun_wipe, 1, 1, FN_REG},
  {"WORDPOS", fun_wordpos, 2, 3, FN_REG},
  {"WORDS", fun_words, 1, 2, FN_REG},
  {"XGET", fun_xget, 2, 2, FN_REG},
  {"XOR", fun_xor, 2, INT_MAX, FN_REG},
  {"ZEMIT", fun_zemit, 2, -2, FN_REG},
  {"ZFUN", fun_zfun, 1, 11, FN_REG},
  {"ZONE", fun_zone, 1, 2, FN_REG},
  {"VADD", fun_vadd, 2, 3, FN_REG},
  {"VSUB", fun_vsub, 2, 3, FN_REG},
  {"VMUL", fun_vmul, 2, 3, FN_REG},
  {"VDOT", fun_vdot, 2, 3, FN_REG},
  {"VMAG", fun_vmag, 1, 2, FN_REG},
  {"VDIM", fun_words, 1, 2, FN_REG},
  {"VUNIT", fun_vunit, 1, 2, FN_REG},
#ifdef FLOATING_POINTS
  {"ACOS", fun_acos, 1, 1, FN_REG},
  {"ASIN", fun_asin, 1, 1, FN_REG},
  {"ATAN", fun_atan, 1, 1, FN_REG},
  {"CEIL", fun_ceil, 1, 1, FN_REG},
  {"COS", fun_cos, 1, 1, FN_REG},
  {"E", fun_e, 0, 0, FN_REG},
  {"EXP", fun_exp, 1, 1, FN_REG},
  {"FDIV", fun_fdiv, 2, 2, FN_REG},
  {"FLOOR", fun_floor, 1, 1, FN_REG},
  {"LOG", fun_log, 1, 1, FN_REG},
  {"LN", fun_ln, 1, 1, FN_REG},
  {"PI", fun_pi, 0, 0, FN_REG},
  {"POWER", fun_power, 2, 2, FN_REG},
  {"ROUND", fun_round, 2, 2, FN_REG},
  {"SIN", fun_sin, 1, 1, FN_REG},
  {"SQRT", fun_sqrt, 1, 1, FN_REG},
  {"TAN", fun_tan, 1, 1, FN_REG},
#endif				/* FLOATING_POINTS */
  {"HTML", fun_html, 1, 1, FN_REG},
  {"TAG", fun_tag, 1, INT_MAX, FN_REG},
  {"ENDTAG", fun_endtag, 1, 1, FN_REG},
  {"TAGWRAP", fun_tagwrap, 2, 3, FN_REG},
  {NULL, NULL, 0, 0}
};

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

  FUN *fp;
  char *ptrs[BUFFER_LEN / 2];
  char buff[BUFFER_LEN];
  char *bp;
  int nptrs = 0, i;
  fp = hash_firstentry(&htab_function);
  while (fp) {
    ptrs[nptrs++] = (char *) fp->name;
    fp = hash_nextentry(&htab_function);
  }
  do_gensort(ptrs, nptrs, 0);
  bp = buff;
  safe_str("Functions:", buff, &bp);
  for (i = 0; i < nptrs; i++) {
    safe_chr(' ', buff, &bp);
    safe_str(ptrs[i], buff, &bp);
  }
  *bp = '\0';

  notify(player, buff);
}

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


FUN *
func_hash_lookup(name)
    char *name;
{
  return (FUN *) hashfind(strupper(name), &htab_function);
}

void
func_hash_insert(name, func)
    char *name;
    FUN *func;
{
  hashadd(name, (void *) func, &htab_function);
}

void
init_func_hashtab()
{
  FUN *fp;

  hashinit(&htab_function, 256);
  for (fp = flist; fp->name; fp++)
    func_hash_insert((char *) fp->name, (FUN *) fp);
  local_functions();
}

void
function_add(name, fun, minargs, maxargs, ftype)
    char *name;
    void (*fun) ();
    int minargs;
    int maxargs;
    int ftype;
{
  FUN *fp;
  fp = (FUN *) mush_malloc(sizeof(FUN), "function");
  memset(fp, 0, sizeof(FUN));
  fp->name = name;
  fp->fun = fun;
  fp->minargs = minargs;
  fp->maxargs = maxargs;
  fp->ftype = ftype;
  func_hash_insert(name, fp);
}

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

char *
strip_braces(str)
    char const *str;
{
  /* this is a hack which just strips a level of braces. It malloc()s memory
   * which must be free()d later.
   */

  char *buff;
  char *bufc;

  buff = (char *) mush_malloc(BUFFER_LEN, "strip_braces.buff");
  bufc = buff;

  while (isspace(*str))		/* eat spaces at the beginning */
    str++;

  switch (*str) {
  case '{':
    str++;
    process_expression(buff, &bufc, &str, 0, 0, 0, PE_NOTHING, PT_BRACE, NULL);
    *bufc = '\0';
    return buff;
    break;			/* NOT REACHED */
  default:
    strcpy(buff, str);
    return buff;
  }
}

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

USERFN_ENTRY userfn_tab[MAX_GLOBAL_FNS];

static int userfn_count = 0;

/* ARGSUSED */
FUNCTION(fun_gfun)
{
  /* this function is a dummy. */
}

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

  char tbuf1[BUFFER_LEN];
  char *bp = tbuf1;
  dbref thing;
  FUN *fp;

  /* Player must be see_all */
  if (!See_All(player)) {
    notify(player, "Permission denied.");
    return;
  }
  /* 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, "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,
	     "Function Name                   Dbref #    Attrib");
      for (fp = (FUN *) hash_firstentry(&htab_function);
	   fp; fp = (FUN *) hash_nextentry(&htab_function)) {
	if (fp->ftype >= GLOBAL_OFFSET)
	  notify(player,
		 tprintf("%-32s %6d    %s", fp->name,
			 userfn_tab[GF_Index(fp->ftype)].thing,
			 userfn_tab[GF_Index(fp->ftype)].name));
      }
    } else {
      /* just print out the list of available functions */
      safe_str("User functions:", tbuf1, &bp);
      for (fp = (FUN *) hash_firstentry(&htab_function);
	   fp; fp = (FUN *) hash_nextentry(&htab_function)) {
	if (fp->ftype >= GLOBAL_OFFSET) {
	  safe_chr(' ', tbuf1, &bp);
	  safe_str(fp->name, tbuf1, &bp);
	}
      }
      *bp = '\0';
      notify(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, "Permission denied.");
    return;
  }
  if (userfn_count >= MAX_GLOBAL_FNS) {
    notify(player, "Function table full.");
    return;
  }
  if (!argv[1] || !*argv[1] || !argv[2] || !*argv[2]) {
    notify(player, "You must specify an object and an attribute.");
    return;
  }
  /* make sure the function name length is okay */
  if (strlen(name) >= SBUF_LEN) {
    notify(player, "Function name too long.");
    return;
  }
  /* find the object. For some measure of security, the player must
   * be able to examine it.
   */
  if ((thing = noisy_match_result(player, argv[1], NOTYPE, MAT_EVERYTHING)) == NOTHING)
    return;
  if (!Can_Examine(player, thing)) {
    notify(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).
   * We do, however, need to make sure that the user isn't trying
   * to replace a built-in function.
   */

  fp = func_hash_lookup(upcasestr(name));
  if (!fp) {
    /* a completely new entry. First, insert it into general hash table */
    fp = (FUN *) mush_malloc(sizeof(FUN), "func_hash.FUN");
    fp->name = (char *) strdup(name);
#ifdef MEM_CHECK
    add_check("func_hash.name");
#endif
    fp->fun = fun_gfun;
    fp->minargs = 0;
    fp->maxargs = 10;
    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]));
#ifdef MEM_CHECK
    add_check("userfn_tab.name");
#endif
    userfn_tab[userfn_count].fn = (char *) strdup(upcasestr(name));
#ifdef MEM_CHECK
    add_check("userfn_tab.fn");
#endif

    userfn_count++;

    notify(player, "Function added.");
    return;
  } else {

    /* we are modifying an old entry */
    if ((fp->ftype == FN_REG) || (fp->ftype == FN_NOPARSE)) {
      notify(player, "You cannot change a built-in function.");
      return;
    }
    userfn_tab[GF_Index(fp->ftype)].thing = thing;
    mush_free((Malloc_t) userfn_tab[GF_Index(fp->ftype)].name,
	      "userfn_tab.name");
    userfn_tab[GF_Index(fp->ftype)].name =
      (char *) strdup(upcasestr(argv[2]));
#ifdef MEM_CHECK
    add_check("userfn_tab.name");
#endif
    notify(player, "Function updated.");
  }
}

void
do_function_delete(player, name)
    dbref player;
    char *name;
{
  /* Deletes a user-defined function. 
   * For security, you must control the object the function uses
   * to delete the function.
   */
  int table_index;
  int i;
  FUN *fp;

  if (!Global_Funcs(player)) {
    notify(player, "Permission denied.");
    return;
  }
  fp = func_hash_lookup(upcasestr(name));
  if (!fp) {
    notify(player, "No such function.");
    return;
  }
  if ((fp->ftype == FN_REG) || (fp->ftype == FN_NOPARSE)) {
    notify(player, "You cannot delete a built-in function.");
    return;
  }
  table_index = GF_Index(fp->ftype);
  if (!controls(player, userfn_tab[table_index].thing)) {
    notify(player, "You can't delete that @function.");
    return;
  }
  /* Remove it from the hash table */
  hashdelete((char *) fp->name, &htab_function);
  /* Free its memory */
  mush_free((Malloc_t) fp->name, "func_hash.name");
  mush_free((Malloc_t) fp, "func_hash.FUN");
  /* Fix up the user function table. Expensive, but how often will
   * we need to delete an @function anyway?
   */
  mush_free((Malloc_t) userfn_tab[table_index].name, "userfn_tab.name");
  mush_free((Malloc_t) userfn_tab[table_index].fn, "userfn_tab.fn");
  userfn_count--;
  for (i = table_index; i < userfn_count; i++) {
    fp = func_hash_lookup(userfn_tab[i + 1].fn);
    fp->ftype = i + GLOBAL_OFFSET;
    userfn_tab[i].thing = userfn_tab[i + 1].thing;
    userfn_tab[i].name = (char *) strdup(userfn_tab[i + 1].name);
#ifdef MEM_CHECK
    add_check("userfn_tab.name");
#endif
    mush_free((Malloc_t) userfn_tab[i + 1].name, "userfn_tab.name");
    userfn_tab[i].fn = (char *) strdup(userfn_tab[i + 1].fn);
#ifdef MEM_CHECK
    add_check("userfn_tab.fn");
#endif
    mush_free((Malloc_t) userfn_tab[i + 1].fn, "userfn_tab.fn");
  }
  notify(player, "Function deleted.");
}