/
bin/95/
docs/DM/
docs/creatures/
docs/objects/
docs/rooms/
docs/utils/
help/
log/
objmon/ddesc/
player/
post/
rooms/
util/
util/fing/
util/italk/
util/lev-reset/
util/lib/
util/list/
util/msg/
util/tdel/
util/vote/
util/wtfix/
/*
 * COMMAND4.C:
 *
 *	Additional user routines
 *
 *	Copyright (C) 1991, 1992, 1993, 1997 Brooke Paul & Brett Vickers
 *
 */

#include "mstruct.h"
#include "mextern.h"

#ifndef WIN32
        #include <sys/time.h>
#else
        #include <time.h>
#endif

#include <string.h>
#ifdef DMALLOC
  #include "/usr/local/include/dmalloc.h"
#endif

/**********************************************************************/
/*				health				      */
/**********************************************************************/

/* This function shows a player his current hit points, magic points,  */
/* experience, gold and level.					       */

int health(ply_ptr, cmnd)
creature	*ply_ptr;
cmd		*cmnd;
{
	int	fd;

	fd = ply_ptr->fd;
	
	if(F_ISSET(ply_ptr, PBLIND)){
		ANSI(fd, RED);
		ANSI(fd, BOLD);
		ANSI(fd, BLINK);
		print(fd, "You're obviously blind!\n");
		ANSI(fd, NORMAL);
		ANSI(fd, WHITE);
		return(0);
	}
	ANSI(fd, WHITE);
	print(fd, "%s the %s (level %d)", ply_ptr->name, title_ply(ply_ptr), ply_ptr->level);
	if(F_ISSET(ply_ptr, PHIDDN)) {
		ANSI(fd, CYAN);
		print(fd, " *Hidden*");
		ANSI(fd, WHITE);
	}
	if(F_ISSET(ply_ptr, PNSUSN)) {
		ANSI(fd, YELLOW);
		print(fd, " *Thirsty|Hungry*");
		ANSI(fd, WHITE);
	}
	if(F_ISSET(ply_ptr, PPOISN)) {
		ANSI(fd, BLINK);
		ANSI(fd, GREEN);
		print(fd, " *Poisoned*");
		ANSI(fd, NORMAL);
	}
	if(F_ISSET(ply_ptr, PCHARM)) {
		ANSI(fd, BOLD);
		ANSI(fd, CYAN);
		print(fd, " *Charmed*");
		ANSI(fd, NORMAL);
	}
	if(F_ISSET(ply_ptr, PSILNC)) {
		ANSI(fd, BLINK);
		ANSI(fd, MAGENTA);
		print(fd, " *Mute*");
		ANSI(fd, NORMAL);
	}
	if(F_ISSET(ply_ptr, PDISEA)) {
		ANSI(fd, BLINK);
		ANSI(fd, RED);	
		print(fd," *Diseased*");
		ANSI(fd, NORMAL);
	}
	ANSI(fd, GREEN);
	print(fd, "\n %3d/%3d Hit Points    %3d/%3d Magic Points",
		ply_ptr->hpcur, ply_ptr->hpmax, ply_ptr->mpcur, ply_ptr->mpmax);
	ANSI(fd, RED);
	print(fd, "    AC: %d\n", ply_ptr->armor/10);
	ANSI(fd, YELLOW);
	print(fd, " %7ld Experience    %7ld Gold Pieces\n", ply_ptr->experience, ply_ptr->gold);
	ANSI(fd, NORMAL);
	ANSI(fd, WHITE);
	return(0);
}

/**********************************************************************/
/*				help				      */
/**********************************************************************/

/* This function allows a player to get help in general, or help for a */
/* specific command.  If help is typed by itself, a list of commands   */
/* is produced.  Otherwise, help is supplied for the command specified */

