mux2.0/
mux2.0/game/bin/
mux2.0/game/data/
mux2.0/src/tools/
// functions.cpp - MUX function handlers 
//
// $Id: functions.cpp,v 1.28 2000/09/20 22:22:47 sdennis Exp $
//

#include "copyright.h"
#include "autoconf.h"
#include "config.h"
#include "externs.h"

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

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

UFUN *ufun_head;

extern NAMETAB indiv_attraccess_nametab[];

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

/*
 * Function definitions from funceval.c 
 */

#define XFUNCTION(x) extern void x(char *buff, char **bufc, dbref player, dbref cause, char *fargs[], int nfargs, char *cargs[], int ncargs)

XFUNCTION(fun_cwho);
XFUNCTION(fun_beep);
XFUNCTION(fun_ansi);
XFUNCTION(fun_zone);
#ifdef SIDE_EFFECT_FUNCTIONS
XFUNCTION(fun_link);
XFUNCTION(fun_tel);
XFUNCTION(fun_pemit);
XFUNCTION(fun_create);
XFUNCTION(fun_set);
#endif
XFUNCTION(fun_last);
XFUNCTION(fun_matchall);
XFUNCTION(fun_ports);
XFUNCTION(fun_mix);
XFUNCTION(fun_foreach);
XFUNCTION(fun_munge);
XFUNCTION(fun_visible);
XFUNCTION(fun_elements);
XFUNCTION(fun_grab);
XFUNCTION(fun_scramble);
XFUNCTION(fun_shuffle);
XFUNCTION(fun_sortby);
XFUNCTION(fun_default);
XFUNCTION(fun_edefault);
XFUNCTION(fun_udefault);
XFUNCTION(fun_findable);
XFUNCTION(fun_isword);
XFUNCTION(fun_hasattr);
XFUNCTION(fun_hasattrp);
XFUNCTION(fun_zwho);
XFUNCTION(fun_inzone);
XFUNCTION(fun_children);
XFUNCTION(fun_encrypt);
XFUNCTION(fun_decrypt);
XFUNCTION(fun_objeval);
XFUNCTION(fun_squish);
XFUNCTION(fun_stripansi);
XFUNCTION(fun_zfun);
XFUNCTION(fun_columns);
XFUNCTION(fun_playmem);
XFUNCTION(fun_objmem);
XFUNCTION(fun_orflags);
XFUNCTION(fun_andflags);
XFUNCTION(fun_strtrunc);
XFUNCTION(fun_ifelse);
XFUNCTION(fun_inc);
XFUNCTION(fun_dec);
XFUNCTION(fun_mail);
XFUNCTION(fun_mailfrom);
XFUNCTION(fun_die);
XFUNCTION(fun_lit);
XFUNCTION(fun_shl);
XFUNCTION(fun_shr);
XFUNCTION(fun_band);
XFUNCTION(fun_bor);
XFUNCTION(fun_bnand);
XFUNCTION(fun_vadd);
XFUNCTION(fun_vsub);
XFUNCTION(fun_vmul);
XFUNCTION(fun_vmag);
XFUNCTION(fun_vunit);
XFUNCTION(fun_vdim);
XFUNCTION(fun_strcat);
XFUNCTION(fun_grep);
XFUNCTION(fun_grepi);
XFUNCTION(fun_art);
XFUNCTION(fun_alphamax);
XFUNCTION(fun_alphamin);
XFUNCTION(fun_valid);
XFUNCTION(fun_hastype);
XFUNCTION(fun_lparent);
XFUNCTION(fun_empty);
XFUNCTION(fun_push);
XFUNCTION(fun_peek);
XFUNCTION(fun_pop);
XFUNCTION(fun_items);
XFUNCTION(fun_lstack);
XFUNCTION(fun_regmatch);
XFUNCTION(fun_translate);
XFUNCTION(fun_doing);      // in netcommon.cpp
XFUNCTION(fun_poll);       // in netcommon.cpp
XFUNCTION(fun_motd);       // in netcommon.cpp
XFUNCTION(fun_channels);   // in comsys.cpp
XFUNCTION(fun_comtitle);   // in comsys.cpp
XFUNCTION(fun_comalias);   // in comsys.cpp
XFUNCTION(fun_iadd);
XFUNCTION(fun_isub);
XFUNCTION(fun_imul);
XFUNCTION(fun_digittime);
XFUNCTION(fun_singletime);
XFUNCTION(fun_exptime);
XFUNCTION(fun_writetime);
XFUNCTION(fun_cmds);
XFUNCTION(fun_startsecs);
XFUNCTION(fun_lflags);
XFUNCTION(fun_lattrcmds);
XFUNCTION(fun_lcmds);
XFUNCTION(fun_conntotal);
XFUNCTION(fun_connmax);
XFUNCTION(fun_connlast);
XFUNCTION(fun_connnum);
XFUNCTION(fun_connleft);
 
// Trim off leading and trailing spaces if the separator char is a
// space -- known length version.
//
char *trim_space_sep_LEN(char *str, int nStr, char sep, int *nTrim)
{
    if (sep != ' ')
    {
        *nTrim = nStr;
        return str;
    }

    // Advance over leading spaces.
    //
    char *pBegin = str;
    char *pEnd = str + nStr - 1;
    while (*pBegin == ' ')
    {
        pBegin++;
    }

    // Advance over trailing spaces.
    //
    for (; *pEnd == ' ' && pEnd > pBegin; pEnd--)
    {
        // Nothing
    }
    pEnd++;

    *pEnd = '\0';
    *nTrim = pEnd - pBegin;
    return pBegin;
}


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

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

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

// next_token: Point at start of next token in string -- known length
// version.
//
char *next_token_LEN(char *str, int *nStr, char sep)
{
    char *pBegin = str;
    while (*pBegin && (*pBegin != sep))
    {
        pBegin++;
    }
    if (!*pBegin)
    {
        *nStr = 0;
        return NULL;
    }
    pBegin++;
    if (sep == ' ')
    {
        while (*pBegin == ' ')
        {
            pBegin++;
        }
    }
    *nStr -= pBegin - str;
    return pBegin;
}

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

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

// split_token: Get next token from string as null-term string. String is
// destructively modified -- known length version.
//
char *split_token_LEN(char **sp, int *nStr, char sep, int *nToken)
{
    char *str, *save;

    save = str = *sp;
    if (!str)
    {
        *nStr = 0;
        *sp = NULL;
        *nToken = 0;
        return NULL;
    }

    // Advance over token
    //
    while (*str && (*str != sep))
    {
        str++;
    }
    *nToken = str - save;

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

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

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

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

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

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

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

    sort_type = NUMERIC_LIST;
    for (i = 0; i < nitems; i++)
    {
        switch (sort_type)
        {
        case NUMERIC_LIST:

            if (!is_number(ptrs[i]))
            {
                // If non-numeric, switch to alphanum sort. Exception: if this
                // is the first element and it is a good dbref, switch to a
                // dbref sort. We're a little looser than the normal 'good
                // dbref' rules, any number following # the #-sign is
                // accepted.  
                //
                if (i == 0)
                {
                    p = ptrs[i];
                    if (*p++ != NUMBER_TOKEN)
                    {
                        return ALPHANUM_LIST;
                    }
                    else if (is_integer(p, 0))
                    {
                        sort_type = DBREF_LIST;
                    }
                    else
                    {
                        return ALPHANUM_LIST;
                    }
                }
                else
                {
                    return ALPHANUM_LIST;
                }
            }
            else if (strchr(ptrs[i], '.'))
            {
                sort_type = FLOAT_LIST;
            }
            break;

        case FLOAT_LIST:

            if (!is_number(ptrs[i]))
            {
                sort_type = ALPHANUM_LIST;
                return ALPHANUM_LIST;
            }
            break;

        case DBREF_LIST:

            p = ptrs[i];
            if (*p++ != NUMBER_TOKEN)
            {
                return ALPHANUM_LIST;
            }
            if (!is_integer(p, 0))
            {
                return ALPHANUM_LIST;
            }
            break;

        default:

            return ALPHANUM_LIST;
        }
    }
    return sort_type;
}

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

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

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

void arr2list(char *arr[], int alen, char *list, char **bufc, char sep)
{
    int i;
    for (i = 0; i < alen-1; i++)
    {
        safe_str(arr[i], list, bufc);
        print_sep(sep, list, bufc);
    }
    if (alen)
    {
        safe_str(arr[i], list, bufc);
    }
}

static int dbnum(char *dbr)
{
    if (dbr[0] != '#' || dbr[1] == '\0')
    {
        return 0;
    }
    else
    {
        return Tiny_atol(dbr + 1);
    }
}

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

int nearby_or_control(dbref player, dbref thing)
{
    if (!Good_obj(player) || !Good_obj(thing))
        return 0;
    if (Controls(player, thing))
        return 1;
    if (!nearby(player, thing))
        return 0;
    return 1;
}

static char *strip_useless_zeroes(char *pBegin, char *pEnd)
{
    // Remove useless trailing 0's between pBegin and pEnd.
    //
    while (pBegin <= pEnd && pEnd[0] == '0')
    {
        pEnd--;
    }

    // Take care of dangling '.'
    //
    if (pBegin <= pEnd && pEnd[0] == '.')
    {
        pEnd--;
    }

    // Terminate string.
    //
    pEnd++;
    pEnd[0] = '\0';
    return pEnd;
}

#ifdef HAVE_IEEE_FP_FORMAT

char *TinyFPStrings[] = { "+Inf", "-Inf", "Ind", "NaN", "0", "0", "0", "0" };

#define TINY_FPGROUP_PASS  0x00 // Pass-through to printf
#define TINY_FPGROUP_ZERO  0x10 // Force to be zero.
#define TINY_FPGROUP_PINF  0x20 // "+Inf"
#define TINY_FPGROUP_NINF  0x30 // "-Inf"
#define TINY_FPGROUP_IND   0x40 // "Ind"
#define TINY_FPGROUP_NAN   0x50 // "NaN"
#define TINY_FPGROUP(x) ((x) & 0xF0)

// Tiny_fpclass returns an integer that is one of the following:
//
#define TINY_FPCLASS_PINF  (TINY_FPGROUP_PINF|0) // Positive infinity (+INF)
#define TINY_FPCLASS_NINF  (TINY_FPGROUP_NINF|1) // Negative infinity (-INF)
#define TINY_FPCLASS_QNAN  (TINY_FPGROUP_IND |2) // Quiet NAN (Indefinite)
#define TINY_FPCLASS_SNAN  (TINY_FPGROUP_NAN |3) // Signaling NAN
#define TINY_FPCLASS_ND    (TINY_FPGROUP_ZERO|4) // Negative denormalized
#define TINY_FPCLASS_NZ    (TINY_FPGROUP_ZERO|5) // Negative zero (-0)
#define TINY_FPCLASS_PZ    (TINY_FPGROUP_ZERO|6) // Positive zero (+0)
#define TINY_FPCLASS_PD    (TINY_FPGROUP_ZERO|7) // Positive denormalized
#define TINY_FPCLASS_PN    (TINY_FPGROUP_PASS|8) // Positive normalized non-zero
#define TINY_FPCLASS_NN    (TINY_FPGROUP_PASS|9) // Negative normalized non-zero
#define TINY_FPCLASS(x)    ((x) & 0x0F)

#ifdef WIN32
#define IEEE_MASK_SIGN     0x8000000000000000ui64
#define IEEE_MASK_EXPONENT 0x7FF0000000000000i64
#define IEEE_MASK_MANTISSA 0x000FFFFFFFFFFFFFi64
#define IEEE_MASK_QNAN     0x0008000000000000i64
#else
#define IEEE_MASK_SIGN     0x8000000000000000ull
#define IEEE_MASK_EXPONENT 0x7FF0000000000000ull
#define IEEE_MASK_MANTISSA 0x000FFFFFFFFFFFFFull
#define IEEE_MASK_QNAN     0x0008000000000000ull
#endif

static int Tiny_fpclass(double result)
{
    UINT64 i64;

    *((double *)&i64) = result;
 
    if ((i64 & IEEE_MASK_EXPONENT) == 0)
    {
        if (i64 & IEEE_MASK_MANTISSA)
        {
            if (i64 & IEEE_MASK_SIGN) return TINY_FPCLASS_ND;
            else                      return TINY_FPCLASS_PD;
        }
        else
        {
            if (i64 & IEEE_MASK_SIGN) return TINY_FPCLASS_NZ;
            else                      return TINY_FPCLASS_PZ;
        }
    }
    else if ((i64 & IEEE_MASK_EXPONENT) == IEEE_MASK_EXPONENT)
    {
        if (i64 & IEEE_MASK_MANTISSA)
        {
            if (i64 & IEEE_MASK_QNAN) return TINY_FPCLASS_QNAN;
            else                      return TINY_FPCLASS_SNAN;
        }
        else
        {
            if (i64 & IEEE_MASK_SIGN) return TINY_FPCLASS_NINF;
            else                      return TINY_FPCLASS_PINF;
        }
    }
    else
    {
        if (i64 & IEEE_MASK_SIGN)     return TINY_FPCLASS_NN;
        else                          return TINY_FPCLASS_PN;
    }
}
#endif // HAVE_IEEE_FP_FORMAT

/*
 * ---------------------------------------------------------------------------
 * * fval: copy the floating point value into a buffer and make it presentable
 */
static void fval(char *buff, char **bufc, double result)
{
    char *pBegin = *bufc;

    // Get double val into buffer.
    //
#ifdef HAVE_IEEE_FP_FORMAT
    int fpc = Tiny_fpclass(result);
    if (TINY_FPGROUP(fpc) == TINY_FPGROUP_PASS)
    {
#endif // HAVE_IEEE_FP_FORMAT
        safe_tprintf_str(buff, bufc, "%.6f", result);

        char *pEnd = (*bufc) - 1;

        // Remove useless trailing 0's between pBegin and pEnd.
        //
        pEnd = strip_useless_zeroes(pBegin, pEnd);

        // Update bufc pointer.
        //
        *bufc = pEnd;
#ifdef HAVE_IEEE_FP_FORMAT
    }
    else
    {
        safe_str(TinyFPStrings[TINY_FPCLASS(fpc)], buff, bufc);
    }
#endif // HAVE_IEEE_FP_FORMAT
}

static void fval_buf(char *buff, double result)
{
    char *pBegin = buff;

    // Get double val into buffer.
    //
#ifdef HAVE_IEEE_FP_FORMAT
    int fpc = Tiny_fpclass(result);
    if (TINY_FPGROUP(fpc) == TINY_FPGROUP_PASS)
    {
#endif // HAVE_IEEE_FP_FORMAT
        sprintf(buff, "%.6f", result);

        char *pEnd = buff + strlen(buff) - 1;

        // Remove useless trailing 0's between pBegin and pEnd.
        //
        strip_useless_zeroes(pBegin, pEnd);
#ifdef HAVE_IEEE_FP_FORMAT
    }
    else
    {
        strcpy(buff, TinyFPStrings[TINY_FPCLASS(fpc)]);
    }
#endif // HAVE_IEEE_FP_FORMAT
}

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

int fn_range_check(const char *fname, int nfargs, int minargs, int maxargs, char *result, char **bufc)
{
    if ((nfargs >= minargs) && (nfargs <= maxargs))
        return 1;
    
    if (maxargs == (minargs + 1))
        safe_tprintf_str(result, bufc, "#-1 FUNCTION (%s) EXPECTS %d OR %d ARGUMENTS", fname, minargs, maxargs);
    else
        safe_tprintf_str(result, bufc, "#-1 FUNCTION (%s) EXPECTS BETWEEN %d AND %d ARGUMENTS", fname, minargs, maxargs);
    return 0;
}

/*
 * ---------------------------------------------------------------------------
 * * delim_check: obtain delimiter
 */
int delim_check
(
    char *fargs[], int nfargs, int sep_arg, char *sep, char *buff,
    char **bufc, int eval, dbref player, dbref cause, char *cargs[],
    int ncargs, int allow_special
)
{
    int bSuccess = 1;
    if (nfargs >= sep_arg)
    {
        // First, we decide whether to evalute fargs[sep_arg-1] or not.
        //
        char *tstr = fargs[sep_arg-1];
        int tlen = strlen(tstr);
        if (tlen <= 1)
        {
            eval = 0;
        }
        if (eval)
        {
            char *str = tstr;
            char *bp = tstr = alloc_lbuf("delim_check");
            TinyExec(tstr, &bp, 0, player, cause, EV_EVAL | EV_FCHECK, &str, cargs, ncargs);
            *bp = '\0';
            tlen = bp - tstr;
        }

        // Regardless of evaulation or no, tstr contains the we need to
        // look at, and tlen is the length of this string.
        //
        if (tlen == 1)
        {
            *sep = tstr[0];
        }
        else if (tlen == 0)
        {
            *sep = ' ';
        }
        else
        {
            // We might have an issue.
            //
            if (tlen == 2 && allow_special)
            {
                if (memcmp(tstr, (char *)NULL_DELIM_VAR, 2) == 0)
                {
                    *sep = '\0';
                }
                else if (memcmp(tstr, (char *)"\r\n", 2) == 0)
                {
                    *sep = '\r';
                }
                else
                {
                    bSuccess = 0;
                }
            }
            else
            {
                bSuccess = 0;
            }
        }

        // Clean up the evaluation buffer.
        //
        if (eval)
        {
            free_lbuf(tstr);
        }
        if (!bSuccess)
        {
            safe_str("#-1 SEPARATOR MUST BE ONE CHARACTER", buff, bufc);
            return 0;
        }
    }
    else
    {
        *sep = ' ';
    }
    return 1;
}

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

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

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

FUNCTION(fun_words)
{
    char sep;

    if (nfargs == 0)
    {
        safe_chr('0', buff, bufc);
        return;
    }
    varargs_preamble("WORDS", 2);
    safe_ltoa(countwords(fargs[0], sep), buff, bufc, LBUF_SIZE-1);
}

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

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

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

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

FUNCTION(fun_rand)
{
    int num = Tiny_atol(fargs[0]);
    if (num < 1)
    {
        safe_chr('0', buff, bufc);
    }
    else
    {
        safe_ltoa(RandomLong(0,num-1), buff, bufc, LBUF_SIZE-1);
    }
}

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

FUNCTION(fun_abs)
{
    double num;

    num = safe_atof(fargs[0]);
    if (num == 0.0)
    {
        safe_chr('0', buff, bufc);
    }
    else if (num < 0.0)
    {
        fval(buff, bufc, -num);
    }
    else
    {
        fval(buff, bufc, num);
    }
}

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

FUNCTION(fun_sign)
{
    double num;

    num = safe_atof(fargs[0]);
    if (num < 0)
    {
        safe_str("-1", buff, bufc);
    }
    else
    {
        safe_chr((num > 0) ? '1' : '0', buff, bufc);
    }
}

// ---------------------------------------------------------------------------
// fun_time:
//
// With no arguments, it returns local time in the 'Ddd Mmm DD HH:MM:SS YYYY'
// format.
//
// If an argument is provided, "utc" gives a UTC time string, and "local"
// gives the local time string.
//
FUNCTION(fun_time)
{
    CLinearTimeAbsolute ltaNow;
    if (!fn_range_check("TIME", nfargs, 0, 1, buff, bufc))
    {
        return;
    }
    if (nfargs == 0 || _stricmp("utc", fargs[0]) != 0)
    {
        ltaNow.GetLocal();
    }
    else
    {
        ltaNow.GetUTC();
    }
    char *temp = ltaNow.ReturnDateString();
    safe_str(temp, buff, bufc);
}

