dawn/notes/
dawn/src/
dawn/src/docs/
/**************************************************************************/
// mob_prog.cpp - Mobprogs engine, most enhancements by Kal
/***************************************************************************
 * The Dawn of Time v1.69r (c)1997-2004 Michael Garratt                    *
 * >> A number of people have contributed to the Dawn codebase, with the   *
 *    majority of code written by Michael Garratt - www.dawnoftime.org     *
 * >> To use this source code, you must fully comply with all the licenses *
 *    in licenses.txt... In particular, you may not remove this copyright  *
 *    notice.                                                              *
 ***************************************************************************
 * >> Original Diku Mud copyright (c)1990, 1991 by Sebastian Hammer,       *
 *    Michael Seifert, Hans Henrik St{rfeldt, Tom Madsen, & Katja Nyboe.   *
 * >> Merc Diku Mud improvements copyright (C) 1992, 1993 by Michael       *
 *    Chastain, Michael Quan, and Mitchell Tse.                            *
 * >> ROM 2.4 is copyright 1993-1995 Russ Taylor and has been brought to   *
 *    you by the ROM consortium: Russ Taylor(rtaylor@pacinfo.com),         *
 *    Gabrielle Taylor(gtaylor@pacinfo.com) & Brian Moore(rom@rom.efn.org) *
 * >> Oblivion 1.2 is copyright 1996 Wes Wagner                            *
 **************************************************************************/
/***************************************************************************
 *  MOBprograms for ROM 2.4 v0.98g (C) M.Nylander 1996                     *
 *  Based on MERC 2.2 MOBprograms concept by N'Atas-ha.                    *
 *  Written and adapted to ROM 2.4 by                                      *
 *          Markku Nylander (markku.nylander@uta.fi)                       *
 ***************************************************************************/
#include "include.h" // dawn standard includes
#include "security.h"

extern int flag_lookup( const char *word, const struct flag_type *flag_table );

/*
 * These defines correspond to the entries in fn_keyword[] table.
 * If you add a new if_check, you must also add a #define here.
 */
#define CHK_RAND		(0)
#define CHK_MOBHERE     (1)
#define CHK_OBJHERE     (2)
#define CHK_MOBEXISTS   (3)
#define CHK_OBJEXISTS   (4)
#define CHK_PEOPLE      (5)
#define CHK_PLAYERS     (6)
#define CHK_MOBS        (7)
#define CHK_CLONES      (8)
#define CHK_ORDER       (9)
#define CHK_HOUR        (10)
#define CHK_ISPC        (11)
#define CHK_ISNPC       (12)
#define CHK_ISGOOD      (13)
#define CHK_ISEVIL      (14)
#define CHK_ISNEUTRAL   (15)
#define CHK_ISIMMORT    (16)
#define CHK_ISCHARM     (17)
#define CHK_ISFOLLOW    (18)
#define CHK_ISACTIVE    (19)
#define CHK_ISDELAY     (20)
#define CHK_ISVISIBLE   (21)
#define CHK_HASTARGET   (22)
#define CHK_ISTARGET    (23)
#define CHK_EXISTS      (24)
#define CHK_AFFECTED    (25)
#define CHK_ACT         (26)
#define CHK_OFF         (27)
#define CHK_IMM         (28)
#define CHK_CARRIES     (29)
#define CHK_WEARS       (30)
#define CHK_HAS         (31)
#define CHK_USES        (32)
#define CHK_NAME        (33)
#define CHK_POS         (34)
#define CHK_CLAN        (35)
#define CHK_RACE        (36)
#define CHK_CLASS       (37)
#define CHK_OBJTYPE     (38)
#define CHK_VNUM        (39)
#define CHK_HPCNT       (40)
#define CHK_ROOM        (41)
#define CHK_SEX         (42)
#define CHK_LEVEL       (43)
#define CHK_ALIGN       (44)
#define CHK_MONEY       (45)
#define CHK_OBJVAL0     (46)
#define CHK_OBJVAL1     (47)
#define CHK_OBJVAL2     (48)
#define CHK_OBJVAL3     (49)
#define CHK_OBJVAL4     (50)
#define CHK_GRPSIZE     (51)
#define CHK_EXACT_NAME  (52)
#define CHK_AREA		(53)
#define CHK_SECTOR		(54)
#define CHK_ROOMFLAG	(55)
#define CHK_ISQUESTER	(56)
#define CHK_HASPKTIMER	(57)
#define CHK_HASTOKEN	(58)
#define CHK_PKKILLS		(59)
#define CHK_PKDEFEATS	(60)
#define CHK_KARNS		(61)
#define CHK_RPS			(62)
#define CHK_DRUNK		(63)
#define CHK_FULL		(64)
#define CHK_THIRST		(65)
#define CHK_HUNGER		(66)
#define CHK_TIRED		(67)
#define CHK_ISCOURT		(68)
#define CHK_ACT2		(69)
#define CHK_VALUE		(70)
#define CHK_CHARHERE    (71)
#define CHK_HASSKILL	(72)
#define CHK_HASTRAINS	(73)
#define CHK_HASPRACS	(74)
#define CHK_HASBONUS	(75)
#define CHK_RACESIZE	(76)
#define CHK_ISEXITOPEN	(77)
#define CHK_ISTHIEF		(78)
#define CHK_ISKILLER	(79)
#define CHK_ISSHEATHED	(80)
#define CHK_ISCONCEALED	(81)
	
 /*
  * These defines correspond to the entries in fn_evals[] table.
  */
#define EVAL_EQ            0
#define EVAL_GE            1
#define EVAL_LE            2
#define EVAL_GT            3
#define EVAL_LT            4
#define EVAL_NE            5

/**************************************************************************/
/*
 * if-check keywords:
 */
struct	fn_keyword_type
{
    const char * name;
	const char * descript;
	const char * syntax;
	const char * notes;
	const char * example;
};

const	struct	fn_keyword_type		fn_keyword[] =
{
    {"rand",		"random number check", "if rand <number>", 
		"Will be true if a random number between 1 and 100 is less than <number>.",
		"if rand 30"},
    {"mobhere",		"check if a mob is in the room by vnum", "if mobhere <mobvnum>",
					"While technically it could be used like 'if mobhere dog' if charhere is better for that, since charhere supports 'if charhere $q' etc."
					"if mobhere 3001"},		
    {"objhere",		"check if an object is in the room by vnum/keyword", "if objhere <keyword|vnum>",
					"", 
					"if objhere 3001`1if objhere bottle"},
    {"mobexists", "check if a mob exists somewhere in the game", "if mobexists <mob>"},
    {"objexists", "check if a object exists somewhere in the game", "if objexists <keyword>"},

    {"people", "use to check for a count of people in the room (mobs + players)", "if people <operation> <number>",
				"'if people > 4' is true when does room contain more than 4 people"},
    {"players", "same as people check, only for players"},
    {"mobs", "same as people check, only for mobs"},
    {"clones", "if clones > 3	- are there > 3 mobs of same vnum here"},
    {"order", "if order == 0	- is mob the first in room"},
    {"hour", "if hour > 11		- is the time > 11 o'clock"},

    {"ispc",		"if ispc $n 		- is $n a pc "},
    {"isnpc",		"if isnpc $n 		- is $n a mobile "},
    {"isgood",		"if isgood $n 	- is $n good "},
    {"isevil",		"if isevil $n 	- is $n evil "},
    {"isneutral",	"if isneutral $n 	- is $n neutral "},
    {"isimmort",	"if isimmort $n	- is $n immortal "},
    {"ischarm",		"if ischarm $n	- is $n charmed "},
    {"isfollow",	"if isfollow $n	- is $n following someone "},
    {"isactive",	"if isactive $n	- is $n's position > SLEEPING ", 
			"useful in mobprogs to wake a mob up if they are asleep`1"
			"              (on triggers that work on asleep mobs)",
			"The following code will wakeup a mob, put this at the top of a prog`1"
			"              (isactive can obviously used on players also)",
			"`1"
			"              if !isactive $i`1"
			"                 mob echoaround $i $I yawns and stretches.`1"
			"                 mq2 wake`1"
			"                 mq5 '>$n Hello there.`1"
			"              else`1"
			"                 '>$n Hello there.`1"
			"              endif"},
    {"isdelay",		"if isdelay $i   - does $i have mobprog pending "},
    {"isvisible",	"if isvisible $n - can mob see $n "},
    {"hastarget",	"if hastarget $i - does $i have a valid target "},
    {"istarget",	"if istarget $n  - is $n mob's target "},
    {"exists",		"if exists $n    - does $n exist somewhere "},

    {"affected",	"if affected $n blind - is $n affected by blind "},
    {"act",			"if act $i sentinel	- is $i flagged sentinel "},
    {"off",			"if off $i berserk	- is $i flagged berserk "},
    {"imm",			"if imm $i fire	- is $i immune to fire "},
    {"carries",		"if carries $n sword	- does $n have a 'sword'`1"
					"if carries $n 1233	- does $n have obj vnum 1233"},
    {"wears",		"if wears $n lantern	- is $n wearing a 'lantern'`1"
					"if wears $n 1233	- is $n wearing obj vnum 1233"},
    {"has",		"if has $n weapon	- does $n have obj of type weapon "},
    {"uses",	"if uses $n armor	- is $n wearing obj of type armor"},
    {"name",	"if name $n puff	- does $n's name contain 'puff', 'if name $o iron' - is the word iron in the objects name."},
    {"pos",		"if pos $n standing	- is $n standing"},
    {"clan",	"if clan $n 'whatever'- does $n belong to clan 'whatever'"},
    {"race",	"if race $n elf	- is $n of 'elf' race"},
    {"class",	"if class $n mage	- is $n's class 'mage'"},
    {"objtype", "if objtype $p scroll	- is $p a scroll"},

    {"vnum",	"if vnum $i == 1233 - virtual number check"},
    {"hpcnt",	"if hpcnt $i > 30	- hit point percent check"},
    {"room",	"if room $i == 1233	- room virtual number"},
    {"sex",		"if sex $i == 0	  - sex check, 0=it, 1=male,2=female"},
    {"level",	"'if level $n < 5' or 'if level $o < 5'	etc - character/object level check"},
    {"align",	"if align $n < -1000	- alignment check"},
    {"money",	"if money $n"},
    {"objval0", "if objval0 $o > 1000 	- object value[] checks 0..4"},
    {"objval1", ""},
    {"objval2", ""},
    {"objval3", ""},
    {"objval4", ""},
    {"grpsize", "if grpsize $n > 6	- group size check"},
    {"exactname", "if exactname $n puff	- is $n's exactname 'puff'"},
	{"area",	"if area $n 'full exact name of area' - check if "
								"$n is in the area which name matches."},
	{"sector",		"if sector $n sectortype  - strprefix checking not exact"},
	{"roomflag",	"if room $n roomflag"},
	{"isquester",	"if quester $n"},
	{"haspktimer",	"if haspktimer $n"},
    {"hastoken",	"if hastoken $n 3087 - is $n carrying 1 or more of token 3087`1"
					"if hastoken $n 3.1233 - is $n carrying 3 or more of token 1233`1"
					"if hastoken $n tokendescr - does $n have a 'tokendescr'"},

	{"pkkills",		"if pkkills $n > 1000 - ch pkkill checks"},
	{"pkdefeats",	"if pkdefeats $n > 1000 - ch pkdefeat checks"},
	{"karns",		"if karns $n > 1000 - ch karn checks"},
	{"rps",			"if rps $n > 1000 - ch rps checks"},

	{"drunk",	"if drunk $n > 10"},
	{"full",	"if full $n > 10"},
	{"thirst",	"if thirst $n > 10"}, 	
	{"hunger",	"if hunger $n > 10"},
	{"tired",	"if tired $n > 10"},	
	{"iscourt", "errr, if is in the court thingie"},
	{"act2",	"act2 flags"},
	{"value", "use this to check room numbers in premove triggers (see mobhelp preventmove)"},
	{"charhere", "if charhere $n"},
	{"hasskill", "if hasskill $n parry > 10"},
	{"hastrains","if hastrains $n > 1 - necessary for mob setskill to ensure enuff trains exist"},
	{"haspracs", "if haspracs $n > 1 - necessary for mob setskill to ensure enuff pracs exist"},
	{"hasbonus", "if hasbonus $n strength > 10 - Strength bonus of $n > 10"},
	{"racesize", "if racesize $n > 1 - checks racial size -- sizes are:`1"
				 "   tiny=0 small=1 medium=2 large=3 huge=4 giant=5"},
	{"isexitopen", "if isexitopen <direction> - returns true if <direction> is an open exit (open door, no door etc)`1"
				 "   <direction> can be a word for the direction or the direction number."},
	{"isthief",	"if isthief $n - true if player is currently flagged as a thief"},
	{"iskiller","if iskiller $n - true if player is currently flagged as a killer"},
	{"issheathed", "if issheathed $n - has weapon sheathed"}, // Ix
	{"isconcealed", "if isconcealed $n - has weapon concealed"}, // Ix
    {"", ""},	// Table terminator 
};