int help(ply_ptr, cmnd)
creature	*ply_ptr;
cmd		*cmnd;
{
	char 	file[80];
	int	fd, c=0, match=0, num=0;
	
	fd = ply_ptr->fd;
	strcpy(file, DOCPATH);

	if(cmnd->num < 2) {
		strcat(file, "/helpfile");
		view_file(fd, 1, file);
		return(DOPROMPT);
	}
	if(!strcmp(cmnd->str[1], "spells")) {
		strcat(file, "/spellfile");
		view_file(fd, 1, file);
		return(DOPROMPT);
	}

	if(!strcmp(cmnd->str[1], "policy")) {
		strcat(file, "/policy");
		view_file(fd, 1, file);
		return(DOPROMPT);
	}

	do {
		if(!strcmp(cmnd->str[1], cmdlist[c].cmdstr)) {
			match = 1;
			num = c;
			break;
		}
		else if(!strncmp(cmnd->str[1], cmdlist[c].cmdstr, 
			strlen(cmnd->str[1]))) {
			match++;
			num = c;
		}
		c++;
	} while(cmdlist[c].cmdno);

	if(num > 200 && ply_ptr->class < CARETAKER) {
		print(fd, "Command not found.\n");
		return(0);
	}

	if(match == 1) {
		sprintf(file, "%s/help.%d", DOCPATH, cmdlist[num].cmdno);
		view_file(fd, 1, file);
		return(DOPROMPT);
	}
	else if(match > 1) {
			print(fd, "Command is not unique.\n");
			return(0);
	}

	c = num = 0;
	do {
		if(!strcmp(cmnd->str[1], spllist[c].splstr)) {
			match = 1;
			num = c;
			break;
		}
		else if(!strncmp(cmnd->str[1], spllist[c].splstr, 
			strlen(cmnd->str[1]))) {
			match++;
			num = c;
		}
		c++;
	} while(spllist[c].splno != -1);

	if(match == 0) {
		print(fd, "That command does not exist.\n");
		return(0);
	}
	else if(match > 1) {
		print(fd, "Spell name not unique.\n");
		return(0);
	}

	sprintf(file, "%s/spell.%d", DOCPATH, spllist[num].splno);
	view_file(fd, 1, file);
	return(DOPROMPT);
}

/**********************************************************************/
/*				welcome				      */
/**********************************************************************/

/* Outputs welcome file to user, giving him/her info on how to play   */
/* the game 							      */

int welcome(ply_ptr, cmnd)
creature	*ply_ptr;
cmd		*cmnd;
{
	char	file[80];
	int	fd;

	fd = ply_ptr->fd;

	sprintf(file, "%s/welcome", DOCPATH);

	view_file(fd, 1, file);
	return(DOPROMPT);
}

/**********************************************************************/
/*				info				      */
/**********************************************************************/

/* This function displays a player's entire list of information, including */
/* player stats, proficiencies, level and class.			   */

