bast/
bast/area/
bast/backup/
bast/clans/
bast/doc/MSP/
bast/doc/OLC11/
bast/doc/OLC11/doc/
bast/doc/OLC11/options/
bast/log/
bast/mobprogs/
bast/player/
/***************************************************************************
 *  Original Diku Mud copyright (C) 1990, 1991 by Sebastian Hammer,        *
 *  Michael Seifert, Hans Henrik St{rfeldt, Tom Madsen, and Katja Nyboe.   *
 *                                                                         *
 *  Merc Diku Mud improvments copyright (C) 1992, 1993 by Michael          *
 *  Chastain, Michael Quan, and Mitchell Tse.                              *
 *                                                                         *
 *  In order to use any part of this Merc Diku Mud, you must comply with   *
 *  both the original Diku license in 'license.doc' as well the Merc       *
 *  license in 'license.txt'.  In particular, you may not remove either of *
 *  these copyright notices.                                               *
 *                                                                         *
 *  Much time and thought has gone into this software and you are          *
 *  benefitting.  We hope that you share your changes too.  What goes      *
 *  around, comes around.                                                  *
 *                                                                         *
 *  The MOBprograms have been contributed by N'Atas-ha.  Any support for   *
 *  these routines should not be expected from Merc Industries.  However,  *
 *  under no circumstances should the blame for bugs, etc be placed on     *
 *  Merc Industries.  They are not guaranteed to work on all systems due   *
 *  to their frequent use of strxxx functions.  They are also not the most *
 *  efficient way to perform their tasks, but hopefully should be in the   *
 *  easiest possible way to install and begin using. Documentation for     *
 *  such installation can be found in INSTALL.  Enjoy...         N'Atas-Ha *
 ***************************************************************************/

#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include "merc.h"



/*
 *  Mudprogram additions
 */
ACT_PROG_DATA *	mob_act_list;

/*
 * Local function prototypes
 */
char *	mprog_next_command	args( ( char *clist ) );
bool 	mprog_seval		args( ( char *lhs, char *opr, char *rhs ) );
bool 	mprog_veval		args( ( int lhs, char *opr, int rhs ) );
bool 	mprog_do_ifchck		args( ( char *ifchck, CHAR_DATA *mob,
				       CHAR_DATA *actor, OBJ_DATA *obj,
				       void *vo, CHAR_DATA *rndm ) );
char *	mprog_process_if	args( ( char *ifchck, char *com_list,
				       CHAR_DATA *mob, CHAR_DATA *actor,
				       OBJ_DATA *obj, void *vo,
				       CHAR_DATA *rndm ) );
void 	mprog_translate		args( ( char ch, char *t, CHAR_DATA *mob,
				       CHAR_DATA *actor, OBJ_DATA *obj,
				       void *vo, CHAR_DATA *rndm ) );
void 	mprog_process_cmnd	args( ( char *cmnd, CHAR_DATA * mob,
				       CHAR_DATA *actor, OBJ_DATA *obj,
				       void *vo, CHAR_DATA *rndm ) );
void 	mprog_driver		args( ( char *com_list, CHAR_DATA *mob,
				       CHAR_DATA *actor, OBJ_DATA *obj,
				       void *vo ) );

/*
 * Local function code and brief comments.
 */

/*
 * Used to get sequential lines of a multi line string (separated by "\n\r")
 * Thus its like one_argument(), but a trifle different. It is destructive
 * to the multi line string argument, and thus clist must not be shared.
 */
char *mprog_next_command( char *clist )
{
    char *pointer = clist;

    while ( *pointer != '\n' && *pointer != '\0' )
	pointer++;
    if ( *pointer == '\n' )
	*pointer++ = '\0';
    if ( *pointer == '\r' )
	*pointer++ = '\0';

    return ( pointer );
}


/*
 * These two functions do the basic evaluation of ifcheck operators.
 * It is important to note that the string operations are not what
 * you probably expect.  Equality is exact and division is substring.
 * remember that lhs has been stripped of leading space, but can
 * still have trailing spaces so be careful when editing since:
 * "guard" and "guard " are not equal.
 */
bool mprog_seval( char *lhs, char *opr, char *rhs )
{
    if ( !lhs || !rhs )
	return 0;

    if ( !str_cmp( opr, "==" ) )
	return (bool) ( !str_cmp( lhs, rhs ) );
    if ( !str_cmp( opr, "!=" ) )
	return (bool) ( str_cmp( lhs, rhs ) );
    if ( !str_cmp( opr, "/" ) )
	return (bool) ( !str_infix( rhs, lhs ) );
    if ( !str_cmp( opr, "!/" ) )
	return (bool) ( str_infix( rhs, lhs ) );

    bug( "MOBProgs: Improper MOBprog operator\n\r", 0 );
    return 0;
}


bool mprog_veval( int lhs, char *opr, int rhs )
{
    if ( !lhs || !rhs )
	return 0;

    if ( !str_cmp( opr, "==" ) )
	return ( lhs == rhs );
    if ( !str_cmp( opr, "!=" ) )
	return ( lhs != rhs );
    if ( !str_cmp( opr, ">" ) )
	return ( lhs > rhs );
    if ( !str_cmp( opr, "<" ) )
	return ( lhs < rhs );
    if ( !str_cmp( opr, ">=" ) )
	return ( lhs <= rhs );
    if ( !str_cmp( opr, ">=" ) )
	return ( lhs >= rhs );
    if ( !str_cmp( opr, "&" ) )
	return ( lhs & rhs );
    if ( !str_cmp( opr, "|" ) )
	return ( lhs | rhs );

    bug( "MOBProgs: Improper MOBprog operator\n\r", 0 );
    return 0;
}