/**************************************************************************/
const char *fn_evals[] =
{
    "==", ">=", "<=", ">", "<", "!=", "\n"
};
/**************************************************************************/
// a command to display all the mobprog if checks
void do_ifhelp( char_data *ch, char *argument )
{
    int cmd;
	// do the checks to make sure they can use the command here
	if (!HAS_SECURITY(ch,1))
	{
		ch->printf("The ifhelp command is an olc mobprog programming related command, "
			"you dont have olc permissions.\r\n");
		return;
	}

    if ( !HAS_SECURITY(ch, MPEDIT_MINSECURITY))
    {
    	ch->printlnf("ifhelp: Insufficient olc security to use this command %d required.", 
			MPEDIT_MINSECURITY);
    	return;
    }
	
	// Return a valid keyword from a keyword table
	if(IS_NULLSTR(argument)){
		// command list syntax
		for ( cmd = 0; !IS_NULLSTR(fn_keyword[cmd].name); cmd++ )
		{
			ch->printlnf("%s%-11s `S- `B%s",
				(IS_NULLSTR(fn_keyword[cmd].notes)?"`W":"`Y"),
				capitalize(fn_keyword[cmd].name),
				IS_NULLSTR(fn_keyword[cmd].descript)?"`Sno descripton":fn_keyword[cmd].descript);
		}
		ch->println("`xTo see more info on a given command, type `=Cifhelp <command>`x");
		ch->println("`xNote: you can put an ! before any keyword to negate its operation (e.g. if !class $n mage)");
		ch->println("`YYellow commands have example code/notes`x");

	}else{
		// specific list syntax
		for ( cmd = 0; !IS_NULLSTR(fn_keyword[cmd].name); cmd++ )
		{
			// filter in only required commands
			if(str_infix(argument,fn_keyword[cmd].name))
				continue;
			ch->printlnf("`W%-11s `S- `B%s\r\n"
				"      `Ssyntax: `Y%s\r\n"
				"      `Snotes:  `g%s\r\n"
				"      `Sexample:`x%s\r\n", 
				capitalize(fn_keyword[cmd].name),
				IS_NULLSTR(fn_keyword[cmd].descript)?"`Sno descripton":fn_keyword[cmd].descript,
				IS_NULLSTR(fn_keyword[cmd].syntax)?"`Sno syntax":fn_keyword[cmd].syntax,
				IS_NULLSTR(fn_keyword[cmd].notes)?"`Sno notes":fn_keyword[cmd].notes,
				IS_NULLSTR(fn_keyword[cmd].example)?"`Sno example":fn_keyword[cmd].example);
		}
	}
}

/**************************************************************************/
// Return a valid keyword from a keyword table
int keyword_lookup( const char **table, char *keyword )
{
    register int i;
    for( i = 0; table[i][0] != '\n'; i++ )
        if( !str_cmp( table[i], keyword ))
            return( i );
	return -1;
}

/**************************************************************************/
// Return a valid keyword from a keyword table
int fn_keyword_lookup( char *keyword )
{
    register int i;
    for( i = 0; !IS_NULLSTR(fn_keyword[i].name); i++ ){
        if( !str_cmp( fn_keyword[i].name, keyword )){
            return( i );
		}
	}
	return -1;
}
/**************************************************************************/
// Perform numeric evaluation.
// Called by cmd_eval()
int num_eval( int lval, int oper, int rval )
{
    switch( oper )
    {
	case EVAL_EQ:
		return ( lval == rval );
	case EVAL_GE:
		return ( lval >= rval );
	case EVAL_LE:
		return ( lval <= rval );
	case EVAL_NE:
		return ( lval != rval );
	case EVAL_GT:
		return ( lval > rval );
	case EVAL_LT:
		return ( lval < rval );
	default:
		bug("num_eval: invalid oper");
		return 0;
    }
}

/**************************************************************************/
/*
 * ---------------------------------------------------------------------
 * UTILITY FUNCTIONS USED BY CMD_EVAL()
 * ----------------------------------------------------------------------
 */

/*
 * Get a random PC in the room (for $r parameter)
 */
char_data *get_random_char( char_data *mob )
{
    char_data *vch, *victim = NULL;
    int now = 0, highest = 0;
    for( vch = mob->in_room->people; vch; vch = vch->next_in_room )
    {
        if ( mob != vch 
			&&   !IS_NPC( vch ) 
			&&   can_see( mob, vch )
			&&   ( now = number_percent() ) > highest )
        {
            victim = vch;
            highest = now;
        }
    }
    return victim;
}
/**************************************************************************/
/* 
 * How many other players / mobs are there in the room
 * iFlag: 0: all, 1: players, 2: mobiles 3: mobs w/ same vnum 4: same group
 */
int count_people_room( char_data *mob, int iFlag )
{
    char_data *vch;
    int count;
    for ( count = 0, vch = mob->in_room->people; vch; vch = vch->next_in_room )
		if ( mob != vch 
			&&   (iFlag == 0
			|| (iFlag == 1 && !IS_NPC( vch )) 
			|| (iFlag == 2 && IS_NPC( vch ))
			|| (iFlag == 3 && IS_NPC( mob ) && mob->vnum() == vch->vnum() )
			|| (iFlag == 4 && is_same_group( mob, vch )) )
			&& can_see( mob, vch ) ) 
			count++;
		return ( count );
}

/**************************************************************************/
/*
 * Get the order of a mob in the room. Useful when several mobs in
 * a room have the same trigger and you want only the first of them
 * to act 
 */
int get_order( char_data *ch )
{
    char_data *vch;
    int i;
	
    if ( !IS_NPC(ch) )
		return 0;
    for ( i = 0, vch = ch->in_room->people; vch; vch = vch->next_in_room )
    {
		if ( vch == ch )
			return i;
		if ( IS_NPC(vch) 
			&&   vch->vnum() == ch->vnum() )
			i++;
    }
    return 0;
}

/**************************************************************************/
/*
 * Check if ch has a given item or item type
 * vnum: item vnum or -1
 * item_type: item type or -1
 * fWear: true: item must be worn, false: don't care
 */
bool has_item( char_data *ch, vn_int vnum, int item_type, bool fWear )
{
    OBJ_DATA *obj;
    for ( obj = ch->carrying; obj; obj = obj->next_content ){	
		if ( ( vnum < 0 || obj->pIndexData->vnum == vnum )
			&&   ( item_type < 0 || obj->pIndexData->item_type == item_type )
			&&   ( !fWear || obj->wear_loc != WEAR_NONE ) )
		{
			// can't find the token with this sytsem
			if(obj->item_type == ITEM_TOKEN){
				mpbug( "trying to match a token using a syntax other than 'if hastoken'.");
				return false;
			}
			return true;
		}
	}
	return false;
}

/**************************************************************************/
// Check if ch has a given token - Kal August 99
//  
// Supports the hastoken trigger... can do things like
// if hastoken $* 3.3005 to see if the player has 3 or more of token 3005
// or also supports using 3.textnameoftoken
bool has_token( char_data *ch, char *argument)
{
	char arg[MIL];
	OBJ_DATA *obj;
	int number;
	int count;
	int tokenvnum;

	number = number_argument( argument, arg );

	count  = 0;

	tokenvnum=0;
	if(is_number(arg)){ 
		tokenvnum=atoi(arg);
	}

	if(tokenvnum){ // token is being referenced by number
		for ( obj = ch->carrying; obj; obj = obj->next_content ) {
			if ( obj->item_type == ITEM_TOKEN
			&&	obj->pIndexData 
			&& 	obj->pIndexData->vnum==tokenvnum) {
				if ( ++count == number )
					return true;
			}
		}
	}else{ // token is being referenced by textname
		for ( obj = ch->carrying; obj; obj = obj->next_content ) {
			if ( obj->item_type == ITEM_TOKEN
			&&	 is_name( arg, obj->name )) {
				if ( ++count == number )
					return true;
			}
		}
	}
	return false;
}

/**************************************************************************/
/*
 * Check if there's a mob with given vnum in the room
 */
bool get_mob_vnum_room( char_data *ch, int vnum )
{
    char_data *mob;
    for ( mob = ch->in_room->people; mob; mob = mob->next_in_room )
		if ( IS_NPC( mob ) && mob->vnum() == vnum )
			return true;
	return false;
}

/**************************************************************************/
/*
 * Check if there's an object with given vnum in the room
 */
bool get_obj_vnum_room( char_data *ch, int vnum )
{
    OBJ_DATA *obj;
    for ( obj = ch->in_room->contents; obj; obj = obj->next_content )
		if ( obj->pIndexData->vnum == vnum )
			return true;
	return false;
}

/**************************************************************************/
/* ---------------------------------------------------------------------
 * CMD_EVAL
 * This monster evaluates an if/or/and statement
 * There are five kinds of statement:
 * 1) keyword  and value (no $-code)			if random 30
 * 2) keyword, comparison and value				if people > 2
 * 3) keyword  and actor						if isnpc $n
 * 4) keyword, actor and value					if carries $n sword
 * 5) keyword, actor, comparison and value		if level $n >= 10
 *----------------------------------------------------------------------
 */