// ---------------------------------------------------------------------------
// fun_secs.
//
// With no arguments, it returns seconds since Jan 01 00:00:00 1970 UTC not
// counting leap seconds.
//
// If an argument is provided, "utc" gives UTC seconds, and "local" gives
// an integer which corresponds to a local time string. It is not useful
// as a count, but it can be given to convsecs(secs(),raw) to get the
// corresponding time string.
//
FUNCTION(fun_secs)
{
    CLinearTimeAbsolute ltaNow;
    if (!fn_range_check("SECS", nfargs, 0, 1, buff, bufc))
    {
        return;
    }
    if (nfargs == 0 || _stricmp("local", fargs[0]) != 0)
    {
        ltaNow.GetUTC();
    }
    else
    {
        ltaNow.GetLocal();
    }
    safe_str(ltaNow.ReturnSecondsString(), buff, bufc);
}

// ---------------------------------------------------------------------------
// fun_convsecs.
//
// With one arguments, it converts seconds from Jan 01 00:00:00 1970 UTC to a
// local time string in the 'Ddd Mmm DD HH:MM:SS YYYY' format.
//
// If a second argument is given, it is the <zonename>:
//
//   local - indicates that a conversion for timezone/DST of the server should
//           be applied (default if no second argument is given).
//
//   utc   - indicates that no timezone/DST conversions should be applied.
//           This is useful to give a unique one-to-one mapping between an
//           integer and it's corresponding text-string.
//
FUNCTION(fun_convsecs)
{
    CLinearTimeAbsolute lta;
    if (!fn_range_check("CONVSECS", nfargs, 1, 2, buff, bufc))
    {
        return;
    }
    lta.SetSecondsString(fargs[0]);
    if (nfargs == 1 || _stricmp("utc", fargs[1]) != 0)
    {
        lta.UTC2Local();
    }
    char *temp = lta.ReturnDateString();
    safe_str(temp, buff, bufc);
}

// ---------------------------------------------------------------------------
// fun_convtime.
//
// With one argument, it converts a local time string in the format
//'[Ddd] Mmm DD HH:MM:SS YYYY' to a count of seconds from Jan 01 00:00:00 1970
// UTC.
//
// If a second argument is given, it is the <zonename>:
//
//   local - indicates that the given time string is for the local timezone
//           local DST adjustments (default if no second argument is given).
//
//   utc   - indicates that no timezone/DST conversions should be applied.
//           This is useful to give a unique one-to-one mapping between an
//           integer and it's corresponding text-string.
//
// This function returns -1 if there was a problem parsing the time string.
//
FUNCTION(fun_convtime)
{
    CLinearTimeAbsolute lta;
    if (!fn_range_check("CONVTIME", nfargs, 1, 2, buff, bufc))
    {
        return;
    }
    if (lta.SetString(fargs[0]))
    {
        if (nfargs == 1 || _stricmp("utc", fargs[1]) != 0)
        {
            lta.Local2UTC();
        }
        safe_str(Tiny_i64toa_t(lta.ReturnSeconds()), buff, bufc);
    }
    else
    {
        safe_str("-1", buff, bufc);
    }
}

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

FUNCTION(fun_starttime)
{
    char *temp = mudstate.start_time.ReturnDateString();
    safe_str(temp, buff, bufc);
}

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

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

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

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

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

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

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

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

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

    if (!parse_attrib(player, fargs[0], &thing, &attrib))
    {
        safe_str("#-1 NO MATCH", buff, bufc);
        return;
    }
    if (attrib == NOTHING)
    {
        return;
    }
    free_buffer = 1;

    // We need the attr's flags for this:
    //
    attr = atr_num(attrib);
    if (!attr)
    {
        return;
    }
    int nLen = 0;
    if (attr->flags & AF_IS_LOCK)
    {
        atr_gotten = atr_get_LEN(thing, attrib, &aowner, &aflags, &nLen);
        if (Read_attr(player, thing, attr, aowner, aflags))
        {
            pBoolExp = parse_boolexp(player, atr_gotten, 1);
            free_lbuf(atr_gotten);
            atr_gotten = unparse_boolexp(player, pBoolExp);
            free_boolexp(pBoolExp);
        }
        else
        {
            free_lbuf(atr_gotten);
            atr_gotten = (char *)"#-1 PERMISSION DENIED";
        }
        free_buffer = 0;
    }
    else
    {
        atr_gotten = atr_pget_LEN(thing, attrib, &aowner, &aflags, &nLen);
    }

    // Perform access checks.  c_r_p fills buff with an error message
    // if needed.
    //
    if (check_read_perms(player, thing, attr, aowner, aflags, buff, bufc))
    {
        if (free_buffer)
        {
            if (nLen)
            {
                safe_copy_buf(atr_gotten, nLen, buff, bufc, LBUF_SIZE-1);
            }
        }
        else
        {
            safe_str(atr_gotten, buff, bufc);
        }
    }
    if (free_buffer)
    {
        free_lbuf(atr_gotten);
    }
}

FUNCTION(fun_xget)
{
    dbref thing, aowner;
    int attrib, aflags;
    ATTR *attr;
    char *atr_gotten;
    struct boolexp *pBoolExp;

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

    if (!parse_attrib(player, tprintf("%s/%s", fargs[0], fargs[1]), &thing, &attrib))
    {
        safe_str("#-1 NO MATCH", buff, bufc);
        return;
    }
    if (attrib == NOTHING)
    {
        return;
    }
    // We need the attr's flags for this:
    attr = atr_num(attrib);
    if (!attr)
    {
        return;
    }
    int free_buffer = 1;
    int nLen = 0;
    if (attr->flags & AF_IS_LOCK)
    {
        atr_gotten = atr_get_LEN(thing, attrib, &aowner, &aflags, &nLen);
        if (Read_attr(player, thing, attr, aowner, aflags))
        {
            pBoolExp = parse_boolexp(player, atr_gotten, 1);
            free_lbuf(atr_gotten);
            atr_gotten = unparse_boolexp(player, pBoolExp);
            free_boolexp(pBoolExp);
        }
        else
        {
            free_lbuf(atr_gotten);
            atr_gotten = (char *)"#-1 PERMISSION DENIED";
        }
        free_buffer = 0;
    }
    else
    {
        atr_gotten = atr_pget_LEN(thing, attrib, &aowner, &aflags, &nLen);
    }

    // Perform access checks.  c_r_p fills buff with an error message
    // if needed.
    //
    if (check_read_perms(player, thing, attr, aowner, aflags, buff, bufc))
    {
        if (free_buffer)
        {
            safe_copy_buf(atr_gotten, nLen, buff, bufc, LBUF_SIZE-1);
        }
        else
        {
            safe_str(atr_gotten, buff, bufc);
        }
    }
    if (free_buffer)
    {
        free_lbuf(atr_gotten);
    }
}

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

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

FUNCTION(fun_subeval)
{
    char *str;
    
    if (nfargs != 1) {
        safe_str("#-1 FUNCTION (EVALNOCOMP) EXPECTS 1 OR 2 ARGUMENTS", buff, bufc);
        return;
    }
    
    str = fargs[0];
    TinyExec(buff, bufc, 0, player, cause, EV_EVAL|EV_NO_LOCATION|EV_NOFCHECK|EV_FIGNORE|EV_NO_COMPRESS, &str, (char **)NULL, 0);
}   

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

    if ((nfargs != 1) && (nfargs != 2)) {
        safe_str("#-1 FUNCTION (EVAL) EXPECTS 1 OR 2 ARGUMENTS", buff, bufc);
        return;
    }
    if (nfargs == 1) {
        str = fargs[0];
        TinyExec(buff, bufc, 0, player, cause, EV_EVAL, &str, (char **)NULL, 0);
        return;
    }
    if (!*fargs[0] || !*fargs[1])
        return;

    if (!parse_attrib(player, tprintf("%s/%s", fargs[0], fargs[1]),
              &thing, &attrib)) {
        safe_str("#-1 NO MATCH", buff, bufc);
        return;
    }
    if (attrib == NOTHING) {
        return;
    }
    free_buffer = 1;
    eval_it = 1;
    attr = atr_num(attrib);
    if (!attr) {
        return;
    }
    if (attr->flags & AF_IS_LOCK) {
        atr_gotten = atr_get(thing, attrib, &aowner, &aflags);
        if (Read_attr(player, thing, attr, aowner, aflags)) {
            pBoolExp = parse_boolexp(player, atr_gotten, 1);
            free_lbuf(atr_gotten);
            atr_gotten = unparse_boolexp(player, pBoolExp);
            free_boolexp(pBoolExp);
        } else {
            free_lbuf(atr_gotten);
            atr_gotten = (char *)"#-1 PERMISSION DENIED";
        }
        free_buffer = 0;
        eval_it = 0;
    } else {
        atr_gotten = atr_pget(thing, attrib, &aowner, &aflags);
    }
    if (!check_read_perms(player, thing, attr, aowner, aflags, buff, bufc)) {
        if (free_buffer)
            free_lbuf(atr_gotten);
        return;
    }
    if (eval_it) {
        str = atr_gotten;
        TinyExec(buff, bufc, 0, thing, player, EV_FIGNORE | EV_EVAL, &str, (char **)NULL, 0);
    } else {
        safe_str(atr_gotten, buff, bufc);
    }
    if (free_buffer)
        free_lbuf(atr_gotten);
    return;
}

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

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

    // We need at least one argument.
    //
    if (nfargs < 1) 
    {
        safe_str("#-1 TOO FEW ARGUMENTS", buff, bufc);
        return;
    }

    // Two possibilities for the first arg: <obj>/<attr> and <attr>.
    //
    if (parse_attrib(player, fargs[0], &thing, &anum))
    {
        if ((anum == NOTHING) || (!Good_obj(thing)))
            ap = NULL;
        else
            ap = atr_num(anum);
    } else {
        thing = player;
        ap = atr_str(fargs[0]);
    }

    /*
     * Make sure we got a good attribute 
     */

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

    atext = atr_pget(thing, ap->number, &aowner, &aflags);
    if (!atext) {
        free_lbuf(atext);
        return;
    }
    if (!*atext) {
        free_lbuf(atext);
        return;
    }
    if (!check_read_perms(player, thing, ap, aowner, aflags, buff, bufc)) {
        free_lbuf(atext);
        return;
    }
    /*
     * If we're evaluating locally, preserve the global registers. 
     */

    if (is_local)
    {
        save_global_regs("fun_ulocal_save", preserve, preserve_len);
    }

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

    str = atext;
    TinyExec(buff, bufc, 0, thing, cause, EV_FCHECK | EV_EVAL, &str, &(fargs[1]), nfargs - 1);
    free_lbuf(atext);

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

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

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

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

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

FUNCTION(fun_parent)
{
    dbref it;

    it = match_thing(player, fargs[0]);
    if (Good_obj(it) && (Examinable(player, it) || (it == cause))) {
        safe_tprintf_str(buff, bufc, "#%d", Parent(it));
    } else {
        safe_str("#-1", buff, bufc);
    }
    return;
}

/*
 * ---------------------------------------------------------------------------
 * * fun_parse: Make list from evaluating arg3 with each member of arg2.
 * * arg1 specifies a delimiter character to use in the parsing of arg2.
 * * NOTE: This function expects that its arguments have not been evaluated.
 */

FUNCTION(fun_parse)
{
    char *curr, *objstring, *buff2, *buff3, *cp, sep;
    char *dp, *str;
    int first, number = 0;

    evarargs_preamble("PARSE", 3);
    cp = curr = dp = alloc_lbuf("fun_parse");
    str = fargs[0];
    TinyExec(curr, &dp, 0, player, cause, EV_STRIP_CURLY | EV_FCHECK | EV_EVAL, &str, cargs, ncargs);
    *dp = '\0';
    cp = trim_space_sep(cp, sep);
    if (!*cp) {
        free_lbuf(curr);
        return;
    }
    first = 1;
    while (cp)
    {
        if (!first)
            safe_chr(' ', buff, bufc);
        first = 0;
        number++;
        objstring = split_token(&cp, sep);
        buff2 = replace_string(BOUND_VAR, objstring, fargs[1]);
        buff3 = replace_string(LISTPLACE_VAR, Tiny_ltoa_t(number), buff2);
        str = buff3;
        TinyExec(buff, bufc, 0, player, cause, EV_STRIP_CURLY | EV_FCHECK | EV_EVAL, &str, cargs, ncargs);
        free_lbuf(buff2);
        free_lbuf(buff3);
    }
    free_lbuf(curr);
}

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

FUNCTION(fun_mid)
{
    // Initial checks for iPosition0 [0,LBUF_SIZE), nLength [0,LBUF_SIZE),
    // and iPosition1 [0,LBUF_SIZE).
    //
    int iPosition0 = Tiny_atol(fargs[1]);
    int nLength = Tiny_atol(fargs[2]);
    int iPosition1 = iPosition0 + nLength;

    if (  iPosition0 < 0 || iPosition0 > LBUF_SIZE-1
       || nLength    < 0 || nLength    > LBUF_SIZE-1
       || iPosition1 > LBUF_SIZE-1)
    {
        safe_str("#-1 OUT OF RANGE", buff, bufc);
        return;
    }

    // At this point, iPosition0, nLength, and iPosition1 are reasonable
    // numbers which may -still- not refer to valid data in the string.
    //
    struct ANSI_Context ac;
    ANSI_String_Init(&ac, fargs[0], 0);
    int nDone;
    ANSI_String_Skip(&ac, iPosition0, &nDone);
    if (nDone < iPosition0)
    {
        return;
    }
    int nBufferAvailable = LBUF_SIZE - (*bufc - buff) - 1;
    int nSize = ANSI_String_Copy(&ac, nBufferAvailable, *bufc, nLength, &nDone);
    *bufc += nSize;
}

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

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

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

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

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


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

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

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

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

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

    tbuf = fargs[0];
    if (Tiny_IsAlpha[(unsigned char)tbuf[0]] && tbuf[1])
    {
        // Fetch an attribute from me. First see if it exists,
        // returning a null string if it does not.
        //
        ap = atr_str(fargs[0]);
        if (!ap)
        {
            return;
        }

        // If we can access it, return it, otherwise return a null
        // string.
        //
        int nLen;
        tbuf = atr_pget_LEN(player, ap->number, &aowner, &aflags, &nLen);
        if (See_attr(player, player, ap, aowner, aflags))
        {
            safe_copy_buf(tbuf, nLen, buff, bufc, LBUF_SIZE-1);
        }
        free_lbuf(tbuf);
        return;
    }

    // Not an attribute, process as %<arg>
    //
    sbuf = alloc_sbuf("fun_v");
    sbufc = sbuf;
    safe_sb_chr('%', sbuf, &sbufc);
    safe_sb_str(fargs[0], sbuf, &sbufc);
    *sbufc = '\0';
    str = sbuf;
    TinyExec(buff, bufc, 0, player, cause, EV_EVAL|EV_FIGNORE, &str, cargs, ncargs);
    free_sbuf(sbuf);
}

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

FUNCTION(fun_s)
{
    char *str;

    str = fargs[0];
    TinyExec(buff, bufc, 0, player, cause, EV_FIGNORE | EV_EVAL, &str, cargs, ncargs);
}

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

FUNCTION(fun_con)
{
    dbref it;

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

    if ((it != NOTHING) &&
        (Has_contents(it)) &&
        (Examinable(player, it) ||
         (where_is(player) == it) ||
         (it == cause))) {
        safe_tprintf_str(buff, bufc, "#%d", Contents(it));
        return;
    }
    safe_str("#-1", buff, bufc);
    return;
}

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

FUNCTION(fun_exit)
{
    dbref it, exit;
    int key;

    it = match_thing(player, fargs[0]);
    if (Good_obj(it) && Has_exits(it) && Good_obj(Exits(it))) {
        key = 0;
        if (Examinable(player, it))
            key |= VE_LOC_XAM;
        if (Dark(it))
            key |= VE_LOC_DARK;
        DOLIST(exit, Exits(it)) {
            if (exit_visible(exit, player, key)) {
                safe_tprintf_str(buff, bufc, "#%d", exit);
                return;
            }
        }
    }
    safe_str("#-1", buff, bufc);
    return;
}

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

FUNCTION(fun_next)
{
    dbref it, loc, exit, ex_here;
    int key;

    it = match_thing(player, fargs[0]);
    if (Good_obj(it) && Has_siblings(it)) {
        loc = where_is(it);
        ex_here = Good_obj(loc) ? Examinable(player, loc) : 0;
        if (ex_here || (loc == player) || (loc == where_is(player))) {
            if (!isExit(it)) {
                safe_tprintf_str(buff, bufc, "#%d", Next(it));
                return;
            } else {
                key = 0;
                if (ex_here)
                    key |= VE_LOC_XAM;
                if (Dark(loc))
                    key |= VE_LOC_DARK;
                DOLIST(exit, it) {
                    if ((exit != it) &&
                      exit_visible(exit, player, key)) {
                        safe_tprintf_str(buff, bufc, "#%d", exit);
                        return;
                    }
                }
            }
        }
    }
    safe_str("#-1", buff, bufc);
    return;
}

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

FUNCTION(fun_loc)
{
    dbref it;

    it = match_thing(player, fargs[0]);
    if (locatable(player, it, cause))
        safe_tprintf_str(buff, bufc, "#%d", Location(it));
    else
        safe_str("#-1", buff, bufc);
    return;
}

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

FUNCTION(fun_where)
{
    dbref it;

    it = match_thing(player, fargs[0]);
    if (locatable(player, it, cause))
        safe_tprintf_str(buff, bufc, "#%d", where_is(it));
    else
        safe_str("#-1", buff, bufc);
    return;
}

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

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

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

    it = match_thing(player, fargs[0]);
    if (locatable(player, it, cause)) {
        for (i = 0; i < levels; i++) {
            if (!Good_obj(it) || !Has_location(it))
                break;
            it = Location(it);
        }
        safe_tprintf_str(buff, bufc, "#%d", it);
        return;
    }
    safe_str("#-1", buff, bufc);
}

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

FUNCTION(fun_room)
{
    dbref it;
    int count;

    it = match_thing(player, fargs[0]);
    if (locatable(player, it, cause)) {
        for (count = mudconf.ntfy_nest_lim; count > 0; count--) {
            it = Location(it);
            if (!Good_obj(it))
                break;
            if (isRoom(it)) {
                safe_tprintf_str(buff, bufc, "#%d", it);
                return;
            }
        }
        safe_str("#-1", buff, bufc);
    } else if (isRoom(it)) {
        safe_tprintf_str(buff, bufc, "#%d", it);
    } else {
        safe_str("#-1", buff, bufc);
    }
    return;
}

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

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

    if (parse_attrib(player, fargs[0], &it, &atr)) {
        if (atr == NOTHING) {
            it = NOTHING;
        } else {
            atr_pget_info(it, atr, &aowner, &aflags);
            it = aowner;
        }
    } else {
        it = match_thing(player, fargs[0]);
        if (it != NOTHING)
            it = Owner(it);
    }
    safe_tprintf_str(buff, bufc, "#%d", it);
}

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

FUNCTION(fun_controls)
{
    dbref x, y;

    x = match_thing(player, fargs[0]);
    if (x == NOTHING) {
        safe_tprintf_str(buff, bufc, "%s", "#-1 ARG1 NOT FOUND");
        return;
    }
    y = match_thing(player, fargs[1]);
    if (y == NOTHING) {
        safe_tprintf_str(buff, bufc, "%s", "#-1 ARG2 NOT FOUND");
        return;
    }
    safe_ltoa(Controls(x, y), buff, bufc, LBUF_SIZE-1);
}

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