/*
 * This function performs the evaluation of the if checks.  It is
 * here that you can add any ifchecks which you so desire. Hopefully
 * it is clear from what follows how one would go about adding your
 * own. The syntax for an if check is: ifchck ( arg ) [opr val]
 * where the parenthesis are required and the opr and val fields are
 * optional but if one is there then both must be. The spaces are all
 * optional. The evaluation of the opr expressions is farmed out
 * to reduce the redundancy of the mammoth if statement list.
 * If there are errors, then return FALSE otherwise return boolean 1, 0
 *
 * Killed all that big copy-code that performs the same action on each
 * variable - Zen
 */
bool mprog_do_ifchck( char *ifchck, CHAR_DATA * mob, CHAR_DATA * actor,
		     OBJ_DATA * obj, void *vo, CHAR_DATA * rndm )
{
    char        buf [ MAX_INPUT_LENGTH ];
    char        arg [ MAX_INPUT_LENGTH ];
    char        opr [ MAX_INPUT_LENGTH ];
    char        val [ MAX_INPUT_LENGTH ];
    CHAR_DATA  *vict	= (CHAR_DATA *) vo;
    OBJ_DATA   *v_obj	= (OBJ_DATA *) vo;
    char       *bufpt	= buf;
    char       *argpt	= arg;
    char       *oprpt	= opr;
    char       *valpt	= val;
    char       *point	= ifchck;
    int         lhsvl;
    int         rhsvl;
    CHAR_DATA  *chkchar	= NULL;
    OBJ_DATA   *chkobj	= NULL;

    if ( *point == '\0' )
    {
	bug( "Mob: %d null ifchck", mob->pIndexData->vnum );
	return FALSE;
    }

    /*
     * skip leading spaces 
     */
    while ( *point == ' ' )
	point++;

    /*
     * get whatever comes before the left paren.. ignore spaces 
     */
    while ( *point != '(' )
	if ( *point == '\0' )
	{
	    bug( "Mob: %d ifchck syntax error", mob->pIndexData->vnum );
	    return FALSE;
	}
	else if ( *point == ' ' )
	    point++;
	else
	    *bufpt++ = *point++;

    *bufpt = '\0';
    point++;

    /*
     * get whatever is in between the parens.. ignore spaces 
     */
    while ( *point != ')' )
	if ( *point == '\0' )
	{
	    bug( "Mob: %d ifchck syntax error", mob->pIndexData->vnum );
	    return FALSE;
	}
	else if ( *point == ' ' )
	    point++;
	else
	    *argpt++ = *point++;

    *argpt = '\0';
    point++;

    /*
     * check to see if there is an operator 
     */
    while ( *point == ' ' )
	point++;
    if ( *point == '\0' )
    {
	*opr = '\0';
	*val = '\0';
    }
    else
	/*
	 * there should be an operator and value, so get them 
	 */
    {
	while ( ( *point != ' ' ) && ( !isalnum( *point ) ) )
	    if ( *point == '\0' )
	    {
		bug( "Mob: %d ifchck operator without value",
		    mob->pIndexData->vnum );
		return FALSE;
	    }
	    else
		*oprpt++ = *point++;

	*oprpt = '\0';

	/*
	 * finished with operator, skip spaces and then get the value 
	 */
	while ( *point == ' ' )
	    point++;
	for ( ; ; )
	{
	    if ( ( *point != ' ' ) && ( *point == '\0' ) )
		break;
	    else
		*valpt++ = *point++;
	}

	*valpt = '\0';
    }
    bufpt = buf;
    argpt = arg;
    oprpt = opr;
    valpt = val;

    /*
     * Ok... now buf contains the ifchck, arg contains the inside of the
     * parentheses, opr contains an operator if one is present, and val
     * has the value if an operator was present.
     * So.. basically use if statements and run over all known ifchecks
     * Once inside, use the argument and expand the lhs. Then if need be
     * send the lhs, opr, rhs off to be evaluated.
     */

    if ( arg[0] == '$' )
    {
	char        buf1 [ MAX_INPUT_LENGTH ];	/* Used for output */

	/*
	 * arg should be "$*" so just get the letter 
	 */
	switch( arg[1] )
	{
	case 'i':	chkchar	= mob;					break;
	case 'n':	chkchar	= actor;				break;
	case 't':	chkchar	= vict;					break;
	case 'r':	chkchar	= rndm;					break;
	case 'o':	chkobj	= obj;					break;
	case 'p':	chkobj	= v_obj;				break;
	default:

	    sprintf( buf1, "Mob: %%d bad argument '%s' to '%s'", arg, buf );
	    bug( buf1, mob->pIndexData->vnum );
	    return FALSE;
	}
	if ( !chkchar && !chkobj )
	    return FALSE;
    }

    if ( !str_cmp( buf, "rand" ) )
    {
	return ( number_percent() <= atoi( arg ) );
    }

    if ( chkchar
	&& !chkchar->deleted )
    {
	if ( !str_cmp( buf, "ispc" ) )
	    return ( IS_NPC( chkchar ) ? FALSE : TRUE );

	if ( !str_cmp( buf, "isnpc" ) )
	    return ( IS_NPC( chkchar ) ? TRUE : FALSE );

	if ( !str_cmp( buf, "ismounted" ) )
	    return ( chkchar->riding   ? TRUE : FALSE );

	if ( !str_cmp( buf, "isgood" ) )
	    return ( IS_GOOD( chkchar ) ? TRUE : FALSE );

	if ( !str_cmp( buf, "isneutral" ) )
	    return ( IS_NEUTRAL( chkchar ) ? TRUE : FALSE );

	if ( !str_cmp( buf, "isevil" ) )
	    return ( IS_EVIL( chkchar ) ? TRUE : FALSE );

	if ( !str_cmp( buf, "isfight" ) )
	    return ( chkchar->fighting ? 1 : 0 );

	if ( !str_cmp( buf, "isimmort" ) )
	    return ( IS_IMMORTAL( chkchar ) ? TRUE : FALSE );

	if ( !str_cmp( buf, "ischarmed" ) )
	    return ( IS_AFFECTED( chkchar, AFF_CHARM ) ? TRUE : FALSE );

	if ( !str_cmp( buf, "isfollow" ) )
	    return ( chkchar->master
		    && chkchar->master->in_room == chkchar->in_room );

        // Looks like OLC does not support multiple bitvectors - Veygoth
	if ( !str_cmp( buf, "isaffected" ) )
	    return ( IS_SET( chkchar->affected_by[0], atoi( arg ) ) );

	if ( !str_cmp( buf, "hitprcnt" ) )
	{
	    lhsvl = chkchar->hit / chkchar->max_hit;
	    rhsvl = atoi( val );
	    return mprog_veval( lhsvl, opr, rhsvl );
	}

	if ( !str_cmp( buf, "inroom" ) )
	{
	    lhsvl = chkchar->in_room->vnum;
	    rhsvl = atoi( val );
	    return mprog_veval( lhsvl, opr, rhsvl );
	}

	if ( !str_cmp( buf, "sex" ) )
	{
	    lhsvl = chkchar->sex;
	    rhsvl = atoi( val );
	    return mprog_veval( lhsvl, opr, rhsvl );
	}

	if ( !str_cmp( buf, "position" ) )
	{
	    lhsvl = chkchar->position;
	    rhsvl = atoi( val );
	    return mprog_veval( lhsvl, opr, rhsvl );
	}

	if ( !str_cmp( buf, "level" ) )
	{
	    lhsvl = get_trust( chkchar );
	    rhsvl = atoi( val );
	    return mprog_veval( lhsvl, opr, rhsvl );
	}

	if ( !str_cmp( buf, "class" ) )
	{
	    lhsvl = chkchar->class;
	    rhsvl = atoi( val );
	    return mprog_veval( lhsvl, opr, rhsvl );
	}

	if ( !str_cmp( buf, "goldamt" ) )
	{
	    lhsvl = chkchar->money.gold;
	    rhsvl = atoi( val );
	    return mprog_veval( lhsvl, opr, rhsvl );
	}

	if ( !str_cmp( buf, "number" ) )
	{
	    lhsvl = chkchar->pIndexData->vnum;
	    rhsvl = atoi( val );
	    return mprog_veval( lhsvl, opr, rhsvl );
	}

	if ( !str_cmp( buf, "name" ) )
	    return mprog_seval( chkchar->name, opr, val );
    }

    if ( chkobj
	&& !chkobj->deleted )
    {
	if ( !str_cmp( buf, "objtype" ) )
	{
	    lhsvl = chkobj->item_type;
	    rhsvl = atoi( val );
	    return mprog_veval( lhsvl, opr, rhsvl );
	}

	if ( !str_cmp( buf, "objval0" ) )
	{
	    lhsvl = chkobj->value[0];
	    rhsvl = atoi( val );
	    return mprog_veval( lhsvl, opr, rhsvl );
	}

	if ( !str_cmp( buf, "objval1" ) )
	{
	    lhsvl = chkobj->value[1];
	    rhsvl = atoi( val );
	    return mprog_veval( lhsvl, opr, rhsvl );
	}

	if ( !str_cmp( buf, "objval2" ) )
	{
	    lhsvl = chkobj->value[2];
	    rhsvl = atoi( val );
	    return mprog_veval( lhsvl, opr, rhsvl );
	}

	if ( !str_cmp( buf, "objval3" ) )
	{
	    lhsvl = chkobj->value[3];
	    rhsvl = atoi( val );
	    return mprog_veval( lhsvl, opr, rhsvl );
	}

	if ( !str_cmp( buf, "objval4" ) )
	{
	    lhsvl = chkobj->value[4];
	    rhsvl = atoi( val );
	    return mprog_veval( lhsvl, opr, rhsvl );
	}

	if ( !str_cmp( buf, "number" ) )
	{
	    lhsvl = chkobj->pIndexData->vnum;
	    rhsvl = atoi( val );
	    return mprog_veval( lhsvl, opr, rhsvl );
	}

	if ( !str_cmp( buf, "name" ) )
	    return mprog_seval( chkobj->name, opr, val );
    }

    /*
     * Ok... all the ifchcks are done, so if we didn't find ours then something
     * odd happened.  So report the bug and abort the MOBprogram (return error)
     */
    bug( "Mob: %d unknown ifchck", mob->pIndexData->vnum );
    return FALSE;
}