int info(ply_ptr, cmnd)
creature	*ply_ptr;
cmd		*cmnd;
{
	char	alstr[8], tmp[80];
	int 	fd, cnt;
	long	expneeded, lv;

	fd = ply_ptr->fd;

	update_ply(ply_ptr);

	if(ply_ptr->level < MAXALVL)
		expneeded = needed_exp[ply_ptr->level -1];
	else
		expneeded = (long)((needed_exp[MAXALVL-1]*ply_ptr->level));   

	if(ply_ptr->alignment < -100)
		strcpy(alstr, "Evil");
	else if(ply_ptr->alignment < 101)
		strcpy(alstr, "Neutral");
	else
		strcpy(alstr, "Good");

	for(lv=0,cnt=0; lv<MAXWEAR; lv++)
		if(ply_ptr->ready[lv]) cnt++;
	cnt += count_inv(ply_ptr, 0);

        print(fd, "\n\n->");
	print(fd, "You are ");
	print(fd, "%s the %s\n", ply_ptr->name, title_ply(ply_ptr));
        print(fd, "->");
	print(fd, "Description: ");
	print(fd, "%s\n", ply_ptr->description);
	print(fd, "\nLevel: ");
	print(fd, "%-20d          ", ply_ptr->level);
	print(fd, "Race: ");
	print(fd, "%-20s         ", race_str[ply_ptr->race]);
	print(fd, "\nClass: ");
	print(fd, "%-20s  ", class_str[ply_ptr->class]);
	print(fd, "Alignment: ");
	print(fd, "%-8s %-8s               ", F_ISSET(ply_ptr, PCHAOS) ? "Chaotic" : "Lawful", alstr);
        print(fd, "\n");

#define INTERVAL ply_ptr->lasttime[LT_HOURS].interval

	print(fd, "Time of play: ");
	if(INTERVAL > 86400L)
		print(fd, "[%3d] ", INTERVAL/86400L);
	else
		print(fd, "[  0] ");
	print(fd, "day/s ");
	if(INTERVAL > 3600L)
		print(fd, "[%2d] ", (INTERVAL % 86400L)/3600L);
	else
		print(fd, "[ 0] ");
	print(fd, "hour/s ");
	print(fd, "[%2d] ", (INTERVAL % 3600L)/60L);
	print(fd, "minute/s                     ");

	print(fd, "\n\nSTR: ");
	print(fd, "%-2d        ", ply_ptr->strength);
	print(fd, "DEX: ");
	print(fd, "%-2d        ", ply_ptr->dexterity);
	print(fd, "CON: ");
	print(fd, "%-2d        ", ply_ptr->constitution);
	print(fd, "INT: ");
	print(fd, "%-2d        ", ply_ptr->intelligence);
	print(fd, "PTY: ");
	print(fd, "%-2d     ", ply_ptr->piety);
	print(fd, "\n\n  Hit Points[Hp]: ");
	print(fd, "%5d/%-5d     ", ply_ptr->hpcur, ply_ptr->hpmax);
	print(fd, "Exp: ");
	print(fd, "%9lu /%-9lu ", ply_ptr->experience, MAX(0, expneeded-ply_ptr->experience));
	print(fd, "next level  ");
	print(fd, "\nMagic Points[Mp]: ");
	print(fd, "%5d/%-5d                    ", ply_ptr->mpcur, ply_ptr->mpmax);
	print(fd, "Gold: ");
	print(fd, "%-9lu       ", ply_ptr->gold);
	print(fd, "\nArmour class[AC]:     ");
	print(fd, "%-4d          ", ply_ptr->armor/10);
	print(fd, "Inventory Weight: ");
	print(fd, "%-4d ", weight_ply(ply_ptr));
	print(fd, "Lbs/");
	print(fd, "%-3d ", cnt);
	print(fd, "Obj");
        print(fd, "\n");
	print(fd, "\nProficiencies:                                                          ");
	print(fd, "\nSharp:  ");
	print(fd, "%2d%%   ", profic(ply_ptr, SHARP));
	print(fd, "Thrust: ");
	print(fd, "%2d%%   ", profic(ply_ptr, THRUST));
	print(fd, "Blunt:  ");
	print(fd, "%2d%%   ", profic(ply_ptr, BLUNT));
	print(fd, "Pole:   ");
	print(fd, "%2d%%   ", profic(ply_ptr, POLE));
	print(fd, "Missile: ");
	print(fd, "%2d%%    ", profic(ply_ptr, MISSILE));
	print(fd, "\n\nRealms:                                                                 ");
	print(fd, "\nEarth:  ");
	print(fd, "%3d%%   ", mprofic(ply_ptr, EARTH));
	print(fd, "Wind:   ");
	print(fd, "%3d%%   ", mprofic(ply_ptr, WIND));
	print(fd, "Fire:   ");
	print(fd, "%3d%%   ", mprofic(ply_ptr, FIRE));
	print(fd, "Water:  ");
	print(fd, "%3d%%               ", mprofic(ply_ptr, WATER));
        print(fd, "\n");
	
	F_SET(ply_ptr, PREADI);
	if(ANSILINE)
		ask_for(fd, "[Hit Return, Q to quit]: ");
	else
		print(fd, "[Hit Return, Q to Quit]: ");
	output_buf();
	Ply[fd].io->intrpt &= ~1;
	Ply[fd].io->fn = info_2;
	Ply[fd].io->fnparam = 1;
	return(DOPROMPT);
}

/************************************************************************/
/*				info_2					*/
/************************************************************************/

/* This function is the second half of info which outputs spells	*/