FUNCTION(fun_fullname)
{
    dbref it;

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

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

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

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

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

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

    varargs_preamble("MATCH", 3);

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

    wcount = 1;
    s = trim_space_sep(fargs[0], sep);
    do {
        r = split_token(&s, sep);
        if (quick_wild(fargs[1], r))
        {
            safe_ltoa(wcount, buff, bufc, LBUF_SIZE-1);
            return;
        }
        wcount++;
    } while (s);
    safe_chr('0', buff, bufc);
}

FUNCTION(fun_strmatch)
{
    // Check if we match the whole string.  If so, return 1.
    //
    int cc = quick_wild(fargs[1], fargs[0]);
    safe_chr(cc ? '1' : '0', buff, bufc);
}

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

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

    varargs_preamble("EXTRACT", 4);

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

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

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

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

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

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

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

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

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

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

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

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

    s = fargs[0];
    c = *fargs[1];
    start = Tiny_atol(fargs[2]);
    end = Tiny_atol(fargs[3]);

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

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

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

    /*
     * skip over just spaces 
     */

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

    /*
     * figure out where to end the string 
     */

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

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

    safe_str(s, buff, bufc);
}


FUNCTION(fun_cat)
{
    int i;

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

FUNCTION(fun_version)
{
    safe_str(mudstate.version, buff, bufc);
}
FUNCTION(fun_strlen)
{
    safe_ltoa(strlen(strip_ansi(fargs[0])), buff, bufc, LBUF_SIZE-1);
}

FUNCTION(fun_num)
{
    safe_tprintf_str(buff, bufc, "#%d", match_thing(player, fargs[0]));
}

FUNCTION(fun_pmatch)
{
    if (*fargs[0] == '#')
    {
        safe_tprintf_str(buff, bufc, "#%d", match_thing(player, fargs[0]));
        return;
    }
    dbref thing = lookup_player(player, fargs[0], 1);
    if (thing != NOTHING)
    {
        safe_tprintf_str(buff, bufc, "#%d", thing);
    }
    else
    {
        safe_str("#-1 NO MATCH", buff, bufc);
    }
}

FUNCTION(fun_gt)
{
    int ch = '0';
    int nDigits0, nDigits1;
    if (  (  is_integer(fargs[0], &nDigits0)
          && nDigits0 <= 9
          && is_integer(fargs[1], &nDigits1)
          && nDigits1 <= 9
          && Tiny_atol(fargs[0]) > Tiny_atol(fargs[1]))
       || safe_atof(fargs[0]) > safe_atof(fargs[1]))
    {
        ch = '1';
    }
    safe_chr(ch, buff, bufc);
}

FUNCTION(fun_gte)
{
    int ch = '0';
    int nDigits0, nDigits1;
    if (  (  is_integer(fargs[0], &nDigits0)
          && nDigits0 <= 9
          && is_integer(fargs[1], &nDigits1)
          && nDigits1 <= 9
          && Tiny_atol(fargs[0]) >= Tiny_atol(fargs[1]))
       || safe_atof(fargs[0]) >= safe_atof(fargs[1]))
    {
        ch = '1';
    }
    safe_chr(ch, buff, bufc);
}

FUNCTION(fun_lt)
{
    int ch = '0';
    int nDigits0, nDigits1;
    if (  (  is_integer(fargs[0], &nDigits0)
          && nDigits0 <= 9
          && is_integer(fargs[1], &nDigits1)
          && nDigits1 <= 9
          && Tiny_atol(fargs[0]) < Tiny_atol(fargs[1]))
       || safe_atof(fargs[0]) < safe_atof(fargs[1]))
    {
        ch = '1';
    }
    safe_chr(ch, buff, bufc);
}

FUNCTION(fun_lte)
{
    int ch = '0';
    int nDigits0, nDigits1;
    if (  (  is_integer(fargs[0], &nDigits0)
          && nDigits0 <= 9
          && is_integer(fargs[1], &nDigits1)
          && nDigits1 <= 9
          && Tiny_atol(fargs[0]) <= Tiny_atol(fargs[1]))
       || safe_atof(fargs[0]) <= safe_atof(fargs[1]))
    {
        ch = '1';
    }
    safe_chr(ch, buff, bufc);
}

FUNCTION(fun_eq)
{
    int ch = '0';
    int nDigits0, nDigits1;
    if (  (  is_integer(fargs[0], &nDigits0)
          && nDigits0 <= 9
          && is_integer(fargs[1], &nDigits1)
          && nDigits1 <= 9
          && Tiny_atol(fargs[0]) == Tiny_atol(fargs[1]))
       || safe_atof(fargs[0]) == safe_atof(fargs[1]))
    {
        ch = '1';
    }
    safe_chr(ch, buff, bufc);
}

FUNCTION(fun_neq)
{
    int ch = '0';
    int nDigits0, nDigits1;
    if (  (  is_integer(fargs[0], &nDigits0)
          && nDigits0 <= 9
          && is_integer(fargs[1], &nDigits1)
          && nDigits1 <= 9
          && Tiny_atol(fargs[0]) != Tiny_atol(fargs[1]))
       || safe_atof(fargs[0]) != safe_atof(fargs[1]))
    {
        ch = '1';
    }
    safe_chr(ch, buff, bufc);
}

FUNCTION(fun_and)
{
    if (nfargs <= 0)
    {
        safe_str("#-1 TOO FEW ARGUMENTS", buff, bufc);
        return;
    }
    int val = TRUE;
    for (int i = 0; i < nfargs; i++)
    {
        val = val && Tiny_atol(fargs[i]);
    }
    safe_chr(val ? '1' : '0', buff, bufc);
}

FUNCTION(fun_or)
{
    if (nfargs <= 0)
    {
        safe_str("#-1 TOO FEW ARGUMENTS", buff, bufc);
        return;
    }

    int val = FALSE;
    for (int i = 0; i < nfargs; i++)
    {
        val = val || Tiny_atol(fargs[i]);
    }
    safe_chr(val ? '1' : '0', buff, bufc);
}

FUNCTION(fun_xor)
{
    if (nfargs <= 0)
    {
        safe_str("#-1 TOO FEW ARGUMENTS", buff, bufc);
        return;
    }

    int val = FALSE;
    for (int i = 0; i < nfargs; i++)
    {
        int tval = Tiny_atol(fargs[i]);
        val = (val && !tval) || (!val && tval);
    }
    safe_chr(val ? '1' : '0', buff, bufc);
}

FUNCTION(fun_not)
{
    safe_ltoa(!xlate(fargs[0]), buff, bufc, LBUF_SIZE-1);
}

FUNCTION(fun_sqrt)
{
    double val;

    val = safe_atof(fargs[0]);
#ifndef HAVE_IEEE_FP_SNAN
    if (val < 0.0)
    {
        safe_str("Ind", buff, bufc);
    }
    else if (val == 0.0)
    {
        safe_chr('0', buff, bufc);
    }
    else
    {
        fval(buff, bufc, sqrt(val));
    }
#else
    fval(buff, bufc, sqrt(val));
#endif
}

FUNCTION(fun_add)
{
    if (nfargs <= 0)
    {
        safe_str("#-1 TOO FEW ARGUMENTS", buff, bufc);
        return;
    }
    double sum = 0.0;
    for (int i = 0; i < nfargs; i++)
    {
        sum += safe_atof(fargs[i]);
    }
    fval(buff, bufc, sum);
}

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

FUNCTION(fun_mul)
{
    if (nfargs <= 0)
    {
        safe_str("#-1 TOO FEW ARGUMENTS", buff, bufc);
        return;
    }
    double prod = 1.0;
    for (int i = 0; i < nfargs; i++)
    {
        prod *= safe_atof(fargs[i]);
    }
    fval(buff, bufc, prod);
}

FUNCTION(fun_floor)
{
    double r = floor(safe_atof(fargs[0]));
#ifdef HAVE_IEEE_FP_FORMAT
    int fpc = Tiny_fpclass(r);
    if (TINY_FPGROUP(fpc) == TINY_FPGROUP_PASS)
    {
#endif // HAVE_IEEE_FP_FORMAT
        safe_tprintf_str(buff, bufc, "%.0f", r);
#ifdef HAVE_IEEE_FP_FORMAT
    }
    else
    {
        safe_str(TinyFPStrings[TINY_FPCLASS(fpc)], buff, bufc);
    }
#endif // HAVE_IEEE_FP_FORMAT
}

FUNCTION(fun_ceil)
{
    double r = ceil(safe_atof(fargs[0]));
#ifdef HAVE_IEEE_FP_FORMAT
    int fpc = Tiny_fpclass(r);
    if (TINY_FPGROUP(fpc) == TINY_FPGROUP_PASS)
    {
#endif // HAVE_IEEE_FP_FORMAT
        safe_tprintf_str(buff, bufc, "%.0f", r);
#ifdef HAVE_IEEE_FP_FORMAT
    }
    else
    {
        safe_str(TinyFPStrings[TINY_FPCLASS(fpc)], buff, bufc);
    }
#endif // HAVE_IEEE_FP_FORMAT
}

FUNCTION(fun_round)
{
    const char *fstr;
    char *oldp;
    
    oldp = *bufc;
    
    switch (Tiny_atol(fargs[1]))
    {
    case 1:
        fstr = "%.1f";
        break;
    case 2:
        fstr = "%.2f";
        break;
    case 3:
        fstr = "%.3f";
        break;
    case 4:
        fstr = "%.4f";
        break;
    case 5:
        fstr = "%.5f";
        break;
    case 6:
        fstr = "%.6f";
        break;
    default:
        fstr = "%.0f";
        break;
    }
    double r = safe_atof(fargs[0]);
#ifdef HAVE_IEEE_FP_FORMAT
    int fpc = Tiny_fpclass(r);
    if (TINY_FPGROUP(fpc) == TINY_FPGROUP_PASS)
    {
#endif // HAVE_IEEE_FP_FORMAT
        safe_tprintf_str(buff, bufc, (char *)fstr, r);
#ifdef HAVE_IEEE_FP_FORMAT
    }
    else
    {
        safe_str(TinyFPStrings[TINY_FPCLASS(fpc)], buff, bufc);
    }
#endif // HAVE_IEEE_FP_FORMAT
}

FUNCTION(fun_trunc)
{
    double rArg = safe_atof(fargs[0]);
    double rIntegerPart;
    double rFractionalPart;

    rFractionalPart = modf(rArg, &rIntegerPart);
#ifdef HAVE_IEEE_FP_FORMAT
    int fpc = Tiny_fpclass(rIntegerPart);
    if (TINY_FPGROUP(fpc) == TINY_FPGROUP_PASS)
    {
#endif // HAVE_IEEE_FP_FORMAT
        safe_tprintf_str(buff, bufc, "%.0f", rIntegerPart);
#ifdef HAVE_IEEE_FP_FORMAT
    }
    else
    {
        safe_str(TinyFPStrings[TINY_FPCLASS(fpc)], buff, bufc);
    }
#endif // HAVE_IEEE_FP_FORMAT
}

FUNCTION(fun_div)
{
    INT64 bot, top;

    top = Tiny_atoi64(fargs[0]);
    bot = Tiny_atoi64(fargs[1]);
    if (bot == 0)
    {
        safe_str("#-1 DIVIDE BY ZERO", buff, bufc);
    }
    else
    {
        safe_str(Tiny_i64toa_t(top/bot), buff, bufc);
    }
}

FUNCTION(fun_fdiv)
{
    double bot = safe_atof(fargs[1]);
    double top = safe_atof(fargs[0]);
#ifndef HAVE_IEEE_FP_SNAN
    if (bot == 0.0)
    {
        if (top > 0.0)
        {
            safe_str("+Inf", buff, bufc);
        }
        else if (top < 0.0)
        {
            safe_str("-Inf", buff, bufc);
        }
        else
        {
            safe_str("Ind", buff, bufc);
        }
    }
    else
    {
        fval(buff, bufc, top/bot);
    }
#else
    fval(buff, bufc, top/bot);
#endif
}

FUNCTION(fun_mod)
{
    INT64 bot, top;

    top = Tiny_atoi64(fargs[0]);
    bot = Tiny_atoi64(fargs[1]);
    if (bot == 0)
    {
        bot = 1;
    }
    safe_str(Tiny_i64toa_t(top%bot), buff, bufc);
}

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

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

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

FUNCTION(fun_power)
{
    double val1, val2;

    val1 = safe_atof(fargs[0]);
    val2 = safe_atof(fargs[1]);
#ifndef HAVE_IEEE_FP_SNAN
    if (val1 < 0.0)
    {
        safe_str("Ind", buff, bufc);
    }
    else
    {
        fval(buff, bufc, pow(val1, val2));
    }
#else
    fval(buff, bufc, pow(val1, val2));
#endif
}

FUNCTION(fun_ln)
{
    double val;

    val = safe_atof(fargs[0]);
#ifndef HAVE_IEEE_FP_SNAN
    if (val < 0.0)
    {
        safe_str("Ind", buff, bufc);
    }
    else if (val == 0.0)
    {
        safe_str("-Inf", buff, bufc);
    }
    else
    {
        fval(buff, bufc, log(val));
    }
#else
    fval(buff, bufc, log(val));
#endif
}

FUNCTION(fun_log)
{
    double val;

    val = safe_atof(fargs[0]);
#ifndef HAVE_IEEE_FP_SNAN
    if (val < 0.0)
    {
        safe_str("Ind", buff, bufc);
    }
    else if (val == 0.0)
    {
        safe_str("-Inf", buff, bufc);
    }
    else
    {
        fval(buff, bufc, log10(val));
    }
#else
    fval(buff, bufc, log10(val));
#endif
}

FUNCTION(fun_asin)
{
    double val;

    val = safe_atof(fargs[0]);
#ifndef HAVE_IEEE_FP_SNAN
    if ((val < -1.0) || (val > 1.0))
    {
        safe_str("Ind", buff, bufc);
    }
    else
    {
        fval(buff, bufc, asin(val));
    }
#else
    fval(buff, bufc, asin(val));
#endif
}

FUNCTION(fun_acos)
{
    double val;

    val = safe_atof(fargs[0]);
#ifndef HAVE_IEEE_FP_SNAN
    if ((val < -1.0) || (val > 1.0))
    {
        safe_str("Ind", buff, bufc);
    }
    else
    {
        fval(buff, bufc, acos(val));
    }
#else
    fval(buff, bufc, acos(val));
#endif
}

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

FUNCTION(fun_dist2d)
{
    int d;
    double r;

    d = Tiny_atol(fargs[0]) - Tiny_atol(fargs[2]);
    r = (double)(d * d);
    d = Tiny_atol(fargs[1]) - Tiny_atol(fargs[3]);
    r += (double)(d * d);
    d = (int)(sqrt(r) + 0.5);
    safe_ltoa(d, buff, bufc, LBUF_SIZE-1);
}

FUNCTION(fun_dist3d)
{
    int d;
    double r;

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



//------------------------------------------------------------------------
// Vector functions: VADD, VSUB, VMUL, VCROSS, VMAG, VUNIT, VDIM
// Vectors are space-separated numbers.
//
#define MAXDIM 20
#define VADD_F 0
#define VSUB_F 1
#define VMUL_F 2
#define VDOT_F 3
#define VCROSS_F 4

static void handle_vectors
(
    char *vecarg1, char *vecarg2, char *buff, char **bufc, char sep,
    char osep, int flag
)
{
    char *v1[(LBUF_SIZE+1)/2], *v2[(LBUF_SIZE+1)/2];
    char vres[MAXDIM][LBUF_SIZE];
    double scalar;
    int n, m, i;
    
    // Split the list up, or return if the list is empty.
    //
    if (!vecarg1 || !*vecarg1 || !vecarg2 || !*vecarg2)
    {
        return;
    }
    n = list2arr(v1, (LBUF_SIZE+1)/2, vecarg1, sep);
    m = list2arr(v2, (LBUF_SIZE+1)/2, vecarg2, sep);
    
    // It's okay to have vmul() be passed a scalar first or second arg,
    // but everything else has to be same-dimensional.
    //
    if (  (n != m)
       && (  (flag != VMUL_F)
          || ((n != 1) && (m != 1))
          )
       )
    {
        safe_str("#-1 VECTORS MUST BE SAME DIMENSIONS", buff, bufc);
        return;
    }
    if (n > MAXDIM)
    {
        safe_str("#-1 TOO MANY DIMENSIONS ON VECTORS", buff, bufc);
        return;
    }
    
    switch (flag)
    {
    case VADD_F:

        for (i = 0; i < n; i++)
        {
            fval_buf(vres[i], safe_atof(v1[i]) + safe_atof(v2[i]));
            v1[i] = (char *) vres[i];
        }
        arr2list(v1, n, buff, bufc, osep);
        break;

    case VSUB_F:

        for (i = 0; i < n; i++)
        {
            fval_buf(vres[i], safe_atof(v1[i]) - safe_atof(v2[i]));
            v1[i] = (char *) vres[i];
        }
        arr2list(v1, n, buff, bufc, osep);
        break;

    case VMUL_F:

        // If n or m is 1, this is scalar multiplication.
        // otherwise, multiply elementwise.
        //
        if (n == 1)
        {
            scalar = safe_atof(v1[0]);
            for (i = 0; i < m; i++)
            {
                fval_buf(vres[i], safe_atof(v2[i]) * scalar);
                v1[i] = (char *) vres[i];
            }
            n = m;
        }
        else if (m == 1)
        {
            scalar = safe_atof(v2[0]);
            for (i = 0; i < n; i++)
            {
                fval_buf(vres[i], safe_atof(v1[i]) * scalar);
                v1[i] = (char *) vres[i];
            }
        }
        else
        {
            // Vector elementwise product.
            //
            // Note this is a departure from TinyMUX 1.6, but an
            // imitation of the PennMUSH and MUSH 3.0 behavior:
            // the documentation in Penn claims it's a dot product,
            // but the actual behavior isn't. We implement dot
            // product separately!
            //
            for (i = 0; i < n; i++)
            {
                fval_buf(vres[i], safe_atof(v1[i]) * safe_atof(v2[i]));
                v1[i] = (char *) vres[i];
            }
        }
        arr2list(v1, n, buff, bufc, osep);
        break;

    case VDOT_F:

        scalar = 0.0;
        for (i = 0; i < n; i++)
        {
            scalar += safe_atof(v1[i]) * safe_atof(v2[i]);
        }
        fval(buff, bufc, scalar);
        break;

    case VCROSS_F:

        // cross product: (a,b,c) x (d,e,f) = (bf - ce, cd - af, ae - bd)
        //
        // Or in other words:
        //
        //      | a  b  c |
        //  det | d  e  f | = i(bf-ce) + j(cd-af) + k(ae-bd)
        //      | i  j  k |
        //
        // where i, j, and k are unit vectors in the x, y, and z
        // cartisian coordinate space and are understood when expressed
        // in vector form.
        //
        if (n != 3)
        {
            safe_str("#-1 VECTORS MUST BE DIMENSION OF 3", buff, bufc);
        }
        else
        {
            double a[2][3];
            for (i = 0; i < 3; i++)
            {
                a[0][i] = safe_atof(v1[i]);
                a[1][i] = safe_atof(v2[i]);
                v1[i] = (char *) vres[i];
            }
            fval_buf(vres[0], (a[0][1] * a[1][2]) - (a[0][2] * a[1][1]));
            fval_buf(vres[1], (a[0][2] * a[1][0]) - (a[0][0] * a[1][2]));
            fval_buf(vres[2], (a[0][0] * a[1][1]) - (a[0][1] * a[1][0]));
            arr2list(v1, n, buff, bufc, osep);
        }
        break;

    default:

        // If we reached this, we're in trouble.
        //
        safe_str("#-1 UNIMPLEMENTED", buff, bufc);
    }
}

FUNCTION(fun_vadd)
{
    char sep, osep;
    
    svarargs_preamble("VADD", 4);
    handle_vectors(fargs[0], fargs[1], buff, bufc, sep, osep, VADD_F);
}

FUNCTION(fun_vsub)
{
    char sep, osep;
    
    svarargs_preamble("VSUB", 4);
    handle_vectors(fargs[0], fargs[1], buff, bufc, sep, osep, VSUB_F);
}

FUNCTION(fun_vmul)
{
    char sep, osep;
    
    svarargs_preamble("VMUL", 4);
    handle_vectors(fargs[0], fargs[1], buff, bufc, sep, osep, VMUL_F);
}

FUNCTION(fun_vdot)
{
    // dot product: (a,b,c) . (d,e,f) = ad + be + cf
    //
    char sep, osep;
    
    svarargs_preamble("VDOT", 4);
    handle_vectors(fargs[0], fargs[1], buff, bufc, sep, osep, VDOT_F);
}

FUNCTION(fun_vcross)
{
    // cross product: (a,b,c) x (d,e,f) = (bf - ce, cd - af, ae - bd)
    //
    char sep, osep;
    
    svarargs_preamble("VCROSS", 4);
    handle_vectors(fargs[0], fargs[1], buff, bufc, sep, osep, VCROSS_F);
}

FUNCTION(fun_vmag)
{
    char *v1[LBUF_SIZE];
    int n, i;
    double tmp, res = 0.0;
    char sep;
    
    varargs_preamble("VMAG", 2);
    
    // Split the list up, or return if the list is empty.
    //
    if (!fargs[0] || !*fargs[0])
    {
        return;
    }
    n = list2arr(v1, LBUF_SIZE, fargs[0], sep);
    
    if (n > MAXDIM)
    {
        safe_str("#-1 TOO MANY DIMENSIONS ON VECTORS", buff, bufc);
        return;
    }

    // Calculate the magnitude.
    //
    for (i = 0; i < n; i++)
    {
        tmp = safe_atof(v1[i]);
        res += tmp * tmp;
    }
    
    if (res > 0)
    {
        fval(buff, bufc, sqrt(res));
    }
    else
    {
        safe_chr('0', buff, bufc);
    }
}

FUNCTION(fun_vunit)
{
    char *v1[LBUF_SIZE];
    char vres[MAXDIM][LBUF_SIZE];
    int n, i;
    double tmp, res = 0.0;
    char sep;
    
    varargs_preamble("VUNIT", 2);
    
    // Split the list up, or return if the list is empty.
    //
    if (!fargs[0] || !*fargs[0])
    {
        return;
    }
    n = list2arr(v1, LBUF_SIZE, fargs[0], sep);
    
    if (n > MAXDIM)
    {
        safe_str("#-1 TOO MANY DIMENSIONS ON VECTORS", buff, bufc);
        return;
    }

    // Calculate the magnitude.
    //
    for (i = 0; i < n; i++)
    {
        tmp = safe_atof(v1[i]);
        res += tmp * tmp;
    }
    
    if (res <= 0)
    {
        safe_str("#-1 CAN'T MAKE UNIT VECTOR FROM ZERO-LENGTH VECTOR",
            buff, bufc);
        return;
    }
    for (i = 0; i < n; i++)
    {
        fval_buf(vres[i], safe_atof(v1[i]) / sqrt(res));
        v1[i] = (char *) vres[i];
    }
    
    arr2list(v1, n, buff, bufc, sep);
}

FUNCTION(fun_vdim)
{
    char sep;
    
    if (fargs == 0)
    {
        safe_chr('0', buff, bufc);
    }
    else
    {
        varargs_preamble("VDIM", 2);
        safe_ltoa(countwords(fargs[0],sep), buff, bufc, LBUF_SIZE-1);
    }
}

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

FUNCTION(fun_comp)
{
    int x;

    x = strcmp(fargs[0], fargs[1]);
    if (x < 0)
    {
        safe_str("-1", buff, bufc);
    }
    else
    {
        safe_chr((x == 0) ? '0' : '1', buff, bufc);
    }
}

#ifdef WOD_REALMS
FUNCTION(fun_cansee)
{
    if (nfargs < 2 || nfargs > 3)
    {
        safe_str("#-1", buff, bufc);
        return;
    }

    dbref looker, lookee;
    int mode;
    looker = match_thing(player, fargs[0]);
    if (looker != NOTHING)
    {
        lookee = match_thing(player, fargs[1]);
        if (lookee != NOTHING)
        {
            if (nfargs == 3)
            {
                mode = Tiny_atol(fargs[2]);
                switch (mode)
                {
                case ACTION_IS_STATIONARY:
                case ACTION_IS_MOVING:
                case ACTION_IS_TALKING:
                    break;

                default:
                    mode = ACTION_IS_STATIONARY;
                    break;
                }
            }
            else
            {
                mode = ACTION_IS_STATIONARY;
            }

            // Do it.
            //
            int Realm_Do = DoThingToThingVisibility(looker, lookee, mode);
            int ch = '0';
            if ((Realm_Do & REALM_DO_MASK) != REALM_DO_HIDDEN_FROM_YOU)
            {
                if (!Dark(lookee))
                {
                    ch = '1';
                }
            }
            safe_chr(ch, buff, bufc);
            return;
        }
    }
    safe_str("#-1", buff, bufc);
}
#endif

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

FUNCTION(fun_lcon)
{
    dbref thing, it;
    char *tbuf;
    int first = 1;

    it = match_thing(player, fargs[0]);
    if ((it != NOTHING) &&
        (Has_contents(it)) &&
        (Examinable(player, it) ||
         (Location(player) == it) ||
         (it == cause)))
    {
        tbuf = alloc_sbuf("fun_lcon");
        DOLIST(thing, Contents(it))
        {
#ifdef WOD_REALMS
            if (REALM_DO_HIDDEN_FROM_YOU != DoThingToThingVisibility(player, thing, ACTION_IS_STATIONARY))
            {
#endif
                if (!first)
                {
                    tbuf[0] = ' ';
                    tbuf[1] = '#';
                    Tiny_ltoa(thing, tbuf+2);
                }
                else
                {
                    tbuf[0] = '#';
                    Tiny_ltoa(thing, tbuf+1);
                    first = 0;
                }
                safe_str(tbuf, buff, bufc);
#ifdef WOD_REALMS
            }
#endif
        }
        free_sbuf(tbuf);
    }
    else
    {
        safe_str("#-1", buff, bufc);
    }
}

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

FUNCTION(fun_lexits)
{
    dbref thing, it, parent;
    char *tbuf;
    int exam, lev, key;
    int first = 1;

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

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

    /*
     * Return info for all parent levels 
     */

    ITER_PARENTS(it, parent, lev) {

        /*
         * Look for exits at each level 
         */

        if (!Has_exits(parent))
            continue;
        key = 0;
        if (Examinable(player, parent))
            key |= VE_LOC_XAM;
        if (Dark(parent))
            key |= VE_LOC_DARK;
        if (Dark(it))
            key |= VE_BASE_DARK;
        DOLIST(thing, Exits(parent))
        {
            if (exit_visible(thing, player, key))
            {
                if (!first)
                    sprintf(tbuf, " #%d", thing);
                else
                {
                    sprintf(tbuf, "#%d", thing);
                    first = 0;
                }
                safe_str(tbuf, buff, bufc);
            }
        }
    }
    free_sbuf(tbuf);
    return;
}

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

FUNCTION(fun_home)
{
    dbref it;

    it = match_thing(player, fargs[0]);
    if (!Good_obj(it) || !Examinable(player, it))
        safe_str("#-1", buff, bufc);
    else if (Has_home(it))
        safe_tprintf_str(buff, bufc, "#%d", Home(it));
    else if (Has_dropto(it))
        safe_tprintf_str(buff, bufc, "#%d", Dropto(it));
    else if (isExit(it))
        safe_tprintf_str(buff, bufc, "#%d", where_is(it));
    else
        safe_str("#-1", buff, bufc);
    return;
}

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

FUNCTION(fun_money)
{
    dbref it;

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

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

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

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

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

FUNCTION(fun_lpos)
{
    if (*fargs[0] == '\0')
    {
        return;
    }
    
    char c = *fargs[1];
    if (!c)
    {
        c = ' ';
    }

    int i;
    char *bb_p = *bufc;
    char *s = strip_ansi(fargs[0]);
    for (i = 0; *s; i++, s++)
    {
        if (*s == c)
        {
            if (*bufc != bb_p)
            {
                safe_chr(' ', buff, bufc);
            }
            safe_ltoa(i, buff, bufc, LBUF_SIZE-1);
        }
    }
}

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

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

static void do_itemfuns(char *buff, char **bufc, char *str, int el, char *word, char sep, int flag)
{
    int ct, overrun;
    char *sptr, *iptr, *eptr;
    int slen = 0, ilen = 0, elen = 0;

    char nullb;

    // If passed a null string return an empty string, except that we
    // are allowed to append to a null string.
    //
    if ((!str || !*str) && ((flag != IF_INSERT) || (el != 1)))
    {
        return;
    }
    int nStr = strlen(str);

    // We can't fiddle with anything before the first position.
    //
    if (el < 1)
    {
        safe_copy_buf(str, nStr, buff, bufc, LBUF_SIZE-1);
        return;
    }

    // Split the list up into 'before', 'target', and 'after' chunks
    // pointed to by sptr, iptr, and eptr respectively.
    //
    nullb = '\0';
    if (el == 1)
    {
        // No 'before' portion, just split off element 1
        //
        sptr = NULL;
        slen = 0;
        if (!str || !*str)
        {
            eptr = NULL;
            iptr = NULL;
        }
        else
        {
            eptr = trim_space_sep_LEN(str, nStr, sep, &elen);
            iptr = split_token_LEN(&eptr, &elen, sep, &ilen);
        }
    }
    else
    {
        // Break off 'before' portion.
        //
        sptr = eptr = trim_space_sep_LEN(str, nStr, sep, &elen);
        overrun = 1;
        for (  ct = el;
               ct > 2 && eptr;
               eptr = next_token_LEN(eptr, &elen, sep), ct--)
        {
            // Nothing
        }
        if (eptr)
        {
            // Note: We are using (iptr,ilen) temporarily. It
            // doesn't represent the 'target' word, but the
            // the last token in the 'before' portion.
            //
            overrun = 0;
            iptr = split_token_LEN(&eptr, &elen, sep, &ilen);
            slen = (iptr - sptr) + ilen;
        }

        // If we didn't make it to the target element, just return
        // the string. Insert is allowed to continue if we are exactly
        // at the end of the string, but replace and delete are not.
        //
        if (!(eptr || ((flag == IF_INSERT) && !overrun)))
        {
            safe_copy_buf(str, nStr, buff, bufc, LBUF_SIZE-1);
            return;
        }

        // Split the 'target' word from the 'after' portion.
        //
        if (eptr)
        {
            iptr = split_token_LEN(&eptr, &elen, sep, &ilen);
        }
        else
        {
            iptr = NULL;
            ilen = 0;
        }
    }

    switch (flag)
    {
    case IF_DELETE:
    
        // deletion
        //
        if (sptr)
        {
            safe_copy_buf(sptr, slen, buff, bufc, LBUF_SIZE-1);
            if (eptr)
            {
                safe_chr(sep, buff, bufc);
            }
        }
        if (eptr)
        {
            safe_copy_buf(eptr, elen, buff, bufc, LBUF_SIZE-1);
        }
        break;

    case IF_REPLACE:

        // replacing.
        //
        if (sptr)
        {
            safe_copy_buf(sptr, slen, buff, bufc, LBUF_SIZE-1);
            safe_chr(sep, buff, bufc);
        }
        safe_str(word, buff, bufc);
        if (eptr)
        {
            safe_chr(sep, buff, bufc);
            safe_copy_buf(eptr, elen, buff, bufc, LBUF_SIZE-1);
        }
        break;

    case IF_INSERT:
    
        // Insertion.
        //
        if (sptr)
        {
            safe_copy_buf(sptr, slen, buff, bufc, LBUF_SIZE-1);
            safe_chr(sep, buff, bufc);
        }
        safe_str(word, buff, bufc);
        if (iptr)
        {
            safe_chr(sep, buff, bufc);
            safe_copy_buf(iptr, ilen, buff, bufc, LBUF_SIZE-1);
        }
        if (eptr)
        {
            safe_chr(sep, buff, bufc);
            safe_copy_buf(eptr, elen, buff, bufc, LBUF_SIZE-1);
        }
        break;
    }
}


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

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

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

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

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

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

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

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

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

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

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

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

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

    varargs_preamble("MEMBER", 3);
    wcount = 1;
    s = trim_space_sep(fargs[0], sep);
    do {
        r = split_token(&s, sep);
        if (!strcmp(fargs[1], r))
        {
            safe_ltoa(wcount, buff, bufc, LBUF_SIZE-1);
            return;
        }
        wcount++;
    } while (s);
    safe_chr('0', buff, bufc);
}

// fun_secure: This function replaces any character in the set
// '%$\[](){},;' with a space. It handles ANSI by not replacing
// the '[' character within an ANSI sequence.
//
FUNCTION(fun_secure)
{
    char *pString = fargs[0];
    int nString = strlen(pString);

    while (nString)
    {
        int nTokenLength0;
        int nTokenLength1;
        int iType = ANSI_lex(nString, pString, &nTokenLength0, &nTokenLength1);

        if (iType == TOKEN_TEXT_ANSI)
        {
            // Process TEXT portion (pString, nTokenLength0).
            //
            nString -= nTokenLength0;
            while (nTokenLength0--)
            {
                if (Tiny_IsSecureCharacter[(unsigned char)*pString])
                {
                    safe_chr(' ', buff, bufc);
                }
                else
                {
                    safe_chr(*pString, buff, bufc);
                }
                pString++;
            }
            nTokenLength0 = nTokenLength1;
        }

        if (nTokenLength0)
        {
            // Process ANSI portion (pString, nTokenLength0).
            //
            safe_copy_buf(pString, nTokenLength0, buff, bufc, LBUF_SIZE-1);
            pString += nTokenLength0;
            nString -= nTokenLength0;
        }
    }
}

// fun_escape: This function prepends a '\' to the beginning of a
// string and before any character which occurs in the set "%\[]{};".
// It handles ANSI by not treating the '[' character within an ANSI
// sequence as a special character.
//
FUNCTION(fun_escape)
{
    char *pString = fargs[0];
    int nString = strlen(pString);

    while (nString)
    {
        int nTokenLength0;
        int nTokenLength1;
        int iType = ANSI_lex(nString, pString, &nTokenLength0, &nTokenLength1);

        if (iType == TOKEN_TEXT_ANSI)
        {
            // Process TEXT portion (pString, nTokenLength0).
            //
            nString -= nTokenLength0;
            while (nTokenLength0--)
            {
                if (  Tiny_IsEscapeCharacter[(unsigned char)*pString]
                   || pString == fargs[0])
                {
                    safe_chr('\\', buff, bufc);
                }
                safe_chr(*pString, buff, bufc);
                pString++;
            }
            nTokenLength0 = nTokenLength1;
        }

        if (nTokenLength0)
        {
            // Process ANSI portion (pString, nTokenLength0).
            //
            safe_copy_buf(pString, nTokenLength0, buff, bufc, LBUF_SIZE-1);
            pString += nTokenLength0;
            nString -= nTokenLength0;
        }
    }
}

/*
 * Take a character position and return which word that char is in.
 * * wordpos(<string>, <charpos>)
 */
FUNCTION(fun_wordpos)
{
    unsigned charpos;
    int i;
    char *cp, *tp, *xp, sep;
    
    varargs_preamble("WORDPOS", 3);
    
    charpos = Tiny_atol(fargs[1]);
    cp = fargs[0];
    if ((charpos > 0) && (charpos <= strlen(cp)))
    {
        tp = &(cp[charpos - 1]);
        cp = trim_space_sep(cp, sep);
        xp = split_token(&cp, sep);
        for (i = 1; xp; i++)
        {
            if (tp < (xp + strlen(xp)))
                break;
            xp = split_token(&cp, sep);
        }
        safe_ltoa(i, buff, bufc, LBUF_SIZE-1);
        return;
    }
    safe_str("#-1", buff, bufc);
    return;
}

FUNCTION(fun_type)
{
    dbref it;

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

FUNCTION(fun_hasflag)
{
    dbref it;

    it = match_thing(player, fargs[0]);
    if (!Good_obj(it)) {
        safe_str("#-1 NOT FOUND", buff, bufc);
        return;
    }
    if (mudconf.pub_flags || Examinable(player, it) || (it == cause))
    {
        int cc = has_flag(player, it, fargs[1]);
        safe_chr(cc ? '1' : '0', buff, bufc);
    }
    else
    {
        safe_str("#-1 PERMISSION DENIED", buff, bufc);
    }
}

FUNCTION(fun_haspower)
{
    dbref it;

    it = match_thing(player, fargs[0]);
    if (!Good_obj(it))
    {
        safe_str("#-1 NOT FOUND", buff, bufc);
        return;
    }
    if (mudconf.pub_flags || Examinable(player, it) || (it == cause))
    {
        int ch = '0';
        if (has_power(player, it, fargs[1]))
        {
            ch = '1';
        }
        safe_chr(ch, buff, bufc);
    }
    else
    {
        safe_str("#-1 PERMISSION DENIED", buff, bufc);
    }
}

FUNCTION(fun_delete)
{
    char *s = fargs[0];
    int iStart = Tiny_atol(fargs[1]);
    int nChars = Tiny_atol(fargs[2]);
    int nLen = strlen(s);

    int iEnd = iStart + nChars;

    // Are we deleting anything at all?
    //
    if (iEnd <= 0 || nLen <= iStart)
    {
        if (nLen)
        {
            safe_copy_buf(s, nLen, buff, bufc, LBUF_SIZE-1);
        }
        return;
    }

    if (iStart < 0) iStart = 0;
    if (nLen < iEnd ) iEnd = nLen;

    // ASSERT: Now [iStart,iEnd) exist somewhere within the the string
    // [s,nLen).
    //
    if (iStart)
    {
        safe_copy_buf(s, iStart, buff, bufc, LBUF_SIZE-1);
    }
    if (iEnd < nLen)
    {
        safe_copy_buf(s + iEnd, nLen - iEnd, buff, bufc, LBUF_SIZE-1);
    }
}

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

    /*
     * Parse the argument into obj + lock 
     */

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

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

    tbuf = atr_get(it, attr->number, &aowner, &aflags);
    if (Read_attr(player, it, attr, aowner, aflags)) {
        pBoolExp = parse_boolexp(player, tbuf, 1);
        free_lbuf(tbuf);
        tbuf = (char *)unparse_boolexp_function(player, pBoolExp);
        free_boolexp(pBoolExp);
        safe_str(tbuf, buff, bufc);
    } else
        free_lbuf(tbuf);
}

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

    /*
     * Parse lock supplier into obj + lock 
     */

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

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

    victim = match_thing(player, fargs[1]);
    if (!Good_obj(victim)) {
        safe_str("#-1 NOT FOUND", buff, bufc);
    } else if (!nearby_or_control(player, victim) &&
           !nearby_or_control(player, it)) {
        safe_str("#-1 TOO FAR AWAY", buff, bufc);
    } else {
        tbuf = atr_get(it, attr->number, &aowner, &aflags);
        if ((attr->number == A_LOCK) ||
            Read_attr(player, it, attr, aowner, aflags)) {
            pBoolExp = parse_boolexp(player, tbuf, 1);
            safe_ltoa(eval_boolexp(victim, it, it, pBoolExp), buff, bufc, LBUF_SIZE-1);
            free_boolexp(pBoolExp);
        } else {
            safe_chr('0', buff, bufc);
        }
        free_lbuf(tbuf);
    }
}

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

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

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

FUNCTION(fun_nearby)
{
    dbref obj1, obj2;
    int ch = '0';

    obj1 = match_thing(player, fargs[0]);
    obj2 = match_thing(player, fargs[1]);
    if (  (  nearby_or_control(player, obj1)
          || nearby_or_control(player, obj2))
       && nearby(obj1, obj2))
    {
        ch = '1';
    }
    safe_chr(ch, buff, bufc);
}

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

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

    it = match_thing(player, what);
    if (!Good_obj(it) || (!isPlayer(it) && !nearby_or_control(player, it)))
    {
        safe_str("#-1 NO MATCH", buff, bufc);
    }
    else 
    {
        str = (char *)token;
        TinyExec(buff, bufc, 0, it, it, EV_EVAL, &str, (char **)NULL, 0);
    }
}