/*
 * Quite a long and arduous function, this guy handles the control
 * flow part of MOBprograms.  Basicially once the driver sees an
 * 'if' attention shifts to here.  While many syntax errors are
 * caught, some will still get through due to the handling of break
 * and errors in the same fashion.  The desire to break out of the
 * recursion without catastrophe in the event of a mis-parse was
 * believed to be high. Thus, if an error is found, it is bugged and
 * the parser acts as though a break were issued and just bails out
 * at that point. I haven't tested all the possibilites, so I'm speaking
 * in theory, but it is 'guaranteed' to work on syntactically correct
 * MOBprograms, so if the mud crashes here, check the mob carefully!
 */
static char null[1];

char *mprog_process_if( char *ifchck, char *com_list, CHAR_DATA * mob,
		       CHAR_DATA * actor, OBJ_DATA * obj, void *vo,
		       CHAR_DATA * rndm )
{
    char buf [ MAX_INPUT_LENGTH ];
    char *morebuf = '\0';
    char *cmnd = '\0';
    bool loopdone = FALSE;
    bool flag = FALSE;
    int legal;

    *null = '\0';

    /*
     * check for trueness of the ifcheck 
     */
    if ( ( legal = mprog_do_ifchck( ifchck, mob, actor, obj, vo, rndm ) ) )
	if ( legal == 1 )
	    flag = TRUE;
	else
	    return null;

    while ( !loopdone )		/*
				 * scan over any existing or statements 
				 */
    {
	cmnd = com_list;
	com_list = mprog_next_command( com_list );
	while ( *cmnd == ' ' )
	    cmnd++;
	if ( *cmnd == '\0' )
	{
	    bug( "Mob: %d no commands after IF/OR", mob->pIndexData->vnum );
	    return null;
	}
	morebuf = one_argument( cmnd, buf );
	if ( !str_cmp( buf, "or" ) )
	{
	    if ( ( legal = mprog_do_ifchck( morebuf, mob, actor, obj, vo,
	        rndm ) ) )
		if ( legal == 1 )
		    flag = TRUE;
		else
		    return null;
	}
	else
	    loopdone = TRUE;
    }

    if ( flag )
	for ( ; ; )		/*
				 * ifcheck was true, do commands but ignore else
				 * to endif
				 */
	{
	    if ( !str_cmp( buf, "if" ) )
	    {
		com_list = mprog_process_if( morebuf, com_list, mob, actor, obj,
		    vo, rndm );
		while ( *cmnd == ' ' )
		    cmnd++;
		if ( *com_list == '\0' )
		    return null;
		cmnd = com_list;
		com_list = mprog_next_command( com_list );
		morebuf = one_argument( cmnd, buf );
		continue;
	    }
	    if ( !str_cmp( buf, "break" ) )
		return null;
	    if ( !str_cmp( buf, "endif" ) )
		return com_list;
	    if ( !str_cmp( buf, "else" ) )
	    {
		while ( str_cmp( buf, "endif" ) )
		{
		    cmnd = com_list;
		    com_list = mprog_next_command( com_list );
		    while ( *cmnd == ' ' )
			cmnd++;
		    if ( *cmnd == '\0' )
		    {
			bug( "Mob: %d missing endif after else",
			    mob->pIndexData->vnum );
			return null;
		    }
		    morebuf = one_argument( cmnd, buf );
		}
		return com_list;
	    }
	    mprog_process_cmnd( cmnd, mob, actor, obj, vo, rndm );
	    cmnd = com_list;
	    com_list = mprog_next_command( com_list );
	    while ( *cmnd == ' ' )
		cmnd++;
	    if ( *cmnd == '\0' )
	    {
		bug( "Mob: %d missing else or endif", mob->pIndexData->vnum );
		return null;
	    }
	    morebuf = one_argument( cmnd, buf );
	}
    else
	/*
	 * false ifcheck, find else and do existing commands or quit at endif
	 */
    {
	while ( ( str_cmp( buf, "else" ) ) && ( str_cmp( buf, "endif" ) ) )
	{
	    cmnd = com_list;
	    com_list = mprog_next_command( com_list );
	    while ( *cmnd == ' ' )
		cmnd++;
	    if ( *cmnd == '\0' )
	    {
		bug( "Mob: %d missing an else or endif",
		    mob->pIndexData->vnum );
		return null;
	    }
	    morebuf = one_argument( cmnd, buf );
	}

	/*
	 * found either an else or an endif.. act accordingly 
	 */
	if ( !str_cmp( buf, "endif" ) )
	    return com_list;
	cmnd = com_list;
	com_list = mprog_next_command( com_list );
	while ( *cmnd == ' ' )
	    cmnd++;
	if ( *cmnd == '\0' )
	{
	    bug( "Mob: %d missing endif", mob->pIndexData->vnum );
	    return null;
	}
	morebuf = one_argument( cmnd, buf );

	for ( ; ; )		/*
				 * process the post-else commands until an endif is found.
				 */
	{
	    if ( !str_cmp( buf, "if" ) )
	    {
		com_list = mprog_process_if( morebuf, com_list, mob, actor,
					    obj, vo, rndm );
		while ( *cmnd == ' ' )
		    cmnd++;
		if ( *com_list == '\0' )
		    return null;
		cmnd = com_list;
		com_list = mprog_next_command( com_list );
		morebuf = one_argument( cmnd, buf );
		continue;
	    }
	    if ( !str_cmp( buf, "else" ) )
	    {
		bug( "Mob: %d found else in an else section",
		    mob->pIndexData->vnum );
		return null;
	    }
	    if ( !str_cmp( buf, "break" ) )
		return null;
	    if ( !str_cmp( buf, "endif" ) )
		return com_list;
	    mprog_process_cmnd( cmnd, mob, actor, obj, vo, rndm );
	    cmnd = com_list;
	    com_list = mprog_next_command( com_list );
	    while ( *cmnd == ' ' )
		cmnd++;
	    if ( *cmnd == '\0' )
	    {
		bug( "Mob:%d missing endif in else section",
		    mob->pIndexData->vnum );
		return null;
	    }
	    morebuf = one_argument( cmnd, buf );
	}
    }

}


