daleken/
daleken/data/notes/
daleken/data/player/
daleken/data/system/poses/
daleken/doc/Homepage/images/
daleken/log/
/*___________________________________________________________________________*
   )()(			  DalekenMUD 1.12 (C) 2000			)()(
   `]['		       by Martin Thomson, Lee Brooks,			`]['
    ||		       Ken Herbert and David Jacques			 ||
    || ----------------------------------------------------------------- ||
    || Envy Diku Mud improvements copyright (C) 1994 by Michael Quan,	 ||
    || David Love, Guilherme 'Willie' Arnold, and Mitchell Tse.		 ||
    || Merc Diku Mud improvments copyright (C) 1992, 1993 by Michael	 ||
    || Chastain, Michael Quan, and Mitchell Tse.			 ||
    || Original Diku Mud copyright (C) 1990, 1991			 ||
    || by Sebastian Hammer, Michael Seifert, Hans Henrik St{rfeldt,	 ||
    || Tom Madsen, and Katja Nyboe.					 ||
    || ----------------------------------------------------------------- ||
    || Any use of this software must follow the licenses of the		 ||
    || creators.  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.					 ||
    || ----------------------------------------------------------------- ||
    ||                            mud_prog.c                             ||
    || MUDProgram parsing code.  Based on N'Atas-ha's MOBPrograms and    ||
    || SMAUG's MUDPrograms.                                              ||
 *_/<>\_________________________________________________________________/<>\_*/


/****************************************************************************
 * This version of MOBprograms use the same basic format as N'Atas-ha with  *
 * major modifications to the code.  Parsing is hopefully tidier and now    *
 * they should run to a certain extent with variables.	I trust you will    *
 * find these useful and I hope that they will increase the flexibility of  *
 * your MOBprograms.  Note that some of the functions in this file are non- *
 * standard, I use OLC and have found that the table lookup system is	    *
 * infinitely useful, these references can be removed if you are careful.   *
 *			--Symposium '98					    *
 *									    *
 * News Flash!								    *
 * MOBProgs now work more like shell scripts, substitutions and arithmetic  *
 * are now supported to a certain degree and the variables have been	    *
 * integrated into the parsing.	 I hope this makes your intelligent mobiles *
 * a little smarter.							    *
 *			--Sym 3/99					    *
 *									    *
 * MOBProgs no more, enter MUDPrograms!					    *
 * MUDPrograms include programs for objects and rooms, which are fairly	    *
 * similar to the MOBProgs of old.  There are now tons of possibilities for *
 * programs, with heaps of extra triggers.  There are only a few	    *
 * restrictions on the capability of room and object programs, see the	    *
 * documentation for further info.					    *
 *			--Sym 6/99					    *
 ****************************************************************************/


#include <stdarg.h>
#include "mud.h"
#include "olc.h"
#include "regexp.h"
#include "event.h"


/* make this equal to the maximum number of args to any function */
#define MAX_ARGS 4

DECLARE_DO_FUN(	   mp_aecho		);	/* Daleken */
DECLARE_DO_FUN(	   mp_asound		);
DECLARE_DO_FUN(	   mp_at		);
DECLARE_DO_FUN(	   mp_burrow		);	/* Daleken */
DECLARE_DO_FUN(	   mp_cast		);	/* Daleken */
DECLARE_DO_FUN(	   mp_close		);	/* Daleken */
DECLARE_DO_FUN(	   mp_closepassage	);	/* Daleken */
DECLARE_DO_FUN(	   mp_damage		);	/* Daleken */
DECLARE_DO_FUN(	   mp_delay		);	/* Daleken */
DECLARE_DO_FUN(	   mp_delete		);	/* Daleken */
DECLARE_DO_FUN(	   mp_deposit		);
DECLARE_DO_FUN(	   mp_dream		);	/* Daleken */
DECLARE_DO_FUN(	   mp_echo		);
DECLARE_DO_FUN(	   mp_echoaround	);
DECLARE_DO_FUN(	   mp_echoat		);
DECLARE_DO_FUN(	   mp_force		);
DECLARE_DO_FUN(	   mp_goto		);
DECLARE_DO_FUN(	   mp_grant		);	/* Daleken */
DECLARE_DO_FUN(	   mp_junk		);
DECLARE_DO_FUN(	   mp_kill		);
DECLARE_DO_FUN(	   mp_lock		);	/* Daleken */
DECLARE_DO_FUN(	   mp_mload		);
DECLARE_DO_FUN(	   mp_mset		);	/* Daleken */
DECLARE_DO_FUN(	   mp_oload		);
DECLARE_DO_FUN(	   mp_open		);	/* Daleken */
DECLARE_DO_FUN(	   mp_openpassage	);	/* Daleken */
DECLARE_DO_FUN(	   mp_oset		);	/* Daleken */
DECLARE_DO_FUN(	   mp_otransfer		);	/* Daleken */
DECLARE_DO_FUN(	   mp_peace		);	/* Daleken */
DECLARE_DO_FUN(	   mp_purge		);
DECLARE_DO_FUN(	   mp_qforce		);	/* Daleken */
DECLARE_DO_FUN(	   mp_set		);	/* Daleken */
DECLARE_DO_FUN(	   mp_transfer		);
DECLARE_DO_FUN(	   mp_trigger		);	/* Daleken */
DECLARE_DO_FUN(	   mp_unlock		);	/* Daleken */
DECLARE_DO_FUN(	   mp_withdraw		);

#define OP_PAREN	'('	/* ( - special */

#define OP_NEGATIVE	'M'	/* - */
#define OP_POSITIVE	'P'	/* - */
#define OP_INVERSE	'~'	/* ~ */
#define OP_NOT		'N'	/* ! */

#define OP_PLUS		'+'	/* + */
#define OP_MINUS	'-'	/* - */
#define OP_MULTIPLY	'*'	/* * */
#define OP_DIVIDE	'/'	/* / */
#define OP_MODULUS	'%'	/* % */

#define OP_XOR		'^'	/* ^ */
#define OP_OR		'|'	/* | */
#define OP_AND		'&'	/* & */
#define OP_SHL		'{'	/* << */
#define OP_SHR		'}'	/* >> */

#define OP_LOR		'O'	/* || */
#define	OP_LAND		'A'	/* && */

#define OP_EQUAL	'='	/* == */
#define OP_NOTEQUAL	'!'	/* != */
#define OP_GT		'>'	/* > */
#define OP_LT		'<'	/* < */
#define OP_GTE		'G'	/* >= */
#define OP_LTE		'L'	/* <= */

#define PSTACK_INCR	20	/* initial size of the stack and amount that
				 * it grows by when it runs out of space. */

typedef struct
{
    char *operators;
    int *values;
    int pos;
    int size;
} POLISH_STACK;


struct supermob_data *supermob_list, *supermob_free;
MPROG_LINE *mprog_line_free;
MPROG_GLOBAL *global_progs[ MPROG_GLOBAL_HASH ];

char nul[] = { '\0', '\0' };
MPROG_ACT_LIST *room_act_list;
MPROG_ACT_LIST *obj_act_list;
MPROG_ACT_LIST *mob_act_list;

/*
 * Local function prototypes
 */
void prog_process_cmnd	args( ( const char *cmnd, MPROG_INFO *info ) );
int mprog_driver	args( ( char *com_list, CHAR_DATA *mob,
				CHAR_DATA *actor, OBJ_DATA *obj,
				void *vo, const char *arg ) );
int prog_driver		args( ( char *com_list, MPROG_INFO *info ) );

/* helper functions for parse() */
MPROG_LINE *new_mprog_line args( ( void ) );
MPROG_LINE *split_lines	args( ( const char *commands ) );
void discard_lines	args( ( MPROG_LINE *head ) );
MPROG_STACK *new_stack	args( ( void ) );
void add_stack		args( ( MPROG_STACK *st, int value ) );
int remove_stack	args( ( MPROG_STACK *st ) );
void set_stack		args( ( MPROG_STACK *st, int value ) );
int current_stack	args( ( MPROG_STACK *st ) );
void delete_stack	args( ( MPROG_STACK *st ) );
void copy_line		args( ( char *dest, const char *src ) );

/* helper functions for parse_number() */
bool isoperator		args( ( char *op ) );
int operate		args( ( int left, char operator, int right ) );
char get_unary		args( ( char c ) );
const char *next_word	args( ( const char *str, char *target ) );
const char *next_operator args( ( const char *str, char *target ) );
const char *next_number	args( ( const char *str, int *target ) );
int parse_number	args( ( const char *str, MPROG_INFO *info ) );
void fix_string_arg	args( ( char *dest, char *src, MPROG_INFO *info ) );
int prog_number_func	args( ( char *point, MPROG_INFO *info ) );
char *prog_string_func	args( ( char *target, char *point,
				MPROG_INFO *info ) );
const char *matching_parentheses args( ( const char *str ) );

/* miscellaneous */
void expand_to_eol	args( ( char *t, const char *src, MPROG_INFO *info ) );
char *get_num_args	args( ( char *buf, const char *trigger,
				const char *str ) );
MPROG_DATA *prog_keyword_check args( ( char *arg, MPROG_DATA *mprg,
				MPROG_INFO *info, int type ) );
CHAR_DATA *new_supermob	args( ( void ) );
bool is_time		args( ( char *p, PLANE_DATA *plane ) );
bool comm_trig_aux	args( ( MPROG_DATA *mprg, int *progtypes,
				const char *command, MPROG_INFO *info ) );

/* variable stuff */
char *get_program_var	args( ( MPROG_INFO *info, const char *name ) );
void new_local_var	args( ( MPROG_INFO *info, const char *name,
				const char *value ) );
void set_local_var	args( ( MPROG_INFO *info, const char *name,
				const char *value ) );
void delete_local_var	args( ( MPROG_INFO *info,  const char *name ) );




/*
 * Find the proper global mud prog.
 */
MPROG_GLOBAL *get_global_mudprog_index( int vnum )
{
    MPROG_GLOBAL *mprg;

    if( vnum < 0 )
	return NULL;

    for( mprg = global_progs[vnum % MPROG_GLOBAL_HASH];
	 mprg; mprg = mprg->next )
    {
	if( mprg->vnum == vnum )
	    return mprg;
    }

    return NULL;
}


MPROG_GLOBAL *get_global_prog( MPROG_DATA *mprg )
{
    MPROG_GLOBAL *glob = NULL;

    if( mprg->type == GLOBAL_PROG )
    {
	if( !is_number( mprg->arglist ) )
	    bug( "Bad vnum for global prog (%s).", mprg->arglist );
	else
	    glob = get_global_mudprog_index( atoi( mprg->arglist ) );
    }
    return glob;
}


/*************************************************************************
 * Central command interpreter for special mob commands.
 */
const struct
{
    const char *const name;
    DO_FUN *do_fun;
} prog_command_table[] = {
    { "asound",		mp_asound	},
    { "at",		mp_at		},
    { "burrow",		mp_burrow	},
    { "cast",		mp_cast		},
    { "close",		mp_close	},
    { "damage",		mp_damage	},
    { "delay",		mp_delay	},
    { "delete",		mp_delete	},
    { "dream",		mp_dream	},
    { "echo",		mp_echo		},
    { "echoat",		mp_echoat	},
    { "echoaround",	mp_echoaround	},
    { "force",		mp_force	},
    { "goto",		mp_goto		},
    { "grant",		mp_grant	},
    { "junk",		mp_junk		},
    { "kill",		mp_kill		},
    { "lock",		mp_lock		},
    { "mload",		mp_mload	},
    { "mset",		mp_mset		},
    { "oload",		mp_oload	},
    { "open",		mp_open		},
    { "oset",		mp_oset		},
    { "otransfer",	mp_otransfer	},
    { "passageclose",	mp_closepassage	},
    { "passageopen",	mp_openpassage	},
    { "peace",		mp_peace	},
    { "purge",		mp_purge	},
    { "qforce",		mp_qforce	},
    { "set",		mp_set,		},
    { "transfer",	mp_transfer	},
    { "trigger",	mp_trigger	},
    { "unlock",		mp_unlock	},
    { "unset",		mp_delete	},

    { "",		do_credits	}
};


bool prog_only_cmnd( CHAR_DATA *ch, const char *argument )
{
    char command[MAX_INPUT_LENGTH];
    bool found = FALSE;
    int cmd;

    argument = one_argument( argument, command );
    if( !str_cmp( "cmd", command ) )
	return FALSE;

    for( cmd = 0; prog_command_table[cmd].name[0] != '\0'; cmd++ )
    {
	if( command[0] == prog_command_table[cmd].name[0]
	    && !str_prefix( command, prog_command_table[cmd].name ) )
	{
	    found = TRUE;
	    break;
	}
    }
    if( !found )
	return FALSE;

    sprintf( last_command, "MP[%5d] %s in [%5d] %s: %s %s",
	     IS_NPC( ch ) ? ch->pIndexData->vnum : 0,
	     IS_NPC( ch ) ? ch->short_descr : ch->name,
	     ch->in_room ? ch->in_room->vnum : 0,
	     ch->in_room ? ch->in_room->name : "(not in a room)",
	     command, argument );

    ( *prog_command_table[cmd].do_fun ) ( ch, argument );

    strcat( last_command, " (Finished)" );

    return TRUE;
}


/* This procedure simply copies the cmnd to a buffer while expanding
 * any variables by calling the translate procedure.
 */