FUNCTION(fun_obj)
{
    process_sex(player, fargs[0], "%o", buff, bufc);
}

FUNCTION(fun_poss)
{
    process_sex(player, fargs[0], "%p", buff, bufc);
}

FUNCTION(fun_subj)
{
    process_sex(player, fargs[0], "%s", buff, bufc);
}

FUNCTION(fun_aposs)
{
    process_sex(player, fargs[0], "%a", buff, bufc);
}

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

FUNCTION(fun_mudname)
{
    safe_str(mudconf.mud_name, buff, bufc);
}

void ANSI_TransformTextWithTable
(
    char *buff,
    char **bufc,
    char *pString,
    unsigned char xfrmTable[256])
{
    int   nString = strlen(pString);
    char *pBuffer = *bufc;
    int   nBufferAvailable = LBUF_SIZE - (*bufc - buff) - 1;
    while (nString)
    {
        int nTokenLength0;
        int nTokenLength1;
        int iType = ANSI_lex(nString, pString, &nTokenLength0, &nTokenLength1);

        if (iType == TOKEN_TEXT_ANSI)
        {
            // Determine how much to move.
            //
            int nMove = nTokenLength0;
            if (nMove > nBufferAvailable)
            {
                nMove = nBufferAvailable;
            }
            nBufferAvailable -= nMove;

            // Update pointers and counts.
            //
            char *p = pString;
            nString -= nTokenLength0;
            pString += nTokenLength0;

            // Transform and Move text.
            //
            while (nMove--)
            {
                *pBuffer++ = xfrmTable[(unsigned char)*p++];
            }

            // Determine whether to move the ANSI part.
            //
            if (nTokenLength1)
            {
                if (nTokenLength1 <= nBufferAvailable)
                {
                    memcpy(pBuffer, pString, nTokenLength1);
                    pBuffer += nTokenLength1;
                    nBufferAvailable -= nTokenLength1;
                }
                nString -= nTokenLength1;
                pString += nTokenLength1;
            }
        }
        else
        {
            // TOKEN_ANSI
            //
            // Determine whether to move the ANSI part.
            //
            if (nTokenLength0 <= nBufferAvailable)
            {
                memcpy(pBuffer, pString, nTokenLength0);
                pBuffer += nTokenLength0;
                nBufferAvailable -= nTokenLength0;
            }
            nString -= nTokenLength0;
            pString += nTokenLength0;
        }
    }
    *pBuffer = '\0';
    *bufc = pBuffer;
}

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