/*
 * This routine handles the variables for command expansion.
 * If you want to add any go right ahead, it should be fairly
 * clear how it is done and they are quite easy to do, so you
 * can be as creative as you want. The only catch is to check
 * that your variables exist before you use them. At the moment,
 * using $t when the secondary target refers to an object 
 * i.e. >prog_act drops~<nl>if ispc($t)<nl>sigh<nl>endif<nl>~<nl>
 * probably makes the mud crash (vice versa as well) The cure
 * would be to change act() so that vo becomes vict & v_obj.
 * but this would require a lot of small changes all over the code.
 */
void mprog_translate( char ch, char *t, CHAR_DATA * mob, CHAR_DATA * actor,
		     OBJ_DATA * obj, void *vo, CHAR_DATA * rndm )
{
    static char *he_she [ ] =
    { "it", "he", "she" };
    static char *him_her [ ] =
    { "it", "him", "her" };
    static char *his_her [ ] =
    { "its", "his", "her" };
    CHAR_DATA *vict = (CHAR_DATA *) vo;
    OBJ_DATA *v_obj = (OBJ_DATA *) vo;

    /* Just a little safety check - Zen */
    if ( !mob || mob->deleted )
	return;

    *t = '\0';
    switch ( ch )
    {
	case 'i':
	    one_argument( mob->name, t );
	    break;

	case 'I':
	    strcpy( t, mob->short_descr );
	    break;

	case 'n':
	    if ( !actor || actor->deleted )
		break;
	    if ( can_see( mob, actor ) )
		one_argument( actor->name, t );
	    if ( !IS_NPC( actor ) )
		*t = UPPER(*t);
	    break;

	case 'N':
	    if ( !actor || actor->deleted )
		break;
	    if ( can_see( mob, actor ) )
	    {
		if ( IS_NPC( actor ) )
		    strcpy( t, actor->short_descr );
		else
		{
		    strcpy( t, actor->name );
		    strcat( t, " " );
		    strcat( t, actor->pcdata->title );
		}
	    }
	    else
		strcpy( t, "someone" );
	    break;

	case 't':
	    if ( !vict || vict->deleted )
		break;
	    if ( can_see( mob, vict ) )
		one_argument( vict->name, t );
	    if ( !IS_NPC( vict ) )
		*t = UPPER( *t );
	    break;

	case 'T':
	    if ( !vict || vict->deleted )
		break;
	    if ( can_see( mob, vict ) )
	    {
		if ( IS_NPC( vict ) )
		    strcpy( t, vict->short_descr );
		else
		{
		    strcpy( t, vict->name );
		    strcat( t, " " );
		    strcat( t, vict->pcdata->title );
		}
	    }
	    else
		strcpy( t, "someone" );
	    break;

	case 'r':
	    if ( !rndm || rndm->deleted )
		break;
	    if ( can_see( mob, rndm ) )
		one_argument( rndm->name, t );	    
	    if ( !IS_NPC( rndm ) )
		*t = UPPER( *t );
	    break;

	case 'R':
	    if ( !rndm || rndm->deleted )
		break;
	    if ( can_see( mob, rndm ) )
	    {
		if ( IS_NPC( rndm ) )
		    strcpy( t, rndm->short_descr );
		else
		{
		    strcpy( t, rndm->name );
		    strcat( t, " " );
		    strcat( t, rndm->pcdata->title );
		}
	    }
	    else
		strcpy( t, "someone" );
	    break;

	case 'e':
	    if ( actor && !actor->deleted )
		can_see( mob, actor ) ? strcpy( t, he_she[actor->sex] )
		    : strcpy( t, "someone" );
	    break;

	case 'm':
	    if ( actor && !actor->deleted )
		can_see( mob, actor ) ? strcpy( t, him_her[actor->sex] )
		    : strcpy( t, "someone" );
	    break;

	case 's':
	    if ( actor && !actor->deleted )
		can_see( mob, actor ) ? strcpy( t, his_her[actor->sex] )
		    : strcpy( t, "someone's" );
	    break;

	case 'E':
	    if ( vict && !vict->deleted )
		can_see( mob, vict ) ? strcpy( t, he_she[vict->sex] )
		    : strcpy( t, "someone" );
	    break;

	case 'M':
	    if ( vict && !vict->deleted )
		can_see( mob, vict ) ? strcpy( t, him_her[vict->sex] )
		    : strcpy( t, "someone" );
	    break;

	case 'S':
	    if ( vict && !vict->deleted )
		can_see( mob, vict ) ? strcpy( t, his_her[vict->sex] )
		    : strcpy( t, "someone's" );
	    break;

	case 'j':
	    strcpy( t, he_she[mob->sex] );
	    break;

	case 'k':
	    strcpy( t, him_her[mob->sex] );
	    break;

	case 'l':
	    strcpy( t, his_her[mob->sex] );
	    break;

	case 'J':
	    if ( rndm && !rndm->deleted )
		can_see( mob, rndm ) ? strcpy( t, he_she[rndm->sex] )
		    : strcpy( t, "someone" );
	    break;

	case 'K':
	    if ( rndm && !rndm->deleted )
		can_see( mob, rndm ) ? strcpy( t, him_her[rndm->sex] )
		    : strcpy( t, "someone" );
	    break;

	case 'L':
	    if ( rndm && !rndm->deleted )
		can_see( mob, rndm ) ? strcpy( t, his_her[rndm->sex] )
		    : strcpy( t, "someone's" );
	    break;

	case 'o':
	    if ( obj && !obj->deleted )
		can_see_obj( mob, obj ) ? one_argument( obj->name, t )
		    : strcpy( t, "something" );
	    break;

	case 'O':
	    if ( obj && !obj->deleted )
		can_see_obj( mob, obj ) ? strcpy( t, obj->short_descr )
		    : strcpy( t, "something" );
	    break;

	case 'p':
	    if ( v_obj && !v_obj->deleted )
		can_see_obj( mob, v_obj ) ? one_argument( v_obj->name, t )
		    : strcpy( t, "something" );
	    break;

	case 'P':
	    if ( v_obj && !v_obj->deleted )
		can_see_obj( mob, v_obj ) ? strcpy( t, v_obj->short_descr )
		    : strcpy( t, "something" );
	    break;

	case 'a':
	    if ( obj && !obj->deleted )
		switch (*(obj->name))
		{
		    case 'a':
		    case 'e':
		    case 'i':
		    case 'o':
		    case 'u':
			strcpy( t, "an" );
			break;
		    default:
			strcpy( t, "a" );
		}
	    break;

	case 'A':
	    if ( v_obj && !v_obj->deleted )
		switch (*(v_obj->name))
		{
		    case 'a':
		    case 'e':
		    case 'i':
		    case 'o':
		    case 'u':
			strcpy( t, "an" );
			break;
		    default:
			strcpy( t, "a" );
		}
	    break;

	case '$':
	    strcpy( t, "$" );
	    break;

	default:
	    bug( "Mob: %d bad $var", mob->pIndexData->vnum );
	    break;
    }

    return;
}