void prog_process_cmnd( const char *cmnd, MPROG_INFO *info )
{
    char buf[MAX_INPUT_LENGTH];
    const char *p;
    struct supermob_data *sup;

    expand_to_eol( buf, cmnd, info );

    if( !str_prefix( "sub ", buf ) )
    {
	if( ( sup = get_supermob( info->mob ) ) )
	{
	    if( sup->prog_obj )
		bug( "Supermob for object %d: calling 'sub'",
			   sup->prog_obj->pIndexData->vnum );
	    else if( sup->prog_room )
		bug( "Supermob for room %d: calling 'sub'",
			   sup->prog_room->vnum );
	    else
		bug( "Supermob is calling 'sub' on it's own!" );
	    return;
	}
	else
	{
	    sprintf( buf, "%d", mprog_sub_trigger( info->mob, info->actor, &buf[4] ) );
	    set_local_var( info, "subreturn", buf );
	}
    }
    else if( !str_prefix( "local ", buf ) )
    {
	char var[MAX_INPUT_LENGTH];

	p = first_arg( buf, var, FALSE );	/* discard 'local' */
	p = first_arg( p, var, FALSE );
	set_local_var( info, var, p );
    }
    else if( !str_prefix( "regmatch ", buf ) )
    {
	regexp *preg;
	int i, len;
	bool match = TRUE;
	char patbuf[MAX_INPUT_LENGTH];

	p = one_argument( buf, patbuf );	/* dump the 'regmatch' */
	p = one_argument( p, patbuf );		/* get the pattern (quoted) */

	if( !( preg = regcomp( patbuf, 1 ) ) )
	    match = FALSE;

	if( !regexec( preg, &buf[p - buf] ) )
	    match = FALSE;

	for( i = 0; i < NSUBEXP; ++i )
	{
	    char name[MAX_INPUT_LENGTH];
	    char value[MAX_INPUT_LENGTH];

	    len = preg->endp[i] - preg->startp[i];

	    sprintf( name, "r%d", i );

	    if( !match || len <= 0 )
		delete_local_var( info, name );
	    else
	    {
		strncpy( value, preg->startp[i], len );
		value[len] = '\0';
		set_local_var( info, name, value );
	    }
	}
	free( preg );
    }
    else if( !prog_only_cmnd( info->mob, buf ) )
	interpret( info->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.
 */
int mprog_driver( char *com_list, CHAR_DATA *mob, CHAR_DATA *actor,
		   OBJ_DATA *obj, void *vo, const char *arg )
{
    MPROG_INFO info;
    char buf[MAX_STRING_LENGTH];
    int retval;

    if( IS_AFFECTED( mob, AFF_CHARM ) )
	return 0;

    info.local = NULL;
    info.mob = mob;
    info.actor = actor;
    info.obj = obj;
    info.vo = vo;
    if( arg )
	strcpy( buf, arg );
    else
	buf[0] = '\0';
    set_local_var( &info, "trigger", buf );
    info.rndm = NULL;

    retval = prog_driver( com_list, &info );
    delete_all_locals( &info );
    return retval;
}


int prog_driver( char *com_list, MPROG_INFO *info )
{
    CHAR_DATA *vch;
    int count = 0, retval;
    bool prog_allready = FALSE;

    if( IS_AFFECTED( info->mob, AFF_CHARM ) )
	return 0;

    /* get a random visable mortal player who is in the room with the mob */
    for( vch = info->mob->in_room->people; vch; vch = vch->next_in_room )
	if( !IS_NPC( vch )
	    && vch->level < MAX_LEVEL - 3
	    && can_see( info->mob, vch ) )
	{
	    if( number_range( 0, count ) == 0 )
		info->rndm = vch;
	    count++;
	}
    if( xIS_SET( info->mob->act, ACT_MUDPROG ) )
	prog_allready = TRUE;
    xSET_BIT( info->mob->act, ACT_MUDPROG );
    /*
     * NOTE: the parsing function uses a backwards replace to enable it to
     * reverse along the com_list, a '\0' is searched for and replaced
     * with a '\n' so the command list can be reused, thus a '\0' needs
     * to be placed before the first line.
     * --Symposium
     */
    retval = parse( com_list, info, NULL, 0 );
    if( !prog_allready )
	xREMOVE_BIT( info->mob->act, ACT_MUDPROG );
    return retval;
}


MPROG_LINE *new_mprog_line()
{
    MPROG_LINE *line;

    if( mprog_line_free )
    {
	line = mprog_line_free;
	mprog_line_free = mprog_line_free->next;
    }
    else
    {
	line = alloc_perm( sizeof( MPROG_LINE ) );
    }
    line->next = NULL;
    return line;
}


/*
 * Places each line from a multiline mud program into separate
 * MPROG_LINE objects.  Additionally it removes lines starting with
 * a '#' character to reduce strain on the parser.
 */
MPROG_LINE *split_lines( const char *commands )
{
    MPROG_LINE *head, *current;

    head = new_mprog_line();
    head->line = commands;
    commands = strchr( commands, '\n' );
    if( !commands )
	return head;
    while( isspace( *commands ) )
	commands++;

    current = head;
    while( commands[0] != '\0' )
    {
	if( commands[0] != '#' )	/* Remove comments */
	{
	    current->next = new_mprog_line();
	    current = current->next;

	    current->line = commands;
	}

	commands = strchr( commands, '\n' );
	while( isspace( *commands ) )
	    commands++;
    }
    return head;
}


void discard_lines( MPROG_LINE *head )
{
    MPROG_LINE *tmp;

    for( tmp = head; tmp && tmp->next; tmp = tmp->next )
	;
    tmp->next = mprog_line_free;
    mprog_line_free = head;
}


MPROG_STACK *new_stack()
{
    MPROG_STACK *st = alloc_mem( sizeof( MPROG_STACK ) );

    st->max_size = 5;
    st->stack = alloc_mem( st->max_size * sizeof( int ) );
    st->current = -1;
    return st;
}


void add_stack( MPROG_STACK *st, int value )
{
    ++st->current;

    if( st->current >= st->max_size )
    {
	int *tmp;

	st->max_size += 5;
	tmp = alloc_mem( st->max_size * sizeof( int ) );
	memcpy( tmp, st->stack, ( st->max_size - 10 ) * sizeof( int ) );
	free_mem( st->stack, st->max_size * sizeof( int ) );
	st->stack = tmp;
    }
    st->stack[st->current] = value;
}


int remove_stack( MPROG_STACK *st )
{
    int tmp;

    tmp = st->stack[st->current];
    if( st->current >= 0 )
	st->current--;
    return tmp;
}


void set_stack( MPROG_STACK *st, int value )
{
    st->stack[st->current] = value;
}


int current_stack( MPROG_STACK *st )
{
    if( st->current < 0 )
	return 1;	/* 1 means executing... */
    return st->stack[st->current];
}


void delete_stack( MPROG_STACK *st )
{
    free_mem( st->stack, st->max_size * sizeof( int ) );
    free_mem( st, sizeof( MPROG_STACK ) );
}


void copy_line( char *dest, const char *src )
{
    while( *src && *src != '\n' )
	*dest++ = *src++;
    *dest = '\0';
}


/*
 * The brains of the operation.
 *
 * The stack we use here maintains some sort of reference as to where we are.
 * Each entry is an integer (32 bits) and this integer is split into fields.
 *
 * BIT_00 - the execute flag, TRUE if we are actually running code.
 * BIT_01 - ifdone flag, TRUE when the if statement has already had code
 *          executed some time before (for else if control).
 * BIT_02 to BIT_07 - while loop counter, this is 0 for an 'if' and
 *			 1 - 63 for a 'while' statement.
 * BIT_08 and onward - the line number of the start of the current block.
 */
int parse( const char *commands, MPROG_INFO *info,
	   MPROG_STACK *stack, int line_no )
{
    MPROG_LINE *head, *current;
    char linebuf[MAX_INPUT_LENGTH];
    int i, retval = 1;
    bool ifchk_last = FALSE;

    /*
     * Break the command up into separate lines and find the line we
     * should start on.
     */
    head = split_lines( commands );

    current = head;
    for( i = 0; i < line_no && current; ++i )
	current = current->next;

    /*
     * Initialise the stack object if we don't already have one.
     */
    if( stack == NULL )
	stack = new_stack();

    /*
     * For each line, check for special command,
     * act accordingly.
     */
    for( ; current; current = current->next, line_no++ )
    {
	char cmd[MAX_INPUT_LENGTH];
	const char *rest;
	int tmp;

	copy_line( linebuf, current->line );
	rest = one_argument( linebuf, cmd );

	/*
	 * Multiple expansion, might come in handy for some tricksters.
	 */
	while( !str_cmp( cmd, "eval" ) )
	{
	    char tmp[MAX_INPUT_LENGTH];

	    strcpy( tmp, rest );
	    expand_to_eol( linebuf, tmp, info );
	    rest = one_argument( linebuf, cmd );
	}

	if( !str_cmp( cmd, "if" ) )
	{
	    if( IS_SET( current_stack( stack ), 0x01 ) )
	    {
		tmp = parse_number( rest, info );
		if( tmp == NO_FLAG )
		{
		    progbug( info->mob, "parse: bad if statement, line %d.",
			     line_no + 1 );
		    break;
		}
		add_stack( stack, ( line_no << 8 ) | ( tmp ? 1 : 0 ) );
	    }
	    else
		add_stack( stack, ( line_no << 8 ) );
	}
	else if( ifchk_last && !str_cmp( cmd, "or" ) )
	{
	    if( !IS_SET( current_stack( stack ), 0x01 ) )
	    {
		tmp = parse_number( rest, info );
		if( tmp == NO_FLAG )
		{
		    progbug( info->mob, "parse: bad or statement, line %d.",
				   line_no + 1);
		    break;
		}
		if( tmp )
		    set_stack( stack, current_stack( stack ) | 1 );
	    }
	}
	else if( !str_cmp( cmd, "else" ) )
	{
	    if( current_stack( stack ) & 0xFC )
	    {
		progbug(
		    info->mob, "parse: else found, endwhile expected, line %d.",
		    line_no + 1 );
		break;
	    }

	    /*
	     * Conditions that must be satisfied in order for the block to be
	     * executed:
	     * - The outer block must be running.
	     * - The 'ifdone' flag (0x02) must not be set for the statement.
	     * - If an if check exists it must be passed.
	     */

	    tmp = remove_stack( stack );
	    if( IS_SET( current_stack( stack ), 0x01 ) )
	    {
		if( IS_SET( tmp, 0x01 ) || IS_SET( tmp, 0x02 ) )
		    add_stack( stack, ( tmp | 0x02 ) & ~0x01 );
		else
		{
		    rest = one_argument( rest, cmd );
		    if( !str_cmp( cmd, "if" ) )
		    {
			tmp = parse_number( rest, info );
			if( tmp == NO_FLAG )
			{
			    progbug( info->mob, "parse: bad else if statement, line %d.",
				     line_no + 1 );
			    break;
			}
			add_stack( stack, ( line_no << 8 ) | ( tmp ? 1 : 0 ) );
		    }
		    else
			add_stack( stack, current_stack( stack ) | 0x01 );
		}
	    }
	    else
		add_stack( stack, tmp & ~0x01 );
	}
	else if( !str_cmp( cmd, "endif" ) )
	{
	    if( current_stack( stack ) & 0xFC )
	    {
		progbug(
		    info->mob, "parse: endif found, endwhile expected, line %d",
		    line_no + 1 );
		break;
	    }
	    remove_stack( stack );
	}
	else if( !str_cmp( cmd, "while" ) )
	{
	    if( IS_SET( current_stack( stack ), 0x01 ) )
	    {
		tmp = parse_number( rest, info );
		if( tmp == NO_FLAG )
		{
		    progbug( info->mob, "parse: bad while statement, line %d.",
				   line_no + 1 );
		    break;
		}
		add_stack( stack, ( line_no << 8 ) | 0x04 | ( tmp ? 1 : 0 ) );
	    }
	    else
		add_stack( stack, ( line_no << 8 ) | 0x04 );
	}
	else if( !str_cmp( cmd, "endwhile" ) )
	{
	    int old_stack;
	    MPROG_LINE *start_line;
	    int tmp_line_no = 0;

	    if( !( current_stack( stack ) & 0xFC ) )
	    {
		progbug(
		    info->mob, "parse: endwhile found, else/endif expected, line %d.",
		    line_no + 1 );
		break;
	    }

	    old_stack = remove_stack( stack );
	    if( IS_SET( old_stack, 0x01 ) )
	    {
		for( start_line = head;
		     tmp_line_no < ( old_stack >> 8 ) && start_line;
		     start_line = start_line->next )
		    tmp_line_no++;

		if( !start_line )
		{
		    progbug(
			info->mob, "parse: while expected, program must have changed, line %d.",
			( old_stack >> 8 ) + 1 );
		    break;
		}

		copy_line( linebuf, start_line->line );
		rest = one_argument( linebuf, cmd );

		if( str_cmp( cmd, "while" ) )
		{
		    progbug(
			info->mob, "parse: while expected, program must have changed, line %d.",
			tmp_line_no + 1 );
		    break;
		}

		tmp = parse_number( rest, info );

		if( tmp == NO_FLAG )
		{
		    progbug( info->mob, "parse: bad while statement, line %d.",
				   line_no + 1 );
		    break;
		}

		if( tmp )
		{
		    /* Check for overflow of the loop counter. */
		    if( !( ( old_stack + 0x04 ) & 0xFC ) )
		    {
			progbug(
			    info->mob, "parse: looping too much, line %d.",
			    tmp_line_no + 1 );
			break;
		    }

		    add_stack( stack, old_stack + 0x04 );
		    line_no = tmp_line_no;
		    current = start_line;
		}
	    }
	}
	else if( !str_cmp( cmd, "break" ) )
	{
	    if( IS_SET( current_stack( stack ), 0x01 ) )
	    {
		int count = 1;

		if( rest[0] != '\0' )
		    count = parse_number( rest, info );
		if( count == NO_FLAG )
		{
		    progbug( info->mob, "parse: bad number for break, line %d.",
			     line_no + 1 );
		    break;
		}

		for( tmp = stack->current; count > 0 && tmp >= 0; tmp-- )
		{
		    stack->stack[tmp] &= ~0x01;
		    if( stack->stack[tmp] & 0xFC )
			count--;
		}
	    }
	}
	else if( !str_cmp( cmd, "wait" ) )
	{
	    MPROG_CALLBACK *callback = alloc_mem( sizeof( MPROG_CALLBACK ) );
	    struct supermob_data *sup;
	    EVENT *e;
	    bool ticks = FALSE;

	    if( !str_prefix( "ticks ", rest ) )
	    {
		ticks = TRUE;
		rest = one_argument( rest, cmd );
	    }
	    else if( !str_prefix( "seconds ", rest )
		     || !str_prefix( "sec ", rest ) )
		rest = one_argument( rest, cmd );

	    tmp = parse_number( rest, info );
	    if( tmp < 0 || tmp == NO_FLAG )
	    {
		progbug( info->mob, "parse: bad wait statement, line %d.",
			 line_no + 1 );
		break;
	    }

	    /*
	     * Copy all the values from the old into the new.
	     * Since we also copy the variables, ensure that they
	     * don't get deleted after this function exits with the
	     * old info struct by making the old one point to nothing.
	     */
	    callback->commands = commands;
	    memcpy( &callback->info, info, sizeof( MPROG_INFO ) );
	    info->local = NULL;
	    callback->stack = stack;
	    callback->line_no = line_no + 1;

	    if( ticks )
		tmp *= PULSE_TICK;
	    else
		tmp *= PULSE_PER_SECOND;
	    tmp = number_fuzzy( tmp );

	    /* find which type of thing caused the program and then register
	       an event to finish this program */
	    sup = get_supermob( info->mob );
	    if( sup )
	    {
		if( sup->prog_obj )
		    e = create_obj_event( sup->prog_obj, evn_prog_wait,
					  tmp );
		else if( sup->prog_room )
		    e = create_room_event( sup->prog_room, evn_prog_wait,
					   tmp );
		else
		{
		    progbug( info->mob, "Cannot register 'wait' event." );
		    break;
		}
	    }
	    else
		e = create_char_event( info->mob,  evn_prog_wait,
				       tmp );
	    e->extra.type = TARGET_VOID;
	    e->extra.target.typeless = callback;

	    discard_lines( head );
	    return retval;
	}
	else if( !str_cmp( cmd, "halt" ) )
	{
	    if( IS_SET( current_stack( stack ), 0x01 ) )
		break;
	}
	else if( !str_cmp( cmd, "retval" ) )
	{
	    if( IS_SET( current_stack( stack ), 0x01 ) )
		retval = parse_number( rest, info );
	}
	else if( !str_cmp( cmd, "return" ) )
	{
	    if( IS_SET( current_stack( stack ), 0x01 ) )
	    {
		if( rest[0] != '\0' )
		    retval = parse_number( rest, info );
		break;
	    }
	}
	else if( IS_SET( current_stack( stack ), 0x01 ) )
	    prog_process_cmnd( linebuf, info );

	if( !str_cmp( cmd, "if" ) || ( ifchk_last && !str_cmp( cmd, "or" ) ) )
	    ifchk_last = TRUE;
	else
	    ifchk_last = FALSE;
    }

    discard_lines( head );
    delete_stack( stack );
    return retval;
}


/***************************************************************************
 * Value parser utilises the variable system below to set values.
 * Allows for functions and variables as well as integers.
 * Variables and functions must start with alphabet characters but they
 * may contain digits and _'s.
 */
/*
 * Takes an alphanumeric sequence from 'str' and puts it in 'target'.
 * Returns a pointer to after where it finished.
 */
const char *next_word( const char *str, char *target )
{
    const char *p;

    while( isalpha( *str ) || isdigit( *str ) || *str == '_' || *str == '.' )
	*target++ = *str++;

    while( isspace( *str ) && *str != '\n' && *str != '\r' )
	++str;

    if( *str == '(' )
    {
	p = str;
	str = matching_parentheses( str );
	if( !str )
	{
	    bug( "Too many parentheses or badly constructed statement." );
	    str = &str_empty[0];
	}
	++str;
	while( p <= str )
	    *target++ = *p++;
    }
    *target = '\0';

    return str;
}


/*
 * Translate a character into the proper unary operator code.  Returns 0 if
 * this isn't a unary operator.
 */
char get_unary( char c )
{
    switch( c )
    {
    case '+':
	return OP_POSITIVE;
    case '-':
	return OP_NEGATIVE;
    case '~':
	return OP_INVERSE;
    case '!':
	return OP_NOT;
    default:
	return 0;
    }
}


/*
 * Gets the next operator.
 * Takes the string and places a one character symbol for the operator
 * in 'target', returns what is left of the string.
 */
const char *next_operator( const char *str, char *target )
{
    *target = 0;
    switch( *str++ )
    {
    case '+':
	*target = OP_PLUS;
	break;
    case '-':
	*target = OP_MINUS;
	break;
    case '*':
	*target = OP_MULTIPLY;
	break;
    case '/':
	*target = OP_DIVIDE;
	break;
    case '%':
	*target = OP_MODULUS;
	break;

    case '^':
	*target = OP_XOR;
	break;
    case '|':
	if( *str == '|' )
	{
	    str++;
	    *target = OP_LOR;
	}
	else
	    *target = OP_OR;
	break;
    case '&':
	if( *str == '&' )
	{
	    str++;
	    *target = OP_LAND;
	}
	else
	    *target = OP_AND;
	break;

    case '<':
	if( *str == '<' )
	{
	    str++;
	    *target = OP_SHL;
	}
	else if( *str == '=' )
	{
	    str++;
	    *target = OP_LTE;
	}
	else
	    *target = OP_LT;
	break;
    case '>':
	if( *str == '>' )
	{
	    str++;
	    *target = OP_SHR;
	}
	else if( *str == '=' )
	{
	    str++;
	    *target = OP_GTE;
	}
	else
	    *target = OP_GT;
	break;

    case '=':
	if( *str == '=' )
	{
	    ++str;
	    *target = OP_EQUAL;
	}
	break;
    case '!':
	if( *str == '=' )
	{
	    ++str;
	    *target = OP_NOTEQUAL;
	}
	break;
    }

    return str;
}


/* Check if the next bit of the string is an operator. */
bool isoperator( char *op )
{
    char tgt;
    (void)next_operator( op, &tgt );
    return tgt ? TRUE : FALSE;
}


/*
 * Compare the precedence of two operators.
 */
int operatorcmp( char op1, char op2 )
{
    int i;

    struct
    {
	char op;
	char val;
    } precedence[] = {
	{ OP_PAREN, 0 },
	{ OP_LOR, 1 },
	{ OP_LAND, 2 },
	{ OP_LT, 3 },	{ OP_GT, 3 },	{ OP_LTE, 3 },	{ OP_GTE, 3 },
	{ OP_EQUAL, 3 },	{ OP_NOTEQUAL, 3 },
	{ OP_OR, 4 },	{ OP_AND, 4 },	{ OP_XOR, 4 },
	{ OP_PLUS, 4 },	{ OP_MINUS, 4 },
	{ OP_SHL, 5 },	{ OP_SHR, 5 },
	{ OP_MULTIPLY, 6 },	{ OP_DIVIDE, 6 },	{ OP_MODULUS, 6 },
	{ OP_POSITIVE, 7, },	{ OP_NEGATIVE, 7 },
	{ OP_INVERSE, 7 },	{ OP_NOT, 7 },
	{ '\0', 0 },
    };
    if( op1 == op2 )
	return 0;

    for( i = 0; precedence[i].op != '\0'; ++i )
    {
	if( precedence[i].op == op1 )
	    op1 = precedence[i].val;
	else if( precedence[i].op == op2 )
	    op2 = precedence[i].val;
    }

    if( op1 < op2 )
	return -1;
    else if( op1 == op2 )
	return 0;
    return 1;
}


/*
 * Given an operator code, perform a mathematical operation.
 */
int operate( int left, char operator, int right )
{
    switch( operator )
    {
    default:
    case OP_PLUS:
	return left + right;
    case OP_MINUS:
	return left - right;
    case OP_MULTIPLY:
	return left * right;
    case OP_DIVIDE:
	if( right == 0 )
	    return 0;
	return left / right;
    case OP_MODULUS:
	if( right == 0 )
	    return 0;
	return left % right;
    case OP_OR:
	return left | right;
    case OP_XOR:
	return left ^ right;
    case OP_AND:
	return left & right;
    case OP_LOR:
	return ( left || right ) ? 1 : 0;
    case OP_LAND:
	return ( left && right ) ? 1 : 0;
    case OP_EQUAL:
	return ( left == right ) ? 1 : 0;
    case OP_NOTEQUAL:
	return ( left != right ) ? 1 : 0;
    case OP_LT:
	return ( left < right ) ? 1 : 0;
    case OP_LTE:
	return ( left <= right ) ? 1 : 0;
    case OP_GT:
	return ( left > right ) ? 1 : 0;
    case OP_GTE:
	return ( left >= right ) ? 1 : 0;
    case OP_SHL:
	return left << right;
    case OP_SHR:
	return left >> right;
    }
}


/*
 * From 'str', gets a number and puts it in 'target'.
 * Returns a pointer to str where it left off.
 */
const char *next_number( const char *str, int *target )
{
    bool negative = FALSE;

    *target = 0;
    if( *str == '+' )
	++str;
    else if( *str == '-' )
    {
	++str;
	negative = TRUE;
    }

    while( isdigit( *str ) )
	*target = *target * 10 + *str++ - '0';
    if( negative )
	*target *= -1;

    return str;
}


/* Initialise a stack variable */
POLISH_STACK *pstack_init( void )
{
    POLISH_STACK *s = alloc_mem( sizeof( POLISH_STACK ) );

    s->size = PSTACK_INCR;
    s->values = alloc_mem( s->size * ( sizeof( int ) + sizeof( char ) ) );
    s->operators = (char *)s->values + ( s->size * sizeof( int ) );
    s->pos = -1;

    return s;
}

/* Deallocation the stack */
void pstack_free( POLISH_STACK *s )
{
    free_mem( s->values, s->size * ( sizeof( int ) + sizeof( char ) ) );
    free_mem( s, sizeof( POLISH_STACK ) );
}


/* Push the operator/value pair onto the stack.  This function automatically
 * grows a full stack object */
void pstack_push( POLISH_STACK *s, char operator, int val )
{
    char *tmp_ptr;

    if( ++s->pos >= s->size )
    {
	tmp_ptr = alloc_mem( ( s->size + PSTACK_INCR )
			       * ( sizeof( int ) + sizeof( char ) ) );
	memcpy( (char *)tmp_ptr, (char *)s->values, s->size * sizeof( int ) );
	memcpy( (char *)tmp_ptr + ( ( s->size + PSTACK_INCR ) * sizeof( int ) ),
		(char *)s->values + ( s->size * sizeof( int ) ),
		s->size );
	free_mem( s->values, s->size * ( sizeof( int ) + sizeof( char ) ) );

	s->size += PSTACK_INCR;
	s->values = (int *)tmp_ptr;
	s->operators = tmp_ptr + ( s->size * sizeof( int ) );
    }

    s->operators[s->pos] = operator;
    s->values[s->pos] = val;
}


/* Grab the last entry off the stack */
void pstack_pop( POLISH_STACK *s, char *operator, int *val )
{
    if( s->pos < 0 )
    {
	bug( "Popping an empty number stack.\n" );
	return;
    }

    *operator = s->operators[s->pos];
    *val = s->values[s->pos--];
}


/* Grab the last operator off the stack */
char pstack_pop_op( POLISH_STACK *s )
{
    if( s->pos < 0 )
    {
	bug( "Popping an empty number stack.\n" );
	return '+';
    }

    return s->operators[s->pos--];
}


/* Grab the last value off the stack */
int pstack_pop_val( POLISH_STACK *s )
{
    if( s->pos < 0 )
    {
	bug( "Popping an empty number stack.\n" );
	return 0;
    }

    return s->values[s->pos--];
}


/* Parse a number using a reverse polish parser. */
int parse_number( const char *str, MPROG_INFO *info )
{
    POLISH_STACK *s_val = pstack_init();
    POLISH_STACK *s_op = pstack_init();
    char buf[MAX_INPUT_LENGTH];
    bool need_op = FALSE;
    char tmp_op;
    int tmp_val;

    while( str && *str )
    {
	while( isspace( *str ) && *str != '\n' && *str != '\r' )
	    ++str;

	if( *str == '(' )
	{
	    pstack_push( s_op, OP_PAREN, 0 );
	    ++str;
	    need_op = FALSE;
	}
	else if( *str == ')' )
	{
	    while( s_op->operators[s_op->pos] != OP_PAREN )
	    {
		pstack_pop( s_op, &tmp_op, &tmp_val );
		pstack_push( s_val, tmp_op, tmp_val );
		if( s_op->pos < 0 )
		{
		    progbug( info->mob, "Unmatched ')'\n" );
		    str = NULL;
		    return NO_FLAG;
		}
	    }

	    pstack_pop( s_op, &tmp_op, &tmp_val );
	    ++str;
	    need_op = TRUE;
	}
	else if( isdigit( *str ) )
	{
	    str = next_number( str, &tmp_val );
	    pstack_push( s_val, '\0', tmp_val );
	    need_op = TRUE;
	}
	else if( isalpha( *str ) )		/* string, function/variable */
	{
	    str = next_word( str, buf );
	    if( str )
	    {
		if( strchr( buf, '(' ) )	/* parse function */
		{
		    tmp_val = prog_number_func( buf, info );
		    if( tmp_val == NO_FLAG )
		    {
			progbug( info->mob, "Illegal number from %s.", buf );
			tmp_val = 0;
		    }
		}
		else			/* mob variable */
		{
		    if( *get_mob_var( info->mob, buf ) == '\0' )
			tmp_val = 0;
		    else if( !is_number( get_mob_var( info->mob, buf ) ) )
		    {
			progbug( info->mob, "Variable %s isn't a number.", buf );
			tmp_val = 0;
		    }
		    else
			tmp_val = atoi( get_mob_var( info->mob, buf ) );
		}

		pstack_push( s_val, '\0', tmp_val );
		need_op = TRUE;
	    }
	}
	else if( !need_op && get_unary( *str ) )
	{
	    pstack_push( s_op, get_unary( *str++ ), 0 );
	}
	else	/* an operator */
	{
	    char curr_op;

	    str = next_operator( str, &curr_op );
	    while( s_op->pos >= 0
		   && operatorcmp( s_op->operators[s_op->pos], curr_op ) >= 0 )
	    {
		pstack_push( s_val, pstack_pop_op( s_op ), 0 );
	    }
	    pstack_push( s_op, curr_op, 0 );
	    need_op = FALSE;
	}
    }
    while( str && s_val->pos >= 0 )
    {
	pstack_pop( s_val, &tmp_op, &tmp_val );
	pstack_push( s_op, tmp_op, tmp_val );
    }

    while( str && s_op->pos >= 0 )
    {
	int reg_a, reg_b;

	pstack_pop( s_op, &tmp_op, &tmp_val );
	switch( tmp_op )
	{
	case '\0':
	    pstack_push( s_val, '\0', tmp_val );
	    break;
	case OP_POSITIVE:	/* NOP */
	    break;
	case OP_NEGATIVE:
	    pstack_push( s_val, '\0', -pstack_pop_val( s_val ) );
	    break;
	case OP_INVERSE:
	    pstack_push( s_val, '\0', ~pstack_pop_val( s_val ) );
	    break;
	case OP_NOT:
	    pstack_push( s_val, '\0', !pstack_pop_val( s_val ) );
	    break;
	default:
	    reg_b = pstack_pop_val( s_val );
	    reg_a = pstack_pop_val( s_val );
	    pstack_push( s_val, '\0', operate( reg_a, tmp_op, reg_b ) );
	    break;
	}
    }

    tmp_val = str ? pstack_pop_val( s_val ) : NO_FLAG;

    pstack_free( s_val );
    pstack_free( s_op );
    return tmp_val;
}


void fix_string_arg( char *dest, char *src, MPROG_INFO *info )
{
    char *p;

    p = strchr( src, '\0' );
    while( isspace( *(--p) ) )
	*p = '\0';
    if( src[0] == '"' )
    {
	*p = '\0';
	strcpy( dest, src + 1 );
    }
    else if( strchr( src, '(' ) && strchr( src, ')' ) )
	prog_string_func( dest, src, info );
    else if( !str_cmp( src, "*" ) )
	strcpy( dest, get_program_var( info, "trigger" ) );
    else if( isdigit( src[0] ) )
	get_num_args( dest, get_program_var( info, "trigger" ), src );
    else
	strcpy( dest, get_program_var( info, src ) );
}


/*------------------------------------------------------------------------
  parse a number function in the form:

	<name> ( [ argv1 ] [ , argv2 ] [ , argv3 ] ... )
  <name>	is the name of the function
  [ argv1 ]	optional first argument
  [ , argv2/3 ]	optional second/third argument

  Note that substitution is done on the arguments,
  see note below on string funcs.
*/
int prog_number_func( char *point, MPROG_INFO *info )
{
    CHAR_DATA *mob = info->mob;
    CHAR_DATA *actor = info->actor;
    OBJ_DATA *obj = info->obj;
    CHAR_DATA *vict = (CHAR_DATA *)info->vo;
    OBJ_DATA *v_obj = (OBJ_DATA *)info->vo;
    CHAR_DATA *rndm = info->rndm;

    CHAR_DATA *tarchar = NULL;
    OBJ_DATA *tarobj = NULL;

    char func[MAX_INPUT_LENGTH];
    char *funcpt = func;
    char argv[MAX_ARGS][MAX_INPUT_LENGTH];
    char *argvpt;
    char *p;
    int i;

    /* Crash saver using magic numbers, thanks Erwin A. */
    if( vict && vict->magic == MAGIC_NUM_CHAR )
	v_obj = NULL;
    else
	vict = NULL;

    if( !point || *point == '\0' )
    {
	progbug( mob, "null number function" );
	return NO_FLAG;
    }

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

    /* get whatever comes before the left paren.. ignore spaces */
    while( *point && *point != '(' )
	if( *point == '\0' )
	{
	    progbug( mob, "number function syntax error" );
	    return NO_FLAG;
	}
	else if( *point == ' ' )
	    point++;
	else
	    *funcpt++ = *point++;

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

    /*
     * Extract arguments.
     */
    for( i = 0; i < MAX_ARGS; ++i )
    {
	char buf[20];
	char *lastbrace;

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

	argvpt = &argv[i][0];

	lastbrace = buf;
	*lastbrace++ = ')';
	while( lastbrace - 1 > buf || ( *point != ')' && *point != ',' ) )
	{
	    if( *point == '\0' )
	    {
		progbug( mob, "number func syntax error" );
		return NO_FLAG;
	    }
	    if( *point == '(' )
		*lastbrace++ = ')';
	    else if( *point == '[' )
		*lastbrace++ = ']';
	    else if( *point == '{' )
		*lastbrace++ = '}';
	    else if( *point == *(lastbrace - 1) )
		lastbrace--;
	    *argvpt++ = *point++;
	}
	*argvpt = '\0';

	if( *point == ')' )
	{
	    ++i;
	    break;
	}
	point++;
    }
    for( ; i < MAX_ARGS; ++i )
	argv[i][0] = '\0';

    /* Special case function. */
    if( !str_cmp( func, "exist" ) )
    {
	switch( argv[0][1] )	/* arg should be "$*" so just get the letter */
	{
	case 'i':	return 1;
	case 'n':
	    return ( actor ) ? 1 : 0;
	case 't':
	    return ( vict ) ? 1 : 0;
	case 'r':
	    return ( rndm ) ? 1 : 0;
	case 'o':
	    return ( obj ) ? 1 : 0;
	case 'p':
	    return ( v_obj ) ? 1 : 0;
	default:
	    progbug( mob, "bad argument to '%s'", func );
	    return NO_FLAG;
	}
    }

    /*
     * Find default '$x' type targets.
     */
    if( argv[0][0] == '$' && argv[0][2] == '\0' )
    {
	switch( argv[0][1] )
	{
	case 'i':	tarchar = mob;			break;
	case 'n':	tarchar = actor;		break;
	case 't':	tarchar = vict;			break;
	case 'r':	tarchar = rndm;			break;
	case 'o':	tarobj = obj;			break;
	case 'p':	tarobj = v_obj;			break;
	default:
	    progbug( mob, "bad argument to '%s'", func );
	    return NO_FLAG;
	}
    }
    else if( argv[0][0] == '\0' )
	tarchar = mob;

    /*
     * Substitutions.
     */
    for( i = 0; i < MAX_ARGS; ++i )
    {
	char buf[MAX_INPUT_LENGTH];

	if( argv[i][0] == '\0' )
	    break;
	strcpy( buf, argv[i] );
	expand_to_eol( argv[i], buf, info );
    }

    if( !tarchar && !tarobj )
    {
	tarchar = get_char_world( mob, argv[0] );
	tarobj = get_obj_here( mob, argv[0] );
    }


    /*
     * General functions
     */
    if( !str_cmp( func, "rand" ) )
    {
	if( argv[0][0] != '\0' && is_number( argv[0] )
	    && argv[1][0] == '\0' )
	    return number_range( 1, atoi( argv[0] ) );
	if( is_number( argv[1] ) )
	    return number_range( atoi( argv[0] ), atoi( argv[1] ) );
	return number_percent();
    }

    if( !str_cmp( func, "dice" ) )
    {
	if( argv[0][0] != '\0' && is_number( argv[0] )
	    && argv[1][0] != '\0' && is_number( argv[1] ) )
	    return dice( atoi( argv[0] ), atoi( argv[1] ) );
	return NO_FLAG;
    }

    if( !str_cmp( func, "time" ) )
    {
	if( !str_cmp( argv[0], "day" ) )
	    return mob->in_room->area->plane->time.day;
	if( !str_cmp( argv[0], "month" ) )
	    return mob->in_room->area->plane->time.month;
	if( !str_cmp( argv[0], "year" ) )
	    return mob->in_room->area->plane->time.year;
	return mob->in_room->area->plane->time.hour;
    }

    if( !str_cmp( func, "economy" ) )
	return mob->in_room->area->economy;
    if( !str_cmp( func, "mobinarea" ) )
	return mob->in_room->area->nmobile;
    if( !str_cmp( func, "people" ) )
    {
	ROOM_INDEX_DATA *pRoom = NULL;
	int num = 0;

	if( tarchar )
	    pRoom = tarchar->in_room;
	else if( tarobj )
	    pRoom = tarobj->in_room;
	else if( argv[0][0] != '\0' )
	    pRoom = find_location( mob, argv[0] );
	if( !pRoom )
	    pRoom = mob->in_room;
	for( vict = pRoom->people; vict; vict = vict->next_in_room )
	    if( !vict->deleted )
		num++;
	return num;
    }
    if( !str_cmp( func, "mobs" ) )
    {
	ROOM_INDEX_DATA *pRoom = NULL;
	int num = 0;

	if( tarchar )
	    pRoom = tarchar->in_room;
	else if( tarobj )
	    pRoom = tarobj->in_room;
	else if( argv[0][0] != '\0' )
	    pRoom = find_location( mob, argv[0] );
	if( !pRoom )
	    pRoom = mob->in_room;
	for( vict = pRoom->people; vict; vict = vict->next_in_room )
	    if( !vict->deleted && IS_NPC( vict ) )
		num++;
	return num;
    }
    if( !str_cmp( func, "players" ) )
    {
	ROOM_INDEX_DATA *pRoom = NULL;
	int num = 0;

	if( tarchar )
	    pRoom = tarchar->in_room;
	else if( tarobj )
	    pRoom = tarobj->in_room;
	else if( argv[0][0] != '\0' )
	    pRoom = find_location( mob, argv[0] );
	if( !pRoom )
	    pRoom = mob->in_room;
	for( vict = pRoom->people; vict; vict = vict->next_in_room )
	    if( !vict->deleted && !IS_NPC( vict ) )
		num++;
	return num;
    }
    if( !str_cmp( func, "playerinarea" ) )
	return mob->in_room->area->nplayer;
    if( !str_cmp( func, "timeskilled" ) )
    {
	if( mob->pIndexData )
	    return mob->pIndexData->killed;
	return 0;
    }
    if( !str_cmp( func, "isexit" ) )
    {
	int door;
	if( !mob->in_room )
	    return NO_FLAG;
	if( ( door = find_door( mob, argv[0] ) ) >= 0
	    && mob->in_room->exit[door] )
	    return 1;
	return 0;
    }

    if( !str_cmp( func, "isclear" ) )
    {
	int door;
	if( !mob->in_room )
	    return NO_FLAG;
	if( ( door = find_door( mob, argv[0] ) ) >= 0
	    && mob->in_room->exit[door]
	    && !IS_SET( mob->in_room->exit[door]->exit_info, EX_CLOSED ) )
	    return 1;
	return 0;
    }

    if( !str_cmp( func, "islocked" ) )
    {
	int door;
	if( !mob->in_room )
	    return NO_FLAG;
	if( ( door = find_door( mob, argv[0] ) ) >= 0
	    && mob->in_room->exit[door]
	    && IS_SET( mob->in_room->exit[door]->exit_info, EX_LOCKED ) )
	    return 1;
	return 0;
    }

    /* Locate a character or object. */
    if( !str_prefix( "find", func ) )
    {
	if( !str_cmp( func, "findchar" ) )
	    return tarchar ? 1 : 0;
	if( !str_cmp( func, "findobj" ) )
	{
	    if( argv[1][0] == '\0' )
		return tarobj ? 1 : 0;
	    if( !tarchar )
		return 0;
	    return get_obj_list( mob, argv[1], tarchar->carrying ) ? 1 : 0;
	}
    }

    /*
     * Character targeted functions.
     */
    if( tarchar )
    {
	if( !str_cmp( func, "ispc" ) )
	    return ( !IS_NPC( tarchar ) ? 1 : 0 );
	if( !str_cmp( func, "uid" ) )
	    return tarchar->unique_key;
	if( !str_cmp( func, "isgood" ) )
	    return ( IS_GOOD( tarchar ) ? 1 : 0 );
	if( !str_cmp( func, "isevil" ) )
	    return ( IS_EVIL( tarchar ) ? 1 : 0 );
	if( !str_cmp( func, "isimmort" ) )
	    return ( get_trust( tarchar ) >= LEVEL_IMMORTAL ) ? 1 : 0;
	if( !str_cmp( func, "ischarmed" ) )
	    return ( IS_AFFECTED( tarchar, AFF_CHARM ) ) ? 1 : 0;
	if( !str_cmp( func, "hitprcnt" ) )
	    return 100 * tarchar->hit / tarchar->max_hit;
	if( !str_cmp( func, "inroom" ) )
	    return tarchar->in_room->vnum;
	if( !str_cmp( func, "sex" ) )
	    return tarchar->sex;
	if( !str_cmp( func, "position" ) )
	    return tarchar->position;
	if( !str_cmp( func, "level" ) )
	    return get_trust( tarchar );
	if( !str_cmp( func, "wait" ) )
	    return tarchar->wait;
	if( !str_cmp( func, "class" ) )
	    return tarchar->class;
	if( !str_cmp( func, "race" ) )
	    return tarchar->race;
	if( !str_cmp( func, "origclass" ) )
	    return get_first_class( tarchar );
	if( !str_cmp( func, "goldamt" ) )
	    return tarchar->gold;
	if( !str_cmp( func, "isopen" ) )
	    return ( IS_NPC( tarchar ) && tarchar->pIndexData->pShop
		     && tarchar->in_room->area->plane->time.hour
			>= tarchar->pIndexData->pShop->open_hour
		     && tarchar->in_room->area->plane->time.hour
			<= tarchar->pIndexData->pShop->close_hour )
		? 1 : 0;
	if( !str_cmp( func, "isflying" ) )
	    return ( IS_SET( tarchar->body_parts, BODY_PART_WINGS )
		     || IS_AFFECTED( tarchar, AFF_FLYING ) ) ? 1 : 0;
	if( !str_cmp( func, "isoutlaw" ) )
	{
	    if( IS_NPC( tarchar ) )
	    {
		progbug( mob, "checking outlaw on NPC." );
		return NO_FLAG;
	    }
	    return xIS_SET( tarchar->act, PLR_OUTLAW ) ? 1 : 0;
	}
	if( !str_cmp( func, "hp" ) )
	    return tarchar->hit;
	if( !str_cmp( func, "maxhp" ) )
	    return tarchar->max_hit;
	if( !str_cmp( func, "mana" ) )
	    return total_mana( tarchar->mana );
	if( !str_prefix( "mana", func ) )
	{
	    int which = flag_value( NULL, magic_flags, &func[4] );
	    which = URANGE( 0, which, MAGIC_MAX - 1 );
	    return tarchar->mana[which];
	}
	if( !str_cmp( func, "maxmana" ) )
	    return total_mana( tarchar->max_mana );
	if( !str_prefix( "maxmana", func ) )
	{
	    int which = flag_value( NULL, magic_flags, &func[4] );
	    which = URANGE( 0, which, MAGIC_MAX - 1 );
	    return tarchar->max_mana[which];
	}
	if( !str_cmp( func, "move" ) )
	    return tarchar->move;
	if( !str_cmp( func, "maxmove" ) )
	    return tarchar->max_move;
	if( !str_cmp( func, "str" ) )
	    return get_curr_str( tarchar );
	if( !str_cmp( func, "int" ) )
	    return get_curr_int( tarchar );
	if( !str_cmp( func, "wis" ) )
	    return get_curr_wis( tarchar );
	if( !str_cmp( func, "dex" ) )
	    return get_curr_dex( tarchar );
	if( !str_cmp( func, "con" ) )
	    return get_curr_con( tarchar );
	if( !str_prefix( "magic", func ) )
	{
	    int which = flag_value( NULL, magic_flags, &func[4] );
	    return get_magic( tarchar, URANGE( 0, which, 4 ) );
	}
	if( !str_cmp( func, "getskill" ) )
	{
	    int sn;
	    if( !argv[1] || argv[1][0] == '\0'
		|| ( sn = skill_lookup( argv[1] ) ) < 0 )
	    {
		progbug( mob, "Bad skill name '%s' for '%s'.",
			       argv[1], func );
		return NO_FLAG;
	    }
	    if( IS_NPC( tarchar ) )
	    {
		progbug( mob, "%s: target character '%s' NPC.",
			       func, tarchar->name );
		return NO_FLAG;
	    }
	    return tarchar->pcdata->learned[sn];
	}
	if( !str_cmp( func, "getsuccess" ) )
	{
	    int sn, scale = 100;
	    if( !argv[1] || argv[1][0] == '\0'
		|| ( sn = skill_lookup( argv[1] ) ) < 0 )
	    {
		progbug( mob, "Bad skill name '%s' for '%s'.",
			       argv[1], func );
		return NO_FLAG;
	    }
	    if( argv[2] && is_number( argv[2] ) )
		scale = URANGE( 0, atoi( argv[2] ), 500 );
	    return get_success( tarchar, sn, scale );
	}

	if( !str_cmp( func, "isfight" ) )
	{
	    CHAR_DATA *ch2;
	    if( argv[1][0] == '\0' )
		return ( ( tarchar->fighting ) ? 1 : 0 );
	    ch2 = get_char_world( mob, argv[0] );
	    if( !ch2 )
		return NO_FLAG;
	    return ( ( tarchar->fighting == ch2 ) ? 1 : 0 );
	}

	if( !str_cmp( func, "cansee" ) )
	{
	    CHAR_DATA *ch2;
	    if( argv[1][0] == '\0' )
		return ( can_see( mob, tarchar ) ? 1 : 0 );
	    ch2 = get_char_world( mob, argv[0] );
	    if( !ch2 )
		return NO_FLAG;
	    return ( can_see( tarchar, ch2 ) ? 1 : 0 );
	}

	if( !str_cmp( func, "isfollow" ) )
	{
	    CHAR_DATA  *ch2;
	    if( argv[1][0] == '\0' )
		return ( ( tarchar->master
			   && tarchar->master->in_room == tarchar->in_room )
			 ? 1 : 0 );
	    ch2 = get_char_world( mob, argv[0] );
	    if( !ch2 )
		return NO_FLAG;
	    return ( ( tarchar->master == ch2
		       && tarchar->master->in_room == tarchar->in_room )
		     ? 1 : 0 );
	}

	if( !str_cmp( func, "isaffected" ) )
	{
	    int vect[MAX_VECTOR];

	    if( argv[1][0] == '\0' )
		return NO_FLAG;
	    if( flag_value( vect, affect_flags, argv[1] ) == NO_FLAG )
		return NO_FLAG;
	    for( i = 0; i < MAX_VECTOR; ++i )
		if( ( tarchar->affected_by[i] | vect[i] ) )
		    return 1;
	    return 0;
	}

	if( !str_cmp( func, "bodypart" ) )
	{
	    int part;

	    if( argv[1][0] == '\0' )
		return NO_FLAG;
	    if( flag_value( &part, body_part_flags, argv[1] ) == NO_FLAG )
		return NO_FLAG;
	    return ( tarchar->body_parts | part ) ? 1 : 0;
	}
	if( !str_cmp( func, "variable" ) )
	{
	    /* second argument follows same rules as the str?? functions */
	    fix_string_arg( argv[2], argv[1], info );
	    p = get_mob_var( tarchar, argv[2] );
	    return atoi( p );
	}
	if( !str_cmp( func, "sub" ) )
	{
	    for( i = 2; i < MAX_ARGS; ++i )
	    {
		strcat( argv[1], " " );
		strcat( argv[1], argv[i] );
	    }
	    return mprog_sub_trigger( tarchar, actor, argv[1] );
	}
    }

    /*
     * Object target functions.
     */
    if( tarobj )
    {
	if( !str_cmp( func, "uid" ) )
	    return tarobj->unique_key;
	if( !str_cmp( func, "objtype" ) )
	    return tarobj->item_type;
	if( !str_cmp( func, "objval" ) )
	{
	    int ind = 0;

	    ind = argv[1][0] - '0';
	    if( ind < 0 || ind >= 4 )
	    {
		progbug( mob, "bad argument to '%s': %s",
			       func, argv[1] );
		return NO_FLAG;
	    }
	    return tarobj->value[ind];
	}
    }

    if( !str_cmp( func, "vnum" ) )
    {
	if( tarobj )
	    return tarobj->pIndexData->vnum;
	else if( tarchar && tarchar->pIndexData )
	    return tarchar->pIndexData->vnum;
	return NO_FLAG;
    }

    if( !str_cmp( func, "isvar" ) )
    {
	MPROG_VAR * var;
	for( var = mob->variables; var; var = var->next )
	    if( !str_cmp( var->name, argv[0] )	)
		return 1;
	return 0;
    }

    /*
     * String functions.
     * These all start with "str" and have the following rules:
     *  1) plain strings are assumed to be the name of a variable.
     *	2) a double quote signifies a literal string, this assumes that
     *	   the last character is also a quote and this is chopped off,
     *     normal '$' substitutions are performed on these strings.
     *	3) anything with a pair of parentheses is considered a string
     *     function.
     *
     * e.g. target - the value of the variable 'target'
     *      "hello $n" - hello xxx, with xxx being the actor's name
     *      name($n) - the actor's full name, from prog_string_func()
     */
    if( !str_prefix( "str", func ) )
    {
	fix_string_arg( argv[2], argv[0], info );
	fix_string_arg( argv[3], argv[1], info );

	if( !str_cmp( func, "strcmp" ) )
	    return !str_cmp( argv[2], argv[3] ) ? 1 : 0;
	if( !str_cmp( func, "strprefix" ) )
	    return !str_prefix( argv[2], argv[3] ) ? 1 : 0;
	if( !str_cmp( func, "strinfix" ) )
	    return str_infix( argv[2], argv[3] ) ? 0 : 1;
	if( !str_cmp( func, "strkey" ) )
	    return is_name( argv[2], argv[3] ) ? 1 : 0;
	if( !str_cmp( func, "strlen" ) )
	    return strlen( argv[2] );
    }

    /*
     * Miscellaneous string-to-constant functions.
     */
    fix_string_arg( argv[2], argv[0], info );
    strcpy( argv[0], argv[2] );
    fix_string_arg( argv[3], argv[1], info );
    strcpy( argv[1], argv[3] );

    if( !str_cmp( func, "speclookup" ) )
	return spec_lookup( argv[0] );
    if( !str_cmp( func, "slookup" ) )
	return skill_lookup( argv[0] );
    if( !str_cmp( func, "racelookup" ) )
	return race_lookup( argv[0] );

    if( !str_cmp( func, "table" )	/* enable table(name,value) */
	|| !str_cmp( func, "lookup" ) )	/* enable lookup(name,value) */
    {
	strcpy( func, argv[0] );
	strcpy( argv[0], argv[1] );
    }
    if( !str_prefix( "table-", func ) )	/* enable table-name(value) */
	strcpy( func, func + 6 );
    for( i = 0; bit_table[i].structure; ++i )
    {
	int vect[MAX_VECTOR];

	if( !str_cmp( func, bit_table[i].command ) )
	    return flag_value( vect, bit_table[i].structure, argv[0] );
    }

    /* Ok... all the ifchcks are done, so if we didnt find ours then something
     * odd happened.  So report the bug and abort the MOBprogram (return error)
     */
    progbug( mob, "unknown function '%s'", func );
    return NO_FLAG;
}


char *prog_string_func( char *target, char *point, MPROG_INFO *info )
{
    CHAR_DATA *mob = info->mob;
    CHAR_DATA *actor = info->actor;
    OBJ_DATA *obj = info->obj;
    CHAR_DATA *vict = (CHAR_DATA *)info->vo;
    OBJ_DATA *v_obj = (OBJ_DATA *)info->vo;
    CHAR_DATA *rndm = info->rndm;

    CHAR_DATA *tarchar = NULL;
    OBJ_DATA *tarobj = NULL;

    char func[MAX_INPUT_LENGTH];
    char *funcpt = func;
    char argv[MAX_ARGS][MAX_INPUT_LENGTH];
    char *argvpt;
    int i;

    /* Crash protection using magic numbers, thanks to Erwin A. */
    if( vict && vict->magic == MAGIC_NUM_CHAR )
	v_obj = NULL;
    else
	vict = NULL;

    if( !point || *point == '\0' )
    {
	progbug( mob, "string func null function" );
	return strcpy( target, "" );
    }
    /* skip leading spaces */
    while( *point == ' ' )
	point++;

    /* get whatever comes before the left paren.. ignore spaces */
    while( *point && *point != '(' )
	if( *point == '\0' )
	{
	    progbug( mob, "string func syntax error" );
	    return strcpy( target, "" );
	}
	else if( *point == ' ' )
	    point++;
	else
	    *funcpt++ = *point++;

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

    for( i = 0; i < MAX_ARGS; ++i )
    {
	char buf[20];
	char *lastbrace;

	/* skip leading spaces */
	while( isspace( *point ) )
	    point++;

	argvpt = &argv[i][0];

	lastbrace = buf;
	*lastbrace++ = ')';
	while( lastbrace - 1 > buf || ( *point != ')' && *point != ',' ) )
	{
	    if( *point == '\0' )
	    {
		progbug( mob, "string func syntax error" );
		return strcpy( target, "" );
	    }
	    if( *point == '(' )
		*lastbrace++ = ')';
	    else if( *point == '[' )
		*lastbrace++ = ']';
	    else if( *point == '{' )
		*lastbrace++ = '}';
	    else if( *point == *(lastbrace - 1) )
		lastbrace--;
	    *argvpt++ = *point++;
	}
	*argvpt = '\0';

	if( *point == ')' )
	{
	    ++i;
	    break;
	}
	point++;
    }
    for( ; i < MAX_ARGS; ++i )
	argv[i][0] = '\0';

    if( argv[0][0] == '$' && argv[0][2] == '\0' )
    {
	switch( argv[0][1] )
	{
	case 'i':	tarchar = mob;			break;
	case 'n':	tarchar = actor;		break;
	case 't':	tarchar = vict;			break;
	case 'r':	tarchar = rndm;			break;
	case 'o':	tarobj = obj;			break;
	case 'p':	tarobj = v_obj;			break;
	default:
	    progbug( mob, "bad argument to '%s'", func );
	    return strcpy( target, "" );
	}
    }

    for( i = 0; i < MAX_ARGS; ++i )
    {
	char buf[MAX_INPUT_LENGTH];

	if( argv[i][0] == '\0' )
	    break;
	strcpy( buf, argv[i] );
	expand_to_eol( argv[i], buf, info );
    }

    if( !tarchar && !tarobj )
    {
	tarchar = get_char_world( mob, argv[0] );
	tarobj = get_obj_here( mob, argv[0] );
    }

    if( tarchar )
    {
	if( !str_cmp( func, "name" ) )
	    return strcpy( target, tarchar->name );
	if( !str_cmp( func, "short" ) )
	    return strcpy( target, tarchar->short_descr );
	if( !str_cmp( func, "long" ) )
	    return strcpy( target, tarchar->long_descr );
	if( !str_cmp( func, "pers" ) )
	{
	    CHAR_DATA *ch2;

	    if( argv[1][0] == '\0' )
		return strcpy( target, PERS( tarchar, mob ) );
	    ch2 = get_char_world( mob, argv[0] );
	    if( !ch2 )
	    {
		progbug(
		    mob, "strfunc: 2nd argument to pers '%s' isn't right.",
			   argv[1] );
		return strcpy( target, "" );
	    }
	    return strcpy( target, PERS( tarchar, ch2 ) );
	}

    }
    if( tarobj )
    {
	if( !str_cmp( func, "name" ) )
	    return strcpy( target, tarobj->name );
	if( !str_cmp( func, "short" ) )
	    return strcpy( target, tarobj->short_descr );
	if( !str_cmp( func, "long" ) )
	    return strcpy( target, tarobj->description );
	if( !str_cmp( func, "action" ) )
	    return strcpy( target, tarobj->action );
    }

    if( !str_cmp( func, "table" ) )
    {
	int val, vect[MAX_VECTOR];

	val = prog_number_func( argv[1], info );
	if( val == NO_FLAG )
	{
	    progbug( mob, "strfunc: 2nd argument to table '%s' isn't a number.",
			   argv[1] );
	    return strcpy( target, "" );
	}
	vzero( vect );
	vect[0] = val;
	if( !str_cmp( argv[0], "class" ) )
	    return strcpy( target, class_table[URANGE( 0, val,
						       MAX_CLASS - 1 )].name );
	if( !str_cmp( argv[0], "race" ) )
	    return strcpy( target, race_table[URANGE( 0, val, MAX_RACE - 1 )].name );
	if( !str_cmp( argv[0], "skill" ) )
	    return strcpy( target, skill_table[URANGE( 0, val, MAX_SKILL - 1 )].name );
	if( !str_cmp( argv[0], "spec" ) )
	    return strcpy( target, spec_table[val].spec_name );

	for( i = 0; bit_table[i].structure; ++i )
	{
	    if( !str_cmp( argv[0], bit_table[i].command ) )
		return strcpy( target, flag_string( bit_table[i].structure,
						    vect ) );
	}
	progbug( mob, "strfunc: unknown table '%s'", argv[0] );
	return strcpy( target, "" );
    }

    progbug( mob, "strfunc: unknown function" );
    return strcpy( target, "" );
}


const char *matching_parentheses( const char *str )
{
    char bracebuf[32];
    char *lastbrace = &bracebuf[0];

    switch( *str )
    {
    case '(':
	*lastbrace++ = ')';		break;
    case '[':
	*lastbrace++ = ']';		break;
    case '{':
	*lastbrace++ = '}';		break;
    }

    while( *str != '\0'
	   && lastbrace > bracebuf && lastbrace < bracebuf + 32 )
    {
	str++;
	if( *str == '\\' )
	    str++;
	else if( *str == '"' )
	    *lastbrace = '"';
	else if( *str == '(' )
	    *lastbrace++ = ')';
	else if( *str == '[' )
	    *lastbrace++ = ']';
	else if( *str == '{' )
	    *lastbrace++ = '}';
	else if( *str == *(lastbrace - 1) )
	    lastbrace--;
    }
    if( *str == '\0' || lastbrace >= bracebuf + 32 )
	return "";
    return str;
}


/*
 * Substitution.
 * Turns all those $ codes into something more readable.
 */
void expand_to_eol( char *t, const char *src, MPROG_INFO *info )
{
    static const char *he_she[] = { "it", "he", "she" };
    static const char *him_her[] = { "it", "him", "her" };
    static const char *his_her[] = { "its", "his", "her" };
    CHAR_DATA *mob = info->mob;
    CHAR_DATA *actor = info->actor;
    OBJ_DATA *obj = info->obj;
    CHAR_DATA *vict = (CHAR_DATA *)info->vo;
    OBJ_DATA *v_obj = (OBJ_DATA *)info->vo;
    CHAR_DATA *rndm = info->rndm;

    char buf[MAX_INPUT_LENGTH];
    const char *p;
    bool byuid;

    /* Crash protection using magic numbers, thanks to Erwin A.
       Such a wonderful hack, only possible in C, but not neccessary
       in other languages. */
    if( vict && vict->magic == MAGIC_NUM_CHAR )
	v_obj = NULL;
    else
	vict = NULL;

    while( *src != '\0' && *src != '\n' )
    {
	char key;

	if( *src == '\\' )
	{
	    src++;
	    *t++ = *src++;
	    continue;
	}
	if( *src != '$' )
	{
	    *t++ = *src++;
	    continue;
	}

	src++;
	key = *src++;

	byuid = FALSE;
	if( key == '{' || key == '[' || key == '(' )
	{
	    p = matching_parentheses( src - 1 );
	    if( !p )
		continue;
	    strncpy( buf, src, p - src );
	    buf[p - src] = '\0';
	    src = p + 1;
	}
	else if( key == '#' )
	{
	    byuid = TRUE;
	    key = *src++;
	}

	switch( key )
	{
	case '{':
	    if( isdigit( buf[0] ) )
		get_num_args( t, get_program_var( info, "trigger" ), buf );
	    else
		strcpy( t, get_program_var( info, buf ) );
	    break;

	case '[':
	    sprintf( t, "%d", parse_number( buf, info ) );
	    break;

	case '(':
	    prog_string_func( t, buf, info );
	    break;

	case '*':
	    strcpy( t, get_program_var( info, "trigger" ) );
	    break;

	case 'i':
	    if( byuid )
		sprintf( t, "%d", mob->unique_key );
	    else
		one_argument( mob->name, t );
	    break;

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

	case 'n':
	    if( actor )
	    {
		if( byuid )
		    sprintf( t, "%d", actor->unique_key );
		else if( can_see( mob, actor ) )
		{
		    one_argument( actor->name, t );
		    if( !IS_NPC( actor ) )
			*t = UPPER( *t );
		}
		else
		    strcpy( t, "someone" );
	    }
	    break;

	case 'N':
	    if( actor )
	    {
		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 )
	    {
		if( byuid )
		    sprintf( t, "%d", vict->unique_key );
		else if( can_see( mob, vict ) )
		{
		    one_argument( vict->name, t );
		    if( !IS_NPC( vict ) )
			*t = UPPER( *t );
		}
		else
		    strcpy( t, "someone" );
	    }
	    break;

	case 'T':
	    if( vict )
	    {
		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 )
	    {
		if( byuid )
		    sprintf( t, "%d", rndm->unique_key );
		else if( can_see( mob, rndm ) )
		{
		    one_argument( rndm->name, t );
		    if( !IS_NPC( rndm ) )
			*t = UPPER( *t );
		}
		else
		    strcpy( t, "someone" );
	    }
	    break;

	case 'R':
	    if( rndm )
	    {
		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 )
		can_see( mob, actor ) ? strcpy( t, he_she[actor->sex] )
		    : strcpy( t, "someone" );
	    break;

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

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

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

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

	case 'S':
	    if( vict )
		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 )
		can_see( mob, rndm ) ? strcpy( t, he_she[rndm->sex] )
		    : strcpy( t, "someone" );
	    break;

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

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

	case 'o':
	    if( obj )
	    {
		if( byuid )
		    sprintf( t, "%d", obj->unique_key );
		else if( can_see_obj( mob, obj ) )
		    one_argument( obj->name, t );
		else
		    strcpy( t, "something" );
	    }
	    break;

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

	case 'p':
	    if( v_obj )
	    {
		if( byuid )
		    sprintf( t, "%d", v_obj->unique_key );
		else if( can_see_obj( mob, v_obj ) )
		    one_argument( v_obj->name, t );
		else
		    strcpy( t, "something" );
	    }
	    break;

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

	case 'a':
	    if( obj )
		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 )
		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:
	    progbug( mob, "bad $var" );
	    return;
	}
	t = strchr( t, '\0' );
    }
    *t = '\0';
    return;
}


