mux2.0/game/
mux2.0/game/data/
mux2.0/src/tools/
// funceval.cpp - MUX function handlers.
//
// $Id: funceval.cpp,v 1.20 2000/07/31 16:43:35 sdennis Exp $
//
#include "copyright.h"
#include "autoconf.h"
#include "config.h"
#include "externs.h"

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

#include "mudconf.h"
#include "db.h"
#include "flags.h"
#include "powers.h"
#include "attrs.h"
#include "externs.h"
#include "match.h"
#include "command.h"
#include "functions.h"
#include "misc.h"
#include "alloc.h"
#include "ansi.h"
#include "comsys.h"
#ifdef RADIX_COMPRESSION
#include "radix.h"
#endif

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

extern NAMETAB indiv_attraccess_nametab[];
extern char *FDECL(next_token, (char *, char));
extern char *FDECL(split_token, (char **, char));
extern int FDECL(countwords, (char *, char));
extern int FDECL(check_read_perms, (dbref, dbref, ATTR *, int, int, char *, char **));
extern void arr2list(char *arr[], int alen, char *list, char **bufc, char sep);
extern void FDECL(make_portlist, (dbref, dbref, char *, char **));

FUNCTION(fun_cwho)
{
    struct channel *ch = select_channel(fargs[0]);
    if (!ch)
    {
        safe_str("#-1 CHANNEL NOT FOUND", buff, bufc);
        return;
    }
    if (!mudconf.have_comsys || (!Comm_All(player) && (player != ch->charge_who)))
    {
        safe_str("#-1 NO PERMISSION TO USE", buff, bufc);
        return;
    }
    DTB pContext;
    struct comuser *user;
    DbrefToBuffer_Init(&pContext, buff, bufc);
    for (user = ch->on_users; user; user = user->on_next)
    {
        if (  Connected(user->who)
           && !DbrefToBuffer_Add(&pContext, user->who))
        {
            break;
        }
    }
    DbrefToBuffer_Final(&pContext);
}

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

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

FUNCTION(fun_ansi)
{
    extern char *ColorTable[256];

    char *s = fargs[0];

    while (*s)
    {
        char *pColor = ColorTable[*s];
        if (pColor)
        {
            safe_str(pColor, buff, bufc);
        }
        s++;
    }
    safe_str(fargs[1], buff, bufc);
    **bufc = '\0';

    // ANSI_NORMAL is guaranteed to be written on the end.
    //
    char Temp[LBUF_SIZE];
    int nVisualWidth;
    int nLen = ANSI_TruncateToField(buff, LBUF_SIZE, Temp, sizeof(Temp), &nVisualWidth, FALSE);
    memcpy(buff, Temp, nLen+1);
    *bufc = buff + nLen;
}

FUNCTION(fun_zone)
{
    dbref it;

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

#ifdef SIDE_EFFECT_FUNCTIONS

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

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

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

static int check_command(dbref player, char *name, char *buff, char **bufc)
{
    CMDENT *cmdp = (CMDENT *)hashfindLEN(name, strlen(name), &mudstate.command_htab);
    if (cmdp)
    {
        // Perform checks similiar to (but not exactly like) the
        // ones in process_cmdent(): object type checks, permission
        // checks, ands global flags.
        //
        if (  Invalid_Objtype(player)
           || !check_access(player, cmdp->perms)
           || (  !Builder(player)
              && Protect(CA_GBL_BUILD)
              && !(mudconf.control_flags & CF_BUILD)))
        {
            safe_str(NOPERM_MESSAGE, buff, bufc);
            return 1;
        }
    }
    return 0;
}

// ------------------------------------------------------------------------
// fun_create: Creates a room, thing or exit.
//
FUNCTION(fun_create)
{
    dbref thing;
    int cost;
    char sep, *name;

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

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

    switch (sep)
    {
    case 'r':

        if (check_command(player, "@dig", buff, bufc))
        {
            return;
        }
        thing = create_obj(player, TYPE_ROOM, name, 0);
        break;

    case 'e':

        if (check_command(player, "@open", buff, bufc))
        {
            return;
        }
        thing = create_obj(player, TYPE_EXIT, name, 0);
        if (thing != NOTHING)
        {
            s_Exits(thing, player);
            s_Next(thing, Exits(player));
            s_Exits(player, thing);
        }
        break;

    default:

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

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

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

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

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

    if (check_command(player, "@set", buff, bufc))
    {
        return;
    }

    // obj/attr form?
    //
    if (parse_attrib(player, fargs[0], &thing, &atr))
    {
        if (atr != NOTHING)
        {
            // Must specify flag name
            //
            if (!*fargs[1])
            {
                safe_str("#-1 UNSPECIFIED PARAMETER", buff, bufc);
            }

            // Are we clearing?
            //
            clear = 0;
            if (*fargs[0] == NOT_TOKEN)
            {
                fargs[0]++;
                clear = 1;
            }
            
            // valid attribute flag?
            //
            flagvalue = search_nametab(player, indiv_attraccess_nametab, fargs[1]);
            if (flagvalue < 0)
            {
                safe_str("#-1 CAN NOT SET", buff, bufc);
                return;
            }

            // Make sure attribute is present
            //
            if (!atr_get_info(thing, atr, &aowner, &aflags))
            {
                safe_str("#-1 ATTRIBUTE NOT PRESENT ON OBJECT", buff, bufc);
                return;
            }

            // Can we write to attribute?
            //
            attr = atr_num(atr);
            if (!attr || !Set_attr(player, thing, attr, aflags))
            {
                safe_str("#-1 PERMISSION DENIED", buff, bufc);
                return;
            }

            // Just do it!
            //
            if (clear)
                aflags &= ~flagvalue;
            else
                aflags |= flagvalue;
            could_hear = Hearer(thing);
            atr_set_flags(thing, atr, aflags);

            return;
        }
    }

    // Find thing.
    //
    if ((thing = match_controlled(player, fargs[0])) == NOTHING)
    {
        safe_str("#-1", buff, bufc);
        return;
    }

    // Check for attr set first.
    //
    for (p = fargs[1]; *p && (*p != ':'); p++)
    {
        // Nothing
        ;
    }

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

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

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

        // Set it.
        //
        set_attr_internal(player, thing, atr, p, 0, buff, bufc);
        free_lbuf(buff2);
        return;
    }

    // Set/clear a flag.
    //
    flag_set(thing, player, fargs[1], 0);
}
#endif

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

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

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

    if (!text && !*text)
        return ((char *)"");
    strcpy(codebuff, crunch_code(code));
    if (!code || !*code || !codebuff || !*codebuff)
        return (text);
    textbuff[0] = '\0';

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

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

    if (!mudconf.have_zones || (!Controls(player, it) && !WizRoy(player)))
    {
        safe_str("#-1 NO PERMISSION TO USE", buff, bufc);
        return;
    }
    dbref i;
    DTB pContext;
    DbrefToBuffer_Init(&pContext, buff, bufc);
    for (i = 0; i < mudstate.db_top; i++)
    {
        if (  Typeof(i) == TYPE_PLAYER
           && Zone(i) == it
           && !DbrefToBuffer_Add(&pContext, i))
        {
            break;
        }
    }
    DbrefToBuffer_Final(&pContext);
}

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

    if (!mudconf.have_zones || (!Controls(player, it) && !WizRoy(player)))
    {
        safe_str("#-1 NO PERMISSION TO USE", buff, bufc);
        return;
    }
    dbref i;
    DTB pContext;
    DbrefToBuffer_Init(&pContext, buff, bufc);
    for (i = 0; i < mudstate.db_top; i++)
    {
        if (  Typeof(i) == TYPE_ROOM
           && db[i].zone == it
           && !DbrefToBuffer_Add(&pContext, i))
        {
            break;
        }
    }
    DbrefToBuffer_Final(&pContext);
}