/*
 * This procedure simply copies the cmnd to a buffer while expanding
 * any variables by calling the translate procedure.  The observant
 * code scrutinizer will notice that this is taken from act()
 */
void mprog_process_cmnd( char *cmnd, CHAR_DATA * mob, CHAR_DATA * actor,
			OBJ_DATA * obj, void *vo, CHAR_DATA * rndm )
{
    char buf [ MAX_INPUT_LENGTH ];
    char tmp [ MAX_INPUT_LENGTH ];
    char *str;
    char *i;
    char *point;

    point = buf;
    str = cmnd;

    while ( *str != '\0' )
    {
	if ( *str != '$' )
	{
	    *point++ = *str++;
	    continue;
	}
	str++;
	mprog_translate( *str, tmp, mob, actor, obj, vo, rndm );
	i = tmp;
	++str;
	while ( ( *point = *i ) != '\0' )
	    ++point, ++i;
    }
    *point = '\0';
    interpret( mob, buf );

    return;
}


/*
 * The main focus of the MOBprograms.  This routine is called 
 * whenever a trigger is successful.  It is responsible for parsing
 * the command list and figuring out what to do. However, like all
 * complex procedures, everything is farmed out to the other guys.
 */
void mprog_driver( char *com_list, CHAR_DATA * mob, CHAR_DATA * actor,
		  OBJ_DATA * obj, void *vo )
{
    char tmpcmndlst [ MAX_STRING_LENGTH ];
    char buf [ MAX_INPUT_LENGTH ];
    char *morebuf;
    char *command_list;
    char *cmnd;
    CHAR_DATA *rndm = NULL;
    CHAR_DATA *vch = NULL;
    int count = 0;

    if ( IS_AFFECTED( mob, AFF_CHARM ) )
	return;

    /*
     * get a random visable mortal player who is in the room with the mob 
     */
    for ( vch = mob->in_room->people; vch; vch = vch->next_in_room )
	if ( !IS_NPC( vch )
	    && !vch->deleted
	    && vch->level < LEVEL_IMMORTAL
	    && can_see( mob, vch ) )
	{
	    if ( number_range( 0, count ) == 0 )
		rndm = vch;
	    count++;
	}

    strcpy( tmpcmndlst, com_list );
    command_list = tmpcmndlst;
    cmnd = command_list;
    command_list = mprog_next_command( command_list );
    while ( *cmnd != '\0' )
    {
	morebuf = one_argument( cmnd, buf );
	if ( !str_cmp( buf, "if" ) )
	    command_list = mprog_process_if( morebuf, command_list, mob,
					    actor, obj, vo, rndm );
	else
	    mprog_process_cmnd( cmnd, mob, actor, obj, vo, rndm );
	cmnd = command_list;
	command_list = mprog_next_command( command_list );
    }

    return;
}