char *get_num_args( char *buf, const char *trigger, const char *str )
{
    const char *start, *end;
    char *p;
    int i, j;

    buf[0] = '\0';

    if( trigger[0] == '\0' )
	return buf;
    if( !strcmp( str, "*" ) )
	return strcpy( buf, trigger );

    if( ( p = strchr( str, '+' ) ) && *(p + 1) == '\0' )
    {
	*p = '\0';
	j = -1;
	i = atoi( str );
    }
    else if( ( p = strchr( str, '-' ) ) && p > str )
    {
	*p++ = '\0';
	i = atoi( str );
	j = atoi( p );
    }
    else if( is_number( str ) )
    {
	i = atoi( str );
	j = i + 1;
    }
    else
    {
	bug( "get_num_args: bad variable (integer start of name)" );
	return buf;
    }
    if( i <= 0 )
    {
	bug( "get_num_args: bad variable (-ve number)" );
	return buf;
    }

    for( start = trigger; start[0] != '\0' && i > 1; i--, j-- )
	start = first_arg( start, buf, FALSE );

    if( j < i )
	return strcpy( buf, start );

    if( i + 1 == j )
    {
	first_arg( start, buf, FALSE );
	return buf;
    }

    for( end = start; end[0] != '\0' && j > 1; j-- )
	end = first_arg( end, buf, FALSE );

    memset( buf, 0, ((char *)end - (char *)start) + 2 );
    return strncpy( buf, start, end - start );
}


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