/*
 * Borrowed from DarkZone 
 */
FUNCTION(fun_children)
{
    dbref it = match_thing(player, fargs[0]);
    if (!(Controls(player, it)) || !(WizRoy(player)))
    {
        safe_str("#-1 NO PERMISSION TO USE", buff, bufc);
        return;
    }

    dbref i;
    DTB pContext;
    DbrefToBuffer_Init(&pContext, buff, bufc);
    for (i = 0; i < mudstate.db_top; i++)
    {
        if (  Parent(i) == it
           && !DbrefToBuffer_Add(&pContext, i))
        {
            break;
        }
    }
    DbrefToBuffer_Final(&pContext);
}

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

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

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

    if (!Controls(player, obj))
    {
        // The right circumstances were not met, so we are evaluating
        // as the player who gave the command instead of the
        // requested object.
        //
        obj = player;
    }

    mudstate.nObjEvalNest++;
    str = fargs[1];
    TinyExec(buff, bufc, 0, obj, cause, EV_FCHECK | EV_STRIP_CURLY | EV_EVAL, &str, cargs, ncargs);
    free_lbuf(name);
    mudstate.nObjEvalNest--;
}

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

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

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

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

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

    dbref zone = Zone(player);

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

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

FUNCTION(fun_columns)
{
    int rturn = 1;
    char *curr, *objstring, *bp, *cp, sep, *str;
    
    evarargs_preamble("COLUMNS", 3);
    
    int nWidth = Tiny_atol(fargs[1]);
    if ((nWidth < 1) || (nWidth > 78))
    {
        safe_str("#-1 OUT OF RANGE", buff, bufc);
        return;
    }
    cp = curr = bp = alloc_lbuf("fun_columns");
    str = fargs[0];
    TinyExec(curr, &bp, 0, player, cause, EV_STRIP_CURLY | EV_FCHECK | EV_EVAL, &str, cargs, ncargs);
    *bp = '\0';
    cp = trim_space_sep(cp, sep);
    if (!*cp)
    {
        free_lbuf(curr);
        return;
    }
    safe_chr(' ', buff, bufc);
    
    int nBufferAvailable = LBUF_SIZE - (*bufc-buff) - 1;
    while (cp && nBufferAvailable > 0)
    {
        objstring = split_token(&cp, sep);
        int nVisualWidth;
        int nLen = ANSI_TruncateToField(objstring, nBufferAvailable, *bufc, nWidth, &nVisualWidth, FALSE);
        *bufc += nLen;
        nBufferAvailable -= nLen;

        int spaces = nWidth - nVisualWidth;
        if (spaces > nBufferAvailable)
        {
            spaces = nBufferAvailable;
        }
        if (spaces)
        {
            memset(*bufc, ' ', spaces);
            *bufc += spaces;
            nBufferAvailable -= spaces;
        }
        
        if (!(rturn % (int)(78 / nWidth)))
        {
            safe_str((char *)"\r\n ", buff, bufc);
            nBufferAvailable -= 3;
        }
        
        rturn++;
    }
    free_lbuf(curr);
}

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

    k = sizeof(struct object);

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