void info_2(fd, param, instr)
int 	fd, param;
char 	*instr;
{
	char		str[2048];
	char		spl[128][20];
	int		i, j;
	creature	*ply_ptr;

	ply_ptr = Ply[fd].ply;

	if(instr[0]) {
		print(fd, "Aborted.\n");
		F_CLR(ply_ptr, PREADI);
		RETURN(fd, command, 1);
	}

	strcpy(str, "\nSpells known: ");
	for(i=0,j=0; i< spllist_size; i++)
		if(S_ISSET(ply_ptr, i))
			strcpy(spl[j++], spllist[i].splstr);

	if(!j)
		strcat(str, "None.");
	else {
		qsort((void *)spl, j, 20, strcmp);
		for(i=0; i<j; i++) {
			strcat(str, spl[i]);
			strcat(str, ", ");
		}
		str[strlen(str)-2] = '.';
		str[strlen(str)-1] = 0;
	}
	print(fd, "%s\n", str);

	strcpy(str, "Spells under: ");
	if(F_ISSET(ply_ptr, PBLESS)) strcat(str, "bless, ");
	if(F_ISSET(ply_ptr, PLIGHT)) strcat(str, "light, ");
	if(F_ISSET(ply_ptr, PPROTE)) strcat(str, "protection, ");
	if(F_ISSET(ply_ptr, PINVIS)) strcat(str, "invisibility, ");
	if(F_ISSET(ply_ptr, PDINVI)) strcat(str, "detect-invisible, ");
	if(F_ISSET(ply_ptr, PDMAGI)) strcat(str, "detect-magic, ");
	if(F_ISSET(ply_ptr, PLEVIT)) strcat(str, "levitation, ");
	if(F_ISSET(ply_ptr, PRFIRE)) strcat(str, "resist-fire, ");
	if(F_ISSET(ply_ptr, PFLYSP)) strcat(str, "fly, ");
	if(F_ISSET(ply_ptr, PRMAGI)) strcat(str, "resist-magic, ");
	if(F_ISSET(ply_ptr, PKNOWA)) strcat(str, "know-aura, ");
	if(F_ISSET(ply_ptr, PRCOLD)) strcat(str, "resist-cold, ");
	if(F_ISSET(ply_ptr, PBRWAT)) strcat(str, "breathe-water, ");
	if(F_ISSET(ply_ptr, PSSHLD)) strcat(str, "earth-shield, ");
	if(strlen(str) == 14)
		strcat(str, "None.");
	else {
		str[strlen(str)-2] = '.';
		str[strlen(str)-1] = 0;
	}
	print(fd, "%s\n\n", str);

	F_CLR(Ply[fd].ply, PREADI);
	RETURN(fd, command, 1);
}

/**********************************************************************/
/*				psend				      */
/**********************************************************************/

/* This function allows a player to send a message to another player.  If */
/* the other player is logged in, the message is sent successfully.       */