bool cmd_eval( int vnum, char *line, int check,
	char_data *mob, char_data *ch, 
	const void *arg1, const void *arg2, char_data *rch )
{
	char_data *lval_char = mob;
	char_data *vch  = (char_data *) arg2;
	OBJ_DATA  *obj1 = (OBJ_DATA  *) arg1;
	OBJ_DATA  *obj2 = (OBJ_DATA  *) arg2;
	OBJ_DATA  *lval_obj = NULL;

	char *original, buf[MIL], code='\0';
	int lval = 0, oper = 0, rval = -1;
	
	original = line;
	line = one_argument( line, buf );
	if ( buf[0] == '\0' || mob == NULL )
		return false;
	
	/*
	 * If this mobile has no target, let's assume our victim is the one
	 */
	if ( mob->mprog_target == NULL )
		mob->mprog_target = ch;

	// had to put this up here since the hasskill check doesn't comply with
	// any of the 5 cmd_eval types, and making a sixth one would mean a
	// rewrite of #5, and by that time, all the arguments and such have been
	// really destroyed beyond recognition, needing the use of original, and
	// since this is a unique situation, a little mini-hack was the simplest
	// solution, and we all know very well....  K.I.S.S. :)
	//
	// keyword, actor, >keyword2<, comp, value -- if hasskill $n parry > 10
	// <MINIHACK>
	if ( check == CHK_HASSKILL )
	{
		char actor[MIL];
		char buf2[MIL];

		strcpy( actor, buf );

		if ( IS_NULLSTR( line ))
		{
			mpbug("Cmd_eval(): prog %d  if hasskill (null)", vnum );
			return false;
		}
		
		if ( actor[0] != '$' || buf[1] == '\0' )
		{
			mpbug( "Cmd_eval: prog %d syntax error in actor '%s'", vnum, original );
			return false;
		}
		else
			code = buf[1];
		switch( code )
		{
		case 'i':	lval_char = mob;										 break;
		case 'n':	lval_char = ch;											 break;
		case 't':	lval_char = vch;										 break;
		case 'r':	lval_char = rch == NULL ? get_random_char( mob ) : rch ; break;
		case 'q':	lval_char = mob->mprog_target;							 break;
		default:
			mpbug( "Cmd_eval: prog %d syntax error in actor '%s'", vnum, original );
			return false;
		}

		line = one_argument( line, buf );		// buf has skillname
		line = one_argument( line, buf2 );		// buf2 has operator
		
		if (( oper = keyword_lookup( fn_evals, buf2 )) < 0 )
		{
			mpbug( "Cmd_eval: prog %d incorrect operator value '%s'", vnum, original );
			return false;
		}

		rval = atoi( line );

		if ( lval_char != NULL && !IS_NPC( lval_char )) {
			lval = get_skill( lval_char, skill_lookup(buf));
			logf( "lval %d rval %d", lval,rval );
		}
		else
		{
			mpbug("Cmd_eval(): prog %d is buggy '%s'", vnum, original );
			return false;
		}

		if(num_eval( lval, oper, rval )){
			return true;
		}
		return false;
	}
	if ( check == CHK_HASBONUS )
	{
		char actor[MIL];
		char buf2[MIL];

		strcpy( actor, buf );

		if ( IS_NULLSTR( line ))
		{
			mpbug("Cmd_eval(): prog %d  if hasbonus (null)", vnum );
			return false;
		}
		
		if ( actor[0] != '$' || buf[1] == '\0' )
		{
			mpbug( "Cmd_eval: prog %d syntax error in actor '%s'", vnum, original );
			return false;
		}
		else
			code = buf[1];
		switch( code )
		{
		case 'i':	lval_char = mob;										 break;
		case 'n':	lval_char = ch;											 break;
		case 't':	lval_char = vch;										 break;
		case 'r':	lval_char = rch == NULL ? get_random_char( mob ) : rch ; break;
		case 'q':	lval_char = mob->mprog_target;							 break;
		default:
			mpbug( "Cmd_eval: prog %d syntax error in actor '%s'", vnum, original );
			return false;
		}

		line = one_argument( line, buf );		// buf has bonus
		line = one_argument( line, buf2 );		// buf2 has operator
		
		if (( oper = keyword_lookup( fn_evals, buf2 )) < 0 )
		{
			mpbug( "Cmd_eval: prog %d incorrect operator value '%s'", vnum, original );
			return false;
		}

		rval = atoi( line );

		if ( lval_char != NULL && !IS_NPC( lval_char )) {
//			lval = get_skill( lval_char, skill_lookup(buf));
			lval = lval_char->modifiers[stat_lookup(buf)];

			logf( "lval %d rval %d", lval,rval );
		}
		else
		{
			mpbug("Cmd_eval(): prog %d is buggy '%s'", vnum, original );
			return false;
		}

		if( num_eval( lval, oper, rval )){
			return true;
		}
		return false;
	}
	// </MINIHACK>         ps - ker->geekfactor += 1; for using html-like comments :)
	switch ( check )
	{
	/*
	 * Case 1: keyword and value
	 */
	case CHK_RAND:
		{
			int numval;
			
			if(is_number(buf))
			{
				numval=atoi( buf );
			}
			else
			{
				mpbug( "Cmd_eval: prog %d syntax with rand command - '%s ' is not a number!",
					vnum, buf);
				return false;
			}
			
			int numpercent=number_percent();
//			logf("prog=%d, numval=%d, numpercent=%d, returning %s", 
//				vnum, numval, numpercent,	numval>numpercent?"true":"false");	
			return( numval>numpercent);
		}
	case CHK_MOBHERE:
		if ( is_number( buf ))
			return( get_mob_vnum_room( mob, (vn_int)atoi(buf) ) );
		else{
			if(buf[0] == '$'){
				mpbug( "Cmd_eval - mobhere if check: Cant process variable arguments (parameters with $ in them, use 'if charhere $n' etc.");				
				return false;
			}
			return( (bool) (get_char_room( mob, buf) != NULL) );
		}
	case CHK_OBJHERE:
		if ( is_number( buf ) ){
			return( get_obj_vnum_room(mob, atoi(buf)) );
		}else{
			return( (bool) (get_obj_here( mob, buf) != NULL) );
		}
	case CHK_MOBEXISTS:
		return( (bool) (get_char_world( mob, buf) != NULL) );
	case CHK_OBJEXISTS:
		return( (bool) (get_obj_world( mob, buf) != NULL) );

	case CHK_ISEXITOPEN:
		{
			int processed_dir;
			if(IS_NULLSTR(buf)){
				mpbug("Cmd_eval(): prog %d  if isexitopen (null)", vnum );
				return false;
			}
			// calculate the direction
			if(is_number(buf)){
				processed_dir=atoi( buf);
			}else{
				processed_dir=dir_lookup(buf);					
			}
			if(processed_dir<0 || processed_dir>=MAX_DIR){
				mpbug("cmd_eval(): Unrecognised direction '%s' in is isexitopen check - program %d.",
					buf, vnum);
				return false;
			}
			if(mob->in_room 
				&& mob->in_room->exit[processed_dir] 
				&& mob->in_room->exit[processed_dir]->u1.to_room
				&& can_see_room(mob, mob->in_room->exit[processed_dir]->u1.to_room) 
				&& !IS_SET(mob->in_room->exit[processed_dir]->exit_info, EX_CLOSED)){
				return true;		
			}
			return false;
		}
		break;

	case CHK_ISTHIEF:
		return( lval_char != NULL && !IS_NPC(lval_char) && IS_THIEF(lval_char) );

	case CHK_ISKILLER:
		return( lval_char != NULL && !IS_NPC(lval_char) && IS_KILLER(lval_char) );
			

	/*
	 * Case 2 begins here: We sneakily use rval to indicate need
	 * 		       for numeric eval...
	 */
	case CHK_PEOPLE:
		rval = count_people_room( mob, 0 ); break;
	case CHK_PLAYERS:
		rval = count_people_room( mob, 1 ); break;
	case CHK_MOBS:
		rval = count_people_room( mob, 2 ); break;
	case CHK_CLONES:
		rval = count_people_room( mob, 3 ); break;
	case CHK_ORDER:
		rval = get_order( mob ); break;
	case CHK_HOUR:
		rval = time_info.hour; break;
	case CHK_VALUE:
		{
			if(is_number(buf)){
				rval = atoi(buf); 
			}else{
				mpbug( "Cmd_eval - 'value' if check: non numeric input on line '%s'",
					original );
				return false;
			}
		    line = one_argument( line, buf );
		}
		break;
	default:;
    }
	
    /*
	 * Case 2 continued: evaluate expression
	 */
	if ( rval >= 0 )
	{
		if ( (oper = keyword_lookup( fn_evals, buf )) < 0 )
		{
			mpbug( "Cmd_eval: prog %d syntax error(2) '%s'",
				vnum, original );
			return false;
		}
		one_argument( line, buf );
		lval = rval;
		rval = atoi( buf );
		if( num_eval( lval, oper, rval )){
			return true;
		}
		return false;

	}
	
	/*
	 * Case 3,4,5: Grab actors from $* codes
	 */
	if ( buf[0] != '$' || buf[1] == '\0' )
    {
		mpbug( "Cmd_eval: prog %d syntax error(3) '%s'",
			vnum, original );
        return false;
    }
    else
        code = buf[1];
    switch( code )
    {
	case 'i':
		lval_char = mob; break;
	case 'n':
		lval_char = ch; break;
	case 't':
		lval_char = vch; break;
	case 'r':
		lval_char = rch == NULL ? get_random_char( mob ) : rch ; break;
	case 'o':
		lval_obj = obj1; break;
	case 'p':
		lval_obj = obj2; break;
	case 'q':
		lval_char = mob->mprog_target; break;
	default:
		mpbug( "Cmd_eval: prog %d syntax error(4) '%s'",
			vnum, original );
		return false;
	}
    /*
	 * From now on, we need an actor, so if none was found, bail out
	 */
    if ( lval_char == NULL && lval_obj == NULL )
		return false;
	
	/*
	 * Case 3: Keyword, comparison and value
	 */
    switch( check )
    {
	case CHK_ISPC:
		return( lval_char != NULL && !IS_NPC( lval_char ) );
	case CHK_ISNPC:
		return( lval_char != NULL && IS_NPC( lval_char ) );
	case CHK_ISGOOD:
		return( lval_char != NULL && IS_GOOD( lval_char ) );
	case CHK_ISEVIL:
		return( lval_char != NULL && IS_EVIL( lval_char ) );
	case CHK_ISNEUTRAL:
		return( lval_char != NULL && IS_NEUTRAL( lval_char ) );
	case CHK_ISIMMORT:
		return( lval_char != NULL && IS_IMMORTAL( lval_char ) );
		
	case CHK_ISCOURT:
		// using IS_SET to check for the court flag since when switched it 
		// shouldn't get the court status of the controling player, but the mob
		return( lval_char != NULL && !IS_NPC(lval_char) && IS_COURT(lval_char) );
		
	case CHK_ISQUESTER:
		return( lval_char != NULL && !IS_NPC(lval_char) && IS_SET(lval_char->act, PLR_QUESTER) );
		
	case CHK_HASPKTIMER:
		return( lval_char != NULL && !IS_NPC(lval_char) 
			&& (UMAX(lval_char->pknoquit,lval_char->pknorecall)>0) );		
		
	case CHK_ISCHARM: /* A relic from MERC 2.2 MOBprograms */
		return( lval_char != NULL && IS_AFFECTED( lval_char, AFF_CHARM ) );
	case CHK_ISFOLLOW:
		return( lval_char != NULL && lval_char->master != NULL 
			&& lval_char->master->in_room == lval_char->in_room );
	case CHK_ISACTIVE:
		return( lval_char != NULL && lval_char->position > POS_SLEEPING );
	case CHK_ISDELAY:
		return( lval_char != NULL && lval_char->mprog_delay > 0 );

	case CHK_CHARHERE:
		return( lval_char != NULL && lval_char->in_room == mob->in_room);

	case CHK_ISVISIBLE:
		switch( code )
		{
		default :
		case 'i':
		case 'n':
		case 't':
		case 'r':
		case 'q':
			return( lval_char != NULL && can_see( mob, lval_char ) );
		case 'o':
		case 'p':
			return( lval_obj != NULL && can_see_obj( mob, lval_obj ) );
		}
		case CHK_HASTARGET:
			return( lval_char != NULL && lval_char->mprog_target != NULL
				&&  lval_char->in_room == lval_char->mprog_target->in_room );
		case CHK_ISTARGET:
			return( lval_char != NULL && mob->mprog_target == lval_char );

		case CHK_ISSHEATHED: 
			{
				if(lval_char == NULL){
					return false;
				}
				if ( get_eq_char( lval_char, WEAR_SHEATHED ) != NULL ){
					return true;
				}
				return false;
			}

		case CHK_ISCONCEALED: 
			{
				if(lval_char == NULL){
					return false;
				}
				if ( get_eq_char( lval_char, WEAR_CONCEALED ) != NULL ){
					return true;
				}
				return false;
			}

		default:;
	}
	
	/* 
	 * Case 4: Keyword, actor and value
	 */
	line = one_argument( line, buf );
	switch( check )
	{
	case CHK_AFFECTED:
		{
			if(!lval_char){
				mpbug("affected check, lval==NULL prob target of check wasn't found.");
				return false;
			}
			if(NO_FLAG!=flag_lookup(buf, affect_flags))
			{
				if(IS_AFFECTED( lval_char, flag_lookup(buf, affect_flags))){
					return true;
				}			
			}
			int sn=skill_lookup( buf);
			if(sn<0){
				mpbug("affected check for '%s', '%s' wasn't found.", buf, buf);
				return false;
			}
			if(is_affected( lval_char, sn)){
				return true;
			}
			return false;
			//			return( lval_char != NULL 
			//			&&  IS_SET(lval_char->affected_by, flag_lookup(buf, affect_flags)) );
		}
	case CHK_ACT:
		return( lval_char != NULL 
			&&  IS_SET(lval_char->act, flag_lookup(buf, act_flags)) );
	case CHK_ACT2:
		return( lval_char != NULL 
			&&  IS_SET(lval_char->act2, flag_lookup(buf, act2_flags)) );
	case CHK_IMM:
		return( lval_char != NULL 
			&&  IS_SET(lval_char->imm_flags, flag_lookup(buf, imm_flags)) );
	case CHK_OFF:
		return( lval_char != NULL 
			&&  IS_SET(lval_char->off_flags, flag_lookup(buf, off_flags)) );
	case CHK_CARRIES:
		if ( is_number( buf ) )
			return( lval_char != NULL && has_item( lval_char, (vn_int)atoi(buf), -1, false ) );
		else
			return( lval_char != NULL && (get_obj_carry( lval_char, buf ) != NULL) );
		
	case CHK_HASTOKEN:
		return( lval_char != NULL && has_token( lval_char, buf) );
		
	case CHK_WEARS:
		if ( is_number( buf ) )
			return( lval_char != NULL && has_item( lval_char, (vn_int)atoi(buf), -1, true ) );
		else
			return( lval_char != NULL && (get_obj_wear( lval_char, buf ) != NULL) );
	case CHK_HAS:
		return( lval_char != NULL && has_item( lval_char, -1, (short) item_lookup(buf), false ) );
	case CHK_USES:
		return( lval_char != NULL && has_item( lval_char, -1, (short) item_lookup(buf), true ));
	case CHK_NAME:
		switch( code )
		{
		default :
		case 'i':
		case 'n':
		case 't':
		case 'r':
		case 'q':
			return( lval_char != NULL && is_name( buf, lval_char->name ) );
		case 'o':
		case 'p':
			return( lval_obj != NULL && is_name( buf, lval_obj->name ) );
		}
		case CHK_EXACT_NAME:
            switch( code )
            {
			default :
			case 'i':
			case 'n':
			case 't':
			case 'r':
			case 'q':
				return( lval_char != NULL && is_exact_name( buf, lval_char->name ) );
			case 'o':
			case 'p':
				return( lval_obj != NULL && is_exact_name( buf, lval_obj->name ) );
			}
			
			case CHK_POS:
				return( lval_char != NULL && lval_char->position == position_lookup( buf ) );
			case CHK_CLAN:
				return( lval_char != NULL && lval_char->clan == clan_nlookup( buf ) );
			case CHK_RACE:
				{
					if (race_lookup( buf )==-1 )
						return( lval_char != NULL && lval_char->race == 0 );
					else
						return( lval_char != NULL && lval_char->race == race_lookup( buf ) );
				}
			case CHK_CLASS:
				return( lval_char != NULL && lval_char->clss == class_lookup( buf ) );
			case CHK_OBJTYPE:
				return( lval_obj != NULL && lval_obj->item_type == item_lookup( buf ) );
			case CHK_AREA:
				//				return( lval_obj != NULL && lval_char->in_room 
				return( lval_char->in_room 		
					&& lval_char->in_room->area==area_lookup( buf ));
			case CHK_SECTOR:
				return( lval_char != NULL && lval_char->in_room->sector_type == sector_lookup( buf ));
			case CHK_ROOMFLAG:
				return( lval_char != NULL
					&& IS_SET( lval_char->in_room->room_flags, flag_lookup( buf, room_flags )));
			default:;
    }
	
    /*
	 * Case 5: Keyword, actor, comparison and value
	 */
    if ( (oper = keyword_lookup( fn_evals, buf )) < 0 )
    {
		mpbug( "Cmd_eval: prog %d syntax error(5): '%s'", vnum, original );
		return false;
    }
    one_argument( line, buf );
    rval = atoi( buf );
	
    switch( check )
    {
	case CHK_VNUM:
		switch( code )
		{
		default :
		case 'i':
		case 'n':
		case 't':
		case 'r':
		case 'q':
			if( lval_char != NULL && IS_NPC( lval_char ) )
				lval = lval_char->vnum();
			break;
		case 'o':
		case 'p':
			if ( lval_obj != NULL )
				lval = lval_obj->pIndexData->vnum;
		}
		break;
		
	case CHK_HPCNT:
		if ( lval_char != NULL ) lval = (lval_char->hit * 100)/(UMAX(1,lval_char->max_hit)); break;
	case CHK_ROOM:
		if ( lval_char != NULL && lval_char->in_room != NULL )
			lval = lval_char->in_room->vnum; break;
	case CHK_SEX:
		if ( lval_char != NULL ) lval = lval_char->sex; break;
	case CHK_LEVEL:
		switch( code )
		{
		default :
		case 'i':
		case 'n':
		case 't':
		case 'r':
		case 'q':
			if ( lval_char != NULL ) lval = lval_char->level; 
			break;
		case 'o':
		case 'p':
			if ( lval_obj != NULL ) lval = lval_obj->level;
		}
		break;

	case CHK_ALIGN:
		if ( lval_char != NULL ) lval = lval_char->alliance; break;
	case CHK_MONEY:  /* Money is converted to silver... */
		if ( lval_char != NULL ) 
			lval = lval_char->gold + (lval_char->silver * 100); break;
	case CHK_OBJVAL0:
		if ( lval_obj != NULL ) lval = lval_obj->value[0]; break;
	case CHK_OBJVAL1:
		if ( lval_obj != NULL ) lval = lval_obj->value[1]; break;
	case CHK_OBJVAL2: 
		if ( lval_obj != NULL ) lval = lval_obj->value[2]; break;
	case CHK_OBJVAL3:
		if ( lval_obj != NULL ) lval = lval_obj->value[3]; break;
	case CHK_OBJVAL4:
		if ( lval_obj != NULL ) lval = lval_obj->value[4]; break;
	case CHK_GRPSIZE:
		if( lval_char != NULL ) lval = count_people_room( lval_char, 4 ); break;
	case CHK_PKKILLS:
		if ( lval_char != NULL ) lval = lval_char->pkkills; break;
	case CHK_PKDEFEATS:
		if ( lval_char != NULL ) lval = lval_char->pkdefeats; break;
	case CHK_KARNS:
		if ( lval_char != NULL ) 
			lval = IS_NPC(lval_char)?0:lval_char->pcdata->karns; break;
	case CHK_RPS:
		if ( lval_char != NULL ) 
			lval = IS_NPC(lval_char)?0:lval_char->pcdata->rp_points; break;
	case CHK_DRUNK:
		if ( lval_char != NULL ) 
			lval = IS_NPC(lval_char)?0:lval_char->pcdata->condition[COND_DRUNK];
		break;
	case CHK_FULL:
		if ( lval_char != NULL ) 
			lval = IS_NPC(lval_char)?0:lval_char->pcdata->condition[COND_FULL];
		break;
	case CHK_THIRST:
		if ( lval_char != NULL )
			lval = IS_NPC(lval_char)?0:lval_char->pcdata->condition[COND_THIRST];
		break;
	case CHK_HUNGER:
		if ( lval_char != NULL )
			lval = IS_NPC(lval_char)?0:lval_char->pcdata->condition[COND_HUNGER];
		break;
	case CHK_TIRED:
		if ( lval_char != NULL )
			lval = IS_NPC(lval_char)?0:lval_char->pcdata->tired;
		break;
	case CHK_HASTRAINS:
		if ( lval_char != NULL )
			lval = lval_char->train;
		break;
	case CHK_HASPRACS:
		if ( lval_char != NULL )
			lval = lval_char->practice;
		break;
	case CHK_RACESIZE:
		if ( lval_char != NULL )
			lval = IS_NPC(lval_char)?0:race_table[lval_char->race]->size;
		break;
	default:
		return false;
	}
	if( num_eval( lval, oper, rval )){
		return true;
	}
	return false;

}
/**************************************************************************/
/*
 * ------------------------------------------------------------------------
 * EXPAND_ARG
 * This is a hack of act() in comm.c. I've added some safety guards,
 * so that missing or invalid $-codes do not crash the server
 * ------------------------------------------------------------------------
 */

 void expand_arg(
	 char *buf, const char *format, char_data *mob, char_data *ch, 
	 const void *arg1, const void *arg2, char_data *rch )
 {
	 const char *someone = "someone";
	 const char *something = "something";
	 const char *someones = "someone's";
	 
	 char fname[MIL];
	 char_data *vch = (char_data *) arg2;
	 OBJ_DATA *obj1 = (OBJ_DATA  *) arg1;
	 OBJ_DATA *obj2 = (OBJ_DATA  *) arg2;
	 const char *str;
	 const char *i;
	 char *point;
	 
	 /*
     * Discard null and zero-length messages.
     */
	 if ( format == NULL || format[0] == '\0' )
		 return;
	 
	 point   = buf;
	 str     = format;
	 while ( *str != '\0' )
	 {
		 if ( *str != '$' )
		 {
			 *point++ = *str++;
			 continue;
		 }
		 ++str;
		 
		 switch ( *str )
		 {
		 default:  bugf( "Expand_arg: bad code %d (%c).", *str, *str);
			 i = " <@@@> ";
			 break;
		 case 'i':
			 one_argument( mob->name, fname );
			 i = fname;
			 break;
		 case 'I': 
			 i = mob->short_descr;
			 break;
		 case 'n': 
			 i = someone;
			 if ( ch != NULL && can_see( mob, ch ) )
			 {
            	    one_argument( ch->name, fname );
					i = capitalize(fname);
			 }
			 break;
		 case 'N': 
			 i = (ch != NULL && can_see( mob, ch ) )
				 ? (ch->short_descr): someone;
			 break;
			 
			//	i = (ch != NULL && can_see( mob, ch ) )
			//	? ( IS_NPC( ch ) ? ch->short_descr : ch->name )
			//	: someone;
			//	break;
			 
		 case 't': 
			 i = someone;
			 if ( vch != NULL && can_see( mob, vch ) )
			 {
				 one_argument( vch->name, fname );
				 i = capitalize(fname);
			 }
			 break;
		 case 'T': 
			 i = (vch != NULL && can_see( mob, vch ))
				 ? ( IS_NPC( vch ) ? vch->short_descr : vch->name )
				 : someone;
			 break;
		 case 'r': 
			 if ( rch == NULL ) 
				 rch = get_random_char( mob );
			 i = someone;
			 if( rch != NULL && can_see( mob, rch ) )
			 {
				 one_argument( rch->name, fname );
				 i = capitalize(fname);
			 }
			 break;
		 case 'R': 
			 if ( rch == NULL ) 
				 rch = get_random_char( mob );
			 i  = ( rch != NULL && can_see( mob, rch ) )
				 ?rch->short_descr:someone;
			 break;
		 case 'q':
			 i = someone;
			 if ( mob->mprog_target != NULL && can_see( mob, mob->mprog_target ) )
			 {
				 one_argument( mob->mprog_target->name, fname );
				 i = capitalize( fname );
			 }
			 break;
		 case 'Q':
			 i = (mob->mprog_target != NULL && can_see( mob, mob->mprog_target ))
				 ? ( IS_NPC( mob->mprog_target ) ? mob->mprog_target->short_descr : mob->mprog_target->name )
				 : someone;
			 break;
		 case 'j':
			 i = he_she  [URANGE(0, mob->sex, 2)];
			 break;
		 case 'e': 
			 i = (ch != NULL && can_see( mob, ch ))
				 ? he_she  [URANGE(0, ch->sex, 2)]        
				 : someone;
			 break;
		 case 'E': 
			 i = (vch != NULL && can_see( mob, vch ))
				 ? he_she  [URANGE(0, vch->sex, 2)]        
				 : someone;
			 break;
		 case 'J': 
			 i = (rch != NULL && can_see( mob, rch ))
				 ? he_she  [URANGE(0, rch->sex, 2)]        
				 : someone;
			 break;
		 case 'X':
			 i = (mob->mprog_target != NULL && can_see( mob, mob->mprog_target))
				 ? he_she  [URANGE(0, mob->mprog_target->sex, 2)]
				 : someone;
			 break;
		 case 'k':
			 i = him_her [URANGE(0, mob->sex, 2)];
			 break;
		 case 'm': 
			 i = (ch != NULL && can_see( mob, ch ))
				 ? him_her [URANGE(0, ch  ->sex, 2)]
				 : someone;
			 break;
		 case 'M': 
			 i = (vch != NULL && can_see( mob, vch ))
				 ? him_her [URANGE(0, vch ->sex, 2)]        
				 : someone;
			 break;
		 case 'K':
			 if ( rch == NULL ) 
				 rch = get_random_char( mob );
			 i = (rch != NULL && can_see( mob, rch ))
				 ? him_her [URANGE(0, rch ->sex, 2)]
				 : someone;
			 break;
		 case 'Y': 
			 i = (mob->mprog_target != NULL && can_see( mob, mob->mprog_target ))
				 ? him_her [URANGE(0, mob->mprog_target->sex, 2)]        
				 : someone;
			 break;
		 case 'l':
			 i = his_her [URANGE(0, mob ->sex, 2)];
			 break;
		 case 's': 
			 i = (ch != NULL && can_see( mob, ch ))
				 ? his_her [URANGE(0, ch ->sex, 2)]
				 : someones;
			 break;
		 case 'S': 
			 i = (vch != NULL && can_see( mob, vch ))
				 ? his_her [URANGE(0, vch ->sex, 2)]
				 : someones;
			 break;
		 case 'L': 
			 if ( rch == NULL ) 
				 rch = get_random_char( mob );
			 i = ( rch != NULL && can_see( mob, rch ) )
				 ? his_her [URANGE(0, rch ->sex, 2)]
				 : someones;
			 break;
		 case 'Z': 
			 i = (mob->mprog_target != NULL && can_see( mob, mob->mprog_target ))
				 ? his_her [URANGE(0, mob->mprog_target->sex, 2)]
				 : someones;
			 break;
		 case 'o':
			 i = something;
			 if ( obj1 != NULL && can_see_obj( mob, obj1 ) )
			 {
            	    one_argument( obj1->name, fname );
                    i = fname;
			 }
			 break;
		 case 'O':
			 i = (obj1 != NULL && can_see_obj( mob, obj1 ))
				 ? obj1->short_descr
				 : something;
			 break;
		 case 'p':
			 i = something;
			 if ( obj2 != NULL && can_see_obj( mob, obj2 ) )
			 {
            	    one_argument( obj2->name, fname );
					i = fname;
			 }
			 break;
		 case 'P':
			 i = (obj2 != NULL && can_see_obj( mob, obj2 ))
				? obj2->short_descr
				: something;
			 break;

		 case 'V': // saved room
			 {
				 static char room_vnum_text[10];
				 sprintf(room_vnum_text,"%d", mob->mprog_remember_room_vnum);
				 i = room_vnum_text;
			 }
			 break;
        }
		
        ++str;
        while ( ( *point = *i ) != '\0' )
            ++point, ++i;
		
    }
    *point = '\0';
	
    return;
}    
/**************************************************************************/
char * mprog_type_to_name ( int type );
/**************************************************************************/
// used for mptrace command
#define MAX_MPTRACE	500 // Number of mobprogs to trace the calls of
int mptrace_current;
int mptrace_calledby[MAX_MPTRACE];
vn_int mptrace_pvnum[MAX_MPTRACE];
vn_int mptrace_mvnum[MAX_MPTRACE];
vn_int mptrace_rvnum[MAX_MPTRACE];
unsigned char mptrace_calllevel[MAX_MPTRACE];
time_t mptrace_time[MAX_MPTRACE];
bool mptrace_aborted[MAX_MPTRACE];
/**************************************************************************/
void do_mptrace(char_data *ch, char *argument )
{
	char arg1[MIL];
	BUFFER *output;
	int lines_to_show;

	if (!HAS_SECURITY(ch,1))
	{
		ch->println("The mptrace command is an olc command, you dont have olc permissions.");
		return;
	}

	if (!HAS_SECURITY(ch,7))
	{
		ch->println("You must have an OLC security of 7 or higher to use mptrace.");
		return;
	}

    argument = one_argument( argument, arg1 );
	if (IS_NULLSTR(arg1))
	{
		if(ch->lines){
			lines_to_show=ch->lines-2;
		}else{
			lines_to_show=PAGELEN-2; // default page length 
		}
		lines_to_show%=MAX_MPTRACE;
	}
	else if(is_number( arg1 ))
	{
	    lines_to_show= atoi(arg1);	
		if (lines_to_show<1)
		{
			ch->println("Lines to show, increased to 1");
			lines_to_show=1;
		}
		else if(lines_to_show>MAX_MPTRACE)
		{
			ch->printlnf("Lines to show, decreased to the number logged in the trace - %d",
				MAX_MPTRACE);
			lines_to_show=MAX_MPTRACE;
		}		
	}
	else
	{
		ch->printf("`RThe only parameter for mptrace must be a numeric value\r\n"
			"for the number of lines of the trace you wish to see.`x\r\n");
		return;
	}
	ch->printlnf("Number of trace lines to show=%d", lines_to_show);

	output = new_buf();

	add_buf(output,"`xMPTrace `G#mobprog`x, `Ymobvnum`x, `Binroom`x\r\n");
	
	// generate our lines of info
	int line=mptrace_current+MAX_MPTRACE-lines_to_show;
	int count;
	int i;
	int lc=0;
	char buf[MIL];
	for( count=0; count<lines_to_show; count++)
	{
		++line%=MAX_MPTRACE;

		if(mptrace_time[line]==0)
			continue;

		// put the line count and time per line
	    char * tbuf = ctime( &mptrace_time[line] );
		tbuf[str_len(tbuf)-6] = '\0';
		sprintf(buf,"[%3d]%s  ",++lc, (char *)&tbuf[11]);
		add_buf( output, buf);

		// indent to represent nested calling
		for(i=0; i<mptrace_calllevel[line]; i++){
			add_buf( output, "        ");
		}

		sprintf(buf,"`G%5d`x,`Y%5d`x,`B%5d`x, %s\r\n", 
			mptrace_pvnum[line],
			mptrace_mvnum[line],
			mptrace_rvnum[line],
			mprog_type_to_name(mptrace_calledby[line]));
		add_buf( output, buf);
	}
    ch->sendpage(buf_string(output));
    free_buf(output);
}