/* The next few 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..
 */
MPROG_DATA *prog_keyword_check( char *arg, MPROG_DATA *mprg,
				MPROG_INFO *info, int type )
{
    char temp1[MAX_STRING_LENGTH];
    char temp2[MAX_INPUT_LENGTH];
    char word[MAX_INPUT_LENGTH];
    const char *list;
    char *start;
    char *dupl;
    char *end;
    MPROG_GLOBAL *glob;

    for( ; mprg != NULL; mprg = mprg->next )
    {
	glob = get_global_prog( mprg );
	if( glob && glob->type != type )
	    glob = NULL;

	if( glob || mprg->type == type )
	{
	    if( glob )
		expand_to_eol( temp1, glob->arglist, info );
	    else
		expand_to_eol( temp1, mprg->arglist, info );
	    list = temp1;

	    strcpy( temp2, arg );
	    dupl = temp2;

	    if( ( list[0] == 'p' ) && ( list[1] == ' ' ) )
	    {
		list += 2;
		while( ( start = str_str( dupl, list ) ) )
		    if( ( start == dupl || *( start - 1 ) == ' ' )
			&& ( *( end = start + strlen( list ) ) == ' '
			     || *end == '\n'
			     || *end == '\r'
			     || *end == '\0' ) )
		    {
			set_local_var( info, "trigger", arg );
			return mprg;
		    }
		    else
			dupl = start + 1;
	    }
	    else if( list[0] == 'l' && list[1] == ' ' )
	    {
		list += 2;
		/* created in act, this is compensation */
		strcat( temp1, "\n\r" );
		if( !str_cmp( list, temp2 ) )
		{
		    set_local_var( info, "trigger", arg );
		    return mprg;
		}
	    }
	    else if( LOWER( list[0] ) == 'r' && list[1] == ' ' )
	    {
		regexp *preg;
		int i, len;

		for( start = &temp1[2]; isspace( *start ); ++start )
		    ;

		if( strchr( temp2, '\n' ) )
		    *strchr( temp2, '\n' ) = '\0';

		if( !( preg = regcomp( start, 1 ) ) )
		    return NULL;

		if( !regexec( preg, temp2 ) )
		{
		    free( preg );
		    return NULL;
		}

		/* a match! now we create a set of variables to suit */
		for( i = 0; i < NSUBEXP; ++i )
		{
		    len = preg->endp[i] - preg->startp[i];
		    if( len <= 0 )
			break;

		    sprintf( temp1, "r%d", i );
		    strncpy( temp2, preg->startp[i], len );
		    temp2[len] = '\0';
		    set_local_var( info, temp1, temp2 );
		}

		free( preg );
		set_local_var( info, "trigger", arg );
		return mprg;
	    }
	    else
	    {
		list = one_argument( list, word );
		for( ; word[0] != '\0'; list = one_argument( list, word ) )
		    while( ( start = str_str( dupl, word ) ) )
		    {
			if( ( start == dupl || *( start - 1 ) == ' ' )
			    && ( *( end = start + strlen( word ) ) == ' '
				 || *end == '\n'
				 || *end == '\r'
				 || *end == '\0' ) )
			{
			    set_local_var( info, "trigger", arg );
			    return mprg;
			}
			else
			    dupl = start + 1;
		    }
	    }
	}
    }

    return NULL;
}