/*
 * Global function code and brief comments.
 */

/*
 * The next two routines are the basic trigger types. Either trigger
 * on a certain percent, or trigger on a keyword or word phrase.
 * To see how this works, look at the various trigger routines..
 */
void mprog_wordlist_check( char *arg, CHAR_DATA * mob, CHAR_DATA * actor,
			  OBJ_DATA * obj, void *vo, int type )
{
    MPROG_DATA   *mprg;
    char         *list;
    char         *start;
    char         *dupl;
    char         *end;
    char          temp1 [ MAX_STRING_LENGTH ];
    char          temp2 [ MAX_INPUT_LENGTH ];
    char          word  [ MAX_INPUT_LENGTH ];
    unsigned int  i;

    for ( mprg = mob->pIndexData->mobprogs; mprg; mprg = mprg->next )
	if ( mprg->type & type )
	{
	    strcpy( temp1, mprg->arglist );
	    list = temp1;
	    for ( i = 0; i < strlen( list ); i++ )
		list[i] = LOWER( list[i] );

	    /*
	     * BUGFIX: Copy AT MOST sizeof( temp2) characters.  Walker 
	     */
	    strncpy( temp2, arg, sizeof( temp2 ) );
	    /*
	     * Add a NULL char at end of temp2.  Walker 
	     */
	    temp2[ MAX_INPUT_LENGTH - 1 ] = '\0';

	    dupl = temp2;
	    for ( i = 0; i < strlen( dupl ); i++ )
		dupl[i] = LOWER( dupl[i] );
	    if ( ( list[0] == 'p' ) && ( list[1] == ' ' ) )
	    {
		list += 2;
		while ( ( start = strstr( dupl, list ) ) )
		    if ( ( start == dupl || *(start - 1) == ' ' )
			&& ( *(end = start + strlen(list)) == ' '
			    || *end == '\n'
			    || *end == '\r'
			    || *end == 0 ) )
		    {
			mprog_driver( mprg->comlist, mob, actor, obj, vo );
			break;
		    }
		    else
			dupl = start + 1;
	    }
	    else
	    {
		list = one_argument( list, word );
		for ( ; word[0] != '\0'; list = one_argument( list, word ) )
		    while ( ( start = strstr( dupl, word ) ) )
			if ( ( start == dupl || *(start - 1) == ' ' )
			    && ( *(end = start + strlen( word )) == ' '
				|| *end == '\n'
				|| *end == '\r'
				|| *end == 0 ) )
			{
			    mprog_driver( mprg->comlist, mob, actor, obj, vo );
			    break;
			}
			else
			    dupl = start + 1;
	    }
	}

    return;
}