/**************************************************************************/
DECLARE_DO_FUN( do_mpdzecho		);  // Kal
void mpbuggy_prog(MPROG_LIST *program, char * fmt, ...); 
/**************************************************************************/
/*
 * ------------------------------------------------------------------------
 *  PROGRAM_FLOW
 *  This is the program driver. It parses the mob program code lines
 *  and passes "executable" commands to interpret()
 *  Lines beginning with 'mob' are passed to mob_interpret() to handle
 *  special mob commands (in mob_cmds.c)
 *-------------------------------------------------------------------------
 */
#define MAX_NESTED_LEVEL 12 /* Maximum nested if-else-endif's (stack size) */
#define BEGIN_BLOCK       0 /* Flag: Begin of if-else-endif block */
#define IN_BLOCK         -1 /* Flag: Executable statements */
#define END_BLOCK        -2 /* Flag: End of if-else-endif block */

void program_flow( MPROG_LIST *program,
	char_data *mob, char_data *ch, const void *arg1, const void *arg2 )
{
	char *source=program->prog->code;
	static bool init_mptrace=true;

	REMOVE_BIT(mob->dyn,DYN_MOB_SEE_ALL);
	REMOVE_BIT(mob->dyn,DYN_RUNNING_MOBPROG_CMD);

	if(init_mptrace){
		memset(&mptrace_time[0], 0, sizeof(time_t));
		init_mptrace=false;
	}

	if(!program){
		bug("program_flow(): program==NULL!!!");
		mpbug("program_flow(): program==NULL!!!");
		return;
	}
    vn_int pvnum=program->prog->vnum;

    char_data *rch = NULL;
    char *code, *line;
    char buf[MSL];
    char control[MIL], data[MSL];
	
	
	// moved to be a global
	//    static int call_level; /* Keep track of nested "mpcall"s */
	
	static time_t last_wiznet;
	static int count_since_wiznet;
	
    int level, check;
	bool eval;
    int state[MAX_NESTED_LEVEL], /* Block state (BEGIN,IN,END) */
		cond[MAX_NESTED_LEVEL];  /* Boolean value based on the last if-check */
	
	// moved to be globals
	// callstack system written by Kalahn - april 98
	//	static int callstack_pvnum[MAX_NESTED_LEVEL];
	//	static int callstack_mvnum[MAX_NESTED_LEVEL];

	// if the mob isn't considered valid, don't run progs on it.
	if(!mob || !IS_VALID(mob)){
		logf("Mob prog %d running on mob vnum %d - which is no longer valid!", pvnum, mob->vnum());
		if(mob){
			REMOVE_BIT(mob->dyn,DYN_MOB_SEE_ALL);
		}
		return;
	}
	
    vn_int mvnum = mob->vnum();
	
	callstack_pvnum[call_level]=pvnum;
	callstack_mvnum[call_level]=mvnum;
	callstack_rvnum[call_level]=mob->in_room->vnum;
	callstack_aborted[call_level]= true; // assumed abort till completed
	callstack_line[call_level]=0;

	++mptrace_current%=MAX_MPTRACE;
	mptrace_calledby[mptrace_current]=program->trig_type;
	mptrace_pvnum[mptrace_current]=pvnum;
	mptrace_mvnum[mptrace_current]=mvnum;
	mptrace_rvnum[mptrace_current]=mob->in_room->vnum;
	mptrace_time[mptrace_current]=current_time;
	mptrace_calllevel[mptrace_current]=call_level;

    if( ++call_level > MAX_CALL_LEVEL )
    {
		char buf[MSL];
		int i;
		
		//bug( "MOBprogs: MAX_CALL_LEVEL exceeded, vnum %d", mob->vnum() );
		sprintf(buf, "MOBprogs BUG: MAX_CALL_LEVEL exceeded, running prog %d on mob %d", 
			pvnum, mvnum );
		log_string(buf);
		mpbug(buf);
		
		// display the call stack
		log_string("Mobprog callstack:\n");
		for (i=0; i<MAX_CALL_LEVEL;i++)
		{
			sprintf(buf, "[%2d] MOBprog %d on mob %d (in room %d)", i,
				callstack_pvnum[i], callstack_mvnum[i], callstack_rvnum[i]);
			log_string(buf);
		}
		
		// dont log the bug on wiznet more than once every 5 minutes
		if (last_wiznet<current_time-300)
		{
			mpbug(buf);
			// put it on the bug wiznet channel
			wiznet(buf,NULL,NULL,WIZ_BUGS,0,AVATAR); 
			sprintf(buf, "WIZNET_BUGS: In the last 5 mins the bug has happened %d time%s.\r\n", 
				count_since_wiznet, (count_since_wiznet==1?"":"s") );
			wiznet(buf,NULL,NULL,WIZ_BUGS,0,AVATAR); 
			
			wiznet("Mobprog callstack:\r\n",NULL,NULL,WIZ_BUGS,0,AVATAR); // put it on the bug wiznet channel
			
			// display the call stack
			for (i=0; i<MAX_CALL_LEVEL;i++)
			{
				sprintf(buf, "[%2d] MOBprog %d on mob %d (in room %d)", i,
					callstack_pvnum[i], callstack_mvnum[i], callstack_rvnum[i]);
				wiznet(buf,NULL,NULL,WIZ_BUGS,0,AVATAR); 
			}
			wiznet("Use `=Cmpinfo`x to see the info on mobprogs at anytime.\r\n"
				"and `=Cmpreset`x to reset the mobprogs (normally once the offending prog is fixed).\r\n",
				NULL,NULL,WIZ_BUGS,0,AVATAR); 
			last_wiznet = current_time;
			count_since_wiznet=0;
		}else{
			count_since_wiznet++;
		}
		REMOVE_BIT(mob->dyn,DYN_MOB_SEE_ALL);
		return;
    }

	// check if a prog is disabled
	if(program->prog->disabled)
	{
		char bufmsg[MSL];
		if(ch){
			sprintf(bufmsg,"Prog %d triggered (%s) on me by '%s', not run cause prog is disabled.",
				program->prog->vnum, mprog_type_to_name(program->trig_type), ch->name);
		}else{
			sprintf(bufmsg,"Prog %d triggered (%s), not run cause prog is disabled.",
				program->prog->vnum, mprog_type_to_name(program->trig_type));
		}
		do_mpdzecho(mob, bufmsg);
		call_level--;
		callstack_aborted[call_level]= false; 
		REMOVE_BIT(mob->dyn,DYN_MOB_SEE_ALL);
		return;
	}
	
    /*
	* Reset "stack"
	*/
    for ( level = 0; level < MAX_NESTED_LEVEL; level++ )
    {
		state[level] = IN_BLOCK;
        cond[level]  = true;
    }
    level = 0;
	
    code = source;
	SET_BIT(mob->dyn,DYN_RUNNING_MOBPROG_CMD);
    /*
	* Parse the MOBprog code
	*/
    while ( *code && IS_VALID(mob))
    {
		bool first_arg = true;
		char *b = buf, *c = control, *d = data;
		/*
		* Get a command line. We sneakily get both the control word
		* (if/and/or) and the rest of the line in one pass.
		*/
		while( is_space( *code ) && *code ) {
			if(*code == '\n'){
				callstack_line[call_level-1]++;
			}
			code++;
		}

		while ( *code )
		{
			if ( *code == '\r' || *code == '\n'){
				break; // end of line - dont count the lines here... count them above
			}
			else if ( is_space(*code) )
			{
				if ( first_arg ){
					first_arg = false;
				}else{
					*d++ = *code;
				}
			}
			else
			{
				if ( first_arg ){
					*c++ = *code;
				}else{
					*d++ = *code;
				}
			}
			*b++ = *code++;
		}
		*b = *c = *d = '\0';
		
		if ( buf[0] == '\0' ){ // end of the source code
			break;
		}
		if ( buf[0] == '*' ){ // Comment - skip to end of line
			continue;
		}
		
        line = data;
		// Match control words
		if ( !str_cmp( control, "if" ) )
		{
			if ( state[level] == BEGIN_BLOCK )
			{
				mpbuggy_prog(program, "Mobprog: misplaced if statement, mob %d prog %d",
					mvnum, pvnum );
				REMOVE_BIT(mob->dyn,DYN_MOB_SEE_ALL);
				REMOVE_BIT(mob->dyn,DYN_RUNNING_MOBPROG_CMD);
				return;
			}
			state[level] = BEGIN_BLOCK;
            if ( ++level >= MAX_NESTED_LEVEL )
            {
				mpbuggy_prog(program, "Mobprog: Max nested level exceeded, mob %d prog %d",
					mvnum, pvnum );
				REMOVE_BIT(mob->dyn,DYN_MOB_SEE_ALL);
				REMOVE_BIT(mob->dyn,DYN_RUNNING_MOBPROG_CMD);
				return;
			}
			if ( level && cond[level-1] == false ) 
			{
				cond[level] = false;
				continue;
			}
			line = one_argument( line, control );

			bool negate=false;	
			char *pControl=control;
			if(*pControl=='!'){
				negate=true;
				pControl++; // skip over the !
			}

			if ( ( check = fn_keyword_lookup( pControl ) ) >= 0 )
			{
				if(negate){
					cond[level] = !cmd_eval( pvnum, line, check, mob, ch, arg1, arg2, rch );
				}else{
					cond[level] = cmd_eval( pvnum, line, check, mob, ch, arg1, arg2, rch );
				}
			}
			else
			{
				mpbuggy_prog(program, "Mobprog: invalid if_check (if) '%s', mob %d prog %d", pControl, mvnum, pvnum );
				REMOVE_BIT(mob->dyn,DYN_MOB_SEE_ALL);		
				REMOVE_BIT(mob->dyn,DYN_RUNNING_MOBPROG_CMD);
				return;
			}
			state[level] = END_BLOCK;
		}
		else if ( !str_cmp( control, "or" ) )
		{
			if ( !level || state[level-1] != BEGIN_BLOCK )
			{
				mpbuggy_prog(program, "Mobprog: or without if, mob %d prog %d", mvnum, pvnum );
				REMOVE_BIT(mob->dyn,DYN_MOB_SEE_ALL);		
				REMOVE_BIT(mob->dyn,DYN_RUNNING_MOBPROG_CMD);
				return;
			}
			if ( level && cond[level-1] == false ){
				continue;	// if the parent nest is false, no point in processing the or
			}

			line = one_argument( line, control );
			bool negate=false;	
			char *pControl=control;
			if(*pControl=='!'){
				negate=true;
				pControl++; // skip over the !
			}

			if ( ( check = fn_keyword_lookup( pControl ) ) >= 0 )
			{
				eval = cmd_eval( pvnum, line, check, mob, ch, arg1, arg2, rch );
				if(negate){
					eval=!eval;
				}
			}
			else
            {
				mpbuggy_prog(program, "Mobprog: invalid if_check (or), mob %d prog %d", mvnum, pvnum );
				REMOVE_BIT(mob->dyn,DYN_MOB_SEE_ALL);		
				REMOVE_BIT(mob->dyn,DYN_RUNNING_MOBPROG_CMD);
				return;
            }
            cond[level] = ((bool)eval == true) ? true : cond[level];
		}
		else if ( !str_cmp( control, "and" ) )
		{
			if ( !level || state[level-1] != BEGIN_BLOCK )
			{
				mpbuggy_prog(program, "Mobprog: and without if, mob %d prog %d", mvnum, pvnum );
				REMOVE_BIT(mob->dyn,DYN_MOB_SEE_ALL);		
				REMOVE_BIT(mob->dyn,DYN_RUNNING_MOBPROG_CMD);
				return;
			}
			if ( level && cond[level-1] == false ) {
				continue; // if the parent nest is false, no point in processing the or
			}

			line = one_argument( line, control );

			bool negate=false;	
			char *pControl=control;
			if(*pControl=='!'){
				negate=true;
				pControl++; // skip over the !
			}

			if ( ( check = fn_keyword_lookup( pControl ) ) >= 0 )
			{
				eval = cmd_eval( pvnum, line, check, mob, ch, arg1, arg2, rch );
				if(negate){
					eval=!eval;
				}
			}
			else
			{
				mpbuggy_prog(program, "Mobprog: invalid if_check (and), mob %d prog %d", mvnum, pvnum );
				REMOVE_BIT(mob->dyn,DYN_MOB_SEE_ALL);		
				REMOVE_BIT(mob->dyn,DYN_RUNNING_MOBPROG_CMD);
				return;
			}
			if(!(cond[level] && eval)){
				cond[level]=false;
			}
		}
		else if ( !str_cmp( control, "endif" ) )
		{
			if ( !level || state[level-1] != BEGIN_BLOCK )
			{
				mpbuggy_prog(program, "Mobprog: endif without if, mob %d prog %d",	mvnum, pvnum );
				REMOVE_BIT(mob->dyn,DYN_MOB_SEE_ALL);		
				REMOVE_BIT(mob->dyn,DYN_RUNNING_MOBPROG_CMD);
				return;
			}
			cond[level] = true;
			state[level] = IN_BLOCK;
            state[--level] = END_BLOCK;
        }
		else if ( !str_cmp( control, "else" ) )
		{
			if ( !level || state[level-1] != BEGIN_BLOCK )
			{
				mpbuggy_prog(program, "Mobprog: else without if, mob %d prog %d", mvnum, pvnum );
				REMOVE_BIT(mob->dyn,DYN_MOB_SEE_ALL);		
				REMOVE_BIT(mob->dyn,DYN_RUNNING_MOBPROG_CMD);
				return;
			}
			if ( level && cond[level-1] == false ) continue;
            state[level] = IN_BLOCK;
			if(cond[level]){
				cond[level]=false; 
			}else{
				cond[level]=true; 
			}
        }
		else if ( cond[level] && ( !str_cmp( control, "break" ) || !str_cmp( control, "end" ) ) )
		{
			call_level--;
			callstack_aborted[call_level]= false; 
			REMOVE_BIT(mob->dyn,DYN_MOB_SEE_ALL);		
			REMOVE_BIT(mob->dyn,DYN_RUNNING_MOBPROG_CMD);
            return;
		}
		else if ( (!level || cond[level]) && buf[0] != '\0' )
		{
			state[level] = IN_BLOCK;
            expand_arg( data, buf, mob, ch, arg1, arg2, rch );
			if ( !str_cmp( control, "mob" ) ){
				// Found a mob restricted command, pass it to mob interpreter
				line = one_argument( data, control );
				mob_interpret( mob, line );
			}else{
				//Found a normal mud command, pass it to interpreter
				interpret( mob, data );
			}
		}
    }
    call_level--;
	callstack_aborted[call_level]= false; 
	REMOVE_BIT(mob->dyn,DYN_MOB_SEE_ALL);
}
/**************************************************************************/
// global place to disable a trigger running due to something about a 
// player, something about the trigger or something about the mobprog
// e.g. quester only triggers etc
// true means run the program
bool run_trigger_by_on( MPROG_LIST *prg, char_data *mob, char_data *ch)
{
	// doesn't bother with prg yet - may do one day

	if(!ch) {
		return true;
	}
	assert(IS_NPC(mob));	

	// do position checks 
	if(prg && prg->pos_flags){
		if(!IS_SET(prg->pos_flags, (1<<mob->position))){
			return false;
		}
	}

	if(!IS_NPC(ch))
	{
		if(IS_SET(mob->act, ACT_MPIGN_QUESTER) && IS_QUESTER(ch))
			return false;

		if(IS_SET(mob->act, ACT_MPIGN_NONQUESTER) && !IS_QUESTER(ch))
			return false;
	}

	return true;
}