void mprog_wordlist_check( char *arg, CHAR_DATA *mob, CHAR_DATA *actor,
			   OBJ_DATA *obj, void *vo, int type )
{
    MPROG_DATA *mprg;
    MPROG_INFO info;
    MPROG_GLOBAL *glob;

    info.local = NULL;
    info.mob = mob;
    info.actor = actor;
    info.obj = obj;
    info.vo = vo;
    info.rndm = NULL;

    mprg = mob->pIndexData->mudprogs;
    while( ( mprg = prog_keyword_check( arg, mprg, &info, type ) ) )
    {
	glob = get_global_prog( mprg );
	if( glob && glob->type == type )
	    prog_driver( glob->comlist, &info );
	else
	    prog_driver( mprg->comlist, &info );

	delete_all_locals( &info );
	mprg = mprg->next;
    }

    return;
}


void oprog_wordlist_check( char *arg, CHAR_DATA *actor,
			   OBJ_DATA *obj, void *vo, int type )
{
    MPROG_DATA *mprg;
    MPROG_INFO info;
    MPROG_GLOBAL *glob;

    info.local = NULL;
    info.mob = oset_supermob( obj );
    info.actor = actor;
    info.obj = obj;
    info.vo = vo;
    info.rndm = NULL;

    mprg = obj->pIndexData->mudprogs;
    while( ( mprg = prog_keyword_check( arg, mprg, &info, type ) ) )
    {
	glob = get_global_prog( mprg );
	if( glob && glob->type == type )
	    prog_driver( glob->comlist, &info );
	else
	    prog_driver( mprg->comlist, &info );

	delete_all_locals( &info );
	mprg = mprg->next;
    }
    release_supermob( );
    return;
}