FUNCTION(fun_objmem)
{
    dbref thing;

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

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

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

/*
 * Code for andflags() and orflags() borrowed from PennMUSH 1.50 
 */
// 0 for orflags, 1 for andflags
//
static int handle_flaglists(dbref player, char *name, char *fstr, int type)
{
    char *s;
    char flagletter[2];
    FLAGSET fset;
    FLAG p_type;
    int negate, temp;
    int ret = type;
    dbref it = match_thing(player, name);

    negate = temp = 0;

    if (it == NOTHING)
        return 0;

    for (s = fstr; *s; s++) {

        /*
         * Check for a negation sign. If we find it, we note it and 
         * * * * * increment the pointer to the next character. 
         */

        if (*s == '!') {
            negate = 1;
            s++;
        } else {
            negate = 0;
        }

        if (!*s) {
            return 0;
        }
        flagletter[0] = *s;
        flagletter[1] = '\0';

        if (!convert_flags(player, flagletter, &fset, &p_type)) {

            /*
             * Either we got a '!' that wasn't followed by a * *
             * * letter, or * we couldn't find that flag. For
             * AND, * * * since we've failed * a check, we can
             * return * * false.  * Otherwise we just go on. 
             */

            if (type == 1)
                return 0;
            else
                continue;

        } else {

            /*
             * does the object have this flag? 
             */

            if ((Flags(it) & fset.word1) ||
                (Flags2(it) & fset.word2) ||
                (Flags3(it) & fset.word3) ||
                (Typeof(it) == p_type)) {
                if (isPlayer(it) && (fset.word2 == CONNECTED) &&
                    ((Flags(it) & (WIZARD | DARK)) == (WIZARD | DARK)) &&
                    !Wizard(player))
                    temp = 0;
                else
                    temp = 1;
            } else {
                temp = 0;
            }

            if ((type == 1) && ((negate && temp) || (!negate && !temp))) {

                /*
                 * Too bad there's no NXOR function... * At * 
                 * 
                 * *  * * this point we've either got a flag
                 * and * we * * don't want * it, or we don't
                 * have a  * flag * * and we want it. Since
                 * it's * AND,  * we * * return false. 
                 */
                return 0;

            } else if ((type == 0) &&
                 ((!negate && temp) || (negate && !temp))) {

                /*
                 * We've found something we want, in an OR. * 
                 * 
                 * *  * * We OR a * true with the current
                 * value. 
                 */

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

FUNCTION(fun_orflags)
{
    safe_ltoa(handle_flaglists(player, fargs[0], fargs[1], 0), buff, bufc, LBUF_SIZE-1);
}

FUNCTION(fun_andflags)
{
    safe_ltoa(handle_flaglists(player, fargs[0], fargs[1], 1), buff, bufc, LBUF_SIZE-1);
}

FUNCTION(fun_strtrunc)
{
    int maxVisualWidth = Tiny_atol(fargs[1]);
    if (maxVisualWidth < 0)
    {
        safe_str("#-1 OUT OF RANGE", buff, bufc);
        return;
    }
    if (maxVisualWidth == 0)
    {
        return;
    }
    int nVisualWidth;
    char buf[LBUF_SIZE+1];
    ANSI_TruncateToField(fargs[0], LBUF_SIZE, buf, maxVisualWidth, &nVisualWidth, FALSE);
    safe_str(buf, buff, bufc);
}

FUNCTION(fun_ifelse)
{
    /* This function now assumes that its arguments have not been
       evaluated. */
    
    char *str, *mbuff, *bp;
    
    mbuff = bp = alloc_lbuf("fun_ifelse");
    str = fargs[0];
    TinyExec(mbuff, &bp, 0, player, cause, EV_STRIP_CURLY | EV_FCHECK | EV_EVAL, &str, cargs, ncargs);
    *bp = '\0';
    
    if (!mbuff || !*mbuff || ((Tiny_atol(mbuff) == 0) && is_number(mbuff)))
    {
        str = fargs[2];
        TinyExec(buff, bufc, 0, player, cause, EV_STRIP_CURLY | EV_FCHECK | EV_EVAL, &str, cargs, ncargs);
    }
    else
    {
        str = fargs[1];
        TinyExec(buff, bufc, 0, player, cause, EV_STRIP_CURLY | EV_FCHECK | EV_EVAL, &str, cargs, ncargs);
    }
    free_lbuf(mbuff);
}

FUNCTION(fun_inc)
{
    if (!is_number(fargs[0]))
    {
        safe_str("#-1 ARGUMENT MUST BE A NUMBER", buff, bufc);
        return;
    }
    safe_ltoa(Tiny_atol(fargs[0]) + 1, buff, bufc, LBUF_SIZE-1);
}

FUNCTION(fun_dec)
{
    if (!is_number(fargs[0]))
    {
        safe_str("#-1 ARGUMENT MUST BE A NUMBER", buff, bufc);
        return;
    }
    safe_ltoa(Tiny_atol(fargs[0]) - 1, buff, bufc, LBUF_SIZE-1);
}

// Mail functions borrowed from DarkZone.
//
// This function can take one of three formats:
//
// 1. mail(num)         --> returns message <num> for privs.
// 2. mail(player)      --> returns number of messages for <player>.
// 3. mail(player, num) --> returns message <num> for <player>.
// 4. mail()            --> returns number of messages for executor.
//
FUNCTION(fun_mail)
{
    dbref playerask;
    int num, rc, uc, cc;
#ifdef RADIX_COMPRESSION
    char *msgbuff;
#endif

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

    if (num < 1 || !isPlayer(playerask))
    {
        safe_str("#-1 NO SUCH MESSAGE", buff, bufc);
        return;
    }
    struct mail *mp = mail_fetch(playerask, num);
    if (mp)
    {
#ifdef RADIX_COMPRESSION
        msgbuff = alloc_lbuf("fun_mail");
        string_decompress(get_mail_message(mp->number), msgbuff);
        safe_str(msgbuff, buff, bufc);
        free_lbuf(msgbuff);
#else
        safe_str(get_mail_message(mp->number), buff, bufc);
#endif
        return;
    }

    // Ran off the end of the list without finding anything.
    //
    safe_str("#-1 NO SUCH MESSAGE", buff, bufc);
}

// This function can take these formats:
//
//  1) mailfrom(<num>)
//  2) mailfrom(<player>,<num>)
//
// It returns the dbref of the player the mail is from.
//
FUNCTION(fun_mailfrom)
{
    // Make sure we have the right number of arguments.
    //
    int num;
    dbref playerask;
    if (nfargs == 1)
    {
        playerask = player;
        num = Tiny_atol(fargs[0]);
    }
    else if (nfargs == 2)
    {
        playerask = lookup_player(player, fargs[0], 1);
        if (playerask == NOTHING)
        {
            safe_str("#-1 NO SUCH PLAYER", buff, bufc);
            return;
        }
        if (playerask == player || Wizard(player))
        {
            num = Tiny_atol(fargs[1]);
        }
        else
        {
            safe_str("#-1 PERMISSION DENIED", buff, bufc);
            return;
        }
    }
    else
    {
        safe_str("#-1 FUNCTION (MAILFROM) EXPECTS 1 OR 2 ARGUMENTS", buff, bufc);
        return;
    }

    if (num < 1 || !isPlayer(playerask))
    {
        safe_str("#-1 NO SUCH MESSAGE", buff, bufc);
        return;
    }
    struct mail *mp = mail_fetch(playerask, num);
    if (mp != NULL)
    {
        safe_tprintf_str(buff, bufc, "#%d", mp->from);
        return;
    }

    // Ran off the end of the list without finding anything.
    //
    safe_str("#-1 NO SUCH MESSAGE", buff, bufc);
}

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

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

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

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

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

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

/*
 * ---------------------------------------------------------------------------
 * * fun_default, fun_edefault, and fun_udefault:
 * * These check for the presence of an attribute. If it exists, then it
 * * is gotten, via the equivalent of get(), get_eval(), or u(), respectively.
 * * Otherwise, the default message is used.
 * * In the case of udefault(), the remaining arguments to the function
 * * are used as arguments to the u().
 */

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

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

    /*
     * First we check to see that the attribute exists on the object. * * 
     * 
     * *  * * If so, we grab it and use it. 
     */

    if (objname != NULL) {
        if (parse_attrib(player, objname, &thing, &attrib) &&
            (attrib != NOTHING)) {
            attr = atr_num(attrib);
            if (attr && !(attr->flags & AF_IS_LOCK)) {
                atr_gotten = atr_pget(thing, attrib, &aowner, &aflags);
                if (*atr_gotten &&
                check_read_perms(player, thing, attr, aowner,
                         aflags, buff, bufc)) {
                    safe_str(atr_gotten, buff, bufc);
                    free_lbuf(atr_gotten);
                    free_lbuf(objname);
                    return;
                }
                free_lbuf(atr_gotten);
            }
        }
        free_lbuf(objname);
    }
    /*
     * If we've hit this point, we've not gotten anything useful, so * we 
     * 
     * *  * *  * * go and evaluate the default. 
     */

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

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

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

    /*
     * First we check to see that the attribute exists on the object. * * 
     * 
     * *  * * If so, we grab it and use it. 
     */

    if (objname != NULL) {
        if (parse_attrib(player, objname, &thing, &attrib) &&
            (attrib != NOTHING)) {
            attr = atr_num(attrib);
            if (attr && !(attr->flags & AF_IS_LOCK)) {
                atr_gotten = atr_pget(thing, attrib, &aowner, &aflags);
                if (*atr_gotten &&
                check_read_perms(player, thing, attr, aowner,
                         aflags, buff, bufc)) {
                    str = atr_gotten;
                    TinyExec(buff, bufc, 0, thing, player, EV_FIGNORE | EV_EVAL, &str, (char **)NULL, 0);
                    free_lbuf(atr_gotten);
                    free_lbuf(objname);
                    return;
                }
                free_lbuf(atr_gotten);
            }
        }
        free_lbuf(objname);
    }
    /*
     * If we've hit this point, we've not gotten anything useful, so * we 
     * 
     * *  * *  * * go and evaluate the default. 
     */

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

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


    if (nfargs < 2)
    {
        // must have at least two arguments.
        //
        return;
    }

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

    /*
     * First we check to see that the attribute exists on the object. * * 
     * 
     * *  * * If so, we grab it and use it. 
     */

    if (objname != NULL) {
        if (parse_attrib(player, objname, &thing, &anum)) {
            if ((anum == NOTHING) || (!Good_obj(thing)))
                ap = NULL;
            else
                ap = atr_num(anum);
        } else {
            thing = player;
            ap = atr_str(objname);
        }
        if (ap) {
            atext = atr_pget(thing, ap->number, &aowner, &aflags);
            if (atext) {
                if (*atext &&
                    check_read_perms(player, thing, ap, aowner, aflags,
                             buff, bufc)) {
                    str = atext;
                    TinyExec(buff, bufc, 0, thing, cause, EV_FCHECK | EV_EVAL, &str, &(fargs[2]), nfargs - 1);
                    free_lbuf(atext);
                    free_lbuf(objname);
                    return;
                }
                free_lbuf(atext);
            }
        }
        free_lbuf(objname);
    }

    // If we've hit this point, we've not gotten anything useful, so we
    // go and evaluate the default.
    //
    str = fargs[1];
    TinyExec(buff, bufc, 0, player, cause, EV_EVAL | EV_STRIP_CURLY | EV_FCHECK, &str, cargs, ncargs);
}

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

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

    if (obj == NOTHING)
        safe_str("#-1 ARG1 NOT FOUND", buff, bufc);
    else if (victim == NOTHING)
        safe_str("#-1 ARG2 NOT FOUND", buff, bufc);
    else
    {
#ifdef WOD_REALMS
        if (REALM_DO_HIDDEN_FROM_YOU != DoThingToThingVisibility(obj, victim, ACTION_IS_STATIONARY))
        {
            safe_ltoa(locatable(obj, victim, obj), buff, bufc, LBUF_SIZE-1);
        }
        else safe_chr('0', buff, bufc);
#else
        safe_ltoa(locatable(obj, victim, obj), buff, bufc, LBUF_SIZE-1);
#endif
    }
}

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

/*
 * Borrowed from PennMUSH 1.50 
 */
FUNCTION(fun_isword)
{
    char *p;
    int ch = '1';

    for (p = fargs[0]; *p; p++)
    {
        if (!Tiny_IsAlpha[(unsigned char)*p])
        {
            ch = '0';
            break;
        }
    }
    safe_chr(ch, buff, bufc);
}

/*
 * ---------------------------------------------------------------------------
 * * fun_visible:  Can X examine Y. If X does not exist, 0 is returned.
 * *               If Y, the object, does not exist, 0 is returned. If
 * *               Y the object exists, but the optional attribute does
 * *               not, X's ability to return Y the object is returned.
 */

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

    if ((it = match_thing(player, fargs[0])) == NOTHING)
    {
        safe_chr('0', buff, bufc);
        return;
    }
    if (parse_attrib(player, fargs[1], &thing, &atr))
    {
        if (atr == NOTHING)
        {
            safe_ltoa(Examinable(it, thing), buff, bufc, LBUF_SIZE-1);
            return;
        }
        ap = atr_num(atr);
        atr_pget_info(thing, atr, &aowner, &aflags);
        safe_ltoa(See_attr(it, thing, ap, aowner, aflags), buff, bufc, LBUF_SIZE-1);
        return;
    }
    thing = match_thing(player, fargs[1]);
    if (!Good_obj(thing)) {
        safe_chr('0', buff, bufc);
        return;
    }
    safe_ltoa(Examinable(it, thing), buff, bufc, LBUF_SIZE-1);
}

/*
 * ---------------------------------------------------------------------------
 * * fun_elements: given a list of numbers, get corresponding elements from
 * * the list.  elements(ack bar eep foof yay,2 4) ==> bar foof
 * * The function takes a separator, but the separator only applies to the
 * * first list.
 */

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

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

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

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

    s = trim_space_sep(fargs[1], ' ');

    /*
     * Go through the second list, grabbing the numbers and finding the * 
     * 
     * *  * *  * * corresponding elements. 
     */

    do {
        r = split_token(&s, ' ');
        cur = Tiny_atol(r) - 1;
        if ((cur >= 0) && (cur < nwords) && ptrs[cur]) {
            if (oldp != *bufc)
                safe_chr(sep, buff, bufc);
            safe_str(ptrs[cur], buff, bufc);
        }
    } while (s);
    free_lbuf(wordlist);
}

/*
 * ---------------------------------------------------------------------------
 * * fun_grab: a combination of extract() and match(), sortof. We grab the
 * *           single element that we match.
 * *
 * *   grab(Test:1 Ack:2 Foof:3,*:2)    => Ack:2
 * *   grab(Test-1+Ack-2+Foof-3,*o*,+)  => Ack:2
 */

/*
 * Borrowed from PennMUSH 1.50 
 */
FUNCTION(fun_grab)
{
    char *r, *s, sep;

    varargs_preamble("GRAB", 3);

    /*
     * Walk the wordstring, until we find the word we want. 
     */

    s = trim_space_sep(fargs[0], sep);
    do {
        r = split_token(&s, sep);
        if (quick_wild(fargs[1], r)) {
            safe_str(r, buff, bufc);
            return;
        }
    } while (s);
}

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

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

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

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

    n = strlen(old);

    for (i = 0; i < n; i++)
    {
        j = RandomLong(i, n-1);
        c = old[i];
        old[i] = old[j];
        old[j] = c;
    }
}

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