int psend(ply_ptr, cmnd)
creature	*ply_ptr;
cmd		*cmnd;
{
	creature	*crt_ptr = 0;
	etag		*ign;
	int			spaces=0, i, j, fd, len;

	fd = ply_ptr->fd;

	if(cmnd->num < 2) {
		print(fd, "Send to whom?\n");
		return 0;
	}

	if(!F_ISSET(ply_ptr, PSECOK)) {
		print(fd, "You may not do that yet.\n");
		return(0);
	}

	cmnd->str[1][0] = up(cmnd->str[1][0]);
	for(i=0; i<Tablesize; i++) {
		if(!Ply[i].ply) continue;
		if(Ply[i].ply->fd == -1) continue;
		if(F_ISSET(Ply[i].ply, PDMINV) && ply_ptr->class < CARETAKER) 
			continue;
		if(!strncmp(Ply[i].ply->name, cmnd->str[1], 
		   strlen(cmnd->str[1])))
			crt_ptr = Ply[i].ply;
		if(!strcmp(Ply[i].ply->name, cmnd->str[1]))
			break;
	}
	if(!crt_ptr) {
		print(fd, "Send to whom?\n");
		return(0);
	}
	if(ply_ptr->class < CARETAKER && (F_ISSET(crt_ptr, PINVIS) && !F_ISSET(ply_ptr, PDINVI))) {
		print(fd, "Send to whom?\n");
		return(0);
	}
 	if(F_ISSET(crt_ptr, PIGNOR) && (ply_ptr->class < CARETAKER)) {
		print(fd, "%s is ignoring everyone.\n", crt_ptr->name);
		return(0);
	}

	ign = Ply[crt_ptr->fd].extr->first_ignore;
	while(ign) {
		if(!strcmp(ign->enemy, ply_ptr->name)) {
			print(fd, "%s is ignoring you.\n", crt_ptr->name);
			return(0);
		}
		ign = ign->next_tag;
	}
	
	len = strlen(cmnd->fullstr);
	for(i=0; i< len && i<256; i++) {
		if(cmnd->fullstr[i] == ' ' && cmnd->fullstr[i+1] != ' ')
			spaces++;
		if(spaces==2) break;
	}
	cmnd->fullstr[255] = 0;
	/* Check for modem escape code */
        for(j=0; j<len && j < 256; j++) {
                if(cmnd->fullstr[j] == '+' && cmnd->fullstr[j+1] == '+') {
                        spaces=0;
                        break;
                }
        }

	if(spaces < 2 || strlen(&cmnd->fullstr[i+1]) < 1) {
		print(fd, "Send what?\n");
		return(0);
	}
	if(F_ISSET(ply_ptr, PSILNC)) {
		print(fd, "You can't speak.\n");
		return(0);
	}
	if(F_ISSET(ply_ptr, PLECHO)){
		ANSI(fd, CYAN);
		print(fd, "You sent: \"%s\" to %M.\n", &cmnd->fullstr[i+1], crt_ptr);
		ANSI(fd, NORMAL)
	}
	else
		print(fd, "Message sent to %s.\n", crt_ptr->name);
	
	print(crt_ptr->fd, "### %M just flashed, \"%s\".\n", ply_ptr,
	      &cmnd->fullstr[i+1]);
	
	if(F_ISSET(ply_ptr, PDMINV) && crt_ptr->class < CARETAKER) {
		print(fd, "They will be unable to reply.\n");
		if(F_ISSET(ply_ptr, PALIAS)) 
			print(fd, "Sent from: %s.\n", Ply[fd].extr->alias_crt);
	}

	if(ply_ptr->class > CARETAKER || crt_ptr->class > CARETAKER)
		return(0);

	broadcast_eaves("--- %s sent to %s, \"%s\".", ply_ptr->name,
		crt_ptr->name, &cmnd->fullstr[i+1]);

	return(0);

}

/**********************************************************************/
/*				broadsend			      */
/**********************************************************************/

/* This function is used by players to broadcast a message to every   */
/* player in the game.  Broadcasts by players are of course limited,  */
/* so the number used that day is checked before the broadcast is     */
/* allowed.							      */

int broadsend(ply_ptr, cmnd)
creature	*ply_ptr;
cmd		*cmnd;
{
	int	i, j, found=0, fd;
	int 	len;

	fd = ply_ptr->fd;
	len = strlen(cmnd->fullstr);
	for(i=0; i<len && i < 256; i++) {
		if(cmnd->fullstr[i] == ' ' && cmnd->fullstr[i+1] != ' ')
			found++;
		if(found==1) break;
	}
	cmnd->fullstr[255] = 0;

	/* Check for modem escape code */
        for(j=0; j<len && j < 256; j++) {
                if(cmnd->fullstr[j] == '+' && cmnd->fullstr[j+1] == '+') {
                        found=0;
                        break;
                }
        }

	if(found < 1 || strlen(&cmnd->fullstr[i+1]) < 1) {
		print(fd, "Send what?\n");
		return(0);
	}

	if(RFC1413) {
		if((!strcmp(Ply[fd].io->userid, "no_port") || !strcmp(Ply[fd].io->userid, "unknown")) && !F_ISSET(ply_ptr, PAUTHD)){
			print(fd, "You are not authorized to broadcast.\n");
			return(0);
		}
	} /* RFC1413 */

	if(!F_ISSET(ply_ptr, PSECOK)) {
		print(fd, "You may not do that yet.\n");
		return(0);
	}

	if(!HEAVEN)
		if(!dec_daily(&ply_ptr->daily[DL_BROAD])) {
			print(fd,"You've used up all your broadcasts today.\n");
			return(0);
		}

	if(F_ISSET(ply_ptr, PSILNC)) {
		print(fd, "Your voice is too weak to do that.\n");
		return(0);
	}
	print(fd, "Message broadcast.\n");
	broadcast("### %M broadcasted, \"%s\".", ply_ptr, &cmnd->fullstr[i+1]);

	return(0);

}