void rprog_wordlist_check( char *arg, ROOM_INDEX_DATA *room, CHAR_DATA *actor,
			   OBJ_DATA *obj, void *vo, int type )
{
    MPROG_DATA *mprg;
    MPROG_INFO info;
    MPROG_GLOBAL *glob;

    info.local = NULL;
    info.mob = rset_supermob( room );
    info.actor = actor;
    info.obj = obj;
    info.vo = vo;
    info.rndm = NULL;

    mprg = room->mudprogs;
    while( ( mprg = prog_keyword_check( arg, mprg, &info, type ) ) )
    {
	glob = get_global_prog( mprg );
	if( glob && glob->type == type )
	    prog_driver( glob->comlist, &info );
	else
	    prog_driver( mprg->comlist, &info );
	delete_all_locals( &info );
	mprg = mprg->next;
    }
    release_supermob();
    return;
}


int mprog_sub_trigger( CHAR_DATA *mob, CHAR_DATA *actor,
			const char *argument )
{
    char name[MAX_INPUT_LENGTH];
    MPROG_DATA *mprg;
    MPROG_GLOBAL *glob;

    argument = one_argument( argument, name );
    for( mprg = mob->pIndexData->mudprogs; mprg != NULL; mprg = mprg->next )
    {
	glob = get_global_prog( mprg );

	if( glob && glob->type == SUB_PROG && !str_cmp( name, mprg->arglist ) )
	{
	    return mprog_driver( glob->comlist, mob, actor, NULL, NULL, argument );
	}
	else if( mprg->type == SUB_PROG && !str_cmp( name, mprg->arglist ) )
	{
	    return mprog_driver( mprg->comlist, mob, actor, NULL, NULL, argument );
	}
    }
    return 0;
}


/**********************************************************************
 * Supermob code.
 * Since rooms and objects can't act in the same way as mobiles, they
 * are given a special mob in lieu of actual powers.
 */
CHAR_DATA *new_supermob()
{
    struct supermob_data *tmp;

    if( supermob_free )
    {
	tmp = supermob_free;
	supermob_free = supermob_free->next;
	tmp->next = supermob_list;
	supermob_list = tmp;

	/* if they already existed then they should have been in a room */
	char_from_room( tmp->supermob );
    }
    else
    {
	tmp = alloc_perm( sizeof( struct supermob_data ) );
	tmp->next = supermob_list;
	supermob_list = tmp;
	supermob_list->supermob = create_mobile( get_mob_index( MOB_VNUM_SUPERMOB ) );
    }
    return supermob_list->supermob;
}


CHAR_DATA *oset_supermob( OBJ_DATA *obj )
{
    ROOM_INDEX_DATA *room;
    OBJ_DATA *in_obj;
    char buf[MAX_INPUT_LENGTH];
    CHAR_DATA *supermob;

    if( !obj )
	return NULL;

    for( in_obj = obj; in_obj->in_obj; in_obj = in_obj->in_obj )
	;

    if( in_obj->carried_by )
	room = in_obj->carried_by->in_room;
    else
	room = in_obj->in_room;

    if( !room )
	return NULL;

    supermob = new_supermob();
    supermob_list->prog_obj = obj;
    supermob_list->prog_room = NULL;

    supermob->level = obj->level;

    if( supermob->short_descr )
	free_string( supermob->short_descr );
    supermob->short_descr = str_dup( obj->short_descr );

    /* Added by Jenny to allow bug messages to show the vnum
       of the object, and not just supermob's vnum */
    sprintf( buf, "Object #%d (%s)", obj->pIndexData->vnum, obj->short_descr );
    free_string( supermob->description );
    supermob->description = str_dup( buf );

    char_to_room( supermob, room );
    return supermob;
}


CHAR_DATA *rset_supermob( ROOM_INDEX_DATA *room )
{
    char buf[MAX_INPUT_LENGTH];
    CHAR_DATA *supermob;

    if( !room )
	return NULL;

    supermob = new_supermob();
    supermob_list->prog_room = room;
    supermob_list->prog_obj = NULL;

    supermob->level = room->area->ave;

    free_string( supermob->short_descr );
    supermob->short_descr = str_dup( room->name );
    free_string( supermob->name );
    supermob->name = str_dup( room->name );

    /* Added by Jenny to allow bug messages to show the vnum of the
       room, and not just supermob's vnum */
    sprintf( buf, "Room #%d (%s)", room->vnum, room->name );
    free_string( supermob->description );
    supermob->description = str_dup( buf );

    char_to_room( supermob, room );
    return supermob;
}


/* Since progs run to their completion, even when they are called from
 * other progs the only supermob needed to be freed is the last one to
 * be created.
 */
void release_supermob( )
{
    OBJ_DATA *obj, *objnext;
    struct supermob_data *tmp;

    tmp = supermob_list;
    supermob_list = supermob_list->next;
    tmp->next = supermob_free;
    supermob_free = tmp;
    tmp->prog_obj = NULL;
    tmp->prog_room = NULL;
    free_string( tmp->supermob->description );
    tmp->supermob->description = &str_empty[0];

    stop_fighting( tmp->supermob, TRUE );

    /* clear inventory if the prog didn't work too well. */
    for( obj = tmp->supermob->carrying; obj; obj = objnext )
    {
	objnext = obj->next_content;
	extract_obj( obj );
    }

    char_from_room( tmp->supermob );
    char_to_room( tmp->supermob, get_room_index( ROOM_VNUM_LIMBO ) );
}


/*
 * Supermob initialisation.
 * When we start of ensure there is at least one supermob available.
 */
void init_supermob( void )
{
    ROOM_INDEX_DATA *office;

    supermob_free = alloc_perm( sizeof( struct supermob_data ) );
    supermob_free->supermob = create_mobile( get_mob_index( MOB_VNUM_SUPERMOB ) );
    supermob_free->next = NULL;
    office = get_room_index( ROOM_VNUM_LIMBO );
    char_to_room( supermob_free->supermob, office );
}


struct supermob_data *get_supermob( CHAR_DATA *mob )
{
    struct supermob_data *tmp;

    for( tmp = supermob_list; tmp; tmp = tmp->next )
    {
	if( tmp->supermob == mob )
	    return tmp;
    }
    return NULL;
}


/*
 * Check to run a "percentage chance" program.  Note how the bit for this
 * paricular program is removed for the running of the program.  This ensures
 * that no recursion occurs.
 */
int mprog_percent_check( CHAR_DATA *mob, CHAR_DATA *actor, OBJ_DATA *obj,
			  void *vo, int type )
{
    MPROG_DATA *mprg;
    MPROG_GLOBAL *glob;
    int retval = NO_FLAG;

    for( mprg = mob->pIndexData->mudprogs; mprg != NULL; mprg = mprg->next )
    {
	glob = get_global_prog( mprg );
	if( glob && glob->type == type
	    && ( number_percent( ) < atoi( mprg->arglist ) ) )
	{
	    xREMOVE_BIT( mob->pIndexData->progtypes, type );
	    retval = mprog_driver( glob->comlist, mob, actor, obj, vo, NULL );
	    xSET_BIT( mob->pIndexData->progtypes, type );
	    if( type != GREET_PROG && type != ALL_GREET_PROG )
		break;
	}
	else if( ( mprg->type == type )
	    && ( number_percent( ) < atoi( mprg->arglist ) ) )
	{
	    xREMOVE_BIT( mob->pIndexData->progtypes, type );
	    retval = mprog_driver( mprg->comlist, mob, actor, obj, vo, NULL );
	    xSET_BIT( mob->pIndexData->progtypes, type );
	    if( type != GREET_PROG && type != ALL_GREET_PROG )
		break;
	}
    }

    return retval;
}


int oprog_percent_check( CHAR_DATA *actor, OBJ_DATA *obj,
			  void *vo, int type )
{
    MPROG_DATA *mprg;
    MPROG_GLOBAL *glob;
    CHAR_DATA *supermob;
    int retval = NO_FLAG;

    for( mprg = obj->pIndexData->mudprogs; mprg; mprg = mprg->next )
    {
	glob = get_global_prog( mprg );
	if( glob && glob->type == type
	    && ( number_percent( ) < atoi( mprg->arglist ) ) )
	{
	    xREMOVE_BIT( obj->pIndexData->progtypes, type );
	    supermob = oset_supermob( obj );
	    retval = mprog_driver( glob->comlist, supermob, actor, obj, vo, FALSE );
	    release_supermob();
	    xSET_BIT( obj->pIndexData->progtypes, type );
	    if( type != GREET_PROG )
		break;
	}
	else if( mprg->type == type
		 && ( number_percent( ) <= atoi( mprg->arglist ) ) )
	{
	    xREMOVE_BIT( obj->pIndexData->progtypes, type );
	    supermob = oset_supermob( obj );
	    retval = mprog_driver( mprg->comlist, supermob, actor, obj, vo, FALSE );
	    release_supermob();
	    xSET_BIT( obj->pIndexData->progtypes, type );
	    if( type != GREET_PROG )
		break;
	}
    }

    return retval;
}


