From: hbrda@dec001.hou281.chevron.com (b.r.daniels DEC)
Newsgroups: rec.games.mud.lp
Subject: Re: RANDOM MOVING AND CHASING
Date: 24 Jul 92 17:16:50 GMT
Organization: Chevron

Here's a version of a follower that I wrote.  It's fairly generic,
and can either be made to always follow, or only have a chance of following.
Let me know if you like it.  (Also, I'm looking for a new home for my
area after some problems with a significant difference in philosophy as
to what should happen on my last MUD, 5th Dimension, so if you like my
code and need a wiz, let me know...)

- Brad (daniels@bigred.enet.dec.com - "Larry" on 5th Dimension)

---- Cut here --- follower.c ----
/* follower.c - written by Brad Daniels (daniels@bigred.enet.dec.com)
   AKA the wizard "Larry", originally on 5th Dimension.
   This file implements a monster which will follow a specific player
   around.  If the player is not around, it wanders randomly, and will
   eventually die of loneliness.  It Inherits from monster.c, and defines
   the following new functions:

	set_guide(ob)		Takes the object to follow.  Object must
				be a player.
	set_shadow_chance(c)	Sets the percentage chance that the follower
				will duplicate any arbitrary action of the
				player.  This object clones /obj/soul, so
				a non-zero shadow chance will mean the
				follower may smile when the guide does, etc.
	set_delay_chance(c)	By default, the follower will always follow
				its guide immediately, so it won't get lost.
				Set the delay chance to non zero to make it
				possible to out-run a follower.
	set_max_delay(d)	Sets the maximum delay in seconds for fol-
				lowing when delay_chance is non-zero.
	set_retaliate_chance(c) Sets the chance that a follower will retaliate
				when someone takes aggressive action against
				the guide.  If porting to a new mud, you will
				need to update the aggressive_verbs array to
				reflect all the aggressive verbs in the soul.
	set_follower(s)		Takes string of the form:
				  follower name:guide name:shadow chance:\
				  delay chance:max delay
				... And sets the values appropriately.
*/

inherit "/obj/monster";

#define MAX_LOST 240
object guide;
string guidename,*aggressive_verbs;
int processing,shadow_chance,waiting,lost_time,delay_chance,max_delay;
int retaliate_chance;
int have_soul;
string normarrive,normleave;
string searcharrive,searchleave;

set_guide(ob) {
    guide = ob;
    guidename = ob->query_real_name();
}

set_shadow_chance(c) { shadow_chance=c; }

set_delay_chance(c) { delay_chance=c; }

set_max_delay(d) { max_delay=d; }

set_retaliate_chance(c) { retaliate_chance=c; }

set_follower(str) {
string n;
    if (!str ||
	(sscanf(str,"%s:%s:%d:%d:%d",
	    n,guidename,shadow_chance,delay_chance,max_delay) != 5)) {
	write("arg should be: name:guidename:shadow %:delay %:max delay\n");
	return;
    }
    set_name(n);
    guide = find_player(guidename);
}

reset(arg) {
    ::reset(arg);
    if (arg) return;

    add_action("up","u");
    add_action("down","d");
    add_action("north", "n");
    add_action("south", "s");
    add_action("east", "e");
    add_action("west", "w");
    add_action("southeast","se");
    add_action("southwest","sw");
    add_action("northeast","ne");
    add_action("northwest","nw");

    aggressive_verbs = ({
	    "curse", "jeer", "kick", "pinch", "puke", "punch", "slap", "spank",
	    "spit", "steamroller", "tackle", "taunt", "tickle", "trip"
	});
}

init() {
object ob;
    ::init();
    if (!guidename) return;
    ob = find_living(guidename);
    if (ob != guide) {
	guide = ob;
	if (ob)
	    write(cap_name+" looks up happily, now that "+ob->query_name()+
		" has rejoined the game.\n");
	else
	    write(cap_name+" looks depressed, now that "+capitalize(guidename)+" is gone.\n");
    }
    add_action("maybe_follow","",1);
    if (!have_soul && (shadow_chance || retaliate_chance)) {
	move_object(clone_object("/obj/soul"),this_object());
	have_soul = 1;
    }
}

maybe_follow(str) {
string cmd,n;
int r;
object t;
    if (processing) return 0;
    if (str)
	t = present(str,environment(this_player()));
    if ((this_player() != guide) && (t && !t->id(guidename))) return 0;
    cmd = query_verb();
    if (this_player() != guide) {
	if (random(100)<retaliate_chance) {
	int i;
	    for (i=0;i<sizeof(aggressive_verbs);i++) {
		if (cmd == aggressive_verbs[i]) {
		    n = this_player()->query_real_name();
		    if (!n)
			n = lower_case(this_player()->query_name());
		    call_out("do_command",1,cmd+" "+n);
		}
	    }
	}
	return 0;
    }
    if (str && (str != ""))
	cmd += " "+str;
    processing = 1;
    r = command(cmd,guide);
    processing = 0;
    if (waiting || !objectp(guide)) {
	while(remove_call_out("search") != -1);
	if (!objectp(guide) || (environment(guide) != environment()))
	    call_out("search",5);
	return 1;
    }
    if (r && environment() && (environment(guide) != environment())) {
	if (normarrive)
	    msgin = normarrive;
	else
	    msgin = "arrives following "+capitalize(guidename);
	if (normleave)
	    msgout = normleave;
	else
	    msgout = "follows "+capitalize(guidename);
	if (delay_chance) {
	    while (remove_call_out("delayed_move")!=-1);
	    if (random(100) < delay_chance) {
		call_out("delayed_move",random(max_delay)+1,cmd);
		processing = 0;
		return 1;
	    }
	}
	command(cmd);
	while(remove_call_out("search") != -1);
	if (environment(guide) != environment())
	    call_out("search",5);
	else
	    lost_time = 0;
    } else if (random(100)<shadow_chance)
	call_out("do_command",random(3)+1,cmd);
    return 1;
}

delayed_move(cmd) {
    if (!guide || (environment() == environment(guide)))
	return;
    processing = 1;
    command(cmd);
    processing = 0;
    while(remove_call_out("search") != -1);
    if (environment(guide) != environment())
	call_out("search",5);
    else
	lost_time = 0;
}

search() {
    if (!waiting && (guide && (environment() != environment(guide)))) {
	if (searcharrive)
	    msgin = searcharrive;
	else
	    msgin = "arrives in search of "+capitalize(guidename);
	if (searchleave)
	    msgout = searchleave;
	else
	    msgout = "\b, searching for "+capitalize(guidename)+", leaves";
	while(remove_call_out("search") != -1);
	random_move();
	call_out("search",5);
    } else if (guide && !waiting) {
	say(cap_name+" looks happy.\n");
	lost_time = -1;
    }
    if (++lost_time > MAX_LOST) {
	say(cap_name+" dies of loneliness.\n");
	hit_player(hit_point+1000);
    }
}

do_command(cmd) {
    processing = 1;
    command(cmd);
    processing = 0;
}

up() { return command("up"); }
down() { return command("down"); }
south() { return command("south"); }
north() { return command("north"); }
east() { return command("east"); }
west() { return command("west"); }
northeast() { return command("northeast"); }
southeast() { return command("southeast"); }
northwest() { return command("northwest"); }
southwest() { return command("southwest"); }
-- 
Brad Daniels			| daniels@bigred.enet.dec.com
"Chevron's DEC whipping boy"	| I wouldn't speak for DEC unless
Digital Equipment Corp.		| they asked me, and they won't ask me.