/*
 * Borrowed from PennMUSH 1.50 
 */
FUNCTION(fun_shuffle)
{
    char *words[LBUF_SIZE];
    int n, i, j;
    char sep;

    if (!nfargs || !fargs[0] || !*fargs[0])
    {
        return;
    }
    varargs_preamble("SHUFFLE", 2);

    n = list2arr(words, LBUF_SIZE, fargs[0], sep);

    for (i = 0; i < n; i++)
    {
        j = RandomLong(i, n-1);

        // Swap words[i] with words[j]
        //
        char *temp = words[i];
        words[i] = words[j];
        words[j] = temp;
    }
    arr2list(words, n, buff, bufc, sep);
}

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

static char ucomp_buff[LBUF_SIZE];
static dbref ucomp_cause;
static dbref ucomp_player;

static int u_comp(const void *s1, const void *s2)
{
    /*
     * Note that this function is for use in conjunction with our own * * 
     * 
     * *  * * sane_qsort routine, NOT with the standard library qsort! 
     */

    char *result, *tbuf, *elems[2], *bp, *str;
    int n;

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

    tbuf = alloc_lbuf("u_comp");
    elems[0] = (char *)s1;
    elems[1] = (char *)s2;
    strcpy(tbuf, ucomp_buff);
    result = bp = alloc_lbuf("u_comp");
    str = tbuf;
    TinyExec(result, &bp, 0, ucomp_player, ucomp_cause, EV_STRIP_CURLY | EV_FCHECK | EV_EVAL, &str, &(elems[0]), 2);
    *bp = '\0';
    if (!result)
        n = 0;
    else {
        n = Tiny_atol(result);
        free_lbuf(result);
    }
    free_lbuf(tbuf);
    return n;
}