/**********************************************************************/
/*				follow				      */
/**********************************************************************/

/* This command allows a player (or a monster) to follow another player. */
/* Follow loops are not allowed; i.e. you cannot follow someone who is   */
/* following you.  Also, you cannot follow yourself.			 */

int follow(ply_ptr, cmnd)
creature	*ply_ptr;
cmd		*cmnd;
{
	creature	*old_ptr, *new_ptr;
	room		*rom_ptr;
	ctag		*cp, *pp, *prev;
	int		fd;

	fd = ply_ptr->fd;
	rom_ptr = ply_ptr->parent_rom;

	if(cmnd->num < 2) {
		print(fd, "Follow who?\n");
		return(0);
	}

	F_CLR(ply_ptr, PHIDDN);
	lowercize(cmnd->str[1], 1);
	new_ptr = find_crt(ply_ptr, rom_ptr->first_ply,
			   cmnd->str[1], cmnd->val[1]);

	if(!new_ptr) {
		print(fd, "No one here by that name.\n");
		return(0);
	}

	if(new_ptr == ply_ptr && !ply_ptr->following) {
		print(fd, "You can't follow yourself.\n");
		return(0);
	}

	if(new_ptr->following == ply_ptr) {
		print(fd, "You can't.  %s's following you.\n",
		      F_ISSET(new_ptr, PMALES) ? "He":"She");
		return(0);
	}

	if(ply_ptr->following) {
		old_ptr = ply_ptr->following;
		cp = old_ptr->first_fol;
		if(cp->crt == ply_ptr) {
			old_ptr->first_fol = cp->next_tag;
			free(cp);
		}
		else while(cp) {
			if(cp->crt == ply_ptr) {
				prev->next_tag = cp->next_tag;
				free(cp);
				break;
			}
			prev = cp;
			cp = cp->next_tag;
		}
		ply_ptr->following = 0;
		print(fd, "You stop following %s.\n", old_ptr->name);
		if(!F_ISSET(ply_ptr, PDMINV))
			print(old_ptr->fd, "%M stops following you.\n", 
			      ply_ptr);
	}

	if(ply_ptr == new_ptr)
		return(0);

	ply_ptr->following = new_ptr;

	pp = (ctag *)malloc(sizeof(ctag));
	if(!pp)
		merror("follow", FATAL);
	pp->crt = ply_ptr;
	pp->next_tag = 0;

	if(!new_ptr->first_fol)
		new_ptr->first_fol = pp;
	else {
		pp->next_tag = new_ptr->first_fol;
		new_ptr->first_fol = pp;
	}

	print(fd, "You start following %s.\n", new_ptr->name);
	if(!F_ISSET(ply_ptr, PDMINV)) {
		print(new_ptr->fd, "%M starts following you.\n", ply_ptr);
		broadcast_rom2(fd, new_ptr->fd, ply_ptr->rom_num,
			      "%M follows %m.", ply_ptr, new_ptr);
	}

	return(0);

}

/**********************************************************************/
/*				lose				      */
/**********************************************************************/

/* This function allows a player to lose another player who might be  */
/* following him.  When successful, that player will no longer be     */
/* following.							      */