/**************************************************************************/
/* 
 * ---------------------------------------------------------------------
 * Trigger handlers. These are called from various parts of the code
 * when an event is triggered.
 * ---------------------------------------------------------------------
 */

/*
 * A general purpose string trigger. Matches argument to a string trigger
 * phrase.
 */
void mp_act_trigger( 
	char *argument, char_data *mob, char_data *ch, 
	const void *arg1, const void *arg2, int type )
{
	MPROG_LIST *prg;
	
	// check for flags on mob and player that might prevent prog being run
	if (!run_trigger_by_on( NULL, mob, ch)) return;

	for ( prg = mob->pIndexData->mprogs; prg != NULL; prg = prg->next )
	{
		if ( prg->trig_type == type 
		&& (!str_infix(prg->trig_phrase, argument) 
		|| !str_cmp(prg->trig_phrase, "*always_trigger*")))
		{
			program_flow( prg, mob, ch, arg1, arg2 );
			break;
		}
	}
	return;
}
/**************************************************************************/
bool mp_cmd_trigger( 
	char *argument, char_data *mob, char_data *ch, 
	const void *arg1, const void *arg2, int type )
{
	// check for flags on mob and player that might prevent prog being run
	if (!run_trigger_by_on( NULL, mob, ch)) return false;

	MPROG_LIST *prg;
	bool cmdfound = false;

	for ( prg = mob->pIndexData->mprogs; prg != NULL; prg = prg->next )
	{
		if ( prg->trig_type == type 
		&& !str_cmp(prg->trig_phrase, argument))
		{
			program_flow( prg, mob, ch, arg1, arg2 );
			cmdfound = true;
			break;
		}
	}
	return cmdfound;
}
/**************************************************************************/
/*
 * A general purpose percentage trigger. Checks if a random percentage
 * number is less than trigger phrase
 */