typedef int PV(const void *, const void *);

static void sane_qsort(void *array[], int left, int right, PV compare)
{
    /*
     * Andrew Molitor's qsort, which doesn't require transitivity between
     * * * * * comparisons (essential for preventing crashes due to *
     * boneheads * * * who write comparison functions where a > b doesn't
     * * mean b < a).  
     */

    int i, last;
    void *tmp;

      loop:
    if (left >= right)
        return;

    /*
     * Pick something at random at swap it into the leftmost slot   
     */
    /*
     * This is the pivot, we'll put it back in the right spot later 
     */

    i = RandomLong(0, right - left);
    tmp = array[left + i];
    array[left + i] = array[left];
    array[left] = tmp;

    last = left;
    for (i = left + 1; i <= right; i++) {

        /*
         * Walk the array, looking for stuff that's less than our 
         */
        /*
         * pivot. If it is, swap it with the next thing along     
         */

        if ((*compare) (array[i], array[left]) < 0) {
            last++;
            if (last == i)
                continue;

            tmp = array[last];
            array[last] = array[i];
            array[i] = tmp;
        }
    }

    /*
     * Now we put the pivot back, it's now in the right spot, we never 
     */
    /*
     * need to look at it again, trust me.                             
     */

    tmp = array[last];
    array[last] = array[left];
    array[left] = tmp;

    /*
     * At this point everything underneath the 'last' index is < the 
     */
    /*
     * entry at 'last' and everything above it is not < it.          
     */

    if ((last - left) < (right - last)) {
        sane_qsort(array, left, last - 1, compare);
        left = last + 1;
        goto loop;
    } else {
        sane_qsort(array, last + 1, right, compare);
        right = last - 1;
        goto loop;
    }
}