void mprog_percent_check( CHAR_DATA * mob, CHAR_DATA * actor, OBJ_DATA * obj,
			 void *vo, int type )
{
    MPROG_DATA *mprg;

    for ( mprg = mob->pIndexData->mobprogs; mprg; mprg = mprg->next )
	if ( (mprg->type & type )
	    && ( number_percent() < atoi( mprg->arglist ) ) )
	{
	    mprog_driver( mprg->comlist, mob, actor, obj, vo );
	    if ( type != GREET_PROG && type != ALL_GREET_PROG )
		break;
	}

    return;
}


void mob_act_add( CHAR_DATA *mob )
{
    ACT_PROG_DATA * runner;
  
    for ( runner = mob_act_list; runner; runner = runner->next )
	if ( runner->vo == mob )
	    return;

    runner		= alloc_mem( sizeof( ACT_PROG_DATA ) );
    runner->vo		= mob;
    runner->next	= mob_act_list;
    mob_act_list	= runner;
}


/*
 * The triggers.. These are really basic, and since most appear only
 * once in the code (hmm. i think they all do) it would be more efficient
 * to substitute the code in and make the mprog_xxx_check routines global.
 * However, they are all here in one nice place at the moment to make it
 * easier to see what they look like. If you do substitute them back in,
 * make sure you remember to modify the variable names to the ones in the
 * trigger calls.
 */
void mprog_act_trigger( char *buf, CHAR_DATA * mob, CHAR_DATA * ch,
		       OBJ_DATA * obj, void *vo )
{
    MPROG_ACT_LIST *tmp_act;

    if ( IS_NPC( mob )
	&& IS_SET( mob->pIndexData->progtypes, ACT_PROG ) )
    {
        /* 
         * Don't let a mob trigger itself, nor one instance of a mob
         * trigger another instance.
         */
	if ( IS_NPC( ch ) && ch->pIndexData == mob->pIndexData )
	    return;
	tmp_act = alloc_mem( sizeof( MPROG_ACT_LIST ) );
	if ( mob->mpactnum > 0 )
	    tmp_act->next = mob->mpact->next;
	else
	    tmp_act->next = NULL;

	mob->mpact = tmp_act;
	mob->mpact->buf = str_dup( buf );
	mob->mpact->ch = ch;
	mob->mpact->obj = obj;
	mob->mpact->vo = vo;
	mob->mpactnum++;
	mob_act_add( mob );
    }

    return;
}