int rprog_percent_check( ROOM_INDEX_DATA *room, CHAR_DATA *actor,
	  OBJ_DATA *obj, void *vo, int type )
{
    MPROG_DATA *mprg;
    MPROG_GLOBAL *glob;
    CHAR_DATA *supermob;
    int retval = 1;

    if( !room )
	return NO_FLAG;

    for( mprg = room->mudprogs; mprg; mprg = mprg->next )
    {
	glob = get_global_prog( mprg );
	if( glob && glob->type == type
	    && ( number_percent( ) < atoi( mprg->arglist ) ) )
	{
	    xREMOVE_BIT( room->progtypes, type );
	    supermob = rset_supermob( room );
	    retval = mprog_driver( glob->comlist, supermob, actor, obj, vo, FALSE );
	    release_supermob();
	    xSET_BIT( room->progtypes, type );
	}
	else if( mprg->type == type
		 && number_percent() <= atoi( mprg->arglist ) )
	{
	    xREMOVE_BIT( room->progtypes, type );
	    supermob = rset_supermob( room );
	    retval = mprog_driver( mprg->comlist, supermob, actor, obj, vo, FALSE );
	    release_supermob();
	    xSET_BIT( room->progtypes, type );
	}
    }
    return retval;
}


/* A little time checking diddy.
 */
bool is_time( char *p, PLANE_DATA *plane )
{
    int val = 0;

    while( isspace( *p ) )
	++p;
    if( !*p )
	return FALSE;

    for( ; p && *p; p++ )
    {
	if( !isdigit( *p ) && *p != ' ' )
	{
	    bug( "Invalid argument to time_prog." );
	    return FALSE;
	}
	if( *p == ' ' )
	{
	    if( val == plane->time.hour )
		return TRUE;
	    while( isspace( *(p + 1) ) )
		++p;
	    val = 0;
	}
	else
	    val = val * 10 + *p - '0';
    }
    if( val == plane->time.hour )
	return TRUE;
    return FALSE;
}


/* A smart bug feature, so we can tell where the problem is, even down
 *  to objects and rooms.
 */
void progbug( CHAR_DATA *mob, const char *fmt, ... )
{
    char fmt2[MAX_INPUT_LENGTH];
    char buf[MAX_STRING_LENGTH];
    va_list args;

    va_start( args, fmt );

    /* Check if we're dealing with supermob, which means the bug occurred
       in a room or obj prog. */
    if( mob->pIndexData && mob->pIndexData->vnum == MOB_VNUM_SUPERMOB )
    {
	/* It's supermob.  In set_supermob and rset_supermob, the description
	   was set to indicate the object or room, so we just need to show
	   the description in the bug message. */
	sprintf( fmt2, "[*%s*] %s.", mob->description == NULL
		 ? "(unknown)" : mob->description,
		 fmt );
    }
    else if( mob->pIndexData )
	sprintf( fmt2, "[*Mob %d*] %s", mob->pIndexData->vnum, fmt );
    else
	sprintf( fmt2, "[*Player*] %s", fmt );

    vsprintf( buf, fmt2, args );
    va_end( args );

    bug( buf );
    return;
}


/*
 * Rather than having act progs run when they trigger, this update allows
 * them to seem as if they have occured _after_ the trigger.  This is also
 * much neater.
*/
void update_act_progs( void )
{
    MPROG_ACT_LIST *tmp_act;
    CHAR_DATA *mob;
    OBJ_DATA *obj;
    ROOM_INDEX_DATA *room;
    while( ( tmp_act = mob_act_list ) )
    {
	mob = (CHAR_DATA *)tmp_act->target;
	if( !mob->deleted )
	    prog_driver( tmp_act->mprg->comlist, tmp_act->info );
	mob_act_list = tmp_act->next;
	delete_all_locals( tmp_act->info );
	if( tmp_act->mprg->type == GLOBAL_PROG )
	    free_mprog( tmp_act->mprg );
	free_mem( tmp_act->info, sizeof( MPROG_INFO ) );
	free_mem( tmp_act, sizeof( MPROG_ACT_LIST ) );
    }
    while( ( tmp_act = obj_act_list ) )
    {
	obj = (OBJ_DATA *)tmp_act->target;
	if( !obj->deleted )
	{
	    tmp_act->info->mob = oset_supermob( obj );
	    prog_driver( tmp_act->mprg->comlist, tmp_act->info );
	    release_supermob();
	}
	obj_act_list = tmp_act->next;
	delete_all_locals( tmp_act->info );
	if( tmp_act->mprg->type == GLOBAL_PROG )
	    free_mprog( tmp_act->mprg );
	free_mem( tmp_act->info, sizeof( MPROG_INFO ) );
	free_mem( tmp_act, sizeof( MPROG_ACT_LIST ) );
    }
    while( ( tmp_act = room_act_list ) )
    {
	room = (ROOM_INDEX_DATA *)tmp_act->target;
	tmp_act->info->mob = rset_supermob( room );
	prog_driver( tmp_act->mprg->comlist, tmp_act->info );
	release_supermob();
	room_act_list = tmp_act->next;
	delete_all_locals( tmp_act->info );
	if( tmp_act->mprg->type == GLOBAL_PROG )
	    free_mprog( tmp_act->mprg );
	free_mem( tmp_act->info, sizeof( MPROG_INFO ) );
	free_mem( tmp_act, sizeof( MPROG_ACT_LIST ) );
    }
    return;
}


/* 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 prog_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.
 *
 * Supermob note: These programs trigger without a supermob so the function
 * uses less time.  For the purposes of figuring out whether the trigger has
 * worked, the mob is the same as the actor.
 */
void roomobj_act_trigger( char *buf, ROOM_INDEX_DATA *room, CHAR_DATA *ch,
			  OBJ_DATA *obj, void *vo )
{
    MPROG_ACT_LIST *tmp_act;
    MPROG_DATA *mprg, *mprg_tmp;
    MPROG_INFO info;
    char tmp[MAX_INPUT_LENGTH];
    MPROG_GLOBAL *glob;

    kill_colour( tmp, buf );

    info.local = NULL;
    info.mob = ch;
    info.actor = ch;
    info.obj = obj;
    info.vo = vo;
    info.rndm = NULL;

    if( xIS_SET( room->progtypes, ACT_PROG )
	&& ( mprg = prog_keyword_check( tmp, room->mudprogs, &info, ACT_PROG ) ) )
    {
	tmp_act = alloc_mem( sizeof( MPROG_ACT_LIST ) );

	tmp_act->target = ch->in_room;
	if( ( glob = get_global_prog( mprg ) ) )
	{
	    mprg_tmp = new_mprog( );
	    mprg_tmp->type = GLOBAL_PROG;
	    free_string( mprg_tmp->comlist );
	    mprg_tmp->comlist = str_dup( glob->comlist );
	    tmp_act->mprg = mprg_tmp;
	}
	else
	    tmp_act->mprg = mprg;
	tmp_act->info = alloc_mem( sizeof( MPROG_INFO ) );
	tmp_act->info->local = info.local;
	info.local = NULL;
	tmp_act->info->mob = NULL;
	tmp_act->info->actor = ch;
	tmp_act->info->obj = obj;
	tmp_act->info->vo = vo;
	tmp_act->info->rndm = NULL;

	tmp_act->next = room_act_list;
	room_act_list = tmp_act;
    }

    /* yes, I know this overrides the obj parameter, but it makes
       it very easy, Symposium */
    for( obj = room->contents; obj; obj = obj->next_content )
    {
	if( obj->deleted && !xIS_SET( obj->pIndexData->progtypes, ACT_PROG ) )
	    continue;
	if( ( mprg = prog_keyword_check( tmp, obj->pIndexData->mudprogs,
					 &info, ACT_PROG ) ) )
	{
	    tmp_act = alloc_mem( sizeof( MPROG_ACT_LIST ) );

	    tmp_act->target = obj;
	    if( ( glob = get_global_prog( mprg ) ) )
	    {
		mprg_tmp = new_mprog( );
		mprg_tmp->type = GLOBAL_PROG;
		free_string( mprg_tmp->comlist );
		mprg_tmp->comlist = str_dup( glob->comlist );
		tmp_act->mprg = mprg_tmp;
	    }
	    else
		tmp_act->mprg = mprg;
	    tmp_act->info = alloc_mem( sizeof( MPROG_INFO ) );
	    tmp_act->info->local = info.local;
	    info.local = NULL;
	    tmp_act->info->mob = NULL;
	    tmp_act->info->actor = ch;
	    tmp_act->info->obj = obj;
	    tmp_act->info->vo = vo;
	    tmp_act->info->rndm = NULL;

	    tmp_act->next = obj_act_list;
	    obj_act_list = tmp_act;
	}
    }
    return;
}


void mob_act_trigger( char *buf, CHAR_DATA *mob, CHAR_DATA *ch,
		      OBJ_DATA *obj, void *vo )
{
    MPROG_ACT_LIST *tmp_act;
    MPROG_DATA *mprg, *mprg_tmp;
    MPROG_GLOBAL *glob;
    MPROG_INFO info;
    char tmp[MAX_INPUT_LENGTH];

    if( !IS_NPC( mob ) || !xIS_SET( mob->pIndexData->progtypes, ACT_PROG )
	|| ch->pIndexData == mob->pIndexData )
	return;

    kill_colour( tmp, buf );

    info.local = NULL;
    info.mob = mob;
    info.actor = ch;
    info.obj = obj;
    info.vo = vo;
    info.rndm = NULL;

    /* Don't let a mob trigger itself, nor one instance of a mob
       trigger another instance. */
    if( ( mprg = prog_keyword_check( tmp, mob->pIndexData->mudprogs,
				     &info, ACT_PROG ) ) )
    {
	tmp_act = alloc_mem( sizeof( MPROG_ACT_LIST ) );

	tmp_act->target = mob;
	if( ( glob = get_global_prog( mprg ) ) )
	{
	    mprg_tmp = new_mprog( );
	    mprg_tmp->type = GLOBAL_PROG;
	    free_string( mprg_tmp->comlist );
	    mprg_tmp->comlist = str_dup( glob->comlist );
	    tmp_act->mprg = mprg_tmp;
	}
	else
	    tmp_act->mprg = mprg;
	tmp_act->info = alloc_mem( sizeof( MPROG_INFO ) );
	tmp_act->info->local = info.local;
	info.local = NULL;
	tmp_act->info->mob = mob;
	tmp_act->info->actor = ch;
	tmp_act->info->obj = obj;
	tmp_act->info->vo = vo;
	tmp_act->info->rndm = NULL;

	tmp_act->next = mob_act_list;
	mob_act_list = tmp_act;
    }
    return;
}


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

    if( IS_NPC( mob )
	&& xIS_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 );
	mob->gold -= amount;

	for( mprg = mob->pIndexData->mudprogs; mprg != NULL; mprg = mprg->next )
	{
	    glob = get_global_prog( mprg );
	    if( glob && glob->type == BRIBE_PROG
		&& ( amount >= atoi( mprg->arglist ) ) )
	    {
		mprog_driver( glob->comlist, mob, ch, obj, NULL, NULL );
		break;
	    }
	    else if( mprg->type == BRIBE_PROG
		&& ( amount >= atoi( mprg->arglist ) ) )
	    {
		mprog_driver( mprg->comlist, mob, ch, obj, NULL, NULL );
		break;
	    }
	}
    }
    return;
}


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

    if( IS_NPC( mob )
	&& xIS_SET( mob->pIndexData->progtypes, GIVE_PROG ) )
	for( mprg = mob->pIndexData->mudprogs; mprg != NULL; mprg = mprg->next )
	{
	    glob = get_global_prog( mprg );

	    if( glob )
	    {
		one_argument( glob->arglist, buf );
		if( glob->type == GIVE_PROG
		    && ( !str_cmp( obj->name, glob->arglist )
			 || !str_cmp( "all", buf )
			 || atoi( glob->arglist ) == obj->pIndexData->vnum ) )
		{
		    mprog_driver( glob->comlist, mob, ch, obj, NULL, NULL );
		    break;
		}
	    }
	    else
	    {
		one_argument( mprg->arglist, buf );
		if( mprg->type == GIVE_PROG
		    && ( !str_cmp( obj->name, mprg->arglist )
			 || !str_cmp( "all", buf )
			 || atoi( mprg->arglist ) == obj->pIndexData->vnum ) )
		{
		    mprog_driver( mprg->comlist, mob, ch, obj, NULL, NULL );
		    break;
		}
	    }
	}
    return;
}


void oprog_container_putin( CHAR_DATA *ch, OBJ_DATA *cont, OBJ_DATA *obj )
{
    char buf[MAX_INPUT_LENGTH];
    MPROG_DATA *mprg;
    MPROG_GLOBAL *glob;

    if( obj->deleted || xIS_SET( cont->pIndexData->progtypes, PUTIN_PROG ) )
	return;

    for( mprg = cont->pIndexData->mudprogs; mprg != NULL; mprg = mprg->next )
    {
	glob = get_global_prog( mprg );

	if( glob )
	{
	    one_argument( glob->arglist, buf );
	    if( glob->type == GIVE_PROG
		&& ( !str_cmp( obj->name, glob->arglist )
		     || !str_cmp( "all", buf )
		     || atoi( glob->arglist ) == obj->pIndexData->vnum ) )
	    {
		mprog_driver( mprg->comlist, oset_supermob( cont ),
			      ch, cont, obj, NULL );
		release_supermob();
		break;
	    }
	}
	else
	{
	    one_argument( mprg->arglist, buf );
	    if( mprg->type == GIVE_PROG
		&& ( !str_cmp( obj->name, mprg->arglist )
		     || !str_cmp( "all", buf )
		     || atoi( mprg->arglist ) == obj->pIndexData->vnum ) )
	    {
		mprog_driver( mprg->comlist, oset_supermob( cont ),
			      ch, cont, obj, NULL );
		release_supermob();
		break;
	    }
	}
    }
    return;
}


void mprog_hitprcnt_trigger( CHAR_DATA *mob )
{
    MPROG_DATA *mprg;
    MPROG_GLOBAL *glob;

    for( mprg = mob->pIndexData->mudprogs; mprg != NULL; mprg = mprg->next )
    {
	glob = get_global_prog( mprg );
	if( glob && glob->type == HITPRCNT_PROG
	    && ( ( 100 * mob->hit / mob->max_hit ) < atoi( mprg->arglist ) ) )
	{
	    mprog_driver( glob->comlist, mob, mob->fighting, NULL, NULL, NULL );
	    break;
	}
	else if( mprg->type == HITPRCNT_PROG
		 && ( ( 100 * mob->hit / mob->max_hit ) < atoi( mprg->arglist ) ) )
	{
	    mprog_driver( mprg->comlist, mob, mob->fighting, NULL, NULL, NULL );
	    break;
	}
    }
}


void mprog_time_trigger( CHAR_DATA *mob )
{
    MPROG_DATA *mprg;
    MPROG_GLOBAL *glob;

    for( mprg = mob->pIndexData->mudprogs; mprg; mprg = mprg->next )
    {
	glob = get_global_prog( mprg );
	if( glob && glob->type == TIME_PROG
	    && is_time( glob->arglist, mob->in_room->area->plane ) )
	    mprog_driver( glob->comlist, mob, NULL, NULL, NULL, NULL );
	else if( mprg->type == TIME_PROG
		 && is_time( mprg->arglist, mob->in_room->area->plane ) )
	    mprog_driver( mprg->comlist, mob, NULL, NULL, NULL, NULL );
    }

    return;
}