FUNCTION(fun_sortby)
{
    char *atext, *list, *ptrs[LBUF_SIZE / 2], sep;
    int nptrs, aflags, anum;
    dbref thing, aowner;
    ATTR *ap;

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

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

    if (!ap) {
        return;
    }
    atext = atr_pget(thing, ap->number, &aowner, &aflags);
    if (!atext) {
        return;
    } else if (!*atext || !See_attr(player, thing, ap, aowner, aflags)) {
        free_lbuf(atext);
        return;
    }
    strcpy(ucomp_buff, atext);
    ucomp_player = thing;
    ucomp_cause = cause;

    list = alloc_lbuf("fun_sortby");
    strcpy(list, fargs[1]);
    nptrs = list2arr(ptrs, LBUF_SIZE / 2, list, sep);

    if (nptrs > 1)      /*
                 * pointless to sort less than 2 elements 
                 */
        sane_qsort((void **)ptrs, 0, nptrs - 1, u_comp);

    arr2list(ptrs, nptrs, buff, bufc, sep);
    free_lbuf(list);
    free_lbuf(atext);
}

// fun_last: Returns last word in a string. Borrowed from TinyMUSH 2.2.
//
FUNCTION(fun_last)
{
    char sep;

    // If we are passed an empty arglist return a null string.
    //
    if (nfargs <= 0)
    {
        return;
    }
    varargs_preamble("LAST", 2);

    // Trim leading spaces.
    //
    int nLen = strlen(fargs[0]);
    char *pStart = trim_space_sep_LEN(fargs[0], nLen, sep, &nLen);
    char *pEnd = pStart + nLen - 1;

    if (sep == ' ')
    {
        // We're dealing with spaces, so trim off the trailing spaces.
        //
        while (pStart <= pEnd && *pEnd == ' ')
        {
            pEnd--;
        }
        pEnd[1] = '\0';
    }

    // Find the separator nearest the end.
    //
    char *p = pEnd;
    while (pStart <= p && *p != sep)
    {
        p--;
    }

    // Return the last token.
    //
    nLen = pEnd - p;
    safe_copy_buf(p+1, nLen, buff, bufc, LBUF_SIZE-1);
}

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

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

    
    // Check each word individually, returning the word number of all
    // that match. If none match, return 0. 
    //
    wcount = 1;
    s = trim_space_sep(fargs[0], sep);
    do
    {
        r = split_token(&s, sep);
        if (quick_wild(fargs[1], r))
        {
            Tiny_ltoa(wcount, tbuf);
            if (old != *bufc)
                safe_chr(' ', buff, bufc);
            safe_str(tbuf, buff, bufc);
        }
        wcount++;
    } while (s);

    if (*bufc == old)
        safe_chr('0', buff, bufc);
}

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

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

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

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

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

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

    /*
     * Get the attribute, check the permissions. 
     */

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

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

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

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

    while (cp1 && cp2) {
        if (*bufc != oldp)
            safe_chr(sep, buff, bufc);
        os[0] = split_token(&cp1, sep);
        os[1] = split_token(&cp2, sep);
        strcpy(atextbuf, atext);
        str = atextbuf;
        TinyExec(buff, bufc, 0, player, cause, EV_STRIP_CURLY | EV_FCHECK | EV_EVAL, &str, &(os[0]), 2);
    }
    free_lbuf(atext);
    free_lbuf(atextbuf);
}

/*
 * ---------------------------------------------------------------------------
 * * fun_foreach: like map(), but it operates on a string, rather than on a list,
 * * calling a user-defined function for each character in the string.
 * * No delimiter is inserted between the results.
 */

/*
 * Borrowed from TinyMUSH 2.2 
 */