void mprog_bribe_trigger( CHAR_DATA * mob, CHAR_DATA * ch, int amount )
{
    char buf [ MAX_STRING_LENGTH ];
    MPROG_DATA *mprg;
    OBJ_DATA *obj;

    if ( IS_NPC( mob )
	&& IS_SET( mob->pIndexData->progtypes, BRIBE_PROG ) )
    {
	obj = create_object( get_obj_index( OBJ_VNUM_MONEY_SOME ), 0 );
	sprintf( buf, obj->short_descr, amount );
	free_string( obj->short_descr );
	obj->short_descr = str_dup( buf );
	obj->value[0] = amount;
	obj_to_char( obj, mob );
	spend_cash( mob, amount );

	for ( mprg = mob->pIndexData->mobprogs; mprg != NULL;
	     mprg = mprg->next )

	    if ( IS_SET( mprg->type, BRIBE_PROG )
		&& ( amount >= atoi( mprg->arglist ) ) )
	    {
		mprog_driver( mprg->comlist, mob, ch, obj, NULL );
		break;
	    }
    }

    return;
}

void mprog_death_trigger( CHAR_DATA * mob )
{
    if ( IS_NPC( mob )
	&& IS_SET( mob->pIndexData->progtypes, DEATH_PROG ) )
    {
	mprog_percent_check( mob, NULL, NULL, NULL, DEATH_PROG );
    }

    death_cry( mob );
    return;
}

void mprog_entry_trigger( CHAR_DATA * mob )
{
    if ( IS_NPC( mob )
	&& IS_SET( mob->pIndexData->progtypes, ENTRY_PROG ) )
	mprog_percent_check( mob, NULL, NULL, NULL, ENTRY_PROG );

    return;
}

void mprog_fight_trigger( CHAR_DATA * mob, CHAR_DATA * ch )
{
    if ( IS_NPC( mob )
	&& IS_SET( mob->pIndexData->progtypes, FIGHT_PROG ) )
	mprog_percent_check( mob, ch, NULL, NULL, FIGHT_PROG );

    return;
}

void mprog_give_trigger( CHAR_DATA * mob, CHAR_DATA * ch, OBJ_DATA * obj )
{
    char buf [ MAX_INPUT_LENGTH ];
    MPROG_DATA *mprg;

    if ( IS_NPC( mob )
	&& IS_SET( mob->pIndexData->progtypes, GIVE_PROG ) )
	for ( mprg = mob->pIndexData->mobprogs; mprg; mprg = mprg->next )
	{
	    one_argument( mprg->arglist, buf );
	    if ( IS_SET( mprg->type, GIVE_PROG )
		&& ( !str_cmp( obj->name, mprg->arglist )
		    || !str_cmp( "all", buf ) ) )
	    {
		mprog_driver( mprg->comlist, mob, ch, obj, NULL );
		break;
	    }
	}

    return;
}

void mprog_greet_trigger( CHAR_DATA * ch )
{
    CHAR_DATA *vmob;

    if ( ch->deleted
	|| !ch->in_room )
	return;

    for ( vmob = ch->in_room->people; vmob; vmob = vmob->next_in_room )
    {
	if ( vmob->deleted )
	    continue;

	if ( !IS_NPC( vmob )
	    || IS_AFFECTED( vmob, AFF_CHARM )
	    || !IS_AWAKE( vmob )
	    || vmob->fighting )
	    continue;

	if ( ch != vmob
	    && can_see( vmob, ch )
	    && IS_SET( vmob->pIndexData->progtypes, GREET_PROG ) )
	    mprog_percent_check( vmob, ch, NULL, NULL, GREET_PROG );
	else if ( IS_SET( vmob->pIndexData->progtypes, ALL_GREET_PROG ) )
	    mprog_percent_check( vmob, ch, NULL, NULL, ALL_GREET_PROG );

    }
    return;

}

void mprog_hitprcnt_trigger( CHAR_DATA * mob, CHAR_DATA * ch )
{
    MPROG_DATA *mprg;

    if ( IS_NPC( mob )
	&& IS_SET( mob->pIndexData->progtypes, HITPRCNT_PROG ) )
	for ( mprg = mob->pIndexData->mobprogs; mprg; mprg = mprg->next )
	    if ( IS_SET( mprg->type, HITPRCNT_PROG )
	      && ( ( 100 * mob->hit / mob->max_hit ) < atoi( mprg->arglist ) ) )
	    {
		mprog_driver( mprg->comlist, mob, ch, NULL, NULL );
		break;
	    }

    return;
}

void mprog_random_trigger( CHAR_DATA * mob )
{
    if ( IS_SET( mob->pIndexData->progtypes, RAND_PROG ) )
	mprog_percent_check( mob, NULL, NULL, NULL, RAND_PROG );

    return;
}

void mprog_speech_trigger( char *txt, CHAR_DATA * mob )
{
    CHAR_DATA *vmob;

    if ( mob->deleted
	|| !mob->in_room )
	return;

    for ( vmob = mob->in_room->people; vmob; vmob = vmob->next_in_room )
    {
	if ( vmob->deleted )
	    continue;

	if ( !IS_NPC( vmob )
	    || IS_AFFECTED( vmob, AFF_CHARM )
	    || vmob->fighting )
	    continue;

	if ( mob != vmob
	    && IS_SET( vmob->pIndexData->progtypes, SPEECH_PROG ) )
	    mprog_wordlist_check( txt, vmob, mob, NULL, NULL, SPEECH_PROG );

    }

    return;
}