bool mp_percent_trigger( 
	char_data *mob, char_data *ch, 
	const void *arg1, const void *arg2, int type )
{
	// check for flags on mob and player that might prevent prog being run
	if (!run_trigger_by_on( NULL, mob, ch)) return false;

    MPROG_LIST *prg;

    for ( prg = mob->pIndexData->mprogs; prg != NULL; prg = prg->next )
    {
    	if ( prg->trig_type == type 
	&&   number_percent() < atoi( prg->trig_phrase ) )
        {
	    program_flow( prg, mob, ch, arg1, arg2 );
	    return ( true );
	}
    }
    return ( false );
}
/**************************************************************************/
void mp_bribe_trigger( char_data *mob, char_data *ch, int amount )
{
	// check for flags on mob and player that might prevent prog being run
	if (!run_trigger_by_on( NULL, mob, ch)) return;
  
	MPROG_LIST *prg;
	MPROG_LIST *highprog=NULL;
	int highest=-1;

	// first find the most expensive bribe trigger we gave
	// the money for - if there are multiple progs
    for ( prg = mob->pIndexData->mprogs; prg; prg = prg->next )
    {
		if ( prg->trig_type == TRIG_BRIBE
			&& amount >= atoi( prg->trig_phrase ))
		{
			if(highest< atoi( prg->trig_phrase)){
				highest=atoi( prg->trig_phrase);
				highprog=prg;
			}
		}
	}

	// run the highest prog if one was found
	if(highprog){		
		program_flow( highprog, mob, ch, NULL, NULL );
	}
    return;
}
/**************************************************************************/
void mp_hour_trigger( char_data *ch )
{
	MPROG_LIST	*prg;
	int			hour;

	for ( prg = ch->pIndexData->mprogs; prg; prg = prg->next )
	{
		hour = time_info.hour;
		if ( prg->trig_type == TRIG_HOUR
		&& ( hour %= 24 ) == atoi( prg->trig_phrase ))
		{
			program_flow( prg, ch, NULL, NULL, NULL );
			return;
		}
	}
	return;
}
/**************************************************************************/
bool mp_exit_trigger_no_letpass=true;
/**************************************************************************/
bool mp_exit_trigger( char_data *ch, int dir )
{
    char_data *mob;
    MPROG_LIST   *prg;
	int processed_dir=0;

	// exit triggers dont work on wizi LEVEL_IMMORTAL+ imms
	if (INVIS_LEVEL(ch)>=LEVEL_IMMORTAL 
		&& IS_IMMORTAL(ch)){
		return false;
	}

	mp_exit_trigger_no_letpass=true; // use mob letpass to set this to false
	mob = ch->in_room->people;
	int number_in_room=ch->in_room->number_in_room;
	// note: number_in_room is used to prevent a mobprog on two mobs in 
	// a room creating an endless loop by removing themself from
	// the room and putting themselves back in the room - Kal, June 01
    for ( ; mob && --number_in_room>=0; mob = mob->next_in_room )
    {    
		if ( IS_NPC( mob )
			&&   ( HAS_TRIGGER(mob, TRIG_EXIT) || HAS_TRIGGER(mob, TRIG_EXALL) ) )
		{
			// check for flags on mob and player that might prevent prog being run
			if (!run_trigger_by_on( NULL, mob, ch)) continue;

			for ( prg = mob->pIndexData->mprogs; prg; prg = prg->next )
			{
			/*
			* Exit trigger works only if the mobile is not busy
			* (fighting etc.). If you want to be sure all players
			* are caught, use ExAll trigger
				*/
				// check for an all direction trigger
				if ( prg->trig_type == TRIG_EXIT || prg->trig_type == TRIG_EXALL)
				{ 
					if(is_exact_name("all", prg->trig_phrase)){
						if ( prg->trig_type == TRIG_EXIT
							&&  mob->position == mob->pIndexData->default_pos
							&&  can_see( mob, ch ) )
						{
							program_flow( prg, mob, ch, NULL, NULL );
							return mp_exit_trigger_no_letpass;
						}else{
							if ( prg->trig_type == TRIG_EXALL)
							{
								program_flow( prg, mob, ch, NULL, NULL );
								return mp_exit_trigger_no_letpass;
							}
						}
					}
				}

				// figure out the direction
				if ( prg->trig_type == TRIG_EXIT || prg->trig_type == TRIG_EXALL)
				{
					if(is_number(prg->trig_phrase)){
						processed_dir=atoi( prg->trig_phrase );
					}else{
						processed_dir=dir_lookup(prg->trig_phrase);					
					}

					if(processed_dir<0 || processed_dir>=MAX_DIR){
						mpbug("exit_trigger: direction phrase '%s' isn't a valid direction, must be direction number or text version (n,s,ne, southwest,... etc)",
							prg->trig_phrase);
						continue;
					}
				}

				if ( prg->trig_type == TRIG_EXIT
					&&  dir == processed_dir
					&&  mob->position == mob->pIndexData->default_pos
					&&  can_see( mob, ch ) )
				{
					program_flow( prg, mob, ch, NULL, NULL );
					return mp_exit_trigger_no_letpass;
				}else{
					if ( prg->trig_type == TRIG_EXALL
						&&   dir == processed_dir )
					{
						program_flow( prg, mob, ch, NULL, NULL );
						return mp_exit_trigger_no_letpass;
					}
				}
			}
		}
    }
    return false;
}
/**************************************************************************/
// returns true if the trigger would normally be run
bool mp_would_run_give_trigger( char_data *mob, char_data *ch, OBJ_DATA *obj )
{
	// check for flags on mob and player that might prevent prog being run
	if (!run_trigger_by_on( NULL, mob, ch)) return false;
	
    char buf[MIL], *p;
    MPROG_LIST  *prg;
    for ( prg = mob->pIndexData->mprogs; prg; prg = prg->next ){
		if ( prg->trig_type == TRIG_GIVE ){
			p = prg->trig_phrase;
			if ( is_number( p ) ){
				// Vnum argument
				if ( obj->pIndexData->vnum == atoi(p) ){
					return true;
				}
			}else{
				// Object name argument, e.g. 'sword'
				while( *p ){
					p = one_argument( p, buf );
					
					if ( is_name( buf, obj->name )
						||   !str_cmp( "all", buf ) )
					{
						return true;
					}
				}
			}
		}
	}
	return false;
}
/**************************************************************************/
void mp_give_trigger( char_data *mob, char_data *ch, OBJ_DATA *obj )
{
	// check for flags on mob and player that might prevent prog being run
	if (!run_trigger_by_on( NULL, mob, ch)) return;
	
    char        buf[MIL], *p;
    MPROG_LIST  *prg;
	
    for ( prg = mob->pIndexData->mprogs; prg; prg = prg->next ){
		if ( prg->trig_type == TRIG_GIVE )
		{
			p = prg->trig_phrase;
			/*
			* Vnum argument
			*/
			if ( is_number( p ) )
			{
				if ( obj->pIndexData->vnum == atoi(p) )
				{
					program_flow(prg, mob, ch, (void *) obj, NULL);
					return;
				}
			}
			/*
			* Object name argument, e.g. 'sword'
			*/
			else
			{
				while( *p )
				{
					p = one_argument( p, buf );
					
					if ( is_name( buf, obj->name )
						||   !str_cmp( "all", buf ) )
					{
						program_flow(prg, mob, ch, (void *) obj, NULL);
						return;
					}
				}
			}
		}
	}
}
/**************************************************************************/
void mp_greet_trigger( char_data *ch )
{
    char_data *mob;

	// greet triggers dont work on wizi LEVEL_IMMORTAL+ imms
	if (INVIS_LEVEL(ch)>=LEVEL_IMMORTAL 
		&& IS_IMMORTAL(ch)){
		return;
	}

	mob = ch->in_room->people;
	int number_in_room=ch->in_room->number_in_room;
	// note: number_in_room is used to prevent a mobprog on two mobs in 
	// a room creating an endless loop by removing themself from
	// the room and putting themselves back in the room - Kal, June 01
    for ( ; mob && --number_in_room>=0; mob = mob->next_in_room )
    {   		
		if ( IS_NPC( mob ))
		{
			// check for flags on mob and player that might prevent prog being run
			if (!run_trigger_by_on( NULL, mob, ch)) continue;

			// Greet trigger works only if the mobile is in their 
			// default position and can see the character
			// If you want to catch all players, use a GrAll trigger
			if ( HAS_TRIGGER( mob,TRIG_GREET )
				&&   mob->position == mob->pIndexData->default_pos
				&&   can_see( mob, ch ) 
				&&   !IS_AFFECTED(ch, AFF_SNEAK) )
			{
				mp_percent_trigger( mob, ch, NULL, NULL, TRIG_GREET );
			}

			// GrAll triggers will greet all players except
			// wizi LEVEL_IMMORTAL+ immortals
			if ( HAS_TRIGGER( mob, TRIG_GRALL )){
				mp_percent_trigger( mob, ch, NULL, NULL, TRIG_GRALL );
			}
		}
    }
    return;
}
/**************************************************************************/
void mp_hprct_trigger( char_data *mob, char_data *ch )
{
	// check for flags on mob and player that might prevent prog being run
	if (!run_trigger_by_on( NULL, mob, ch)) return;

    MPROG_LIST *prg;

    for ( prg = mob->pIndexData->mprogs; prg != NULL; prg = prg->next )
	if ( ( prg->trig_type == TRIG_HPCNT )
	&& ( (100 * mob->hit / mob->max_hit) < atoi( prg->trig_phrase ) ) )
	{
	    program_flow( prg, mob, ch, NULL, NULL );
	    break;
	}
}
/**************************************************************************/
void do_mpreset( char_data *ch, char *)
{
	int i;

	if (!HAS_SECURITY(ch,1))
	{
		ch->println("The mpreset command is an olc command, you dont have olc permissions.");
		return;
	}

	if (!HAS_SECURITY(ch,7))
	{
		ch->println("You must have an OLC security of 7 or higher to use mpreset.");
		return;
	}


	ch->println("Mobprogs reseted.  (mobprog callstack depth to 0)");
	for (i=0; i<MAX_CALL_LEVEL;i++)
	{
		callstack_pvnum[i]=0;
		callstack_mvnum[i]=0;
		callstack_rvnum[i]=0;
		callstack_aborted[i]=false;
	}
	call_level=0;

	mpbug( "%s reset the mobprog callstack", TRUE_CH(ch)->name);	
    return;
}
/**************************************************************************/
void do_mpinfo( char_data *ch, char *argument)
{
    char buf[MSL], buf2[MSL];
    char arg1 [MIL];
	int i;

    BUFFER *output;

	if (!HAS_SECURITY(ch,1))
	{
		ch->println("The mpinfo command is an olc command, you dont have olc permissions.");
		return;
	}

	if (!HAS_SECURITY(ch,7))
	{
		ch->println("You must have an OLC security of 7 or higher to use mpinfo.");
		return;
	}


    output= new_buf();

	sprintf( buf,"`?`#%s`x", makef_titlebar("MOBPROGS INFO"));
	add_buf(output,buf);
	
	sprintf(buf,"    Displaying mobprog call abort history - calllevel currently is %d\r\n", call_level);
	add_buf(output,buf);
	
	// display the call stack
	for (i=0; i<MAX_CALL_LEVEL;i++)
	{
		if (callstack_aborted[i])
		{
			sprintf(buf, "      [%2d] MOBprog %d on mob %d (in room %d) `RABORTED!!! (BUGGY?)`X\r\n", i,
				callstack_pvnum[i], callstack_mvnum[i], callstack_rvnum[i]);
			add_buf(output,buf);
		}
		else
		{
			sprintf(buf, "      [%2d] MOBprog %d on mob %d (in room %d)\r\n", i,
				callstack_pvnum[i], callstack_mvnum[i], callstack_rvnum[i]);
			add_buf(output,buf);
		}
	}

	add_buf(output,"`xMPBUG `G#mobprog`x, `Ymobvnum`x, `Binroom`x,`rline`x,`Ccall_level`x:   `WNOTE: see also `=CMPTRACE`x \r\n");

	{ // loop thru finding all disabled progs
		MPROG_CODE *prg;
		char msgbuf[MSL];

		for( prg = mprog_list; prg; prg = prg->next )
		{
			if ( prg->disabled){
				sprintf(msgbuf,"`SMobprog %d is disabled with the following text:`x.\r\n",
					prg->vnum);
				add_buf(output,msgbuf);
				add_buf(output,prg->disabled_text);
				add_buf(output,"`x");			
			}			
		}
	}    


	// now display the specified number of lines of the log file.
    argument = one_argument( argument, arg1 );
	if (IS_NULLSTR(arg1))
	{
		sprintf(buf, "tail " MPBUG_FILE " -n 10");
		add_buf(output, "\r\n    You can select the number of loglines, type mpinfo <number of lines>");
	
	}
	else if (is_number ( arg1 ))
	{
	    int value = atoi(arg1);	
		if (value<1 || value>20000)
		{
			add_buf(output,"\r\n`RNumber of lines to tail must be between 1 and 20000.`x\r\n");
			sprintf(buf, "tail " MPBUG_FILE " -n 10");
		}
		else
		{
			sprintf(buf, "tail " MPBUG_FILE " -n %d", value);
		}	
	}
	else
	{
		add_buf(output, "\r\n`RThe only parameter for this command must be a "
			"numeric value\r\nfor the number of lines of the error "
			"log you wish to see.`x\r\n");
		sprintf(buf, "tail " MPBUG_FILE " -n 10");
	}

	sprintf( buf2,"\r\n`^%s`x", makef_titlebar("Piping:`x %s", buf));
	add_buf(output,buf2);

	add_buf(output,get_piperesult(buf));

	ch->sendpage(buf_string(output));
	free_buf(output);

    return;
}
/**************************************************************************/
bool mp_prekill_trigger_result;
/**************************************************************************/
// return true if our triggers stops them
bool mp_prekill_trigger( char_data *mob, char_data *attacker)
{
	// check for flags on mob and player that might prevent prog being run
	if (!run_trigger_by_on( NULL, mob, attacker)) return false;

	mp_prekill_trigger_result=false;

    MPROG_LIST  *prg;

    for ( prg = mob->pIndexData->mprogs; prg; prg = prg->next )
    {
		if ( prg->trig_type == TRIG_PREKILL 
			&&   number_percent() < atoi( prg->trig_phrase ) )
        {
			program_flow( prg, mob, attacker, NULL, NULL);
			return mp_prekill_trigger_result;
		}
    }
    return false;
}
/**************************************************************************/
bool mp_premove_trigger_allowmove=true;
int active_premove_trigger_count=0; // dont allow more than 3
/**************************************************************************/
// return true if they can move - default
bool mp_premove_trigger( char_data *mob, int vnum, int dir)
{
	if(active_premove_trigger_count>3){
		mpbug("premove_trigger: active_premove_trigger_count is %d - "
			"greater than 3, trigger ignored.", active_premove_trigger_count);
		return true;
	}
	active_premove_trigger_count++;
	mp_premove_trigger_allowmove=true; // default to allowing movement

	// record the vnum of where the mob is moving to
	mob->mprog_remember_room_vnum=vnum;
	
	int processed_dir;
	MPROG_LIST  *prg;

	for ( prg = mob->pIndexData->mprogs; prg; prg = prg->next )
	{
		// check for an all direction trigger
		if ( prg->trig_type == TRIG_PREMOVE)
		{ 
			if(is_exact_name("all", prg->trig_phrase)){
				program_flow( prg, mob, mob, NULL, NULL );
				active_premove_trigger_count--;
				return mp_premove_trigger_allowmove;
			}

			// calculate the direction
			if(is_number(prg->trig_phrase)){
				processed_dir=atoi( prg->trig_phrase );
			}else{
				processed_dir=dir_lookup(prg->trig_phrase);					
			}
			if(processed_dir<0 || processed_dir>=MAX_DIR){
				mpbug("premove_trigger: direction phrase '%s' isn't a "
					"valid direction, must be direction number or "
					"text version (n,s,ne, southwest,... etc)",
					prg->trig_phrase);
				continue;
			}
			if(processed_dir==dir){
				program_flow( prg, mob, mob, NULL, NULL );
				active_premove_trigger_count--;
				return mp_premove_trigger_allowmove;
			}
		}
	}
	active_premove_trigger_count--;
    return true;
}
/**************************************************************************/
bool mp_login_trigger_processed=false;
/**************************************************************************/
// mp_login_trigger runs two triggers, loginroom, loginarea.
// if a mob called 'mob loginprocessed' then no more triggers on mobs 
// for this login will be run.
// loginroom triggers are run before loginarea
// ch = character logging in, ran just after the player has been placed in the room
void mp_login_trigger( char_data *ch)
{
	mp_login_trigger_processed=false; // default to no mob processed the login

	if(IS_NPC(ch)) return; // dont trigger on pet logins

	assertp(ch->in_room);
	assertp(ch->in_room->area);

	//loop thru all mobs in the room first - TRIG_LOGINROOM
	MPROG_LIST  *prg;
	char_data *mob, *mob_next_in_room;
	for(mob=ch->in_room->people; mob; mob=mob_next_in_room){
		mob_next_in_room=mob->next_in_room;
		
		// ** reasons for the prog not to run etc
		if(!IS_NPC(mob)) continue;
		// check for flags on mob and player that might prevent prog being run 
		if (!run_trigger_by_on( NULL, mob, ch)) continue;

		for ( prg = mob->pIndexData->mprogs; prg; prg = prg->next )
		{
			if ( prg->trig_type != TRIG_LOGINROOM 
				|| number_percent()>atoi( prg->trig_phrase)){
				continue;
			}

			program_flow( prg, mob, ch, NULL, NULL);

			if(mp_login_trigger_processed){
				return; // a mobprog marked the login as processed
			}
		}
	}

	//loop thru all mobs in the area - TRIG_LOGINAREA
	char_data *mob_next;
	for(mob=char_list; mob; mob=mob_next){
		mob_next=mob->next;
		
		// ** reasons for the prog not to run etc
		if(!IS_NPC(mob)) continue;
		if(!mob->in_room || mob->in_room->area!=ch->in_room->area) continue;
		// check for flags on mob and player that might prevent prog being run 
		if (!run_trigger_by_on( NULL, mob, ch)) continue;

		for ( prg = mob->pIndexData->mprogs; prg; prg = prg->next )
		{
			if ( prg->trig_type != TRIG_LOGINAREA
				|| number_percent()>atoi( prg->trig_phrase)){
				continue;
			}

			program_flow( prg, mob, ch, NULL, NULL);

			if(mp_login_trigger_processed){
				return; // a mobprog marked the login as processed
			}
		}
	}
}
/**************************************************************************/
bool mp_logout_trigger_processed=false;
/**************************************************************************/
// mp_logout_trigger runs two triggers, logoutroom, logoutarea.
// if a mob called 'mob logoutprocessed' then no more triggers on mobs 
// for this logout will be run.
// logoutroom triggers are run before logoutarea
// ch = character logging out, is run just before any of the logout checks - like pknoquit etc
void mp_logout_trigger( char_data *ch)
{
	mp_logout_trigger_processed=false; // default to no mob processed the logout

	if(IS_NPC(ch)) return; // dont trigger on pet logout

	assertp(ch->in_room);
	assertp(ch->in_room->area);

	//loop thru all mobs in the room first - TRIG_LOGOUTROOM
	MPROG_LIST  *prg;
	char_data *mob, *mob_next_in_room;
	for(mob=ch->in_room->people; mob; mob=mob_next_in_room){
		mob_next_in_room=mob->next_in_room;
		
		// ** reasons for the prog not to run etc
		if(!IS_NPC(mob)) continue;
		// check for flags on mob and player that might prevent prog being run 
		if (!run_trigger_by_on( NULL, mob, ch)) continue;

		for ( prg = mob->pIndexData->mprogs; prg; prg = prg->next )
		{
			if ( prg->trig_type != TRIG_LOGOUTROOM 
				|| number_percent()>atoi( prg->trig_phrase)){
				continue;
			}

			program_flow( prg, mob, ch, NULL, NULL);

			if(mp_logout_trigger_processed){
				return; // a mobprog marked the logout as processed
			}
		}
	}

	//loop thru all mobs in the area - TRIG_LOGOUTAREA
	char_data *mob_next;
	for(mob=char_list; mob; mob=mob_next){
		mob_next=mob->next;
		
		// ** reasons for the prog not to run etc
		if(!IS_NPC(mob)) continue;
		if(!mob->in_room || mob->in_room->area!=ch->in_room->area) continue;
		// check for flags on mob and player that might prevent prog being run 
		if (!run_trigger_by_on( NULL, mob, ch)) continue;

		for ( prg = mob->pIndexData->mprogs; prg; prg = prg->next )
		{
			if ( prg->trig_type != TRIG_LOGOUTAREA
				|| number_percent()>atoi( prg->trig_phrase)){
				continue;
			}

			program_flow( prg, mob, ch, NULL, NULL);

			if(mp_logout_trigger_processed){
				return; // a mobprog marked the logout as processed
			}
		}
	}
}
/**************************************************************************/
/**************************************************************************/