FUNCTION(fun_foreach)
{
    dbref aowner, thing;
    int aflags, anum, flag = 0;
    ATTR *ap;
    char *atext, *atextbuf, *str, *cp, *bp;
    char cbuf[2], prev = '\0';

    if ((nfargs != 2) && (nfargs != 4)) {
        safe_str("#-1 FUNCTION (FOREACH) EXPECTS 2 or 4 ARGUMENTS", buff, bufc);
        return;
    }

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

    if (!ap) {
        return;
    }
    atext = atr_pget(thing, ap->number, &aowner, &aflags);
    if (!atext) {
        return;
    } else if (!*atext || !See_attr(player, thing, ap, aowner, aflags)) {
        free_lbuf(atext);
        return;
    }
    atextbuf = alloc_lbuf("fun_foreach");
    cp = trim_space_sep(fargs[1], ' ');

    bp = cbuf;
    
    cbuf[1] = '\0';
    
    if (nfargs == 4) {
        while (cp && *cp) {
            cbuf[0] = *cp++;
            
            if (flag) {
                if ((cbuf[0] == *fargs[3]) && (prev != '\\') && (prev != '%')) {
                    flag = 0;
                    continue;
                }
            } else {
                if ((cbuf[0] == *fargs[2]) && (prev != '\\') && (prev != '%')) {
                    flag = 1;
                    continue;
                } else {
                    safe_chr(cbuf[0], buff, bufc);
                    continue;
                }
            }

            strcpy(atextbuf, atext);
            str = atextbuf;
            TinyExec(buff, bufc, 0, player, cause, EV_STRIP_CURLY | EV_FCHECK | EV_EVAL, &str, &bp, 1);
            prev = cbuf[0];
        }
    } else {
        while (cp && *cp) {
            cbuf[0] = *cp++;
    
            strcpy(atextbuf, atext);
            str = atextbuf;
            TinyExec(buff, bufc, 0, player, cause, EV_STRIP_CURLY | EV_FCHECK | EV_EVAL, &str, &bp, 1);
        }
    }

    free_lbuf(atextbuf);
    free_lbuf(atext);
}

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

/*
 * Borrowed from TinyMUSH 2.2 
 */
FUNCTION(fun_munge)
{
    dbref aowner, thing;
    int aflags, anum, nptrs1, nptrs2, nresults, i, j;
    ATTR *ap;
    char *list1, *list2, *rlist;
    char *ptrs1[LBUF_SIZE / 2], *ptrs2[LBUF_SIZE / 2], *results[LBUF_SIZE / 2];
    char *atext, *bp, *str, sep, *oldp;

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

    /*
     * Find our object and attribute 
     */

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

    if (!ap) {
        return;
    }
    atext = atr_pget(thing, ap->number, &aowner, &aflags);
    if (!atext) {
        return;
    } else if (!*atext || !See_attr(player, thing, ap, aowner, aflags)) {
        free_lbuf(atext);
        return;
    }
    /*
     * Copy our lists and chop them up. 
     */

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

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

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

    /*
     * Now that we have our result, put it back into array form. Search * 
     * 
     * *  * *  * * through list1 until we find the element position, then 
     * copy  * the * * * corresponding element from list2. 
     */

    nresults = list2arr(results, LBUF_SIZE / 2, rlist, sep);

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

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

    if (!fargs[0] || !fargs[1])
    {
        safe_chr('0', buff, bufc);
        return;
    }

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

    if ((n == 0) || (die <= 0))
    {
        safe_chr('0', buff, bufc);
        return;
    }

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

    safe_ltoa(total, buff, bufc, LBUF_SIZE-1);
}

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

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

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

FUNCTION(fun_band)
{
    if (is_number(fargs[0]) && is_number(fargs[1]))
        safe_ltoa(Tiny_atol(fargs[0]) & Tiny_atol(fargs[1]), buff, bufc, LBUF_SIZE-1);
    else
        safe_str("#-1 ARGUMENTS MUST BE NUMBERS", buff, bufc);
}

FUNCTION(fun_bor)
{
    if (is_number(fargs[0]) && is_number(fargs[1]))
        safe_ltoa(Tiny_atol(fargs[0]) | Tiny_atol(fargs[1]), buff, bufc, LBUF_SIZE-1);
    else
        safe_str("#-1 ARGUMENTS MUST BE NUMBERS", buff, bufc);
}

FUNCTION(fun_bnand)
{
    if (is_number(fargs[0]) && is_number(fargs[1]))
        safe_ltoa(Tiny_atol(fargs[0]) & ~(Tiny_atol(fargs[1])), buff, bufc, LBUF_SIZE-1);
    else
        safe_str("#-1 ARGUMENTS MUST BE NUMBERS", buff, bufc);
}

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

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

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

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