int lose(ply_ptr, cmnd)
creature	*ply_ptr;
cmd		*cmnd;
{
	creature	*crt_ptr;
	ctag		*cp, *prev;
	int		fd;

	fd = ply_ptr->fd;

if(cmnd->num == 1) {
 
                if (ply_ptr->following == 0){
                        print(fd, "You're not following anyone.\n");
                        return(0);
 
                }
      crt_ptr = ply_ptr->following;
        cp = crt_ptr->first_fol;
        if(cp->crt == ply_ptr) {
                crt_ptr->first_fol = cp->next_tag;
                free(cp);
        }
        else while(cp) {
                if(cp->crt == ply_ptr) {
                        prev->next_tag = cp->next_tag;
                        free(cp);
                        break;
                }
                prev = cp;
                cp = cp->next_tag;
        }
        ply_ptr->following = 0; 
                print(fd,"You stop following %m\n",crt_ptr);
                if (!F_ISSET(ply_ptr,PDMINV))
                        print(crt_ptr->fd,"%M stops following you\n",ply_ptr);
                return(0);
        }                   

	F_CLR(ply_ptr, PHIDDN);

	lowercize(cmnd->str[1], 1);
	crt_ptr = find_crt(ply_ptr, ply_ptr->first_fol,
			   cmnd->str[1], cmnd->val[1]);

	if(!crt_ptr) {
		print(fd, "That person is not following you.\n");
		return(0);
	}

	if(crt_ptr->following != ply_ptr) {
		print(fd, "That person is not following you.\n");
		return(0);
	}

	cp = ply_ptr->first_fol;
	if(cp->crt == crt_ptr) {
		ply_ptr->first_fol = cp->next_tag;
		free(cp);
	}
	else while(cp) {
		if(cp->crt == crt_ptr) {
			prev->next_tag = cp->next_tag;
			free(cp);
			break;
		}
		prev = cp;
		cp = cp->next_tag;
	}
	crt_ptr->following = 0;

	print(fd, "You lose %s.\n", F_ISSET(crt_ptr, PMALES) ? "him":"her");
	if(!F_ISSET(ply_ptr, PDMINV)) {
		print(crt_ptr->fd, "%M loses you.\n", ply_ptr);
		broadcast_rom2(fd, crt_ptr->fd, "%M loses %m.", 
			       ply_ptr, crt_ptr);
	}

	return(0);

}

/**********************************************************************/
/*				group				      */
/**********************************************************************/

/* This function allows you to see who is in a group or party of people */
/* who are following you.						*/

int group(ply_ptr, cmnd)
creature	*ply_ptr;
cmd		*cmnd;
{
	ctag	*cp;
	char	str[2048];
	int	fd, found = 0;

	fd = ply_ptr->fd;

	cp = ply_ptr->first_fol;
	if(!cp) {
		print(fd, "No one is following you.\n");
		return(0);
	}

	strcpy(str, "People in your group: ");
	while(cp) {
		if(!F_ISSET(cp->crt, PDMINV)) {
			strcat(str, crt_str(cp->crt, 0, 0));
			strcat(str, ", ");
			found = 1;
		}
		cp = cp->next_tag;
	}

	if(!found) {
		print(fd, "No one is following you.\n");
		return(0);
	}

	str[strlen(str)-2] = 0;
	print(fd, "%s.\n", str);

	return(0);

}

/**********************************************************************/
/*				track				      */
/**********************************************************************/

/* This function is the routine that allows rangers and druids to search */
/* for tracks 								 */
/* in a room.  If the ranger is successful, he will be told what dir-    */
/* ection the last person who was in the room left.			 */

int track(ply_ptr, cmnd)
creature	*ply_ptr;
cmd		*cmnd;
{
	long	i, t;
	int	fd, chance;

	fd = ply_ptr->fd;

	if(ply_ptr->class != RANGER && ply_ptr->class != DRUID && ply_ptr->class < CARETAKER) {
		print(fd, "Only rangers and druids can track.\n");
		return(0);
	}

	F_CLR(ply_ptr, PHIDDN);

	t = time(0);
	i = LT(ply_ptr, LT_TRACK);

	if(t < i) {
		please_wait(fd, i-t);
		return(0);
	}

	ply_ptr->lasttime[LT_TRACK].ltime = t;
	ply_ptr->lasttime[LT_TRACK].interval = 5 - bonus[ply_ptr->dexterity];
	
	if(F_ISSET(ply_ptr, PBLIND)) {
		print(fd, "You're blind...how can you see tracks?\n");
		return(0);
	}
	chance = 25 + (bonus[ply_ptr->dexterity] + ply_ptr->level)*5;

	if(mrand(1,100) > chance) {
		print(fd, "You fail to find any tracks.\n");
		return(0);
	}

	if(!ply_ptr->parent_rom->track[0]) {
		print(fd, "There are no tracks in this room.\n");
		return(0);
	}

	print(fd, "You find tracks leading to the %s.\n",
	      ply_ptr->parent_rom->track);
	broadcast_rom(fd, ply_ptr->rom_num, "%M finds tracks.", ply_ptr);

	return(0);

}