FUNCTION(fun_lcstr)
{
    ANSI_TransformTextWithTable(buff, bufc, fargs[0], Tiny_ToLower);
}

FUNCTION(fun_ucstr)
{
    ANSI_TransformTextWithTable(buff, bufc, fargs[0], Tiny_ToUpper);
}

FUNCTION(fun_capstr)
{
    char *pString = fargs[0];
    char *pBuffer = *bufc;
    int nString = strlen(pString);
    nString = safe_copy_buf(pString, nString, buff, bufc, LBUF_SIZE-1);

    // Find the first text character in (nString, pBuffer).
    //
    while (nString)
    {
        int nTokenLength0;
        int nTokenLength1;
        int iType = ANSI_lex(nString, pBuffer, &nTokenLength0, &nTokenLength1);
        if (iType == TOKEN_TEXT_ANSI)
        {
            *pBuffer = Tiny_ToUpper[(unsigned char)*pBuffer];
            return;
        }
        else
        {
            // iType == TOKEN_ANSI
            //
            pBuffer += nTokenLength0;
            nString -= nTokenLength0;
        }
    }
}

/*
 * ---------------------------------------------------------------------------
 * * fun_lnum: Return a list of numbers.
 */
FUNCTION(fun_lnum)
{
    char sep;
    if (  nfargs == 0
       || !fn_range_check("LNUM", nfargs, 1, 3, buff, bufc)
       || !delim_check(fargs, nfargs, 3, &sep, buff, bufc, 0, player, cause, cargs, ncargs, 1))
    {
        return;
    }

    int bot = 0, top;
    if (nfargs == 1)
    {
        top = Tiny_atol(fargs[0]) - 1;
        if (top < 0)
        {
            return;
        }
    }
    else
    {
        bot = Tiny_atol(fargs[0]);
        top = Tiny_atol(fargs[1]);
    }

    int i;
    if (bot == top)
    {
        safe_ltoa(bot, buff, bufc, LBUF_SIZE-1);
    }
    else if (bot < top)
    {
        safe_ltoa(bot, buff, bufc, LBUF_SIZE-1);
        for (i = bot+1; i <= top; i++)
        {
            print_sep(sep, buff, bufc);
            char *p = *bufc;
            safe_ltoa(i, buff, bufc, LBUF_SIZE-1);
            if (p == *bufc) return;
        }
    }
    else if (top < bot)
    {
        safe_ltoa(bot, buff, bufc, LBUF_SIZE-1);
        for (i = bot-1; i >= top; i--)
        {
            print_sep(sep, buff, bufc);
            char *p = *bufc;
            safe_ltoa(i, buff, bufc, LBUF_SIZE-1);
            if (p == *bufc) return;
        }
    }
}

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

FUNCTION(fun_lattr)
{
    dbref thing;
    int ca, first;
    ATTR *attr;

    // Check for wildcard matching.  parse_attrib_wild checks for read
    // permission, so we don't have to.  Have p_a_w assume the
    // slash-star if it is missing.
    //
    first = 1;
    olist_push();
    if (parse_attrib_wild(player, fargs[0], &thing, 0, 0, 1))
    {
        for (ca = olist_first(); ca != NOTHING; ca = olist_next())
        {
            attr = atr_num(ca);
            if (attr)
            {
                if (!first)
                {
                    safe_chr(' ', buff, bufc);
                }
                first = 0;
                safe_str((char *)attr->name, buff, bufc);
            }
        }
    }
    else
    {
        safe_str("#-1 NO MATCH", buff, bufc);
    }
    olist_pop();
}

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

static void Tiny_memrevcpy(char *dest, char *src, unsigned int n)
{
    dest += n - 1;
    while (n--)
    {
        *dest-- = *src++;
    }
}

typedef void MEMXFORM(char *dest, char *src, unsigned int n);
void ANSI_TransformTextReverseWithFunction
(
    char *buff,
    char **bufc,
    char *pString,
    MEMXFORM *pfMemXForm
)
{
    // Bounds checking.
    //
    unsigned int nString = strlen(pString);
    char *pBuffer = *bufc;
    unsigned int nBufferAvailable = LBUF_SIZE - (*bufc - buff) - 1;
    if (nString > nBufferAvailable)
    {
        nString = nBufferAvailable;
        pString[nString] = '\0';
    }

    // How it's done: We have a source string (pString, nString) and a
    // destination buffer (pBuffer, nString) of equal size.
    //
    // We recognize (ANSI,TEXT) phrases and place them at the end of
    // destination buffer working our way to the front. The ANSI part
    // is left inviolate, but the text part is reversed.
    //
    // In this way, (ANSI1,TEXT1)(ANSI2,TEXT2) is traslated into
    // (ANSI2,reverse(TEST2))(ANSI1,reverse(TEXT1)).
    //
    // TODO: Do not reverse the CRLF in the text part either.
    //
    int  nANSI = 0;
    char *pANSI = pString;
    pBuffer += nString;
    *bufc = pBuffer;
    **bufc = '\0';
    while (nString)
    {
        int nTokenLength0;
        int nTokenLength1;
        int iType = ANSI_lex(nString, pString, &nTokenLength0, &nTokenLength1);

        if (iType == TOKEN_TEXT_ANSI)
        {
            // (ANSI,TEXT) is given by (nANSI, nTokenLength0)
            //
            pBuffer -= nANSI + nTokenLength0;
            memcpy(pBuffer, pANSI, nANSI);
            pfMemXForm(pBuffer+nANSI, pString, nTokenLength0);

            // Adjust pointers and counts.
            //
            nString -= nTokenLength0;
            pString += nTokenLength0;
            pANSI = pString;
            nANSI = 0;

            nTokenLength0 = nTokenLength1;
        }
        // TOKEN_ANSI
        //
        nString -= nTokenLength0;
        pString += nTokenLength0;
        nANSI   += nTokenLength0;
    }

    // Copy the last ANSI part (if present). It will be overridden by
    // ANSI further on, but we need to fill up the space. Code
    // elsewhere will compact it before it's sent to the client.
    //
    pBuffer -= nANSI;
    memcpy(pBuffer, pANSI, nANSI);
}

FUNCTION(fun_reverse)
{
    ANSI_TransformTextReverseWithFunction(buff, bufc, fargs[0], Tiny_memrevcpy);
}

char ReverseWordsInText_Seperator;
static void ReverseWordsInText(char *dest, char *src, unsigned int n)
{
    dest += n;
    while (n)
    {
        char *pWord = strchr(src, ReverseWordsInText_Seperator);
        int nLen;
        if (pWord)
        {
            nLen = (pWord - src);
        }
        else
        {
            nLen = n;
        }
        dest -= nLen;
        memcpy(dest, src, nLen);
        src += nLen;
        n -= nLen;
        if (pWord)
        {
            dest--;
            *dest = ReverseWordsInText_Seperator;
            src++;
            n--;
        }
    }
}

FUNCTION(fun_revwords)
{
    // If we are passed an empty arglist return a null string.
    //
    if (nfargs == 0)
    {
        return;
    }
    char sep;
    varargs_preamble("REVWORDS", 2);
    ReverseWordsInText_Seperator = sep;
    ANSI_TransformTextReverseWithFunction(buff, bufc, fargs[0], ReverseWordsInText);
}

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

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

    if (nfargs == 0)
    {
        return;
    }
    if (!fn_range_check("AFTER", nfargs, 1, 2, buff, bufc))
    {
        return;
    }

    // Sanity-check arg1 and arg2.
    //
    char *bp = fargs[0];
    if (nfargs > 1)
    {
        mp = fargs[1];
        mlen = strlen(mp);
    }
    else
    {
        mp = " ";
        mlen = 1;
    }

    if ((mlen == 1) && (*mp == ' '))
    {
        bp = trim_space_sep(bp, ' ');
    }

    // Look for the target string.
    //
    while (*bp)
    {
        // Search for the first character in the target string.
        //
        cp = (char *)strchr(bp, *mp);
        if (cp == NULL)
        {
            // Not found, return empty string.
            //
            return;
        }

        // See if what follows is what we are looking for.
        //
        if (!strncmp(cp, mp, mlen))
        {
            // Yup, return what follows.
            //
            bp = cp + mlen;
            safe_str(bp, buff, bufc);
            return;
        }

        // Continue search after found first character.
        //
        bp = cp + 1;
    }

    // Ran off the end without finding it.
    //
    return;
}

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

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

    // Sanity-check arg1 and arg2.
    //
    char *bp = fargs[0];
    if (nfargs > 1)
    {
        mp = fargs[1];
        mlen = strlen(mp);
    }
    else
    {
        mp = " ";
        mlen = 1;
    }

    if ((mlen == 1) && (*mp == ' '))
    {
        bp = trim_space_sep(bp, ' ');
    }

    ip = bp;

    // Look for the target string.
    //
    while (*bp)
    {
        // Search for the first character in the target string.
        //
        cp = (char *)strchr(bp, *mp);
        if (cp == NULL)
        {
            // Not found, return entire string.
            //
            safe_str(ip, buff, bufc);
            return;
        }

        // See if what follows is what we are looking for.
        //
        if (!strncmp(cp, mp, mlen))
        {
            // Yup, return what follows.
            //
            *cp = '\0';
            safe_str(ip, buff, bufc);
            return;
        }

        // Continue search after found first character.
        //
        bp = cp + 1;
    }

    // Ran off the end without finding it.
    //
    safe_str(ip, buff, bufc);
    return;
}

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

FUNCTION(fun_max)
{
    if (nfargs <= 0)
    {
        safe_str("#-1 TOO FEW ARGUMENTS", buff, bufc);
        return;
    }

    double maximum = DBL_MIN;
    for (int i = 0; i < nfargs; i++)
    {
        double tval = safe_atof(fargs[i]);
        if (tval > maximum)
        {
            maximum = tval;
        }
    }
    fval(buff, bufc, maximum);
}

FUNCTION(fun_min)
{
    if (nfargs <= 0)
    {
        safe_str("#-1 TOO FEW ARGUMENTS", buff, bufc);
        return;
    }

    double minimum = DBL_MAX;
    for (int i = 0; i < nfargs; i++) 
    {
        double tval = safe_atof(fargs[i]);
        if (tval < minimum)
        {
            minimum = tval;
        }
    }
    fval(buff, bufc, minimum);
}

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

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

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

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

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

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

FUNCTION(fun_stats)
{
    dbref who;
    STATS statinfo;

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

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

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

    // Do length checks first.
    //
    if (strlen(fargs[0]) != strlen(fargs[1]))
    {
        safe_str("#-1 STRING LENGTHS MUST BE EQUAL", buff, bufc);
        return;
    }
    if (strlen(fargs[2]) > 1)
    {
        safe_str("#-1 TOO MANY CHARACTERS", buff, bufc);
        return;
    }

    // Find the character to look for. null character is considered a
    // space.
    //
    if (!*fargs[2])
        c = ' ';
    else
        c = *fargs[2];

    // Walk strings, copy from the appropriate string.
    //
    for (str = fargs[0], rep = fargs[1];
         *str && *rep && ((*bufc - buff) < (LBUF_SIZE-1));
         str++, rep++, (*bufc)++)
    {
        if (*str == c)
            **bufc = *rep;
        else
            **bufc = *str;
    }
    return;
}

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

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

    varargs_preamble("SPLICE", 4);

    /*
     * length checks 
     */

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

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

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

FUNCTION(fun_repeat)
{
    int times;

    times = Tiny_atol(fargs[1]);
    if (times < 1 || *fargs[0] == '\0')
    {
        // Legal but no work to do.
        //
        return;
    }
    else if (times == 1)
    {
        // It turns into a string copy.
        //
        safe_str(fargs[0], buff, bufc);
    }
    else
    {
        int len = strlen(fargs[0]);
        if (len == 1)
        {
            // It turns into a memset.
            //
            int nBufferAvailable = LBUF_SIZE - (*bufc - buff) - 1;
            if (times > nBufferAvailable)
            {
                times = nBufferAvailable;
            }

            // Fill with repeat character.
            //
            memset(*bufc, *fargs[0], times);
            *bufc += times;
        }
        else
        {
            int nSize = len*times;
            if (  times > LBUF_SIZE - 1
               || nSize > LBUF_SIZE - 1)
            {
                safe_str("#-1 STRING TOO LONG", buff, bufc);
            }
            else
            {
                int nBufferAvailable = LBUF_SIZE - (*bufc - buff) - 1;
                if (nSize > nBufferAvailable)
                {
                    nSize = nBufferAvailable;
                }
                int nFullCopies = nSize / len;
                int nPartial = nSize - nFullCopies * len;
                while (nFullCopies--)
                {
                    memcpy(*bufc, fargs[0], len);
                    *bufc += len;
                }
                if (nPartial)
                {
                    memcpy(*bufc, fargs[0], nPartial);
                    *bufc += nPartial;
                }
            }
        }
    }
}

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

FUNCTION(fun_iter)
{
    char *curr, *objstring, *buff2, *buff3, *cp, *dp, sep,
    *str;
    int first, number = 0;

    evarargs_preamble("ITER", 3);
    dp = cp = curr = alloc_lbuf("fun_iter");
    str = fargs[0];
    TinyExec(curr, &dp, 0, player, cause, EV_STRIP_CURLY | EV_FCHECK | EV_EVAL, &str, cargs, ncargs);
    *dp = '\0';
    cp = trim_space_sep(cp, sep);
    if (!*cp) {
        free_lbuf(curr);
        return;
    }
    first = 1;
    while (cp)
    {
        if (!first)
            safe_chr(' ', buff, bufc);
        first = 0;
        number++;
        objstring = split_token(&cp, sep);
        buff2 = replace_string(BOUND_VAR, objstring, fargs[1]);
        buff3 = replace_string(LISTPLACE_VAR, Tiny_ltoa_t(number), buff2);
        str = buff3;
        TinyExec(buff, bufc, 0, player, cause, EV_STRIP_CURLY | EV_FCHECK | EV_EVAL, &str, cargs, ncargs);
        free_lbuf(buff2);
        free_lbuf(buff3);
    }
    free_lbuf(curr);
}