FUNCTION(fun_grep)
{
    char *tp;

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

    if (it == NOTHING)
    {
        safe_str("#-1 NO MATCH", buff, bufc);
        return;
    }
    else if (!(Examinable(player, it)))
    {
        safe_str("#-1 PERMISSION DENIED", buff, bufc);
        return;
    }

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

FUNCTION(fun_grepi)
{
    char *tp;

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

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

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

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

/*
 * Borrowed from PennMUSH 1.50 
 */
FUNCTION(fun_alphamax)
{
    if (nfargs <= 0)
    {
        safe_str("#-1 TOO FEW ARGUMENTS", buff, bufc);
        return;
    }

    char *amax = fargs[0];
    for (int i = 1; i < nfargs; i++)
    {
        if (fargs[i] && strcmp(amax, fargs[i]) < 0)
        {
            amax = fargs[i];
        }
    }

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

/*
 * Borrowed from PennMUSH 1.50 
 */
FUNCTION(fun_alphamin)
{
    if (nfargs <= 0)
    {
        safe_str("#-1 TOO FEW ARGUMENTS", buff, bufc);
        return;
    }

    char *amin = fargs[0];
    for (int i = 1; i < nfargs; i++)
    {
        if (fargs[i] && strcmp(amin, fargs[i]) > 0)
        {
            amin = fargs[i];
        }
    }

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

/*
 * Borrowed from PennMUSH 1.50 
 */

FUNCTION(fun_valid)
{
    // Checks to see if a given <something> is valid as a parameter of 
    // a given type (such as an object name)
    //
    if (!*fargs[0] || !*fargs[1])
    {
        safe_chr('0', buff, bufc);
    }
    else if (!_stricmp(fargs[0], "name"))
    {
        int nValidName;
        BOOL bValid;
        char *pValidName = MakeCanonicalObjectName(fargs[1], &nValidName, &bValid);
        char ch = (bValid) ? '1' : '0';
        safe_chr(ch, buff, bufc);
    }
    else
    {
        safe_str("#-1", buff, bufc);
    }
}

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

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

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

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

    tbuf1[0] = '#';
    int nLen = Tiny_ltoa(it, tbuf1+1) + 1;

    safe_copy_buf(tbuf1, nLen, buff, bufc, LBUF_SIZE-1);
    par = Parent(it);

    int iNestLevel = 1;
    while (  Good_obj(par)
          && Examinable(player, it)
          && iNestLevel < mudconf.parent_nest_lim)
    {
        tbuf1[0] = ' ';
        tbuf1[1] = '#';
        nLen = Tiny_ltoa(par, tbuf1+2) + 2;

        safe_copy_buf(tbuf1, nLen, buff, bufc, LBUF_SIZE-1);
        it = par;
        par = Parent(par);
        iNestLevel++;
    }
}

/* stacksize - returns how many items are stuffed onto an object stack */

int stacksize(dbref doer)
{
    int i;
    STACK *sp;
    for (i = 0, sp = Stack(doer); sp != NULL; sp = sp->next, i++)
    {
        // Nothing
        ;
    }
    return i;
}

FUNCTION(fun_lstack)
{
    STACK *sp;
    dbref doer;

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

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

// stack_clr - clear the stack.
//
void stack_clr(dbref obj)
{
    // Clear the stack.
    //
    STACK *sp, *next;
    for (sp = Stack(obj); sp != NULL; sp = next)
    {
        next = sp->next;
        free_lbuf(sp->data);
        MEMFREE(sp);
    }
    s_Stack(obj, NULL);
}

FUNCTION(fun_empty)
{
    dbref doer;

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

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

FUNCTION(fun_items)
{
    dbref doer;

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

    if (!Controls(player, doer))
    {
        safe_str("#-1 PERMISSION DENIED", buff, bufc);
        return;
    }
    safe_ltoa(stacksize(doer), buff, bufc, LBUF_SIZE-1);
}

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

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

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

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

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

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

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

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

    sp = Stack(doer);

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

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

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

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

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

/* ---------------------------------------------------------------------------
 * fun_regmatch: Return 0 or 1 depending on whether or not a regular
 * expression matches a string. If a third argument is specified, dump
 * the results of a regexp pattern match into a set of arbitrary r()-registers.
 *
 * regmatch(string, pattern, list of registers)
 * If the number of matches exceeds the registers, those bits are tossed
 * out.
 * If -1 is specified as a register number, the matching bit is tossed.
 * Therefore, if the list is "-1 0 3 5", the regexp $0 is tossed, and
 * the regexp $1, $2, and $3 become r(0), r(3), and r(5), respectively.
 *
 */

FUNCTION(fun_regmatch)
{
    int i, nqregs, curq, len;
    char *qregs[10];
    int qnums[10];
    regexp *re;

    if (!fn_range_check("REGMATCH", nfargs, 2, 3, buff, bufc))
        return;

    if ((re = regcomp(fargs[1])) == NULL) {
        /* Matching error. */
        notify_quiet(player, (const char *) regexp_errbuf);
        safe_chr('0', buff, bufc);
        return;
    }

    safe_ltoa(regexec(re, fargs[0]), buff, bufc, LBUF_SIZE-1);

    /* If we don't have a third argument, we're done. */
    if (nfargs != 3)
    {
        MEMFREE(re);
        return;
    }

    /* We need to parse the list of registers. Anything that we don't get is
     * assumed to be -1.
     */
    nqregs = list2arr(qregs, 10, fargs[2], ' ');
    for (i = 0; i < 10; i++) {
        if ((i < nqregs) && qregs[i] && *qregs[i])
            qnums[i] = Tiny_atol(qregs[i]);
        else
            qnums[i] = -1;
    }

    // Now we run a copy.
    //
    for (i = 0; (i < NSUBEXP) && (re->startp[i]) && (re->endp[i]); i++)
    {
        curq = qnums[i];
        if ((curq >= 0) && (curq < MAX_GLOBAL_REGS))
        {
            if (!mudstate.global_regs[curq])
            {
                mudstate.global_regs[curq] = alloc_lbuf("fun_regmatch");
            }

            len = re->endp[i] - re->startp[i];
            if (len > LBUF_SIZE - 1)
                len = LBUF_SIZE - 1;
            else if (len < 0)
                len = 0;

            memcpy(mudstate.global_regs[curq], re->startp[i], len);
            mudstate.global_regs[curq][len] = '\0'; /* must null-terminate */
            mudstate.glob_reg_len[curq] = len;
        }
    }

    MEMFREE(re);
}

/* ---------------------------------------------------------------------------
 * fun_translate: Takes a string and a second argument. If the second argument
 * is 0 or s, control characters are converted to spaces. If it's 1 or p,
 * they're converted to percent substitutions.
 */
 
FUNCTION(fun_translate)
{
    int type = 0;

    int ch = fargs[1][0];
    if (ch == 'p' || ch == '1')
    {
        type = 1;
    }
        
    safe_str(translate_string(fargs[0], type), buff, bufc);
}