/**********************************************************************/
/*				peek				      */
/**********************************************************************/

/* This function allows a thief or assassin to peek at the inventory of */
/* another player.  If successful, they will be able to see it and      */
/* another roll is made to see if they get caught.			*/

int peek(ply_ptr, cmnd)
creature	*ply_ptr;
cmd		*cmnd;
{
	creature	*crt_ptr;
	room		*rom_ptr;
	char		str[2048];
	long		i, t;
	int		fd, n, chance;

	fd = ply_ptr->fd;
	rom_ptr = ply_ptr->parent_rom;
	str[0] = 0;

	if(cmnd->num < 2) {
		print(fd, "Peek at who?\n");
		return(0);
	}

	if(ply_ptr->class != THIEF && ply_ptr->class < CARETAKER) {
		print(fd, "Only thieves can peek.\n");
		return(0);
	}
	
	if(F_ISSET(ply_ptr, PBLIND)){
		ANSI(fd, RED);
		print(fd, "You can't do that...You're blind!\n");
		ANSI(fd, WHITE);
		return(0);
	}

	crt_ptr = find_crt(ply_ptr, rom_ptr->first_mon,
			   cmnd->str[1], cmnd->val[1]);

	if(!crt_ptr) {
		lowercize(cmnd->str[1], 1);
		crt_ptr = find_crt(ply_ptr, rom_ptr->first_ply,
				   cmnd->str[1], cmnd->val[1]);

		if(!crt_ptr) {
			print(fd, "That person is not here.\n");
			return(0);
		}
	}

	i = LT(ply_ptr, LT_PEEKS);
	t = time(0);

	if(i > t) {
		please_wait(fd, i-t);
		return(0);
	}

	ply_ptr->lasttime[LT_PEEKS].ltime = t;
	ply_ptr->lasttime[LT_PEEKS].interval = 5;

	if(crt_ptr->type==MONSTER && (F_ISSET(crt_ptr, MUNSTL) || F_ISSET(crt_ptr, MTRADE) || F_ISSET(crt_ptr, MPURIT)) && ply_ptr->class < DM) {        
		print(fd, "You shouldn't do that, someone will think you are a thief.\n");  
		return(0);		    
	} 
		
	chance = (25 + ply_ptr->level*10)-(crt_ptr->level*5);
	if (chance<0) chance=0;
	if (ply_ptr->class >= CARETAKER) chance=100;
	if(mrand(1,100) > chance) {
		print(fd, "You failed.\n");
		return(0);
	}

	chance = MIN(90, 15 + ply_ptr->level*5);

	if(mrand(1,100) > chance && ply_ptr->class < CARETAKER) {
		print(crt_ptr->fd, "%s peeked at your inventory.\n", ply_ptr);
		broadcast_rom2(fd, crt_ptr->fd, ply_ptr->rom_num, 
			       "%M peeked at %m's inventory.", 
			       ply_ptr, crt_ptr);
	}

	sprintf(str, "%s is carrying: ", F_ISSET(crt_ptr, PMALES) ? "He":"She");
	n = strlen(str);
	if(list_obj(&str[n], ply_ptr, crt_ptr->first_obj) > 0)
		print(fd, "%s.\n", str);
	else
		print(fd, "%s isn't holding anything.\n",
		      F_ISSET(crt_ptr, PMALES) ? "He":"She");

	return(0);

}