void rprog_time_trigger( ROOM_INDEX_DATA *room, CHAR_DATA *actor )
{
    MPROG_DATA *mprg;
    MPROG_GLOBAL *glob;

    for( mprg = room->mudprogs; mprg; mprg = mprg->next )
    {
	glob = get_global_prog( mprg );
	if( glob && glob->type == TIME_PROG
	    && is_time( glob->arglist, room->area->plane ) )
	{
	    mprog_driver( glob->comlist, rset_supermob( room ),
			  actor, NULL, NULL, NULL );
	    release_supermob();
	}
	else if( mprg->type == TIME_PROG
	    && is_time( mprg->arglist, room->area->plane ) )
	{
	    mprog_driver( mprg->comlist, rset_supermob( room ),
			  actor, NULL, NULL, NULL );
	    release_supermob();
	}
    }
    return;
}


void oprog_time_trigger( OBJ_DATA *obj )
{
    MPROG_DATA *mprg;
    MPROG_GLOBAL *glob;

    for( mprg = obj->pIndexData->mudprogs; mprg; mprg = mprg->next )
    {
	glob = get_global_prog( mprg );
	if( glob && glob->type == TIME_PROG
	    && is_time( glob->arglist, obj->pIndexData->area->plane ) )
	{
	    mprog_driver( glob->comlist, oset_supermob( obj ),
			  NULL, obj, NULL, NULL );
	    release_supermob();
	}
	else if( mprg->type == TIME_PROG
		 && is_time( mprg->arglist, obj->pIndexData->area->plane ) )
	{
	    mprog_driver( mprg->comlist, oset_supermob( obj ),
			  NULL, obj, NULL, NULL );
	    release_supermob();
	}
    }
    return;
}


void mprog_cast_trigger( CHAR_DATA *mob, CHAR_DATA *actor, int sn )
{
    const char *arg;
    char buf[MAX_INPUT_LENGTH];
    MPROG_DATA *mprg;
    MPROG_GLOBAL *glob;

    for( mprg = mob->pIndexData->mudprogs; mprg != NULL; mprg = mprg->next )
    {
	glob = get_global_prog( mprg );
	if( ( !glob && mprg->type != CAST_PROG )
	    || ( glob && glob->type != CAST_PROG ) )
	    continue;

	if( glob )
	    arg = glob->arglist;
	else
	    arg = mprg->arglist;
	while( arg != NULL && arg[0] != '\0' )
	{
	    arg = one_argument( arg, buf );
	    if( !str_prefix( buf, skill_table[sn].name ) )
	    {
		if( glob )
		    mprog_driver( glob->comlist, mob, actor,
				  NULL, NULL, skill_table[sn].name );
		else
		    mprog_driver( mprg->comlist, mob, actor,
				  NULL, NULL, skill_table[sn].name );
		return;
	    }
	}
    }
    return;
}


/*
 * Allow a trigger in the form: <percentage> [direction] [dir..]
 * for instance: 75 north south
 * returns TRUE mostly, FALSE signifies something blocking passage.
 */
bool greet_leave_trigger( CHAR_DATA *actor, CHAR_DATA *mob, ROOM_INDEX_DATA *room,
			 OBJ_DATA *obj, int dir, int type )
{
    const char *arg;
    char buf[MAX_INPUT_LENGTH];
    MPROG_DATA *mprg;
    MPROG_GLOBAL *glob;
    char dirbuf[8];
    MPROG_INFO info;
    int retval = TRUE;

    if( mob )
    {
	info.mob = mob;
	mprg = mob->pIndexData->mudprogs;
    }
    else if( obj )
    {
	info.mob = oset_supermob( obj );
	mprg = obj->pIndexData->mudprogs;
    }
    else if( room )
    {
	info.mob = rset_supermob( room );
	mprg = room->mudprogs;
    }
    else
    {
	bug( "greet_leave_trigger: no source for programs." );
	return TRUE;
    }

    info.local = NULL;
    info.actor = actor;
    info.obj = obj;
    info.vo = NULL;
    info.rndm = NULL;

    dir = 1 << dir;
    strcpy( dirbuf, flag_string( direction_flags, &dir ) );

    for( ; mprg != NULL; mprg = mprg->next )
    {
	int flags;

	glob = get_global_prog( mprg );
	if( ( !glob && mprg->type != type ) || ( glob && glob->type != type ) )
	    continue;

	if( glob )
	    arg = one_argument( glob->arglist, buf );
	else
	    arg = one_argument( mprg->arglist, buf );
	if( number_percent( ) > atoi( buf ) )
	    continue;

	expand_to_eol( buf, arg, &info );
	flags = flag_value( NULL, direction_flags, buf );

	if( arg[0] == '\0'
	    || ( flags != NO_FLAG && IS_SET( flags, dir ) ) )
	{
	    set_local_var( &info, "trigger", dirbuf );
	    if( ( glob && !prog_driver( glob->comlist, &info ) )
		|| ( !glob && !prog_driver( mprg->comlist, &info ) ) )
		retval = FALSE;
	    delete_all_locals( &info );
	}
    }

    if( !mob )
	release_supermob();
    return retval;
}


/*
 * The trigger for handling commands through mudprogs.
 * Recursion protection in comm_trig_aux() means that whilst the program
 * is being run, the obj/room/mob can't be triggered by another command.
 */
bool command_trigger( CHAR_DATA *ch, const char *comm, const char *argument )
{
    MPROG_INFO info;
    char cmnd_buf[ MAX_INPUT_LENGTH ];
    bool progrun;

    if( IS_NPC( ch ) )
	return FALSE;

    info.local = NULL;
    info.mob = NULL;
    info.actor = ch;
    info.obj = NULL;
    info.vo = NULL;
    info.rndm = NULL;
    sprintf( cmnd_buf, "(%s) %s", comm, argument );

    if( xIS_SET( ch->in_room->progtypes, COMMAND_PROG ) )
    {
	info.mob = rset_supermob( ch->in_room );
	progrun = comm_trig_aux( ch->in_room->mudprogs, ch->in_room->progtypes,
				 cmnd_buf, &info );
	release_supermob( );
	if( progrun )
	    return TRUE;
    }
    for( info.mob = ch->in_room->people; info.mob;
	 info.mob = info.mob->next_in_room )
    {
	if( info.mob->deleted || !IS_NPC( info.mob )
	    || !xIS_SET( info.mob->pIndexData->progtypes, COMMAND_PROG ) )
	    continue;

	progrun = comm_trig_aux( info.mob->pIndexData->mudprogs,
				 info.mob->pIndexData->progtypes,
				 cmnd_buf, &info );
	if( progrun )
	    return TRUE;
    }
    for( info.obj = ch->in_room->contents; info.obj;
	 info.obj = info.obj->next_content )
    {
	if( !info.obj->deleted
	    && xIS_SET( info.obj->pIndexData->progtypes, COMMAND_PROG ) )
	{
	    info.mob = oset_supermob( info.obj );
	    progrun = comm_trig_aux( info.obj->pIndexData->mudprogs,
				     info.obj->pIndexData->progtypes,
				     cmnd_buf, &info );
	    release_supermob( );
	    if( progrun )
		return TRUE;
	}
    }
    for( info.obj = ch->carrying; info.obj;
	 info.obj = info.obj->next_content )
    {
	if( !info.obj->deleted && info.obj->wear_loc != WEAR_NONE
	    && xIS_SET( info.obj->pIndexData->progtypes, COMMAND_PROG ) )
	{
	    info.mob = oset_supermob( info.obj );
	    progrun = comm_trig_aux( info.obj->pIndexData->mudprogs,
				     info.obj->pIndexData->progtypes,
				     cmnd_buf, &info );
	    release_supermob( );
	    if( progrun )
		return TRUE;
	}
    }
    return FALSE;
}


/*
 * Auxiliary function to find a command_prog and execute it.
 * Recursion protection by de-flagging the room/obj/mob of it's
 * COMMAND_PROG flag.
 */
bool comm_trig_aux( MPROG_DATA *mprg, int *progtypes, const char *cmnd_line,
		    MPROG_INFO *info )
{
    const char *arg;
    char argbuf[MAX_INPUT_LENGTH];
    char buf[MAX_INPUT_LENGTH];
    char command[MAX_INPUT_LENGTH];
    int progrun;
    MPROG_GLOBAL *glob;

    one_argument( cmnd_line, command );

    for( ; mprg != NULL; mprg = mprg->next )
    {
	glob = get_global_prog( mprg );
	if( ( glob && glob->type != COMMAND_PROG )
	    || ( !glob && mprg->type != COMMAND_PROG ) )
	    continue;

	if( glob )
	    expand_to_eol( argbuf, glob->arglist, info );
	else
	    expand_to_eol( argbuf, mprg->arglist, info );
	arg = &argbuf[0];
	while( arg != NULL && arg[0] != '\0' )
	{
	    char *p;

	    arg = one_argument( arg, buf );

	    /* Check for pre|fix command matching
	     * So for "abcd|efg", check for "abcd" being prefix to command
	     * and command being prefix to "abcdefg".
	     */
	    if( ( p = strchr( buf, '|' ) ) )
	    {
		*p = '\0';
		if( str_prefix( buf, command ) )
		    p = NULL;
		else
		{
		    strcpy( p, p + 1 );
		    if( str_prefix( command, buf ) )
			p = NULL;
		}
	    }

	    if( p || !str_cmp( command, buf ) )
	    {
		xREMOVE_BIT( progtypes, COMMAND_PROG );
		set_local_var( info, "trigger", cmnd_line );
		if( glob )
		    progrun = prog_driver( glob->comlist, info );
		else
		    progrun = prog_driver( mprg->comlist, info );
		delete_all_locals( info );
		xSET_BIT( progtypes, COMMAND_PROG );
		if( progrun )
		    return TRUE;
	    }
	}
    }
    return FALSE;
}


/**************************************************************************
 * Mob variables.
 * These functions allow you to save information on a mob using the above
 * programs.
 */
char *get_program_var( MPROG_INFO *info, const char *name )
{
    MPROG_VAR *var;
    struct supermob_data *sup;

    if( ( sup = get_supermob( info->mob ) ) )
    {
	if( sup->prog_obj )
	    var = sup->prog_obj->variables;
	else if( sup->prog_room )
	    var = sup->prog_room->variables;
	else
	{
	    bug( "Mob %d was found to be supermob to nothing.",
		       info->mob->pIndexData->vnum );
	    var = NULL;
	}
    }
    else
	var = info->mob->variables;
    for( ; var; var = var->next )
	if( !str_cmp( name, var->name ) )
	    break;
    if( var )
	return var->value;

    for( var = info->local; var; var = var->next )
	if( !str_cmp( name, var->name ) )
	    break;
    if( var )
	return var->value;
    else
	return &str_empty[0];
}

void new_local_var( MPROG_INFO *info, const char *name, const char *value )
{
    MPROG_VAR *var;

    var = new_mpvar( );
    var->name = str_dup( name );
    var->value = str_dup( value );
    var->next = info->local;
    info->local = var;
}


void set_local_var( MPROG_INFO *info, const char *name, const char *value )
{
    MPROG_VAR *var;

    for( var = info->local; var; var = var->next )
	if( !str_cmp( name, var->name ) )
	    break;
    if( !var )
	new_local_var( info, name, value );
    else
    {
	free_string( var->value );
	var->value = str_dup( value );
    }
    return;
}


void delete_local_var( MPROG_INFO *info, const char *name )
{
    MPROG_VAR *var = NULL, *last;

    if( !str_cmp( info->local->name, name ) )
    {
	var = info->local;
	info->local = info->local->next;
    }
    else
    {
	for( last = info->local; last; last = last->next )
	    if( !str_cmp( last->next->name, name ) )
	    {
		var = last->next;
		break;
	    }
	if( var )
	    last->next = var->next;
    }
    if( var )
	free_mpvar( var );
    return;
}


void delete_all_locals( MPROG_INFO *info )
{
    MPROG_VAR *var;

    while( ( var = info->local ) )
    {
	info->local = info->local->next;
	free_mpvar( var );
    }
    return;
}


char *get_mob_var( CHAR_DATA *mob, const char *name )
{
    MPROG_VAR *var;
    struct supermob_data *sup;

    if( ( sup = get_supermob( mob ) ) )
    {
	if( sup->prog_obj )
	    var = sup->prog_obj->variables;
	else if( sup->prog_room )
	    var = sup->prog_room->variables;
	else
	{
	    bug( "Mob %d was found to be supermob to nothing.",
		 mob->pIndexData->vnum );
	    var = NULL;
	}
    }
    else
	var = mob->variables;
    for( ; var; var = var->next )
	if( !str_cmp( name, var->name ) )
	    break;
    if( var )
	return var->value;
    else
	return &str_empty[0];
}


void new_mob_var( CHAR_DATA *mob, const char *name, const char *value )
{
    MPROG_VAR *var;
    struct supermob_data *sup;

    var = new_mpvar( );
    var->name = str_dup( name );
    var->value = str_dup( value );
    if( ( sup = get_supermob( mob ) ) )
    {
	if( sup->prog_obj )
	{
	    var->next = sup->prog_obj->variables;
	    sup->prog_obj->variables = var;
	}
	else if( sup->prog_room )
	{
	    var->next = sup->prog_room->variables;
	    sup->prog_room->variables = var;
	}
	else
	    bug( "Mob %d was found to be supermob to nothing.",
		 mob->pIndexData->vnum );
    }
    else
    {
	var->next = mob->variables;
	mob->variables = var;
    }
}


void set_mob_var( CHAR_DATA *mob, const char *name, const char *value )
{
    MPROG_VAR *var;
    struct supermob_data *sup;

    if( ( sup = get_supermob( mob ) ) )
    {
	if( sup->prog_obj )
	    var = sup->prog_obj->variables;
	else if( sup->prog_room )
	    var = sup->prog_room->variables;
	else
	{
	    bug( "Mob %d was found to be supermob to nothing.",
		 mob->pIndexData->vnum );
	    var = NULL;
	}
    }
    else
	var = mob->variables;
    for( ; var; var = var->next )
	if( !str_cmp( name, var->name ) )
	    break;
    if( !var )
	new_mob_var( mob, name, value );
    else
    {
	free_string( var->value );
	var->value = str_dup( value );
    }
    return;
}


void delete_mob_var( CHAR_DATA * mob, const char *name )
{
    MPROG_VAR **handle, *var, *last;
    struct supermob_data *sup;

    if( ( sup = get_supermob( mob ) ) )
    {
	if( sup->prog_obj )
	    handle = &sup->prog_obj->variables;
	else if( sup->prog_room )
	    handle = &sup->prog_room->variables;
	else
	{
	    bug( "Mob %d was found to be supermob to nothing.",
		 mob->pIndexData->vnum );
	    return;
	}
    }
    else
	handle = &mob->variables;
    for( var = *handle; var; var = var->next )
	if( !str_cmp( name, var->name ) )
	    break;
    if( !var )
    {
	bug( "Mob %d: unknown variable '%s'.",
		   mob->pIndexData->vnum, name );
	return;
    }

    if( var == *handle )
    {
	*handle = (*handle)->next;
    }
    else
    {
	for( last = *handle; last; last = last->next )
	    if( last->next == var )
		break;
	last->next = var->next;
    }
    free_mpvar( var );
    return;
}