FUNCTION(fun_list)
{
    char *curr, *objstring, *buff2, *buff3, *result, *cp, *dp, *str,
     sep;
    int number = 0;

    evarargs_preamble("LIST", 3);
    cp = curr = dp = alloc_lbuf("fun_list");
    str = fargs[0];
    TinyExec(curr, &dp, 0, player, cause, EV_STRIP_CURLY | EV_FCHECK | EV_EVAL, &str, cargs, ncargs);
    cp = trim_space_sep(cp, sep);
    if (!*cp)
    {
        free_lbuf(curr);
        return;
    }
    while (cp)
    {
        number++;
        objstring = split_token(&cp, sep);
        buff2 = replace_string(BOUND_VAR, objstring, fargs[1]);
        buff3 = replace_string(LISTPLACE_VAR, Tiny_ltoa_t(number), buff2);
        dp = result = alloc_lbuf("fun_list.2");
        str = buff3;
        TinyExec(result, &dp, 0, player, cause, EV_STRIP_CURLY | EV_FCHECK | EV_EVAL, &str, cargs, ncargs);
        *dp = '\0';
        free_lbuf(buff2);
        free_lbuf(buff3);
        notify(cause, result);
        free_lbuf(result);
    }
    free_lbuf(curr);
}

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

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

    /*
     * We need two to four arguements only 
     */

    mvarargs_preamble("FOLD", 2, 4);

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

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

    /*
     * Make sure we got a good attribute 
     */

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

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

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

    /*
     * may as well handle first case now 
     */

    if ((nfargs >= 3) && (fargs[2])) {
        clist[0] = fargs[2];
        clist[1] = split_token(&cp, sep);
        result = bp = alloc_lbuf("fun_fold");
        str = atextbuf;
        TinyExec(result, &bp, 0, player, cause, EV_STRIP_CURLY | EV_FCHECK | EV_EVAL, &str, clist, 2);
        *bp = '\0';
    } else {
        clist[0] = split_token(&cp, sep);
        clist[1] = split_token(&cp, sep);
        result = bp = alloc_lbuf("fun_fold");
        str = atextbuf;
        TinyExec(result, &bp, 0, player, cause, EV_STRIP_CURLY | EV_FCHECK | EV_EVAL, &str, clist, 2);
        *bp = '\0';
    }

    rstore = result;
    result = NULL;

    while (cp) {
        clist[0] = rstore;
        clist[1] = split_token(&cp, sep);
        StringCopy(atextbuf, atext);
        result = bp = alloc_lbuf("fun_fold");
        str = atextbuf;
        TinyExec(result, &bp, 0, player, cause, EV_STRIP_CURLY | EV_FCHECK | EV_EVAL, &str, clist, 2);
        *bp = '\0';
        StringCopy(rstore, result);
        free_lbuf(result);
    }
    safe_str(rstore, buff, bufc);
    free_lbuf(rstore);
    free_lbuf(atext);
    free_lbuf(atextbuf);
}

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

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

    varargs_preamble("FILTER", 3);

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

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

    /*
     * Make sure we got a good attribute 
     */

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

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

    cp = curr = trim_space_sep(fargs[1], sep);
    atextbuf = alloc_lbuf("fun_filter");
    first = 1;
    while (cp) {
        objstring = split_token(&cp, sep);
        StringCopy(atextbuf, atext);
        result = bp = alloc_lbuf("fun_filter");
        str = atextbuf;
        TinyExec(result, &bp, 0, player, cause, EV_STRIP_CURLY | EV_FCHECK | EV_EVAL, &str, &objstring, 1);
        *bp = '\0';
        if (!first && *result == '1')
            safe_chr(sep, buff, bufc);
        if (*result == '1') {
            safe_str(objstring, buff, bufc);
            first = 0;
        }
        free_lbuf(result);
    }
    free_lbuf(atext);
    free_lbuf(atextbuf);
}

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

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

    varargs_preamble("MAP", 3);

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

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

    /*
     * Make sure we got a good attribute 
     */

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

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

    cp = trim_space_sep(fargs[1], sep);
    atextbuf = alloc_lbuf("fun_map");
    first = 1;
    while (cp) {
        if (!first)
            safe_chr(sep, buff, bufc);
        first = 0;
        objstring = split_token(&cp, sep);
        StringCopy(atextbuf, atext);
        str = atextbuf;
        TinyExec(buff, bufc, 0, player, cause, EV_STRIP_CURLY | EV_FCHECK | EV_EVAL, &str, &objstring, 1);
    }
    free_lbuf(atext);
    free_lbuf(atextbuf);
}

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

FUNCTION(fun_edit)
{
    char *tstr;

    edit_string((char *)strip_ansi(fargs[0]), &tstr, fargs[1], fargs[2]);
    safe_str(tstr, buff, bufc);
    free_lbuf(tstr);
}

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

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

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

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

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

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

    /*
     * Set up for the search 
     */

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

    /*
     * Search for each requested thing 
     */

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

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

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

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

    safe_tprintf_str(buff, bufc, "#%d", what);
}

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

FUNCTION(fun_switch)
{
    int i;
    char *mbuff, *tbuff, *bp, *str;

    // If we don't have at least 2 args, return nothing.
    //
    if (nfargs < 2)
    {
        return;
    }

    // Evaluate the target in fargs[0].
    //
    mbuff = bp = alloc_lbuf("fun_switch");
    str = fargs[0];
    TinyExec(mbuff, &bp, 0, player, cause, EV_STRIP_CURLY | EV_FCHECK | EV_EVAL, &str, cargs, ncargs);
    *bp = '\0';

    // Loop through the patterns looking for a match.
    //
    for (i = 1; (i < nfargs - 1) && fargs[i] && fargs[i + 1]; i += 2)
    {
        tbuff = bp = alloc_lbuf("fun_switch.2");
        str = fargs[i];
        TinyExec(tbuff, &bp, 0, player, cause, EV_STRIP_CURLY | EV_FCHECK | EV_EVAL, &str, cargs, ncargs);
        *bp = '\0';
        if (quick_wild(tbuff, mbuff))
        {
            free_lbuf(tbuff);
            str = fargs[i+1];
            TinyExec(buff, bufc, 0, player, cause, EV_STRIP_CURLY | EV_FCHECK | EV_EVAL, &str, cargs, ncargs);
            free_lbuf(mbuff);
            return;
        }
        free_lbuf(tbuff);
    }
    free_lbuf(mbuff);

    // Nope, return the default if there is one.
    //
    if ((i < nfargs) && fargs[i])
    {
        str = fargs[i];
        TinyExec(buff, bufc, 0, player, cause, EV_STRIP_CURLY | EV_FCHECK | EV_EVAL, &str, cargs, ncargs);

    }
    return;
}

FUNCTION(fun_case)
{
    int i;
    char *mbuff, *bp, *str;

    // If we don't have at least 2 args, return nothing.
    //
    if (nfargs < 2)
    {
        return;
    }

    // Evaluate the target in fargs[0]
    //
    mbuff = bp = alloc_lbuf("fun_switch");
    str = fargs[0];
    TinyExec(mbuff, &bp, 0, player, cause, EV_STRIP_CURLY | EV_FCHECK | EV_EVAL, &str, cargs, ncargs);
    *bp = '\0';

    // Loop through the patterns looking for a match.
    //
    for (i = 1; (i < nfargs - 1) && fargs[i] && fargs[i + 1]; i += 2)
    {
        if (!string_compare(fargs[i], mbuff))
        {
            str = fargs[i+1];
            TinyExec(buff, bufc, 0, player, cause, EV_STRIP_CURLY | EV_FCHECK | EV_EVAL, &str, cargs, ncargs);
            free_lbuf(mbuff);
            return;
        }
    }
    free_lbuf(mbuff);

    // Nope, return the default if there is one.
    //
    if ((i < nfargs) && fargs[i])
    {
        str = fargs[i];
        TinyExec(buff, bufc, 0, player, cause, EV_STRIP_CURLY | EV_FCHECK | EV_EVAL, &str, cargs, ncargs);
    }
}

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

FUNCTION(fun_space)
{
    // Validate request.
    //
    int num;
    if (*fargs[0] == '\0')
    {
        num = 1;
    }
    else
    {
        num = Tiny_atol(fargs[0]);
        if (num == 0)
        {
            // If 'space(0)', 'space(00)', ..., then allow num == 0,
            // otherwise, we force to num to be 1.
            //
            if (!is_integer(fargs[0], 0))
            {
                num = 1;
            }
        }
        else if (num < 0)
        {
            num = 0;
        }

    }

    // Check for buffer limits.
    //
    int nBufferAvailable = LBUF_SIZE - (*bufc - buff) - 1;
    if (num > nBufferAvailable)
    {
        num = nBufferAvailable;
    }

    // Fill with spaces.
    //
    memset(*bufc, ' ', num);
    *bufc += num;
}

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

FUNCTION(fun_idle)
{
    dbref target;

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

FUNCTION(fun_conn)
{
    dbref target;

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

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

typedef struct f_record f_rec;
typedef struct i_record i_rec;

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

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

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

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

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

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

    switch (sort_type)
    {
    case ALPHANUM_LIST:

        qsort((void *)s, n, sizeof(char *), a_comp);
        break;

    case NUMERIC_LIST:

        ip = (i_rec *) MEMALLOC(n * sizeof(i_rec));
        ISOUTOFMEMORY(ip);
        for (i = 0; i < n; i++)
        {
            ip[i].str = s[i];
            ip[i].data = Tiny_atol(s[i]);
        }
        qsort((void *)ip, n, sizeof(i_rec), i_comp);
        for (i = 0; i < n; i++)
        {
            s[i] = ip[i].str;
        }
        MEMFREE(ip);
        break;

    case DBREF_LIST:
        ip = (i_rec *) MEMALLOC(n * sizeof(i_rec));
        ISOUTOFMEMORY(ip);
        for (i = 0; i < n; i++)
        {
            ip[i].str = s[i];
            ip[i].data = dbnum(s[i]);
        }
        qsort((void *)ip, n, sizeof(i_rec), i_comp);
        for (i = 0; i < n; i++)
        {
            s[i] = ip[i].str;
        }
        MEMFREE(ip);
        break;

    case FLOAT_LIST:

        fp = (f_rec *) MEMALLOC(n * sizeof(f_rec));
        ISOUTOFMEMORY(fp);
        for (i = 0; i < n; i++)
        {
            fp[i].str = s[i];
            fp[i].data = safe_atof(s[i]);
        }
        qsort((void *)fp, n, sizeof(f_rec), f_comp);
        for (i = 0; i < n; i++)
        {
            s[i] = fp[i].str;
        }
        MEMFREE(fp);
        break;
    }
}

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

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

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

    /*
     * Convert the list to an array 
     */

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

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

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

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

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

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

    i1 = i2 = 0;
    first = 1;
    oldp = *bufc;
    **bufc = '\0';

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

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

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

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

            /*
             * Skip over duplicates 
             */

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

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

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

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

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

                /*
                 * Got a match, copy it 
                 */

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

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

                /*
                 * Got a match, increment pointers 
                 */

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

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

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

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

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

        /*
         * Copy remainder of list1 
         */

        while (i1 < n1) {
            if (!first)
                safe_chr(sep, buff, bufc);
            first = 0;
            safe_str(ptrs1[i1], buff, bufc);
            oldp = ptrs1[i1];
            i1++;
            while ((i1 < n1) && !strcmp(ptrs1[i1], oldp))
                i1++;
        }
    }
    free_lbuf(list1);
    free_lbuf(list2);
    return;
}

FUNCTION(fun_setunion)
{
    char sep;

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

FUNCTION(fun_setdiff)
{
    char sep;

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

FUNCTION(fun_setinter)
{
    char sep;

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

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

FUNCTION(fun_ljust)
{
    char sep;

    varargs_preamble("LJUST", 3);
    int nPad = Tiny_atol(fargs[1]) - strlen(strip_ansi(fargs[0]));

    safe_str(fargs[0], buff, bufc);

    if (nPad > 0)
    {
        int nBufferAvailable = LBUF_SIZE - (*bufc - buff) - 1;
        if (nPad > nBufferAvailable)
        {
            nPad = nBufferAvailable;
        }

        // Fill with padding character.
        //
        memset(*bufc, sep, nPad);
        *bufc += nPad;
    }
}

FUNCTION(fun_rjust)
{
    char sep;

    varargs_preamble("RJUST", 3);
    int nPad = Tiny_atol(fargs[1]) - strlen(strip_ansi(fargs[0]));

    if (nPad > 0)
    {
        int nBufferAvailable = LBUF_SIZE - (*bufc - buff) - 1;
        if (nPad > nBufferAvailable)
        {
            nPad = nBufferAvailable;
        }

        // Fill with padding character.
        //
        memset(*bufc, sep, nPad);
        *bufc += nPad;
    }
    safe_str(fargs[0], buff, bufc);
}

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

    varargs_preamble("CENTER", 3);

    width = Tiny_atol(fargs[1]);
    len = strlen((char *)strip_ansi(fargs[0]));

    if (width > LBUF_SIZE)
    {
        safe_str("#-1 OUT OF RANGE", buff, bufc);
        return;
    }
    
    if (len >= width)
    {
        safe_str(fargs[0], buff, bufc);
        return;
    }
    
    // The text is padded with the specified fill character before and
    // after. The length of the suffix padding is always equal to or one
    // greater than the length of the prefix padding.
    //
    lead_chrs = (width - len)/2;
    for (i = 0; i < lead_chrs; i++)
    {
        safe_chr(sep, buff, bufc);
    }

    safe_str(fargs[0], buff, bufc);

    trail_chrs = width - lead_chrs - len;
    for (i = 0; i < trail_chrs; i++)
    {
        safe_chr(sep, buff, bufc);
    }
}

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

FUNCTION(fun_setq)
{
    int regnum;

    regnum = Tiny_atol(fargs[0]);
    if ((regnum < 0) || (regnum >= MAX_GLOBAL_REGS))
    {
        safe_str("#-1 INVALID GLOBAL REGISTER", buff, bufc);
    }
    else
    {
        if (!mudstate.global_regs[regnum])
        {
            mudstate.global_regs[regnum] = alloc_lbuf("fun_setq");
        }
        int n = strlen(fargs[1]);
        memcpy(mudstate.global_regs[regnum], fargs[1], n+1);
        mudstate.glob_reg_len[regnum] = n;
    }
}

FUNCTION(fun_setr)
{
    int regnum;

    regnum = Tiny_atol(fargs[0]);
    int n;
    if ((regnum < 0) || (regnum >= MAX_GLOBAL_REGS))
    {
        safe_str("#-1 INVALID GLOBAL REGISTER", buff, bufc);
        return;
    }
    else
    {
        if (!mudstate.global_regs[regnum])
        {
            mudstate.global_regs[regnum] = alloc_lbuf("fun_setq");
        }
        n = strlen(fargs[1]);
        memcpy(mudstate.global_regs[regnum], fargs[1], n+1);
        mudstate.glob_reg_len[regnum] = n;
    }
    safe_copy_buf(fargs[1], n, buff, bufc, LBUF_SIZE-1);
}

FUNCTION(fun_r)
{
    int regnum;

    regnum = Tiny_atol(fargs[0]);
    if ((regnum < 0) || (regnum >= MAX_GLOBAL_REGS))
    {
        safe_str("#-1 INVALID GLOBAL REGISTER", buff, bufc);
    }
    else if (mudstate.global_regs[regnum])
    {
        safe_copy_buf(mudstate.global_regs[regnum],
            mudstate.glob_reg_len[regnum], buff, bufc, LBUF_SIZE-1);
    }
}

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

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

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

FUNCTION(fun_isdbref)
{
    char *p;
    dbref dbitem;
    int ch = '0';

    p = fargs[0];
    if (*p++ == NUMBER_TOKEN)
    {
        dbitem = parse_dbref(p);
        if (Good_obj(dbitem))
        {
            ch = '1';
        }
    }
    safe_chr(ch, buff, bufc);
}

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

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

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

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

/* ---------------------------------------------------------------------------
 * flist: List of existing functions in alphabetical order.
 */

FUN flist[] =
{
    {"ABS",      fun_abs,      1,  0,          CA_PUBLIC},
    {"ACOS",     fun_acos,     1,  0,          CA_PUBLIC},
    {"ADD",      fun_add,      0,  FN_VARARGS, CA_PUBLIC},
    {"AFTER",    fun_after,    0,  FN_VARARGS, CA_PUBLIC},
    {"ALPHAMAX", fun_alphamax, 0,  FN_VARARGS, CA_PUBLIC},
    {"ALPHAMIN", fun_alphamin, 0,  FN_VARARGS, CA_PUBLIC},
    {"AND",      fun_and,      0,  FN_VARARGS, CA_PUBLIC},
    {"ANDFLAGS", fun_andflags, 2,  0,          CA_PUBLIC},
    {"ANSI",     fun_ansi,     2,  0,          CA_PUBLIC},
    {"APOSS",    fun_aposs,    1,  0,          CA_PUBLIC},
    {"ART",      fun_art,      1,  0,          CA_PUBLIC},
    {"ASIN",     fun_asin,     1,  0,          CA_PUBLIC},
    {"ATAN",     fun_atan,     1,  0,          CA_PUBLIC},
    {"BAND",     fun_band,     2,  0,          CA_PUBLIC},
    {"BEEP",     fun_beep,     0,  0,          CA_WIZARD},
    {"BEFORE",   fun_before,   0,  FN_VARARGS, CA_PUBLIC},
    {"BNAND",    fun_bnand,    2,  0,          CA_PUBLIC},
    {"BOR",      fun_bor,      2,  0,          CA_PUBLIC},
#ifdef WOD_REALMS
    {"CANSEE",   fun_cansee,   0,  FN_VARARGS, CA_PUBLIC},
#endif
    {"CAPSTR",   fun_capstr,  -1,  0,          CA_PUBLIC},
    {"CAT",      fun_cat,      0,  FN_VARARGS, CA_PUBLIC},
    {"CEIL",     fun_ceil,     1,  0,          CA_PUBLIC},
    {"CENTER",   fun_center,   0,  FN_VARARGS, CA_PUBLIC},
    {"CHANNELS", fun_channels, 0,  0,          CA_PUBLIC},
    {"CHILDREN", fun_children, 1,  0,          CA_PUBLIC},
    {"COLUMNS",  fun_columns,  0,  FN_VARARGS, CA_PUBLIC},
    {"COMALIAS", fun_comalias, 2,  0,          CA_PUBLIC},
    {"COMP",     fun_comp,     2,  0,          CA_PUBLIC},
    {"COMTITLE", fun_comtitle, 2,  0,          CA_PUBLIC},
    {"CON",      fun_con,      1,  0,          CA_PUBLIC},
    {"CONN",     fun_conn,     1,  0,          CA_PUBLIC},
    {"CONTROLS", fun_controls, 2,  0,          CA_PUBLIC},
    {"CONVSECS", fun_convsecs, 0,  FN_VARARGS, CA_PUBLIC},
    {"CONVTIME", fun_convtime, 0,  FN_VARARGS, CA_PUBLIC},
    {"COS",      fun_cos,      1,  0,          CA_PUBLIC},
    {"CREATE",   fun_create,   0,  FN_VARARGS, CA_PUBLIC},
    {"CWHO",     fun_cwho,     1,  0,          CA_PUBLIC},
    {"DEC",      fun_dec,      1,  0,          CA_PUBLIC},
    {"DECRYPT",  fun_decrypt,  2,  0,          CA_PUBLIC},
    {"DEFAULT",  fun_default,  2,  FN_NO_EVAL, CA_PUBLIC},
    {"DELETE",   fun_delete,   3,  0,          CA_PUBLIC},
    {"DIE",      fun_die,      2,  0,          CA_PUBLIC},
    {"DIST2D",   fun_dist2d,   4,  0,          CA_PUBLIC},
    {"DIST3D",   fun_dist3d,   6,  0,          CA_PUBLIC},
    {"DIV",      fun_div,      2,  0,          CA_PUBLIC},
    {"DOING",    fun_doing,    1,  0,          CA_PUBLIC},
    {"E",        fun_e,        0,  0,          CA_PUBLIC},
    {"EDEFAULT", fun_edefault, 2,  FN_NO_EVAL, CA_PUBLIC},
    {"EDIT",     fun_edit,     3,  0,          CA_PUBLIC},
    {"ELEMENTS", fun_elements, 0,  FN_VARARGS, CA_PUBLIC},
    {"ELOCK",    fun_elock,    2,  0,          CA_PUBLIC},
    {"EMPTY",    fun_empty,    0,  FN_VARARGS, CA_PUBLIC},
    {"ENCRYPT",  fun_encrypt,  2,  0,          CA_PUBLIC},
    {"EQ",       fun_eq,       2,  0,          CA_PUBLIC},
    {"ESCAPE",   fun_escape,  -1,  0,          CA_PUBLIC},
    {"EXIT",     fun_exit,     1,  0,          CA_PUBLIC},
    {"EXP",      fun_exp,      1,  0,          CA_PUBLIC},
    {"EXTRACT",  fun_extract,  0,  FN_VARARGS, CA_PUBLIC},
    {"EVAL",     fun_eval,     0,  FN_VARARGS, CA_PUBLIC},
    {"SUBEVAL",  fun_subeval,  1,  0,          CA_PUBLIC},
    {"FDIV",     fun_fdiv,     2,  0,          CA_PUBLIC},
    {"FILTER",   fun_filter,   0,  FN_VARARGS, CA_PUBLIC},
    {"FINDABLE", fun_findable, 2,  0,          CA_PUBLIC},
    {"FIRST",    fun_first,    0,  FN_VARARGS, CA_PUBLIC},
    {"FLAGS",    fun_flags,    1,  0,          CA_PUBLIC},
    {"FLOOR",    fun_floor,    1,  0,          CA_PUBLIC},
    {"FOLD",     fun_fold,     0,  FN_VARARGS, CA_PUBLIC},
    {"FOREACH",  fun_foreach,  0,  FN_VARARGS, CA_PUBLIC},
    {"FULLNAME", fun_fullname, 1,  0,          CA_PUBLIC},
    {"GET",      fun_get,      1,  0,          CA_PUBLIC},
    {"GET_EVAL", fun_get_eval, 1,  0,          CA_PUBLIC},
    {"GRAB",     fun_grab,     0,  FN_VARARGS, CA_PUBLIC},
    {"GREP",     fun_grep,     3,  0,          CA_PUBLIC},
    {"GREPI",    fun_grepi,    3,  0,          CA_PUBLIC},
    {"GT",       fun_gt,       2,  0,          CA_PUBLIC},
    {"GTE",      fun_gte,      2,  0,          CA_PUBLIC},
    {"HASATTR",  fun_hasattr,  2,  0,          CA_PUBLIC},
    {"HASATTRP", fun_hasattrp, 2,  0,          CA_PUBLIC},
    {"HASFLAG",  fun_hasflag,  2,  0,          CA_PUBLIC},
    {"HASPOWER", fun_haspower, 2,  0,          CA_PUBLIC},
    {"HASTYPE",  fun_hastype,  2,  0,          CA_PUBLIC},
    {"HOME",     fun_home,     1,  0,          CA_PUBLIC},
    {"IADD",     fun_iadd,     0,  FN_VARARGS, CA_PUBLIC},
    {"IDIV",     fun_div,      2,  0,          CA_PUBLIC},
    {"IDLE",     fun_idle,     1,  0,          CA_PUBLIC},
    {"IFELSE",   fun_ifelse,   3,  FN_NO_EVAL, CA_PUBLIC},
    {"IMUL",     fun_imul,     0,  FN_VARARGS, CA_PUBLIC},
    {"INC",      fun_inc,      1,  0,          CA_PUBLIC},
    {"INDEX",    fun_index,    4,  0,          CA_PUBLIC},
    {"INSERT",   fun_insert,   0,  FN_VARARGS, CA_PUBLIC},
    {"INZONE",   fun_inzone,   1,  0,          CA_PUBLIC},
    {"ISDBREF",  fun_isdbref,  1,  0,          CA_PUBLIC},
    {"ISNUM",    fun_isnum,    1,  0,          CA_PUBLIC},
    {"ISUB",     fun_isub,     2,  0,          CA_PUBLIC},
    {"ISWORD",   fun_isword,   1,  0,          CA_PUBLIC},
    {"ITER",     fun_iter,     0,  FN_VARARGS|FN_NO_EVAL, CA_PUBLIC},
    {"LAST",     fun_last,     0,  FN_VARARGS, CA_PUBLIC},
    {"LATTR",    fun_lattr,    1,  0,          CA_PUBLIC},
    {"LCON",     fun_lcon,     1,  0,          CA_PUBLIC},
    {"LCSTR",    fun_lcstr,   -1,  0,          CA_PUBLIC},
    {"LDELETE",  fun_ldelete,  0,  FN_VARARGS, CA_PUBLIC},
    {"LEXITS",   fun_lexits,   1,  0,          CA_PUBLIC},
    {"LPARENT",  fun_lparent,  1,  0,          CA_PUBLIC}, 
    {"LIST",     fun_list,     0,  FN_VARARGS|FN_NO_EVAL, CA_PUBLIC}, 
    {"LIT",      fun_lit,      1,  FN_NO_EVAL, CA_PUBLIC},
    {"LJUST",    fun_ljust,    0,  FN_VARARGS, CA_PUBLIC},
    {"LINK",     fun_link,     2,  0,          CA_PUBLIC},
    {"LN",       fun_ln,       1,  0,          CA_PUBLIC},
    {"LNUM",     fun_lnum,     0,  FN_VARARGS, CA_PUBLIC},
    {"LOC",      fun_loc,      1,  0,          CA_PUBLIC},
    {"LOCATE",   fun_locate,   3,  0,          CA_PUBLIC},
    {"LOCK",     fun_lock,     1,  0,          CA_PUBLIC},
    {"LOG",      fun_log,      1,  0,          CA_PUBLIC},
    {"LPOS",     fun_lpos,     2,  0,          CA_PUBLIC},
    {"LSTACK",   fun_lstack,   0,  FN_VARARGS, CA_PUBLIC},
    {"LT",       fun_lt,       2,  0,          CA_PUBLIC},
    {"LTE",      fun_lte,      2,  0,          CA_PUBLIC},
    {"LWHO",     fun_lwho,     0,  0,          CA_PUBLIC},
    {"MAIL",     fun_mail,     0,  FN_VARARGS, CA_PUBLIC},
    {"MAILFROM", fun_mailfrom, 0,  FN_VARARGS, CA_PUBLIC},
    {"MAP",      fun_map,      0,  FN_VARARGS, CA_PUBLIC},
    {"MATCH",    fun_match,    0,  FN_VARARGS, CA_PUBLIC},
    {"MATCHALL", fun_matchall, 0,  FN_VARARGS, CA_PUBLIC},
    {"MAX",      fun_max,      0,  FN_VARARGS, CA_PUBLIC},
    {"MEMBER",   fun_member,   0,  FN_VARARGS, CA_PUBLIC},
    {"MERGE",    fun_merge,    3,  0,          CA_PUBLIC},
    {"MID",      fun_mid,      3,  0,          CA_PUBLIC},
    {"MIN",      fun_min,      0,  FN_VARARGS, CA_PUBLIC},
    {"MIX",      fun_mix,      0,  FN_VARARGS, CA_PUBLIC},
    {"MOD",      fun_mod,      2,  0,          CA_PUBLIC},
    {"MONEY",    fun_money,    1,  0,          CA_PUBLIC},
    {"MOTD",     fun_motd,     0,  0,          CA_PUBLIC},
    {"MUDNAME",  fun_mudname,  0,  0,          CA_PUBLIC},
    {"MUL",      fun_mul,      0,  FN_VARARGS, CA_PUBLIC},
    {"MUNGE",    fun_munge,    0,  FN_VARARGS, CA_PUBLIC},
    {"NAME",     fun_name,     1,  0,          CA_PUBLIC},
    {"NEARBY",   fun_nearby,   2,  0,          CA_PUBLIC},
    {"NEQ",      fun_neq,      2,  0,          CA_PUBLIC},
    {"NEXT",     fun_next,     1,  0,          CA_PUBLIC},
    {"NOT",      fun_not,      1,  0,          CA_PUBLIC},
    {"NUM",      fun_num,      1,  0,          CA_PUBLIC},
    {"ITEMS",    fun_items,    0,  FN_VARARGS, CA_PUBLIC},
    {"OBJ",      fun_obj,      1,  0,          CA_PUBLIC},
    {"OBJEVAL",  fun_objeval,  2,  FN_NO_EVAL, CA_PUBLIC},
    {"OBJMEM",   fun_objmem,   1,  0,          CA_PUBLIC},
    {"OR",       fun_or,       0,  FN_VARARGS, CA_PUBLIC},
    {"ORFLAGS",  fun_orflags,  2,  0,          CA_PUBLIC},
    {"OWNER",    fun_owner,    1,  0,          CA_PUBLIC},
    {"PARENT",   fun_parent,   1,  0,          CA_PUBLIC},
    {"PARSE",    fun_parse,    0,  FN_VARARGS|FN_NO_EVAL, CA_PUBLIC},
    {"PEEK",     fun_peek,     0,  FN_VARARGS, CA_PUBLIC},
    {"PEMIT",    fun_pemit,    2,  0,          CA_PUBLIC},
    {"PI",       fun_pi,       0,  0,          CA_PUBLIC},
    {"PLAYMEM",  fun_playmem,  1,  0,          CA_PUBLIC},
    {"PMATCH",   fun_pmatch,   1,  0,          CA_PUBLIC},
    {"POLL",     fun_poll,     0,  0,          CA_PUBLIC},
    {"POP",      fun_pop,      0,  FN_VARARGS, CA_PUBLIC},
    {"PORTS",    fun_ports,    1,  0,          CA_PUBLIC},
    {"POS",      fun_pos,      2,  0,          CA_PUBLIC},
    {"POSS",     fun_poss,     1,  0,          CA_PUBLIC},
    {"POWER",    fun_power,    2,  0,          CA_PUBLIC},
    {"PUSH",     fun_push,     0,  FN_VARARGS, CA_PUBLIC},
    {"CASE",     fun_case,     0,  FN_VARARGS|FN_NO_EVAL, CA_PUBLIC},
    {"R",        fun_r,        1,  0,          CA_PUBLIC},
    {"RAND",     fun_rand,     1,  0,          CA_PUBLIC},
    {"REGMATCH", fun_regmatch, 0,  FN_VARARGS, CA_PUBLIC},
    {"REMOVE",   fun_remove,   0,  FN_VARARGS, CA_PUBLIC},
    {"REPEAT",   fun_repeat,   2,  0,          CA_PUBLIC},
    {"REPLACE",  fun_replace,  0,  FN_VARARGS, CA_PUBLIC},
    {"REST",     fun_rest,     0,  FN_VARARGS, CA_PUBLIC},
    {"REVERSE",  fun_reverse, -1,  0,          CA_PUBLIC},
    {"REVWORDS", fun_revwords, 0,  FN_VARARGS, CA_PUBLIC},
    {"RJUST",    fun_rjust,    0,  FN_VARARGS, CA_PUBLIC},
    {"RLOC",     fun_rloc,     2,  0,          CA_PUBLIC},
    {"ROOM",     fun_room,     1,  0,          CA_PUBLIC},
    {"ROUND",    fun_round,    2,  0,          CA_PUBLIC},
    {"S",        fun_s,       -1,  0,          CA_PUBLIC},
    {"SCRAMBLE", fun_scramble, 1,  0,          CA_PUBLIC},
    {"SEARCH",   fun_search,  -1,  0,          CA_PUBLIC},
    {"SECS",     fun_secs,     0,  FN_VARARGS, CA_PUBLIC},
    {"SECURE",   fun_secure,  -1,  0,          CA_PUBLIC},
    {"SET",      fun_set,      2,  0,          CA_PUBLIC},
    {"SETDIFF",  fun_setdiff,  0,  FN_VARARGS, CA_PUBLIC},
    {"SETINTER", fun_setinter, 0,  FN_VARARGS, CA_PUBLIC},
    {"SETQ",     fun_setq,     2,  0,          CA_PUBLIC},
    {"SETR",     fun_setr,     2,  0,          CA_PUBLIC},
    {"SETUNION", fun_setunion, 0,  FN_VARARGS, CA_PUBLIC},
    {"SHL",      fun_shl,      2,  0,          CA_PUBLIC},
    {"SHR",      fun_shr,      2,  0,          CA_PUBLIC},
    {"SHUFFLE",  fun_shuffle,  0,  FN_VARARGS, CA_PUBLIC},
    {"SIGN",     fun_sign,     1,  0,          CA_PUBLIC},
    {"SIN",      fun_sin,      1,  0,          CA_PUBLIC},
    {"SORT",     fun_sort,     0,  FN_VARARGS, CA_PUBLIC},
    {"SORTBY",   fun_sortby,   0,  FN_VARARGS, CA_PUBLIC},
    {"SPACE",    fun_space,    1,  0,          CA_PUBLIC},
    {"SPLICE",   fun_splice,   0,  FN_VARARGS, CA_PUBLIC},
    {"SQRT",     fun_sqrt,     1,  0,          CA_PUBLIC},
    {"SQUISH",   fun_squish,   1,  0,          CA_PUBLIC},
    {"STARTTIME",fun_starttime,0,  0,          CA_PUBLIC},
    {"STATS",    fun_stats,    1,  0,          CA_PUBLIC},
    {"STRCAT",   fun_strcat,   0,  FN_VARARGS, CA_PUBLIC},
    {"STRIPANSI",fun_stripansi,1,  0,          CA_PUBLIC},
    {"STRLEN",   fun_strlen,  -1,  0,          CA_PUBLIC},
    {"STRMATCH", fun_strmatch, 2,  0,          CA_PUBLIC},
    {"STRTRUNC", fun_strtrunc, 2,  0,          CA_PUBLIC},
    {"SUB",      fun_sub,      2,  0,          CA_PUBLIC},
    {"SUBJ",     fun_subj,     1,  0,          CA_PUBLIC},
    {"SWITCH",   fun_switch,   0,  FN_VARARGS|FN_NO_EVAL, CA_PUBLIC},
    {"TAN",      fun_tan,      1,  0,          CA_PUBLIC},
    {"TEL",      fun_tel,      2,  0,          CA_PUBLIC},
    {"TIME",     fun_time,     0,  FN_VARARGS, CA_PUBLIC},
    {"TRANSLATE",fun_translate,2,  0,          CA_PUBLIC},
    {"TRIM",     fun_trim,     0,  FN_VARARGS, CA_PUBLIC},
    {"TRUNC",    fun_trunc,    1,  0,          CA_PUBLIC},
    {"TYPE",     fun_type,     1,  0,          CA_PUBLIC},
    {"U",        fun_u,        0,  FN_VARARGS, CA_PUBLIC},
    {"UCSTR",    fun_ucstr,   -1,  0,          CA_PUBLIC},
    {"UDEFAULT", fun_udefault, 0,  FN_VARARGS|FN_NO_EVAL, CA_PUBLIC},
    {"ULOCAL",   fun_ulocal,   0,  FN_VARARGS, CA_PUBLIC},
    {"V",        fun_v,        1,  0,          CA_PUBLIC},
    {"VADD",     fun_vadd,     0,  FN_VARARGS, CA_PUBLIC},
    {"VALID",    fun_valid,    2,  0,          CA_PUBLIC},
    {"VCROSS",   fun_vcross,   0,  FN_VARARGS, CA_PUBLIC},
    {"VDIM",     fun_vdim,     0,  FN_VARARGS, CA_PUBLIC},
    {"VDOT",     fun_vdot,     0,  FN_VARARGS, CA_PUBLIC},
    {"VERSION",  fun_version,  0,  0,          CA_PUBLIC},
    {"VISIBLE",  fun_visible,  2,  0,          CA_PUBLIC},
    {"VMAG",     fun_vmag,     0,  FN_VARARGS, CA_PUBLIC},
    {"VMUL",     fun_vmul,     0,  FN_VARARGS, CA_PUBLIC},
    {"VSUB",     fun_vsub,     0,  FN_VARARGS, CA_PUBLIC},
    {"VUNIT",    fun_vunit,    0,  FN_VARARGS, CA_PUBLIC},
    {"WHERE",    fun_where,    1,  0,          CA_PUBLIC},
    {"WORDPOS",  fun_wordpos,  0,  FN_VARARGS, CA_PUBLIC},
    {"WORDS",    fun_words,    0,  FN_VARARGS, CA_PUBLIC},
    {"XGET",     fun_xget,     2,  0,          CA_PUBLIC},
    {"XOR",      fun_xor,      0,  FN_VARARGS, CA_PUBLIC},
    {"ZFUN",     fun_zfun,     0,  FN_VARARGS, CA_PUBLIC},
    {"ZONE",     fun_zone,     1,  0,          CA_PUBLIC},
    {"ZWHO",     fun_zwho,     1,  0,          CA_PUBLIC},
    // Added by D.Piper (del@delphinian.com) 1997 and 2000-APR
    //
    {"DIGITTIME",fun_digittime,1,  0,          CA_PUBLIC},
    {"SINGLETIME", fun_singletime, 1,  0,      CA_PUBLIC},
    {"EXPTIME",  fun_exptime,  1,  0,          CA_PUBLIC},
    {"WRITETIME",fun_writetime,1,  0,          CA_PUBLIC},
    {"CMDS",     fun_cmds,     1,  0,          CA_PUBLIC},
    {"STARTSECS",fun_startsecs,0,  0,          CA_PUBLIC},
    {"LFLAGS",   fun_lflags,   1,  0,          CA_PUBLIC},
    {"LATTRCMDS",fun_lattrcmds,1,  0,          CA_PUBLIC},
    {"LCMDS",    fun_lcmds,    1,  0,          CA_PUBLIC},
    {"CONNTOTAL",fun_conntotal,1,  0,          CA_PUBLIC},
    {"CONNMAX",  fun_connmax,  1,  0,          CA_PUBLIC},
    {"CONNLAST", fun_connlast, 1,  0,          CA_PUBLIC},
    {"CONNNUM",  fun_connnum,  1,  0,          CA_PUBLIC},
    {"CONNLEFT", fun_connleft, 1,  0,          CA_PUBLIC},
    {NULL,       NULL,         0,  0,          0}
};


void NDECL(init_functab)
{
    char *buff = alloc_sbuf("init_functab");
    for (FUN *fp = flist; fp->name; fp++)
    {
        memcpy(buff, fp->name, SBUF_SIZE);
        buff[SBUF_SIZE-1] = '\0';
        _strlwr(buff);
        hashaddLEN(buff, strlen(buff), (int *)fp, &mudstate.func_htab);
    }
    free_sbuf(buff);
    ufun_head = NULL;
}

void do_function(dbref player, dbref cause, int key, char *fname, char *target)
{
    UFUN *ufp, *ufp2;
    ATTR *ap;
    char *np, *bp;
    int atr, aflags;
    dbref obj, aowner;

    if ((key & FN_LIST) || !fname || *fname == '\0')
    {
        notify(player, tprintf("%-28s   %-8s  %-30s Flgs",
            "Function Name", "DBref#", "Attribute"));
        notify(player, tprintf("%28s   %8s  %30s %4s",
            "----------------------------", "--------",
            "------------------------------", " -- "));
        
        int count = 0;
        for (ufp2 = ufun_head; ufp2; ufp2 = ufp2->next)
        {
            ap = atr_num(ufp2->atr);
            notify(player, tprintf("%-28.28s   #%-7d  %-30.30s  %c%c",
                ufp2->name, ufp2->obj, ap->name, ((ufp2->flags & FN_PRIV) ? 'W' : '-'),
                ((ufp2->flags & FN_PRES) ? 'p' : '-')));
            count++;
        }
        
        notify(player, tprintf("%28s   %8s  %30s %4s",
            "----------------------------", "--------",
            "------------------------------", " -- "));

        notify(player, tprintf("Total User-Defined Functions: %d", count));
        return;
    }                                                                                                             

    // Make a local uppercase copy of the function name.
    //
    bp = np = alloc_sbuf("add_user_func");
    safe_sb_str(fname, np, &bp);
    *bp = '\0';
    _strlwr(np);

    // Verify that the function doesn't exist in the builtin table.
    //
    if (hashfindLEN(np, strlen(np), &mudstate.func_htab) != NULL)
    {
        notify_quiet(player, "Function already defined in builtin function table.");
        free_sbuf(np);
        return;
    }

    // Make sure the target object exists.
    //
    if (!parse_attrib(player, target, &obj, &atr))
    {
        notify_quiet(player, "I don't see that here.");
        free_sbuf(np);
        return;
    }


    // Make sure the attribute exists.
    //
    if (atr == NOTHING)
    {
        notify_quiet(player, "No such attribute.");
        free_sbuf(np);
        return;
    }

    // Make sure attribute is readably by me.
    //
    ap = atr_num(atr);
    if (!ap)
    {
        notify_quiet(player, "No such attribute.");
        free_sbuf(np);
        return;
    }
    atr_get_info(obj, atr, &aowner, &aflags);
    if (!See_attr(player, obj, ap, aowner, aflags))
    {
        notify_quiet(player, NOPERM_MESSAGE);
        free_sbuf(np);
        return;
    }

    // Privileged functions require you control the obj.
    //
    if ((key & FN_PRIV) && !Controls(player, obj))
    {
        notify_quiet(player, NOPERM_MESSAGE);
        free_sbuf(np);
        return;
    }

    // See if function already exists.  If so, redefine it.
    //
    ufp = (UFUN *) hashfindLEN(np, strlen(np), &mudstate.ufunc_htab);

    if (!ufp)
    {
        ufp = (UFUN *) MEMALLOC(sizeof(UFUN));
        ISOUTOFMEMORY(ufp);
        ufp->name = StringClone(np);
        _strupr(ufp->name);
        ufp->obj = obj;
        ufp->atr = atr;
        ufp->perms = CA_PUBLIC;
        ufp->next = NULL;
        if (!ufun_head)
        {
            ufun_head = ufp;
        }
        else
        {
            for (ufp2 = ufun_head; ufp2->next; ufp2 = ufp2->next)
            {
                // Nothing
                ;
            }
            ufp2->next = ufp;
        }
        hashaddLEN(np, strlen(np), (int *)ufp, &mudstate.ufunc_htab);
    }
    ufp->obj = obj;
    ufp->atr = atr;
    ufp->flags = key;
    free_sbuf(np);
    if (!Quiet(player))
    {
        notify_quiet(player, tprintf("Function %s defined.", fname));
    }
}

/*
 * ---------------------------------------------------------------------------
 * * list_functable: List available functions.
 */

void list_functable(dbref player)
{
    FUN *fp;
    UFUN *ufp;
    char *buf, *bp, *cp;

    buf = alloc_lbuf("list_functable");
    bp = buf;
    for (cp = (char *)"Functions:"; *cp; cp++)
        *bp++ = *cp;
    for (fp = flist; fp->name; fp++) {
        if (check_access(player, fp->perms)) {
            *bp++ = ' ';
            for (cp = (char *)(fp->name); *cp; cp++)
                *bp++ = *cp;
        }
    }
    for (ufp = ufun_head; ufp; ufp = ufp->next) {
        if (check_access(player, ufp->perms)) {
            *bp++ = ' ';
            for (cp = (char *)(ufp->name); *cp; cp++)
                *bp++ = *cp;
        }
    }
    *bp = '\0';
    notify(player, buf);
    free_lbuf(buf);
}

/*
 * ---------------------------------------------------------------------------
 * * cf_func_access: set access on functions
 */

CF_HAND(cf_func_access)
{
    FUN *fp;
    UFUN *ufp;
    char *ap;

    for (ap = str; *ap && !Tiny_IsSpace[(unsigned char)*ap]; ap++) ;
    if (*ap)
        *ap++ = '\0';

    for (fp = flist; fp->name; fp++)
    {
        if (!string_compare(fp->name, str))
        {
            return (cf_modify_bits(&fp->perms, ap, extra, player, cmd));
        }
    }
    for (ufp = ufun_head; ufp; ufp = ufp->next)
    {
        if (!string_compare(ufp->name, str))
        {
            return (cf_modify_bits(&ufp->perms, ap, extra, player, cmd));
        }
    }
    cf_log_notfound(player, cmd, "Function", str);
    return -1;
}

// Some libraries go nuts...just because you force feed them lots of ASCII.
//
#define ATOF_LIMIT 100
double safe_atof(char *szString)
{
    double ret;

    int n = strlen(szString);
    if (n > ATOF_LIMIT)
    {
        int ch = szString[ATOF_LIMIT-1];
        szString[ATOF_LIMIT-1] = '\0';
        ret = atof(szString);
        szString[ATOF_LIMIT-1] = ch;
    }
    else
    {
        ret = atof(szString);
    }
    return ret;
}

/////////////////////////////////////////////////////////////////
// Function : iadd(Arg[0], Arg[1],..,Arg[n])
//
// Written by : Chris Rouse (Seraphim) 04/04/2000
/////////////////////////////////////////////////////////////////
 
FUNCTION(fun_iadd)
{
    if (nfargs <= 0)
    {
        safe_str("#-1 TOO FEW ARGUMENTS", buff, bufc);
        return;
    }
    INT64 sum = 0;
 
    for (int i = 0; i < nfargs; i++)
    {
        sum += Tiny_atoi64(fargs[i]);
    }
    safe_str(Tiny_i64toa_t(sum), buff, bufc);
}
 
/////////////////////////////////////////////////////////////////
// Function : isub(Arg[0], Arg[1])
//
// Written by : Chris Rouse (Seraphim) 04/04/2000
/////////////////////////////////////////////////////////////////
 
FUNCTION(fun_isub)
{
    INT64 diff = Tiny_atoi64(fargs[0]) - Tiny_atoi64(fargs[1]);
    safe_str(Tiny_i64toa_t(diff), buff, bufc);
}

/////////////////////////////////////////////////////////////////
// Function : imul(Arg[0], Arg[1], ... , Arg[n])
//
// Written by : Chris Rouse (Seraphim) 04/04/2000
/////////////////////////////////////////////////////////////////
 
FUNCTION(fun_imul)
{
    if (nfargs <= 0)
    {
        safe_str("#-1 TOO FEW ARGUMENTS", buff, bufc);
        return;
    }
    INT64 prod = 1;
    for (int i = 0; i < nfargs; i++)
    {
        prod *= Tiny_atoi64(fargs[i]);
    }
    safe_str(Tiny_i64toa_t(prod), buff, bufc);
}

typedef struct
{
    int  iBase;
    char chLetter;
    int  nName;
    char *pName;

} RADIX_ENTRY;

#define N_RADIX_ENTRIES 4
RADIX_ENTRY reTable[N_RADIX_ENTRIES] =
{
    { 86400, 'd', 3, "day"    },
    {  3600, 'h', 4, "hour"   },
    {    60, 'm', 6, "minute" },
    {     1, 's', 6, "second" }
};

#define IDAYS    0
#define IHOURS   1
#define IMINUTES 2
#define ISECONDS 3

// This routine supports most of the time formats using the above
// table.
//
void GeneralTimeConversion
(
    char *Buffer,
    long Seconds,
    int iStartBase,
    int iEndBase,
    BOOL bSingleTerm, 
    BOOL bNames
)
{
    char *p = Buffer;

    if (Seconds < 0)
    {
        Seconds = 0;
    }

    for (int i = iStartBase; i <= iEndBase; i++)
    {
        if (reTable[i].iBase <= Seconds || i == iEndBase)
        {
            int iValue;
            if (bSingleTerm)
            {
                // Round to the nearest.
                //
                iValue = (Seconds + (reTable[i].iBase >> 1))/reTable[i].iBase;
            }
            else
            {
                // Division and remainder.
                //
                iValue = Seconds/reTable[i].iBase;
                Seconds -= iValue * reTable[i].iBase;
            }

            if (iValue != 0 || i == iEndBase)
            {
                if (p != Buffer)
                {
                    *p++ = ' ';
                }
                p += Tiny_ltoa(iValue, p);
                if (bNames)
                {
                    // Use the names with the correct pluralization.
                    //
                    *p++ = ' ';
                    memcpy(p, reTable[i].pName, reTable[i].nName);
                    p += reTable[i].nName;
                    if (iValue != 1)
                    {
                        // More than one or zero.
                        //
                        *p++ = 's';
                    }
                }
                else
                {
                    *p++ = reTable[i].chLetter;
                }
            }
            if (bSingleTerm)
            {
                break;
            }
        }
    }
    *p++ = '\0';
}

// This buffer is used by:
//
//     time_format_1 (23 bytes) uses TimeBuffer64,
//     time_format_2 (17 bytes) uses TimeBuffer32,
//     expand_time   (29 bytes) uses TimeBuffer32,
//     write_time    (52 bytes) uses TimeBuffer64.
//
// time_format_1 and time_format_2 are called from within the same
// printf, so they must use different buffers.
//
// We pick 32 and 64 as a round numbers.
//
static char TimeBuffer32[32];
static char TimeBuffer64[64];

// Show time in days, hours, and minutes
//
// 2^63/86400 is 1.07E14 which is at most 15 digits.
// '(15)d (2):(2)\0' is at most 23 characters.
//
const char *time_format_1(int Seconds)
{
    if (Seconds < 0)
    {
        Seconds = 0;
    }
    
    // We are showing the time in minutes, so round to the nearest
    // minute.
    //
    Seconds += 30;

    // Divide the time down into days, hours, and minutes.
    //
    int Days = Seconds / 86400;
    Seconds -= Days * 86400;
    
    int Hours = Seconds / 3600;
    Seconds -= Hours * 3600;
    
    int Minutes = Seconds / 60;
    
    if (Days > 0)
    {
        sprintf(TimeBuffer64, "%dd %02d:%02d", Days, Hours, Minutes);
    }
    else
    {
        sprintf(TimeBuffer64, "%02d:%02d", Hours, Minutes);
    }
    return TimeBuffer64;
}

// Show time in days, hours, minutes, or seconds.
//
const char *time_format_2(int Seconds)
{
    // 2^63/86400 is 1.07E14 which is at most 15 digits.
    // '(15)d\0' is at most 17 characters.
    //
    GeneralTimeConversion(TimeBuffer32, Seconds, IDAYS, ISECONDS, TRUE, FALSE);
    return TimeBuffer32;
}

// Del's added functions for dooferMUX ! :)
// D.Piper (del@delphinian.com) 1997 & 2000
//

// expand_time - Written (short) time format.
//
const char *expand_time(int Seconds)
{
    // 2^63/86400 is 1.07E14 which is at most 15 digits.
    // '(15)d (2)h (2)m (2)s\0' is at most 29 characters.
    //
    GeneralTimeConversion(TimeBuffer32, Seconds, IDAYS, ISECONDS, FALSE, FALSE);
    return TimeBuffer32;
}

// write_time - Written (long) time format.
//
const char *write_time(int Seconds)
{
    // 2^63/86400 is 1.07E14 which is at most 15 digits.
    // '(15) days (2) hours (2) minutes (2) seconds\0' is at most
    // 52 characters.
    //
    GeneralTimeConversion(TimeBuffer64, Seconds, IDAYS, ISECONDS, FALSE, TRUE);
    return TimeBuffer64;
}

// digittime - Digital format time ([(days)d]HH:MM) from given
// seconds. D.Piper - May 1997 & April 2000
//
FUNCTION(fun_digittime)
{
    int tt = Tiny_atol(fargs[0]);
    safe_str(time_format_1(tt), buff, bufc);
}

// singletime - Single element time from given seconds.
// D.Piper - May 1997 & April 2000
//
FUNCTION(fun_singletime)
{
    int tt = Tiny_atol(fargs[0]);
    safe_str(time_format_2(tt), buff, bufc);
}

// exptime - Written (short) time from given seconds
// D.Piper - May 1997 & April 2000
//
FUNCTION(fun_exptime)
{
    int tt = Tiny_atol(fargs[0]);
    safe_str(expand_time(tt), buff, bufc);
}

// writetime - Written (long) time from given seconds
// D.Piper - May 1997 & April 2000
//
FUNCTION(fun_writetime)
{
    int tt = Tiny_atol(fargs[0]);
    safe_str(write_time(tt), buff, bufc);
}

// cmds - Return player command count (Wizard_Who OR Self ONLY)
// D.Piper - May 1997
//
FUNCTION(fun_cmds)
{
    dbref target = lookup_player(player, fargs[0], 1);
    if (Good_obj(target) && Connected(target))
    {
        if (!(Wizard_Who(player) || Controls(player, target)))
        {
            target = NOTHING;
        }
        safe_ltoa(fetch_cmds(target), buff, bufc, LBUF_SIZE-1);
    }
    else
    {
        safe_str("#-1 PLAYER NOT FOUND", buff, bufc);
    }
}

// startsecs - Time the MUX was started, in seconds
// D.Piper - May 1997 & April 2000
//
FUNCTION(fun_startsecs)
{
    CLinearTimeAbsolute lta;
    lta = mudstate.start_time;
    lta.Local2UTC();
    safe_str(lta.ReturnSecondsString(), buff, bufc);
}

// conntotal - Return player's total online time to the MUX
// (including their current connection). D.Piper - May 1997
//
FUNCTION(fun_conntotal)
{
    dbref target = lookup_player(player, fargs[0], 1);
    if (Good_obj(target))
    {
        long TotalTime = fetch_totaltime(target) + fetch_connect(target);
        safe_ltoa(TotalTime, buff, bufc, LBUF_SIZE-1);
    }
    else
    {
        safe_str("#-1 PLAYER NOT FOUND", buff, bufc);
    }
}

// connmax - Return player's longest session to the MUX
// (including the current one). D.Piper - May 1997
//
FUNCTION(fun_connmax)
{
    dbref target = lookup_player(player, fargs[0], 1);
    if (Good_obj(target))
    {
        long Longest = fetch_longestconnect(target);
        long Current = fetch_connect(target);
        if (Longest < Current)
        {
            Longest = Current;
        }
        safe_ltoa(Longest, buff, bufc, LBUF_SIZE-1);
    }
    else
    {
        safe_str("#-1 PLAYER NOT FOUND", buff, bufc);
    }
}

// connlast - Return player's last connection time to the MUX
// D.Piper - May 1997
//
FUNCTION(fun_connlast)
{
    dbref target = lookup_player(player, fargs[0], 1);
    if (Good_obj(target))
    {
        safe_ltoa(fetch_lastconnect(target), buff, bufc, LBUF_SIZE-1);
    }
    else
    {
        safe_str("#-1 PLAYER NOT FOUND", buff, bufc);
    }
}

// connnum - Return the total number of sessions this player has had
// to the MUX (including the current one). D.Piper - May 1997
//
FUNCTION(fun_connnum)
{
    dbref target = lookup_player(player, fargs[0], 1);
    if (Good_obj(target))
    {
        long NumConnections = fetch_numconnections(target) + 1;
        safe_ltoa(NumConnections, buff, bufc, LBUF_SIZE-1);
    }
    else
    {
        safe_str("#-1 PLAYER NOT FOUND", buff, bufc);
    }
}

// connleft - Return when a player last logged off the MUX as
// UTC seconds. D.Piper - May 1997
//
FUNCTION(fun_connleft)
{
    dbref target = lookup_player(player, fargs[0], 1);
    if (Good_obj(target))
    {
        CLinearTimeAbsolute cl = fetch_logouttime(target);
        safe_str(cl.ReturnSecondsString(), buff, bufc);
    }
    else
    {
        safe_str("#-1 PLAYER NOT FOUND", buff, bufc);
    }
}

// lattrcmds - Output a list of all attributes containing $ commands.
// Altered from lattr(). D.Piper - May 1997 & April 2000
//
FUNCTION(fun_lattrcmds)
{
    dbref thing;
    int ca, first;
    ATTR *attr;

    // Check for wildcard matching.  parse_attrib_wild checks for read
    // permission, so we don't have to.  Have p_a_w assume the
    // slash-star if it is missing.
    //
    first = 1;
    olist_push();
    if (parse_attrib_wild(player, fargs[0], &thing, 0, 0, 1))
    {
        char *buf = alloc_lbuf("fun_lattrcmds");
        for (ca = olist_first(); ca != NOTHING; ca = olist_next())
        {
            attr = atr_num(ca);
            if (attr)
            {
                dbref aowner;
                int   aflags;
                atr_get_str(buf, thing, attr->number, &aowner, &aflags);
                if (buf[0] == '$')
                {
                    if (!first)
                    {
                        safe_chr(' ', buff, bufc);
                    }
                    first = 0;
                    safe_str((char *)attr->name, buff, bufc);
                }
            }
        }
        free_lbuf(buf);
    }
    else
    {
        safe_str("#-1 NO MATCH", buff, bufc);
    }
    olist_pop();
}

// lcmds - Output a list of all $ commands on an object.
// Altered from MUX lattr(). D.Piper - May 1997 & April 2000
//
FUNCTION(fun_lcmds)
{
    dbref thing;
    int ca, first;
    ATTR *attr;

    // Check for wildcard matching.  parse_attrib_wild checks for read
    // permission, so we don't have to.  Have p_a_w assume the
    // slash-star if it is missing.
    //
    first = 1;
    olist_push();
    if (parse_attrib_wild(player, fargs[0], &thing, 0, 0, 1))
    {
        TINY_STRTOK_STATE tts;
        Tiny_StrTokControl(&tts, " *:");
        char *buf = alloc_lbuf("fun_lattrcmds");
        for (ca = olist_first(); ca != NOTHING; ca = olist_next())
        {
            attr = atr_num(ca);
            if (attr)
            {
                dbref aowner;
                int   aflags;
                atr_get_str(buf, thing, attr->number, &aowner, &aflags);
                if (buf[0] == '$')
                {
                    if (!first)
                    {
                        safe_chr(' ', buff, bufc);
                    }

                    Tiny_StrTokString(&tts, buf+1);
                    char *p = Tiny_StrTokParse(&tts);
                    _strlwr(p);
                    safe_str(p, buff, bufc);

                    first = 0;
                }
            }
        }
        free_lbuf(buf);
    }
    else
    {
        safe_str("#-1 NO MATCH", buff, bufc);
    }
    olist_pop();
}

extern FLAGENT gen_flags[];

// lflags - List flags as names - (modified from 'flag_description()' and
// MUX flags(). D.Piper - May 1997 & May 2000
//
FUNCTION(fun_lflags)
{
    dbref target;
    FLAGENT *fp;
    FLAG fv;

    BOOL bFirst = TRUE;
    target = match_thing(player, fargs[0]);
    if (  (target != NOTHING)
       && (mudconf.pub_flags || Examinable(player, target) || (target == cause)))
    {
        for (fp = gen_flags; fp->flagname; fp++)
        {
            if (fp->flagflag & FLAG_WORD3)
                fv = Flags3(target);
            else if (fp->flagflag & FLAG_WORD2)
                fv = Flags2(target);
            else
                fv = Flags(target);

            if (fv & fp->flagvalue)
            {
                if ((fp->listperm & CA_WIZARD) && !Wizard(player))
                    continue;
                if ((fp->listperm & CA_GOD) && !God(player))
                    continue;
                if (  isPlayer(target)
                   && (fp->flagvalue == CONNECTED)
                   && (fp->flagflag & FLAG_WORD2)
                   && ((Flags(target) & (WIZARD | DARK)) == (WIZARD | DARK))
                   && !Wizard(player))
                {
                    continue;
                }

                if (!bFirst)
                {
                    safe_chr(' ', buff, bufc);
                }
                bFirst = FALSE;

                safe_str(fp->flagname, buff, bufc);
            }
        }
    }
    else
    {
        safe_str("#-1", buff, bufc);
    }
}