Mud20/accounts/
Mud20/accounts/c/
Mud20/accounts/f/
Mud20/accounts/k/
Mud20/accounts/s/
Mud20/accounts/t/
Mud20/area_current/
Mud20/area_current/newareas/
Mud20/bin/
Mud20/clans/
Mud20/gods/
Mud20/old-sources/
Mud20/player/
Mud20/player/a/del/
Mud20/player/b/
Mud20/player/b/bak/
Mud20/player/b/del/
Mud20/player/f/
Mud20/player/f/bak/
Mud20/player/f/del/
Mud20/player/k/
Mud20/player/k/bak/
Mud20/player/k/del/
Mud20/player/k/dmp/
Mud20/player/m/
Mud20/player/m/bak/
Mud20/player/o/
Mud20/player/o/bak/
Mud20/player/p/
Mud20/player/s/
Mud20/player/s/bak/
Mud20/player/s/del/
Mud20/player/t/
Mud20/player/t/del/
Mud20/player/v/
Mud20/public_html/
Mud20/races/
Mud20/skilltables/
__MACOSX/Mud20/accounts/
__MACOSX/Mud20/accounts/c/
__MACOSX/Mud20/accounts/f/
__MACOSX/Mud20/accounts/k/
__MACOSX/Mud20/accounts/s/
__MACOSX/Mud20/area_current/
__MACOSX/Mud20/area_current/core_areas/
__MACOSX/Mud20/area_current/helps/
__MACOSX/Mud20/area_current/newareas/
__MACOSX/Mud20/backups/
__MACOSX/Mud20/bin/
__MACOSX/Mud20/clans/
__MACOSX/Mud20/gods/
__MACOSX/Mud20/log/
__MACOSX/Mud20/old-sources/
__MACOSX/Mud20/player/
__MACOSX/Mud20/player/a/del/
__MACOSX/Mud20/player/b/
__MACOSX/Mud20/player/b/bak/
__MACOSX/Mud20/player/f/
__MACOSX/Mud20/player/f/bak/
__MACOSX/Mud20/player/f/del/
__MACOSX/Mud20/player/k/
__MACOSX/Mud20/player/k/bak/
__MACOSX/Mud20/player/k/del/
__MACOSX/Mud20/player/k/dmp/
__MACOSX/Mud20/player/m/
__MACOSX/Mud20/player/m/bak/
__MACOSX/Mud20/player/o/
__MACOSX/Mud20/player/o/bak/
__MACOSX/Mud20/player/p/
__MACOSX/Mud20/player/s/
__MACOSX/Mud20/player/s/bak/
__MACOSX/Mud20/player/t/del/
__MACOSX/Mud20/player/v/
__MACOSX/Mud20/public_html/
__MACOSX/Mud20/races/
__MACOSX/Mud20/skilltables/
/***************************************************************************
 * Mud20 1.0 by Todd H. Johnson (Kregor) a derivative of the Open Gaming   *
 * License by Wizards of the Coast. All comments referring to D20, OGL,    *
 * and SRD refer to the System Reference Document for the Open Gaming      *
 * system. Any inclusion of these derivatives must include credit to the   *
 * Mud20 system, the full and complete Open Gaming LIcense, and credit to  *
 * the respective authors. See ../doc/srd.txt for more information.        *
 *                                                                         *
 * Emud  2.2 by Igor van den Hoven, Michiel Lange, and Martin Bethlehem.   *
 *                                                                         *
 * MrMud 1.4 by David Bills, Dug Michael and Martin Gallwey                *
 *                                                                         *
 * Merc  2.1 Diku Mud improvments copyright (C) 1992, 1993 by Michael      *
 * Chastain, Michael Quan, and Mitchell Tse.                               *
 *                                                                         *
 * Original Diku Mud copyright (C) 1990 1991 by Sebastian Hammer,          *
 * Michael Seifert, Hans Henrik St{rfeld, Tom Madsen, and Katje Nyboe.     *
 ***************************************************************************/

/***************************************************************************
 * act_comm.c: player-initiated communication functions.								   *
 ***************************************************************************/

#include <stdarg.h>
#include "mud.h"

/*
	Local functions.
*/

char	* translate (char *);
bool is_note_to args ((CHAR_DATA * ch, NOTE_DATA * pnote));
void note_attach args ((CHAR_DATA * ch));
void note_remove args ((CHAR_DATA * ch, NOTE_DATA * pnote, bool remove_all));


bool is_note_to (CHAR_DATA * ch, NOTE_DATA * pnote)
{
	push_call("is_note_to (%p, %p)",ch,pnote);

	if (IS_MUTED(ch))
	{
		pop_call();
		return FALSE;
	}

	if (ch->pcdata->note_topic != pnote->topic && !IS_SET(ch->in_room->room_flags, ROOM_NOTE_BOARD))
	{
		pop_call();
		return FALSE;
	}

	if (ch->name[0] == pnote->sender[0] && !strcasecmp(ch->name, pnote->sender))
	{
		pop_call();
		return TRUE;
	}

	if (is_name(ch->name, pnote->to_list))
	{
		pop_call();
		return TRUE;
	}

	if (is_name ("all", pnote->to_list))
	{
		pop_call();
		return TRUE;
	}

	if (IS_IMMORTAL(ch) && is_name("immortal", pnote->to_list))
	{
		pop_call();
		return TRUE;
	}
	pop_call();
	return FALSE;
}

void note_attach (CHAR_DATA * ch)
{
	NOTE_DATA *pnote;

	push_call("note_attach(%p)",ch);

	if (ch->pcdata->pnote != NULL)
	{
		pop_call();
		return;
	}
	ALLOCMEM (pnote, NOTE_DATA, 1);

	pnote->next		= NULL;
	pnote->prev		= NULL;
	pnote->sender		= STRALLOC(ch->name);
	pnote->date		= STRALLOC ("");
	pnote->to_list 	= STRALLOC ("");
	pnote->subject 	= STRALLOC ("");
	pnote->text		= STRALLOC ("");
	pnote->topic		= -1;
	pnote->room_vnum	= 0;
	ch->pcdata->pnote	= pnote;
	pop_call();
	return;
}

void note_remove (CHAR_DATA * ch, NOTE_DATA * pnote, bool remove_all)
{
	char to_new[MAX_INPUT_LENGTH];
	char to_one[MAX_INPUT_LENGTH];
	FILE *fp;
	char *to_list;

	push_call("note_remove(%p,%p,%p)",ch,pnote,remove_all);

	/*
		Build a new to_list.
		Strip out this recipient.
	*/

	to_new[0] = '\0';
	to_list = pnote->to_list;
	while (*to_list != '\0')
	{
		to_list = one_argument (to_list, to_one);
		if (to_one[0] != '\0' && strcasecmp (ch->name, to_one))
		{
			strcat (to_new, " ");
			strcat (to_new, to_one);
		}
	}

	/*
		Just a simple recipient removal?
	*/

	if (!remove_all && strcasecmp (ch->name, pnote->sender) && to_new[0] != '\0')
	{
		STRFREE (pnote->to_list);
		pnote->to_list = STRALLOC (to_new + 1);
		pop_call();
		return;
	}

	/*
		Remove note from linked list.
	*/

	UNLINK(pnote, mud->f_note, mud->l_note, next, prev);

	STRFREE (pnote->text);
	STRFREE (pnote->subject);
	STRFREE (pnote->to_list);
	STRFREE (pnote->date);
	STRFREE (pnote->sender);
	FREEMEM (pnote);

	/*
		Rewrite entire list.
	*/

	close_reserve();

	fp = my_fopen (NOTE_FILE, "w",FALSE);

	for (pnote = mud->f_note ; pnote ; pnote = pnote->next)
	{
		fprintf(fp, "Sender  %s~\nDate    %s~\nTime  %ld\nTo      %s~\nSubject %s~\nTopic   %d\nText\n%s~\nRoom %u\n\n", pnote->sender, pnote->date, pnote->time, pnote->to_list, pnote->subject, pnote->topic, fixer(pnote->text), pnote->room_vnum);
	}
	my_fclose(fp);

	open_reserve();

	pop_call();
	return;
}

void do_refresh (CHAR_DATA * ch, char *argument)
{
	push_call("do_refresh(%p, %p)",ch,argument);

	if (!is_desc_valid(ch))
	{
		pop_call();
		return;
	}

	if (CH(ch->desc)->pcdata->vt100 == 0)
	{
		pop_call();
		return;
	}

	if (CH(ch->desc)->pcdata->tactical != NULL)
	{
		FREEMEM(CH(ch->desc)->pcdata->tactical);
		CH(ch->desc)->pcdata->tactical = NULL;
	}

	CH(ch->desc)->pcdata->vt100 = 2;

	vt100on(ch);

	pop_call();
	return;
}

bool new_notes (CHAR_DATA *ch)
{
	NOTE_DATA *pnote;
	int oldTopic;

	push_call("new_notes(%p)",ch);

	if (ch->level < 5)
	{
		pop_call();
		return FALSE;
	}

	oldTopic = ch->pcdata->note_topic;

	for (pnote = mud->f_note ; pnote ; pnote = pnote->next)
	{
		ch->pcdata->note_topic = pnote->topic;

		if (is_note_to(ch, pnote) && pnote->time >= ch->pcdata->last_time)
		{
			if (pnote->room_vnum == 0 || pnote->room_vnum == ch->in_room->vnum)
			{
				ch->pcdata->note_topic = oldTopic;
				pop_call();
				return TRUE;
			}
		}
	}
	ch->pcdata->note_topic = oldTopic;
	pop_call();
	return FALSE;
}


void do_note (CHAR_DATA * ch, char *argument)
{
	char buf[MAX_STRING_LENGTH];
	char arg[MAX_INPUT_LENGTH];
	PLAYER_GAME *fch;
	NOTE_DATA *pnote;
	int vnum, anum, cnt, room_vnum;
	bool room_board;

	push_call("do_note(%p,%p)",ch,argument);

	if (IS_NPC (ch))
	{
		pop_call();
		return;
	}

	if (!ch->desc)
	{
		bug ("do_note: no descriptor", 0);
		pop_call();
		return;
	}

	switch (ch->pcdata->editmode)
	{
		default:
			break;
		case MODE_RESTRICTED:
			send_to_char ("You cannot use this command while editing something else.\n\r", ch);
			pop_call();
			return;
			break;

		case MODE_WRITING_NOTE:
			STRFREE(ch->pcdata->pnote->text);
			ch->pcdata->pnote->text = copy_buffer (ch);
			stop_editing (ch);
			pop_call();
			return;
			break;
	}

	argument = one_argument (argument, arg);
	smash_tilde (argument);

	if (ch->in_room != NULL && IS_SET(ch->in_room->room_flags, ROOM_NOTE_BOARD) && ch->in_room->vnum > 0)
	{
		room_board = TRUE;
		room_vnum  = ch->in_room->vnum;
	}
	else
	{
		room_board = FALSE;
		room_vnum  = 0;
	}

	*buf = '\0';

	if (!strcasecmp(arg, "list"))
	{
		if (room_vnum)
		{
			strcpy (buf, "{078}Note Board:\n\r");
		}
		else
		{
			sprintf(buf, "{078}TOPIC: %s\n\r", topic_table[ch->pcdata->note_topic].name);
		}

		if (is_number (argument))
		{
			int curtime;

			send_to_char_color(buf, ch);

			curtime = mud->current_time - (24 * 60 * 60 * atol (argument));
			vnum = 0;

			for (buf[0] = '\0', pnote = mud->f_note ; pnote ; pnote = pnote->next)
			{
				if (is_note_to(ch, pnote) && pnote->time >= curtime && pnote->room_vnum == room_vnum)
				{
					if (room_vnum || pnote->time <= ch->pcdata->topic_stamp[pnote->topic])
					{
						cat_sprintf(buf, "{078}[%3u]{038} %s to %s:{078} %s\n\r", vnum, pnote->sender, pnote->to_list, pnote->subject);
					}
					else
					{
						cat_sprintf(buf, "{178}[%3u]{128}*{138}%s to %s:{178} %s\n\r", vnum, pnote->sender, pnote->to_list, pnote->subject);
					}
				}
				vnum++;
			}
			cat_sprintf(buf, "{078}The {128}*{078} denotes an unread note.\n\r");
			send_to_char_color(buf, ch );
		}
		else if (!strcasecmp (argument, "new"))
		{
			int oldTopic	= ch->pcdata->note_topic;

			vnum = 0;

			for (buf[0] = '\0', pnote = mud->f_note ; pnote ; pnote = pnote->next)
			{
				ch->pcdata->note_topic = pnote->topic;

				if (is_note_to(ch, pnote) && pnote->time >= ch->pcdata->last_time && pnote->room_vnum == room_vnum)
				{
					if (pnote->time <= ch->pcdata->topic_stamp[pnote->topic])
					{
						cat_sprintf(buf, "{078}[%3u]{038} %s to %s:{078} %s\n\r", vnum, pnote->sender, pnote->to_list, pnote->subject);
					}
					else
					{
						cat_sprintf(buf, "{178}[%3u]{128}*{138}%s to %s:{178} %s\n\r", vnum, pnote->sender, pnote->to_list, pnote->subject);
					}
				}
				vnum++;
			}
			if (buf[0] == '\0')
			{
				send_to_char ("There have been no new notes since you last logged out.\n\r", ch);
			}
			else
			{
				cat_sprintf(buf, "{078}The {128}*{078} denotes an unread note.\n\r");
				send_to_char_color(buf, ch);
			}
			ch->pcdata->note_topic = oldTopic;
		}
		else if (!strcasecmp (argument, "unread"))
		{
			int unreadNum[MAX_TOPIC]	= {0};
			int oldTopic = ch->pcdata->note_topic;

			buf[0] = '\0';

			for (pnote = mud->f_note ; pnote ; pnote = pnote->next)
			{
				ch->pcdata->note_topic = pnote->topic;

				if (is_note_to(ch, pnote) && pnote->room_vnum == room_vnum && pnote->time > ch->pcdata->topic_stamp[pnote->topic])
				{
					unreadNum[pnote->topic]++;
				}
			}

			for (cnt = 0 ; cnt < MAX_TOPIC - 1 ; cnt++)
			{
				cat_sprintf(buf, "{038}%2d  {078}%-22s {138}%d\n\r", cnt, topic_table[cnt].name, unreadNum[cnt]);
			}
			send_to_char_color(buf, ch);

			ch->pcdata->note_topic = oldTopic;
		}
		else if (argument[0] != '\0')
		{
			send_to_char_color (buf, ch);
			vnum = 0;

			for (buf[0] = '\0', pnote = mud->f_note ; pnote ; pnote = pnote->next)
			{
				if (is_note_to(ch, pnote) && pnote->room_vnum == room_vnum && ((!str_infix(argument, pnote->sender)) || (!str_infix(argument, pnote->subject)) || (!str_infix(argument, pnote->to_list))))
				{
					if (room_vnum || pnote->time <= ch->pcdata->topic_stamp[pnote->topic])
					{
						cat_sprintf(buf, "{078}[%3u]{038} %s to %s:{078} %s\n\r", vnum, pnote->sender, pnote->to_list, pnote->subject);
					}
					else
					{
						cat_sprintf (buf, "{178}[%3u]{128}*{138}%s to %s:{178} %s\n\r", vnum, pnote->sender, pnote->to_list, pnote->subject);
					}
				}
				vnum++;
			}
			cat_sprintf(buf, "{078}The {128}*{078} denotes an unread note.\n\r");
			send_to_char_color (buf, ch );
		}
		else
		{
			send_to_char_color (buf, ch);
			vnum = 0;

			for (buf[0] = '\0', pnote = mud->f_note ; pnote ; pnote = pnote->next)
			{
				if (is_note_to(ch, pnote) && pnote->room_vnum == room_vnum)
				{
					if (room_vnum || pnote->time <= ch->pcdata->topic_stamp[pnote->topic])
					{
						cat_sprintf(buf, "{078}[%3u]{038} %s to %s:{078} %s\n\r", vnum, pnote->sender, pnote->to_list, pnote->subject);
					}
					else
					{
						cat_sprintf(buf, "{178}[%3u]{128}*{138}%s to %s:{178} %s\n\r", vnum, pnote->sender, pnote->to_list, pnote->subject);
					}
				}
				vnum++;
			}
			cat_sprintf(buf, "{078}The {128}*{078} denotes an unread note.\n\r");
			send_to_char_color (buf, ch );
		}
		pop_call();
		return;
	}

	if (!strcasecmp (arg, "next"))
	{
		vnum = 0;

		for (pnote = mud->f_note ; pnote ; pnote = pnote->next, vnum++)
		{
			if (pnote->time <= ch->pcdata->last_note)
			{
				continue;
			}
			if (is_note_to(ch, pnote) && pnote->room_vnum == room_vnum)
			{
				sprintf(buf, "{178}[%3u]{138} %s:{178} %s\n\r%s\t\tTopic: %s\n\rTo: %s{078}\n\r", vnum, pnote->sender, pnote->subject, pnote->date, room_vnum ? "Private Note Board" : topic_table[pnote->topic].name, pnote->to_list);

				ch_printf_color(ch, "%s%s", buf, ansi_justify(pnote->text, 80));

				if (ch->pcdata->topic_stamp[pnote->topic] < pnote->time)
				{
					ch->pcdata->topic_stamp[ch->pcdata->note_topic] = pnote->time;
				}
				ch->pcdata->last_note = pnote->time;
				pop_call();
				return;
			}
		}
		send_to_char ("End of notes.\n\r", ch);
		pop_call();
		return;
	}

	if (!strcasecmp(arg, "unread") || arg[0] == '\0')
	{
		vnum = 0;

		if (room_vnum)
		{
			ch->pcdata->note_topic = 6;
		}

		for (pnote = mud->f_note ; pnote ; pnote = pnote->next, vnum++)
		{
			if (pnote->time <= ch->pcdata->topic_stamp[ch->pcdata->note_topic])
			{
				continue;
			}

			if (pnote->topic != ch->pcdata->note_topic)
			{
				continue;
			}

			if (is_note_to(ch, pnote) && pnote->room_vnum == room_vnum)
			{
				sprintf(buf, "{178}[%3u]{138} %s:{178} %s\n\r%s\t\tTopic: %s\n\rTo: %s{078}\n\r", vnum, pnote->sender, pnote->subject, pnote->date, room_vnum ? "Private Note Board" : topic_table[pnote->topic].name, pnote->to_list);

				ch_printf_color(ch, "%s%s", buf, ansi_justify(pnote->text, 80));

				if (ch->pcdata->topic_stamp[pnote->topic] < pnote->time)
				{
					ch->pcdata->topic_stamp[ch->pcdata->note_topic] = pnote->time;
				}
				ch->pcdata->last_note = pnote->time;
				pop_call();
				return;
			}
		}
		if (!room_vnum && ch->pcdata->note_topic < 5)
		{
			ch->pcdata->note_topic++;
			ch_printf_color(ch, "You've read all the notes in this topic. Proceeding to %s.\n\r", topic_table[ch->pcdata->note_topic].name);
			do_note(ch, "unread");
		}
		else
		{
			send_to_char ("End of notes.\n\r", ch);
			ch->pcdata->note_topic = 0;
		}
		pop_call();
		return;
	}

	if (!strcasecmp(arg, "catchup"))
	{
		for (vnum = 0 ; vnum < MAX_TOPIC ; vnum++)
		{
			ch->pcdata->topic_stamp[vnum] = mud->current_time;
		}
		ch->pcdata->last_note = mud->current_time;

		ch_printf_color(ch, "All notes have been marked as read.\n\r");

		pop_call();
		return;
	}

	if (!strcasecmp(arg, "read") || is_number(arg))
	{
		int old_topic = ch->pcdata->note_topic;

		if (is_number(arg))
		{
			anum = atol(arg);
		}
		else
		{
			if (is_number(argument))
			{
				anum = atol(argument);
			}
			else
			{
				send_to_char("Read what note number?\n\r", ch );
				pop_call();
				return;
			}
		}

		vnum = 0;

		for (pnote = mud->f_note ; pnote ; pnote = pnote->next)
		{
			ch->pcdata->note_topic = pnote->topic;

			if (is_note_to(ch, pnote) && vnum == anum && pnote->room_vnum == room_vnum)
			{
				sprintf(buf, "{178}[%3u]{138} %s:{178} %s\n\r%s\t\tTopic: %s\n\rTo: %s{078}\n\r", vnum, pnote->sender, pnote->subject, pnote->date, room_vnum ? "Private Note Board" : topic_table[pnote->topic].name, pnote->to_list);

				ch_printf_color(ch, "%s%s", buf, ansi_justify(pnote->text, 80));

				ch->pcdata->last_note = pnote->time;
				if (ch->pcdata->topic_stamp[pnote->topic] < pnote->time)
				{
					ch->pcdata->topic_stamp[pnote->topic] = pnote->time;
				}
				ch->pcdata->note_topic = old_topic;
				pop_call();
				return;
			}
			vnum++;
		}
		ch->pcdata->note_topic = old_topic;

		send_to_char ("No such note.\n\r", ch);
		pop_call();
		return;
	}

	if (!strcasecmp (arg, "subject"))
	{
		note_attach (ch);
		STRFREE(ch->pcdata->pnote->subject);
		ch->pcdata->pnote->subject = STRALLOC (argument);
		send_to_char ("Ok.\n\r", ch);
		pop_call();
		return;
	}

	if (!strcasecmp (arg, "to"))
	{
		note_attach (ch);
		STRFREE (ch->pcdata->pnote->to_list);
		ch->pcdata->pnote->to_list = STRALLOC (argument);
		send_to_char ("Ok.\n\r", ch);
		pop_call();
		return;
	}

	if (!strcasecmp (arg, "write"))
	{
		note_attach (ch);
		ch->pcdata->editmode = MODE_WRITING_NOTE;
		ch->pcdata->tempmode = MODE_NONE;
		start_editing (ch, ch->pcdata->pnote->text);
		pop_call();
		return;
	}

	if (!strcasecmp (arg, "reply"))
	{
		NOTE_DATA *oldNote;

		if (!is_number(argument))
		{
			send_to_char ("Use: note reply <note number>.\n\r", ch);
			pop_call();
			return;
		}
		vnum = atol (argument);
		anum = 0;
		for (oldNote = mud->f_note ; oldNote ; oldNote = oldNote->next)
		{
			if (anum++ == vnum)
			{
				break;
			}
		}

		if (oldNote == NULL)
		{
			sprintf (buf, "There is no note #%u.\n\r", vnum);
			send_to_char (buf, ch);
			pop_call();
			return;
		}

		if (oldNote->topic == 0 && oldNote->room_vnum == 0 && !IS_GOD(ch))
		{
			ch_printf_color(ch, "You are not allowed to reply on the Announcements topic.\n\r");
			pop_call();
			return;
		}

		if (oldNote->room_vnum != room_vnum)
		{
			send_to_char ("There is no such note.\n\r", ch);
			pop_call();
			return;
		}

		note_attach (ch);
		STRFREE (ch->pcdata->pnote->to_list);
		STRFREE (ch->pcdata->pnote->subject);

		if (is_name ("all", oldNote->to_list) || is_name (oldNote->sender, oldNote->to_list))
		{
			strcpy (buf, oldNote->to_list);
		}
		else
		{
			sprintf (buf, "%s %s", oldNote->sender, oldNote->to_list);
		}
		ch->pcdata->pnote->to_list = STRALLOC (buf);
		ch->pcdata->pnote->topic   = oldNote->topic;

		if (oldNote->subject[0] == 'R' && oldNote->subject[1] == 'e' && oldNote->subject[2] == ':' && oldNote->subject[3] == ' ')
		{
			ch->pcdata->pnote->subject = STRALLOC (oldNote->subject);
		}
		else
		{
			sprintf (buf, "Re: %s", oldNote->subject);
			ch->pcdata->pnote->subject = STRALLOC (buf);
		}

		sprintf (buf, "%s: %s\n\rTo: %s\n\r", ch->pcdata->pnote->sender, ch->pcdata->pnote->subject, ch->pcdata->pnote->to_list);
		send_to_char (buf, ch);
		pop_call();
		return;
	}

	if (!strcasecmp (arg, "clear"))
	{
		if (ch->pcdata->pnote != NULL)
		{
			STRFREE (ch->pcdata->pnote->text);
			STRFREE (ch->pcdata->pnote->subject);
			STRFREE (ch->pcdata->pnote->to_list);
			STRFREE (ch->pcdata->pnote->date);
			STRFREE (ch->pcdata->pnote->sender);
			FREEMEM (ch->pcdata->pnote);
			ch->pcdata->pnote = NULL;
		}
		else
		{
			send_to_char("There was nothing to clear.\n\r",ch);
		}
		send_to_char ("Ok.\n\r", ch);
		pop_call();
		return;
	}

	if (!strcasecmp (arg, "show"))
	{
		if (ch->pcdata->pnote == NULL)
		{
			send_to_char ("You have no note in progress.\n\r", ch);
			pop_call();
			return;
		}
		ch_printf_color(ch, "{138}%s:{178} %s\n\rTo: %s\n\r", ch->pcdata->pnote->sender, ch->pcdata->pnote->subject, ch->pcdata->pnote->to_list);
		ch_printf_color(ch, "{078}%s", ansi_justify(ch->pcdata->pnote->text, 80));
		pop_call();
		return;
	}

	if (!strcasecmp (arg, "post"))
	{
		FILE *fp;
		bool save_all = FALSE, can_announce = FALSE;
		NOTE_DATA *pnote_next;

		if (!IS_GOD(ch))
		{
			can_announce = FALSE;
		}
		else
		{
			can_announce = TRUE;
		}

		if (ch->pcdata->pnote == NULL)
		{
			send_to_char ("You have no note in progress.\n\r", ch);
			pop_call();
			return;
		}

		if (!strcasecmp(ch->pcdata->pnote->to_list, ""))
		{
			send_to_char ("This note is addressed to no one!\n\r", ch);
			pop_call();
			return;
		}

		if (!strcasecmp (ch->pcdata->pnote->subject, ""))
		{
			send_to_char ("This note has no subject.\n\r", ch);
			pop_call();
			return;
		}

		if (!strcasecmp (ch->pcdata->pnote->text, ""))
		{
			send_to_char ("There is nothing written on this note.\n\r", ch);
			pop_call();
			return;
		}

		if (ch->pcdata->pnote->topic == -1)
		{
			if (room_vnum)
			{
				ch->pcdata->pnote->topic = MAX_TOPIC -1;
			}
			else if (is_number(argument))
			{
				if (atol(argument) >= 0 && atol(argument) < MAX_TOPIC - 1)
				{
					ch->pcdata->pnote->topic = atol(argument);
				}
			}
		}

		if (ch->pcdata->pnote->topic == 0 && can_announce == FALSE)
		{
			send_to_char("You are not allowed to post on the Announcements topic.\n\r", ch);
			pop_call();
			return;
		}

		if (ch->pcdata->pnote->topic == -1)
		{
			ch_printf_color(ch, "You must specify a valid topic number for this note to be posted under.\n\rAvailable topics are:\n\r\n\r");

			for (cnt = !can_announce ; cnt < MAX_TOPIC - 1 ; cnt++)
			{
				if (ch->level >= topic_table[cnt].min_level)
				{
					ch_printf_color(ch, "{038}%2d  {078}%s\n\r", cnt, topic_table[cnt].name);
				}
			}
			pop_call();
			return;
		}

		ch->pcdata->pnote->date = STRALLOC(get_time_string(mud->current_time));

		if (IS_MUTED(ch))
		{
			ch->pcdata->pnote->time = 0;
		}
		else
		{
			ch->pcdata->pnote->time = mud->current_time;
		}
		if (IS_SET(ch->in_room->room_flags, ROOM_NOTE_BOARD))
		{
			ch->pcdata->pnote->room_vnum = ch->in_room->vnum;
		}
		else
		{
			ch->pcdata->pnote->room_vnum = 0;
		}
		LINK(ch->pcdata->pnote, mud->f_note, mud->l_note, next, prev);

		for (pnote = mud->f_note ; pnote->next ; pnote = pnote_next)
		{
			pnote_next = pnote->next;

			if (pnote->time <= mud->current_time - 60 * 60 * 24 * 7 * 4)
			{
				UNLINK(pnote, mud->f_note, mud->l_note, next, prev);

				STRFREE (pnote->text);
				STRFREE (pnote->subject);
				STRFREE (pnote->to_list);
				STRFREE (pnote->date);
				STRFREE (pnote->sender);
				FREEMEM (pnote);
				save_all = TRUE;
			}
		}
		ch->pcdata->pnote = NULL;

		/*  Added for recieving mail while on the game.  Chaos 12/15/93  */

		if (!room_board)
		{
			for (fch = mud->f_player ; fch ; fch = fch->next)
			{
				if (is_name(fch->ch->name, pnote->to_list)
				|| (is_name("immortal", pnote->to_list) && IS_IMMORTAL(fch->ch)))
				{
					send_to_char_color("{088}{506}You just received a note.{088}\n\r", fch->ch);
				}
			}
		}

		close_reserve();

		if (!save_all)
		{
			fp = my_fopen(NOTE_FILE, "a", FALSE);
			fprintf (fp, "Sender  %s~\nDate    %s~\nTime   %ld\nTo      %s~\nSubject %s~\nTopic   %d\nText\n%s~\nRoom %u\n\n", pnote->sender, pnote->date, pnote->time, pnote->to_list, pnote->subject, pnote->topic, fixer(pnote->text), pnote->room_vnum);
			my_fclose(fp);
		}
		else
		{
			fp = my_fopen (NOTE_FILE, "w", FALSE);

			for (pnote = mud->f_note ; pnote ; pnote = pnote->next)
			{
				fprintf (fp, "Sender  %s~\nDate    %s~\nTime   %ld\nTo      %s~\nSubject %s~\nTopic   %d\nText\n%s~\nRoom %u\n\n", pnote->sender, pnote->date, pnote->time, pnote->to_list, pnote->subject, pnote->topic, fixer(pnote->text), pnote->room_vnum);
			}
			my_fclose(fp);
		}

		open_reserve();

		if (!room_board)
		{
			send_to_char ("Ok.\n\r", ch);
		}
		else
		{
			send_to_char ("You post your note onto the noteboard.\n\r", ch);
		}
		pop_call();
		return;
	}

	if (!strcasecmp (arg, "remove"))
	{
		if (!is_number (argument))
		{
			send_to_char ("Note remove which number?\n\r", ch);
			pop_call();
			return;
		}
		anum = atol (argument);
		vnum = 0;
		for (pnote = mud->f_note ; pnote ; pnote = pnote->next)
		{
			if (is_note_to(ch, pnote) && vnum == anum)
			{
				note_remove (ch, pnote, FALSE);
				send_to_char ("Ok.\n\r", ch);
				pop_call();
				return;
			}
			vnum++;
		}
		send_to_char ("No such note.\n\r", ch);
		pop_call();
		return;
	}

	if (is_name(arg, "delete"))
	{
		if (!is_number(argument))
		{
			send_to_char ("Note delete which number?\n\r", ch);
			pop_call();
			return;
		}

		anum = atol (argument);
		vnum = 0;
		for (pnote = mud->f_note ; pnote ; pnote = pnote->next)
		{
			if (vnum == anum)
			{
				if (IS_IMMORTAL(ch) || !strcmp(ch->name, pnote->sender))
				{
					note_remove(ch, pnote, TRUE);
					ch_printf_color(ch, "Note %d has been deleted.\n\r", anum);
				}
				else
				{
					ch_printf_color(ch, "You can only delete your own notes.\n\r");
				}
				pop_call();
				return;
			}
			vnum++;
		}
		send_to_char ("No such note.\n\r", ch);
		pop_call();
		return;
	}

	if (!strcasecmp (arg, "topic"))
	{
		if (room_board)
		{
			send_to_char ("You are in a room containing a note board.\n\r", ch);
			pop_call();
			return;
		}
		if (is_number (argument))
		{
			bool num = atol(argument);

			if (num >= MAX_TOPIC - 1)
			{
				send_to_char("There is no such topic.\n\r", ch);
				pop_call();
				return;
			}
			ch->pcdata->note_topic = num;
			ch_printf_color( ch, "Note topic changed to: %s\n\r", topic_table[ch->pcdata->note_topic].name);
			pop_call();
			return;
		}
		else
		{
			int unreadNum[MAX_TOPIC]	= {0};
			int oldTopic			= ch->pcdata->note_topic;

			for (pnote = mud->f_note ; pnote ; pnote = pnote->next)
			{
				ch->pcdata->note_topic = pnote->topic;

				if (is_note_to(ch, pnote) && pnote->room_vnum == room_vnum && pnote->time > ch->pcdata->topic_stamp[pnote->topic])
				{
					unreadNum[pnote->topic]++;
				}
			}

			ch->pcdata->note_topic = oldTopic;

			send_to_char_color ("{178}List of note topics:\n\r\n\r", ch);
			send_to_char_color ("{138}Reference Number    {178}Topic Name:                 {128}Unread Notes\n\r", ch);
			for (cnt = 0 ; cnt < MAX_TOPIC - 1 ; cnt++)
			{
				if (ch->level >= topic_table[cnt].min_level)
				{
					ch_printf_color(ch, "{038}%2d                  {078}%-25s  {028}%2d\n\r", cnt, topic_table[cnt].name, unreadNum[cnt]);
				}
			}
		}
		pop_call();
		return;
	}
	send_to_char ("Huh?  Type 'help note' for usage.\n\r", ch);
	pop_call();
	return;
}

/*
 * new channel function. Pipe channel broadcasts thru this - Kregor
 */
void broadcast_channel( CHAR_DATA *ch, char *txt, int channel )
{
	PLAYER_GAME *fpl;
	int hearing;
	char clr[20];

	push_call("broadcast_channel(%p,%p,%p)",ch,txt,channel);

	if (channel == CHANNEL_CHAT && !IS_GOD(ch) && in_area(ch, ROOM_VNUM_ABYSS))
	{
		act("You cannot chat while in the abyss.", ch, NULL, NULL, TO_CHAR);
		pop_call();
		return;
	}
	for (fpl = mud->f_player ; fpl ; fpl = fpl->next)
	{
		if (!IS_IMMORTAL(fpl->ch) && blocking(fpl->ch, ch))
			continue;
		if (fpl->ch == ch)
			continue;

		hearing = fpl->ch->pcdata->channel;

		if (IS_SET(channel, CHANNEL_IMMTALK|CHANNEL_LOGSPEECH) && !IS_IMMORTAL(fpl->ch))
			continue;
		if (IS_SET(channel, CHANNEL_QUESTION) && !IS_IMMORTAL(fpl->ch) && !PLR_FUNCTION(fpl->ch, FUNCTION_HELPER))
			continue;
		if (IS_SET(channel, CHANNEL_CHAT) && !IS_SET(hearing, CHANNEL_CHAT))
			continue;
		if (IS_SET(channel, CHANNEL_ALERTS) && !IS_SET(hearing, CHANNEL_ALERTS))
			continue;
		if (IS_SET(channel, CHANNEL_IMMTALK) && !IS_SET(hearing, CHANNEL_IMMTALK))
			continue;
		if (IS_SET(channel, CHANNEL_QUESTION) && !IS_SET(hearing, CHANNEL_QUESTION))
			continue;
		if (IS_SET(channel, CHANNEL_LOGSPEECH) && !IS_SET(hearing, CHANNEL_LOGSPEECH))
			continue;
		if (IS_SET(channel, CHANNEL_PLAYER) && !IS_SET(hearing, CHANNEL_PLAYER))
			continue;

		switch(channel)
		{
			default:
				strcpy(clr, get_color_string(fpl->ch, COLOR_TEXT, VT102_DIM));
				break;
			case CHANNEL_CHAT:
				strcpy(clr, get_color_string(fpl->ch, COLOR_CHAT, VT102_BOLD));
				break;
			case CHANNEL_ALERTS:
				strcpy(clr, get_color_string(fpl->ch, COLOR_ALERTS, VT102_BOLD));
				break;
			case CHANNEL_IMMTALK:
				strcpy(clr, get_color_string(fpl->ch, COLOR_TALK, VT102_BOLD));
				break;
			case CHANNEL_QUESTION:
				strcpy(clr, get_color_string(fpl->ch, COLOR_QUESTION, VT102_BOLD));
				break;
			case CHANNEL_LOGSPEECH:
				strcpy(clr, get_color_string(fpl->ch, COLOR_TALK, VT102_DIM));
				break;
		}

		ch_printf_color(fpl->ch, "%s%s\n\r", clr, ansi_justify(txt, get_page_width(fpl->ch)));
	}
	pop_call();
	return;
}

void do_immtalk (CHAR_DATA * ch, char *argument)
{
	char buf[MAX_STRING_LENGTH];
	char text2[MAX_STRING_LENGTH];

	push_call("do_immtalk(%p,%p)",ch,argument);

	if (argument[0] == '\0')
	{
		send_to_char ("Immtalk what?\n\r", ch);
		pop_call();
		return;
	}

	strcpy(text2, argument);

	sprintf(buf, "[IMM] %s: %s", ch->name, text2);

	broadcast_channel(ch, buf, CHANNEL_IMMTALK);

	pop_call();
	return;
}

/* This allows a player to greet other characters.
 * either a targeted victim, or ALL to greet the
 * the entire room. This writes to the target's greeted array
 */
void do_greet( CHAR_DATA *ch, char *argument )
{
	CHAR_DATA *victim;
	CHAR_DATA *fch;
	char arg[MAX_INPUT_LENGTH];

	push_call("do_greet(%p,%p)",ch,argument);
	
	argument = one_argument(argument, arg);

	if (IS_NPC(ch))
	{
		pop_call();
		return;
	}
	
	if (arg[0] == '\0')
	{
		send_to_char ("Greet whom?\n\r", ch);
		pop_call();
		return;
	}
	if ((victim = get_char_room(ch, arg)) == NULL && strcasecmp(arg,"all"))
	{
		send_to_char ("They aren't here.\n\r", ch);
		pop_call();
		return;
	}
  if (ch == victim)
  {
		send_to_char_color("You greet yourself?\n\r", ch);
		pop_call();
		return;
	}

	if (!strcasecmp(arg,"all"))
	{
		for (fch = ch->in_room->first_person ; fch ; fch = fch->next_in_room)
		{
			if (fch == ch)
				continue;
			
			if (IS_NPC(fch))
				continue;
			
			if (!can_see(fch, ch))
				continue;
				
			if (!IS_AWAKE(fch))
				continue;
				
			if (fch->pcdata->greeted[ch->pcdata->pvnum])
				continue;
			
			fch->pcdata->greeted[ch->pcdata->pvnum] = 1;
			act("$n greets you.", ch, NULL, fch, TO_VICT);
		}
		act("You greet everyone in the room.", ch, NULL, NULL, TO_CHAR);
		pop_call();
		return;
	}
	
	if (!can_see(victim, ch))
	{
		act("$N cannot see you to greet you.", ch, NULL, victim, TO_CHAR);
		pop_call();
		return;
	}

	if (IS_NPC(victim))
	{
		mprog_do_greet_trigger(ch, victim);
		pop_call();
		return;
	}
	
	if (victim->pcdata->greeted[ch->pcdata->pvnum] > 0)
	{
		send_to_char_color("You have already greeted that person.\n\r", ch);
		pop_call();
		return;
	}
	
	victim->pcdata->greeted[ch->pcdata->pvnum] = 1;
	act("$n greets you.", ch, NULL, victim, TO_VICT);
	act("You greet $N", ch, NULL, victim, TO_CHAR);
	pop_call();
	return;
}

/*
 * Send a VT100 beep to a character or the whole room
 */
void do_beep (CHAR_DATA * ch, char *argument)
{
	CHAR_DATA *fch;

	push_call("do_beep(%p,%p)",ch,argument);

	if (argument[0] == '\0')	/*  Beeping Room */
	{
		for (fch = ch->in_room->first_person ; fch ; fch = fch->next_in_room)
		{
			if (!IS_NPC(fch) && fch != ch)
			{
				if (blocking(fch, ch))
				{
					continue;
				}
				if (IS_SET(fch->pcdata->vt100_flags, VT100_BEEP))
				{
					ch_printf_color(fch, "%s beeped.\007\n\r", get_name(ch));
				}
				else
				{
					ch_printf_color(fch, "%s beeped.\n\r", get_name(ch));
				}
			}
		}
		send_to_char ("You beep the room.\n\r", ch);
		pop_call();
		return;
	}

	if ((fch = get_player_world(ch, argument)) == NULL)
	{
		send_to_char ("They aren't here.\n\r", ch);
		pop_call();
		return;
	}

	if (blocking (fch, ch))
	{
		ch_printf_color(ch, "%s refuses to hear you.\n\r", capitalize(get_name(fch)));
		pop_call();
		return;
	}
	if (IS_SET(fch->pcdata->vt100_flags, VT100_BEEP))
	{
		ch_printf_color(fch, "%s beeped.\007\n\r", get_name(ch));
	}
	else
	{
		ch_printf_color(fch, "%s beeped.\n\r", get_name(ch));
	}
	send_to_char ("You beep.\n\r", ch);
	pop_call();
	return;
}


/*
 * OOC Chat channel command
 */
void do_chat (CHAR_DATA * ch, char *argument)
{
	char jbuf[MAX_STRING_LENGTH];
	char text2[MAX_STRING_LENGTH];

	push_call("do_chat(%p,%p)",ch,argument);

	if (argument[0] == '\0')
	{
		send_to_char ("Chat what?\n\r", ch);
		pop_call();
		return;
	}

	if (!IS_NPC(ch) && !IS_SET(ch->pcdata->channel, CHANNEL_CHAT))
	{
		send_to_char ("You decided not to chat.\n\r", ch);
		pop_call();
		return;
	}

	if (IS_SILENCED(ch))
	{
		send_to_char ("You cannot talk.\n\r", ch);
		pop_call();
		return;
	}

	if (NEW_AUTH(ch)) /* new auth */    
	{
		send_to_char( "You cannot chat until you advance past training.\r\n", ch);
		send_to_char( "Please use QUESTION if you need help.\r\n", ch);
		pop_call();
		return;
	}
	
	strcpy(text2, argument);
	
	ch_printf_color(ch, "%s[CHAT] You: %s\n\r", get_color_string(ch, COLOR_CHAT, VT102_BOLD), ansi_justify(text2, get_page_width(ch)));

	sprintf(jbuf, "[CHAT] %s: %s", capitalize(get_name(ch)), text2);

	broadcast_channel(ch, jbuf, CHANNEL_CHAT);

	pop_call();
	return;
}

/*
 * Question and answer channels for support use
 */
void do_question (CHAR_DATA * ch, char *argument)
{
	char jbuf[MAX_STRING_LENGTH];
	char text2[MAX_STRING_LENGTH];

	push_call("do_question(%p,%p)",ch,argument);

	if (IS_NPC(ch))
	{
		pop_call();
		return;
	}

	if (argument[0] == '\0')
	{
		send_to_char ("What question do you want to ask?\n\r", ch);
		pop_call();
		return;
	}

	if (IS_SILENCED(ch))
	{
		send_to_char ("You cannot talk.\n\r", ch);
		pop_call();
		return;
	}

	strcpy(text2, argument);

	sprintf (jbuf, "[QUESTION] You question: %s", text2);
	ch_printf_color(ch, "%s%s\n\r", get_color_string(ch, COLOR_QUESTION, VT102_BOLD), ansi_justify(jbuf, get_page_width(ch)));

	sprintf(jbuf, "[QUESTION] %s questions: %s", get_name (ch), text2);

	broadcast_channel(ch, jbuf, CHANNEL_QUESTION);

	pop_call();
	return;
}

void do_answer (CHAR_DATA * ch, char *argument)
{
	CHAR_DATA *victim;
	char arg[MAX_INPUT_LENGTH];
	char jbuf[MAX_STRING_LENGTH];
	char text2[MAX_STRING_LENGTH];

	push_call("do_answer(%p,%p)",ch,argument);

	if (IS_NPC(ch))
	{
		pop_call();
		return;
	}

	if (argument[0] == '\0')
	{
		send_to_char ("Who do you want to answer?\n\r", ch);
		pop_call();
		return;
	}

	if (IS_SILENCED(ch))
	{
		send_to_char ("You cannot talk.\n\r", ch);
		pop_call();
		return;
	}
	
	argument = one_argument(argument, arg);
	
	if ((victim = get_player_world_even_blinded(arg)) == NULL)
	{
		send_to_char ("There's no one playing by that name.\n\r", ch);
		pop_call();
		return;
	}

	strcpy(text2, argument);

	sprintf (jbuf, "[ANSWER] You answer %s: %s", get_name(victim), text2);
	ch_printf_color(ch, "%s%s\n\r", get_color_string(ch, COLOR_QUESTION, VT102_BOLD), ansi_justify(jbuf, get_page_width(ch)));

	sprintf (jbuf, "[ANSWER] %s answers you: %s", get_name(ch), text2);
	ch_printf_color(victim, "%s%s\n\r", get_color_string(victim, COLOR_QUESTION, VT102_BOLD), ansi_justify(jbuf, get_page_width(victim)));

	sprintf(jbuf, "[ANSWER] %s answers %s: %s", get_name (ch), get_name(victim), text2);

	broadcast_channel(ch, jbuf, CHANNEL_QUESTION);

	pop_call();
	return;
}

void do_buffer (CHAR_DATA * ch, char *argument)
{
	DESCRIPTOR_DATA *d;
	int page, pt, cnt, ct, pages;
	char buf[MAX_STRING_LENGTH], buf2[MAX_INPUT_LENGTH];
	bool done;

	push_call("do_buffer(%p,%p)",ch,argument);

	if (IS_NPC(ch))
	{
		pop_call();
		return;
	}

	d = ch->desc;

	if (d->original != NULL || d->character != ch)
	{
		pop_call();
		return;
	}

	if (argument[0] != '\0')
	{
		pages = UMAX(0, atol(argument));
	}
	else
	{
		pages = 1;
	}

	done = FALSE;
	page = get_pager_breakpt(ch) -1;

	ct = 0 - page;

	if (pages > 99)
	{
		pages = 99;
	}

	for (pt = ch->pcdata->scroll_end ; !done ; pt--)
	{
		if (ch->pcdata->scroll_buf[pt] == '\n')
		{
			ct++;
		}
		if (ct > page * pages)
		{
			done = TRUE;
		}
		if ((ch->pcdata->scroll_start == 0 && pt == 0) || pt == ch->pcdata->scroll_end + 1 || (pt == 0 && ch->pcdata->scroll_end == MAX_BUFFER_LENGTH - 1))
		{
			send_to_char ("That is too far back.\n\r", ch);
			pop_call();
			return;
		}
		if (pt == 0)
		{
			pt = MAX_BUFFER_LENGTH;
		}
	}
	pt++;
	if (pt >= MAX_BUFFER_LENGTH)
	{
		pt = 0;
	}
	while (ch->pcdata->scroll_buf[pt] == '\n' || ch->pcdata->scroll_buf[pt] == '\r')
	{
		pt++;
		if (pt >= MAX_BUFFER_LENGTH)
		{
			pt = 0;
		}
	}

	sprintf(buf, "%s******************************** Pages back: %2d ********************************\n\r", get_color_string(ch, COLOR_PROMPT, VT102_BOLD), pages);
	strcat(buf, get_color_string(ch, COLOR_TEXT, VT102_DIM));

	done = FALSE;
	cnt  = strlen(buf);

	for (ct = 0 ; !done ; cnt++)
	{
		if (cnt >= MAX_STRING_LENGTH-300)
		{
			done = TRUE;
		}
		buf[cnt] = ch->pcdata->scroll_buf[pt];

		if (++pt >= MAX_BUFFER_LENGTH)
		{
			pt = 0;
		}

		if (buf[cnt] == '\n')
		{
			ct++;
		}
		if (ct >= page && buf[cnt] != '\n' && buf[cnt] != '\r')
		{
			done = TRUE;
		}
	}

	buf[cnt - 1] = '\0';

	sprintf(buf2, "%s******************************** Pages back: %2d ********************************\n\r", get_color_string(ch, COLOR_PROMPT, VT102_BOLD), pages);
	strcat(buf, buf2);

	SET_BIT(ch->pcdata->interp, INTERP_SCROLL);
	send_to_char(buf, ch);
	REMOVE_BIT(ch->pcdata->interp, INTERP_SCROLL);

	pop_call();
	return;
}

/*
	1) Find a block of text, this will be done by looking for \r+color or \n
	2) Search the block of text for the search key.
	3) Store the block of text
	4) Return at end of search or 10 blocks of text are found
	5) Buffer is searched downwards from scroll_end till scroll_end + 1
	   unless scroll_start == 0 (means buffer hasn't been filled yet)
	-) Rewritten by Scandum 24-05-2002
*/

void do_grep (CHAR_DATA * ch, char *argument)
{
	int pt, ct, st, cnt, fnd, fnd_max, lend, lstart;
	char buf[MAX_STRING_LENGTH];
	char buf2[MAX_INPUT_LENGTH];
	char buf3[MAX_INPUT_LENGTH];
	char lines[20][MAX_INPUT_LENGTH];

	push_call("do_grep(%p,%p)",ch,argument);

	if (IS_NPC(ch))
	{
		pop_call();
		return;
	}

	if (argument[0] == '\0')
	{
		send_to_char ("Grep what?\n\r", ch);
		pop_call();
		return;
	}

	str_cpy_max(buf2, argument, 40);

	fnd_max = URANGE(10, get_pager_breakpt(ch) / 2, 20);

	for (fnd = 0, lend = pt = ch->pcdata->scroll_end ; pt > -2 ; pt--)
	{
		if (ch->pcdata->scroll_start == 0 && pt == -1)
		{
			break;
		}
		if (pt == ch->pcdata->scroll_end + 1)
		{
			break;
		}
		if (pt == -1)
		{
			pt = MAX_BUFFER_LENGTH-1;
		}

		if (ch->pcdata->ansi == FALSE && ch->pcdata->vt100 == 0)
		{
			if (ch->pcdata->scroll_buf[pt] == '\r')
			{
				lstart = pt+1;
			}
			else
			{
				continue;
			}
		}
		else
		{
			if (ch->pcdata->scroll_buf[pt] == '\033'
			&& (pt == 0 || ch->pcdata->scroll_buf[pt-1] == '\r'))
			{
				lstart = pt;
			}
			else
			{
				continue;
			}
		}
		for (ct = lstart ; ct < lend ; ct++)
		{
			if (ct == MAX_BUFFER_LENGTH)
			{
				ct = 0;
			}
			for (cnt = 0, st = ct ; buf2[cnt] != '\0' ; cnt++, st++)
			{
				if (st == MAX_BUFFER_LENGTH)
				{
					st = 0;
				}
				if (buf2[cnt] != ch->pcdata->scroll_buf[st])
				{
					break;
				}
			}
			if (buf2[cnt] == '\0')
			{
				for (cnt = lstart ; cnt < lend ; cnt++)
				{
					if (cnt == MAX_BUFFER_LENGTH)
					{
						cnt = 0;
					}
					lines[fnd][cnt-lstart] = ch->pcdata->scroll_buf[cnt];
				}
				lines[fnd][cnt - lstart] = '\0';

				if (++fnd >= fnd_max)
				{
					pt = -1;
				}
				lend = 0;
			}
		}
		lend = lstart;
	}

	sprintf (buf3, " Search for the word: %s ", buf2);

	if (strlen(buf3) % 2 == 1)
	{
		strcat(buf3, " ");
	}

	strcpy(buf, get_color_string(ch, COLOR_PROMPT, VT102_BOLD));

	for (cnt = 0 ; cnt < 40 - strlen(buf3) / 2 ; cnt++)
	{
		strcat (buf, "*");
	}
	strcat (buf, buf3);
	for (cnt = 0 ; cnt < 40 - strlen(buf3) / 2 ; cnt++)
	{
		strcat (buf, "*");
	}
	strcat(buf, get_color_string(ch, COLOR_TEXT, VT102_DIM));
	strcat(buf, "\n\r");

	if (fnd == 0)
	{
		strcat(buf, ansi_translate_text(ch, "{128}Search string not found.\n\r"));
	}
	else
	{
		for (cnt = fnd-1 ; cnt >= 0 ; cnt--)
		{
			strcat(buf, lines[cnt]);
		}
	}

	strcat(buf, get_color_string(ch, COLOR_PROMPT, VT102_BOLD));
	strcat(buf, "********************************************************************************\n\r");
	SET_BIT(ch->pcdata->interp, INTERP_SCROLL);
	send_to_char (buf, ch);
	REMOVE_BIT(ch->pcdata->interp, INTERP_SCROLL);
	pop_call();
	return;
}


/*
 * parse_tongue: checks to see if a language is enclosed in <>'s
 * before a phrase in speech commands, shifts language spoken for
 * this single phrase only. - Kregor 11/2012
 */
int parse_tongue( CHAR_DATA *ch, char *src, char *output )
{
  char tongue[MAX_STRING_LENGTH+1];
  bool found = FALSE;
	int lang;
	
	push_call("parse_tongue(%p,%p)",ch,src);
	
  if (!src)
  {
  	pop_call();
		return LANG_NONE;
	}

  /* check for language, then parse and discard */
	if (*src == '<')
	{
		src++; // chop off carat
		src = grab_token(src, tongue, '>');

		if (!is_string(src) || !is_string(tongue))
		{
			send_to_char("Say what?\n\r", ch);
			pop_call();
			return -1;
		}
		if (*src == ' ')
			src++;
		
		found = TRUE;
	}
  
  strcpy(output, src);

  /* copy string if language string was found */
  if ( found )
  {
		if ((lang = lookup_tongue(tongue)) == -1)
		{
			send_to_char("There is no such language.\n\r", ch);
			pop_call();
			return -1;
		}
		if (IS_SET(ch->language, 1 << lang))
		{
			if (ch->speak != 1 << lang)
			{
				ch_printf_color(ch, "You speak in %s.\n\r", lang_names[lang]);
				pop_call();
				return lang;
			}
		}
		else
		{
			ch_printf_color(ch, "You cannot speak %s.\n\r", lang_names[lang]);
			pop_call();
			return -1;
		}
	}
	pop_call();
	return LANG_NONE;
}


/*
 * speech commands - all reworked by Kregor
 */
void do_say (CHAR_DATA * ch, char *argument)
{
	CHAR_DATA *fch;
	PLAYER_GAME *fpl;
	char text[MAX_STRING_LENGTH];
	char say_buf[MAX_STRING_LENGTH];
	char ClrSpch[10];
	int tongue;
	int tongue_old = 0;

	push_call("do_say(%p,%p)",ch,argument);

	if (argument[0] == '\0')
	{
		send_to_char ("Say what?\n\r", ch);
		pop_call();
		return;
	}

	if (!CAN_TALK(ch))
	{
		send_to_char("You cannot currently speak.\n\r", ch);
		pop_call();
		return;
	}

	if (IS_SILENCED(ch))
	{
		send_to_char ("You cannot talk.\n\r", ch);
		pop_call();
		return;
	}
	
	if (strstr(argument, "{"))
	{
		send_to_char("No color codes in speech commands!\n\r", ch);
		pop_call();
		return;
	}
	
	//command uses parse_tongue to speak language in <>'s
	tongue_old = 0;

	if ((tongue = parse_tongue(ch, argument, text)) != LANG_NONE)
	{
		if (tongue == -1)
		{
			pop_call();
			return;
		}
		tongue_old = ch->speak;
		ch->speak = 1 << tongue;
	}

	strcpy(ClrSpch, get_color_string(ch, COLOR_SPEECH, VT102_DIM));
// 	strcpy(text, argument);
	
	if (!IS_PLR(ch, PLR_HOLYLIGHT))
	{
		makedrunk(ch, text);
	}

	if (!IS_NPC(ch) && strstr(text, "!"))
	{
		sprintf(say_buf, "You exclaim, %s\"%s\"\n\r", ClrSpch, text);
	}
	else if (!IS_NPC(ch) && strstr(text, "?"))
	{
		sprintf(say_buf, "You ask, %s\"%s\"\n\r", ClrSpch, text);
	}
	else
	{
		sprintf(say_buf, "You say, %s\"%s\"\n\r", ClrSpch, text);
	}
	send_to_char(justify(say_buf, get_page_width(ch)), ch);

	if (IS_AFFECTED(ch, AFF2_CONFUSION) && number_bits(2) != 0)
	{
		act("$n babbles incoherently.", ch, NULL, NULL, TO_ROOM);
		pop_call();
		return;
	}
	
	// adds broadcast of roleplay to audience in arena - Kregor 3/17/12
	if (in_area(ch, ROOM_VNUM_ARENA))
	{
		for (fpl = mud->f_player ; fpl ; fpl = fpl->next)
		{
			if (blocking(fpl->ch, ch))
				continue;
			if (fpl->ch == ch)
				continue;
			if (fpl->ch->in_room == NULL)
				continue;
			if (in_same_room(ch, fpl->ch))
				continue;
			if (!IS_SET(fpl->ch->in_room->room_flags, ROOM_AUDIENCE))
				continue;
	
			sprintf(say_buf, "%s says, %s\"%s\"\n\r", PERS(ch, fpl->ch), ClrSpch, translate_to_char(ch, fpl->ch, text));
			send_to_char(justify(say_buf, get_page_width(fpl->ch)), fpl->ch);
		}
	}

	for (fch = ch->in_room->first_person ; fch ; fch = fch->next_in_room)
	{
		if (ch == fch || fch->position <= POS_SLEEPING)
		{
			continue;
		}

		if (blocking (fch, ch))
		{
			continue;
		}

		if (ch->speak == LANG_THIEVESCANT && !can_understand(fch, ch, FALSE))
		{
			act("$n gestures subtly.", ch, NULL, fch, TO_VICT);
			continue;
		}

		if (!IS_NPC(ch) && strstr(text, "!"))
		{
			sprintf(say_buf, "%s exclaims, %s\"%s\"\n\r", PERS(ch, fch), ClrSpch, translate_to_char(ch, fch, text));
		}
		else if (!IS_NPC(ch) && strstr(text, "?"))
		{
			sprintf(say_buf, "%s asks, %s\"%s\"\n\r", PERS(ch, fch), ClrSpch, translate_to_char(ch, fch, text));
		}
		else
		{
			sprintf(say_buf, "%s says, %s\"%s\"\n\r", PERS(ch, fch), ClrSpch, translate_to_char(ch, fch, text));
		}
		send_to_char(justify(say_buf, get_page_width(fch)), fch);
	}

	if (tongue_old)
		ch->speak = tongue_old;

	if (IS_NPC(ch))
	{
		pop_call();
		return;
	}

	mprog_speech_trigger (argument, ch);
	oprog_speech_trigger (argument, ch);
	rprog_speech_trigger (argument, ch);
	pop_call();
	return;
}

/*
 * SAY command targeted to another character - Kregor
 */
void do_sayto (CHAR_DATA * ch, char *argument)
{
	CHAR_DATA *fch, *victim;
	PLAYER_GAME *fpl;
	char arg[MAX_INPUT_LENGTH];
	char text1[MAX_STRING_LENGTH];
	char text2[MAX_STRING_LENGTH];
	char say_buf[MAX_STRING_LENGTH];
	char ClrSpch[10];
	int tongue, tongue_old;

	push_call("do_sayto(%p,%p)",ch,argument);

	if (!CAN_TALK(ch))
	{
		send_to_char("You cannot currently speak.\n\r", ch);
		pop_call();
		return;
	}

	if (IS_SILENCED(ch))
	{
		send_to_char ("You cannot talk.\n\r", ch);
		pop_call();
		return;
	}

	argument = one_argument(argument, arg);

	if (arg[0] == '\0' || argument[0] == '\0')
	{
		send_to_char ("Say what to whom?\n\r", ch);
		pop_call();
		return;
	}

	if ((victim = get_char_room(ch, arg)) == NULL )
	{
		send_to_char ("They aren't here.\n\r", ch);
		pop_call();
		return;
	}

  if (ch == victim)
  {
		send_to_char ("Talking to yourself is the first sign of insanity.\n\r", ch);
		act( "$n babbles to $mself about random thoughts.", ch, NULL, NULL, TO_ROOM);
		pop_call();
		return;
	}

	if (strstr(argument, "{"))
	{
		send_to_char("No color codes in speech commands!\n\r", ch);
		pop_call();
		return;
	}

	//command uses parse_tongue to speak language in <>'s
	tongue_old = 0;

	if ((tongue = parse_tongue(ch, argument, text2)) != LANG_NONE)
	{
		if (tongue == -1)
		{
			pop_call();
			return;
		}
		tongue_old = ch->speak;
		ch->speak = 1 << tongue;
	}

	strcpy(ClrSpch, get_color_string(ch, COLOR_SPEECH, VT102_DIM));	

	if (!IS_PLR(ch, PLR_HOLYLIGHT))
		makedrunk(ch, text2);

	if (IS_AFFECTED(ch, AFF2_CONFUSION) && number_bits(2) != 0)
	{
		act("$n babbles incoherently.", ch, NULL, NULL, TO_ROOM);
		ch_printf_color(ch, "You say to %s, %s\"%s\"\n\r", PERS(victim, ch), ClrSpch, text2);
		pop_call();
		return;
	}

	for (text1[0] = '\0', fch = ch->in_room->first_person ; fch ; fch = fch->next_in_room)
	{
		if (fch->position <= POS_SLEEPING)
		{
			continue;
		}

		if (blocking (fch, ch))
		{
			continue;
		}
		
		if (!can_understand(fch, ch, TRUE))
		{
			if (ch->speak == LANG_THIEVESCANT)
			{
				act("$n gestures subtly.", ch, NULL, fch, TO_VICT);
				continue;
			}
			if (IS_NPC(fch) && fch == victim)
			{
				act("$N looks as if $E cannot understand you.", ch, NULL, fch, TO_CHAR);
			}
		}

		sprintf(text1, "%s, %s\"%s\"", fch == victim ? "you" : PERS(victim, fch), ClrSpch, translate_to_char(ch, fch, text2));
			
		if (!IS_NPC(ch) && strstr(text1, "!"))
		{
			if (fch == ch)
				sprintf(say_buf, "You exclaim to %s\n\r", text1);
			else
				sprintf(say_buf, "%s exclaims to %s\n\r", PERS(ch, fch), text1);
		}
		else if (!IS_NPC(ch) && strstr(text1, "?"))
		{
			if (fch == ch)
				sprintf(say_buf, "You ask %s\n\r", text1);
			else
				sprintf(say_buf, "%s asks %s\n\r", PERS(ch, fch), text1);
		}
		else
		{
			if (fch == ch)
				sprintf(say_buf, "You say to %s\n\r", text1);
			else
				sprintf(say_buf, "%s says to %s\n\r", PERS(ch, fch), text1);
		}
		send_to_char(justify(say_buf, get_page_width(fch)), fch);
	}

	// adds broadcast of roleplay to audience in arena - Kregor 3/17/12
	if (in_area(ch, ROOM_VNUM_ARENA))
	{
		for (fpl = mud->f_player ; fpl ; fpl = fpl->next)
		{
			if (blocking(fpl->ch, ch))
				continue;
			if (fpl->ch == ch)
				continue;
			if (fpl->ch->in_room == NULL)
				continue;
			if (in_same_room(ch, fpl->ch))
				continue;
			if (!IS_SET(fpl->ch->in_room->room_flags, ROOM_AUDIENCE))
				continue;
	
			sprintf(say_buf, "%s says to %s, %s\"%s\"\n\r", PERS(ch, fpl->ch), PERS(victim, fpl->ch), ClrSpch, translate_to_char(ch, fpl->ch, text2));
			send_to_char(justify(say_buf, get_page_width(fpl->ch)), fpl->ch);
		}
	}

	if (tongue_old)
		ch->speak = tongue_old;

	if (IS_NPC(ch))
	{
		pop_call();
		return;
	}
	mprog_speech_direct_trigger(argument, ch, victim);
	mprog_sayto_trigger(argument, ch, victim);
	pop_call();
	return;
}

/*
 * whisper command allows to communicate between two
 * people in the room, with a possibility of being
 * overheard on a successful listen check - Kregor 6/24/07
 * added direct speech_trigger to allow mobile to be
 * triggered if whispered to - Kregor 4/20/08
 */
void do_whisper (CHAR_DATA * ch, char *argument)
{
	CHAR_DATA *fch;
	char arg[MAX_INPUT_LENGTH];
	char text1[MAX_STRING_LENGTH];
	char text2[MAX_STRING_LENGTH];
	char say_buf[MAX_STRING_LENGTH];
	char ClrSpch[10];
	CHAR_DATA *victim;
	int tongue, tongue_old;

	push_call("do_whisper(%p,%p)",ch,argument);

	if (!CAN_TALK(ch))
	{
		send_to_char("You cannot currently speak.\n\r", ch);
		pop_call();
		return;
	}

	if (IS_SILENCED(ch))
	{
		send_to_char ("You cannot talk.\n\r", ch);
		pop_call();
		return;
	}

	argument = one_argument(argument, arg);

	if (arg[0] == '\0' || argument[0] == '\0')
	{
		send_to_char ("Whisper what to whom?\n\r", ch);
		pop_call();
		return;
	}
	if ((victim = get_char_room(ch, arg)) == NULL )
	{
		send_to_char ("They aren't here.\n\r", ch);
		pop_call();
		return;
	}
  if (ch == victim)
  {
		send_to_char ("Talking to yourself is the first sign of insanity.\n\r", ch);
		act( "$n babbles to $mself about random thoughts.", ch, NULL, NULL, TO_ROOM);
		pop_call();
		return;
	}
	if (blocking (victim, ch) && !IS_IMMORTAL(ch))
	{
		send_to_char ("They are ignoring you.\n\r", ch);
		pop_call();
		return;
	}

	if (strstr(argument, "{"))
	{
		send_to_char("No color codes in speech commands!\n\r", ch);
		pop_call();
		return;
	}

	//command uses parse_tongue to speak language in <>'s
	tongue_old = 0;

	if ((tongue = parse_tongue(ch, argument, text2)) != LANG_NONE)
	{
		if (tongue == -1)
		{
			pop_call();
			return;
		}
		tongue_old = ch->speak;
		ch->speak = 1 << tongue;
	}

	strcpy(ClrSpch, get_color_string(ch, COLOR_SPEECH, VT102_DIM));	
	
	if (!IS_PLR(ch, PLR_HOLYLIGHT))
		makedrunk(ch, text2);

	for (text1[0] = '\0', fch = ch->in_room->first_person ; fch ; fch = fch->next_in_room)
	{
		int check = perception_roll(fch, SENSE_SOUND);

		if (fch->position <= POS_SLEEPING)
		{
			continue;
		}

		if (IS_AFFECTED(fch, AFF_DEAF))
		{
			continue;
		}

		if (blocking (fch, ch))
		{
			continue;
		}

		if (!IS_PLR(fch, PLR_HOLYLIGHT) && fch != ch && fch != victim && check < 15)
		{
			continue;
		}
		if (!IS_PLR(fch, PLR_HOLYLIGHT) && fch != ch && fch != victim && check < 25)
		{
			ch_printf_color(fch, "You notice %s whispering to %s.", PERS(ch,fch), PERS(victim,fch));
			continue;
		}

		if (ch->speak == LANG_THIEVESCANT && !can_understand(fch, ch, FALSE))
		{
			act("$n gestures subtly.", ch, NULL, fch, TO_VICT);
			continue;
		}

		sprintf(text1, "%s, %s'%s'", fch == victim ? "you" : PERS(victim, fch), ClrSpch, translate_to_char(ch, fch, text2));

		if (fch == ch)
		{
			sprintf(say_buf, "You whisper to %s\n\r", text1);
		}
		else
		{
			sprintf(say_buf, "%s whispers to %s\n\r", PERS(ch, fch), text1);
		}
		send_to_char(justify(say_buf, get_page_width(fch)), fch);
	}

	if (tongue_old)
		ch->speak = tongue_old;

	if (IS_NPC(ch))
	{
		pop_call();
		return;
	}
	mprog_speech_direct_trigger(argument, ch, victim);

	pop_call();
	return;
}

/*
 * Edited shout to only allow shout to travel a certain distance - Kregor
 */
void do_shout (CHAR_DATA * ch, char *argument)
{
	DESCRIPTOR_DATA *d;

	char text1[MAX_STRING_LENGTH];
	char text2[MAX_STRING_LENGTH];
	char say_buf[MAX_STRING_LENGTH];
	char ClrSpch[10];
	int range, tongue, tongue_old;

	push_call("do_shout(%p,%p)",ch,argument);

	if (!CAN_TALK(ch))
	{
		send_to_char("You cannot currently speak.\n\r", ch);
		pop_call();
		return;
	}

	if (ch->in_room->area->nplayer == 0)
	{
		pop_call();
		return;
	}

	if (argument[0] == '\0')
	{
		send_to_char ("Shout what?\n\r", ch);
		pop_call();
		return;
	}

	if (IS_SILENCED(ch))
	{
		send_to_char ("You cannot shout.\n\r", ch);
		pop_call();
		return;
	}

	if (NOT_AUTHED(ch))
	{
		send_to_char ("You cannot shout, because you are not authorized.\n\r", ch);
		pop_call();
		return;
	}

	if (strstr(argument, "{"))
	{
		send_to_char("No color codes in speech commands!\n\r", ch);
		pop_call();
		return;
	}
	
	if (ch->speak == LANG_THIEVESCANT)
	{
		send_to_char("You can't shout in gestures.\n\r", ch);
		pop_call();
		return;
	}

	//command uses parse_tongue to speak language in <>'s
	tongue_old = 0;

	if ((tongue = parse_tongue(ch, argument, text2)) != LANG_NONE)
	{
		if (tongue == -1)
		{
			pop_call();
			return;
		}
		tongue_old = ch->speak;
		ch->speak = 1 << tongue;
	}

	strcpy(ClrSpch, get_color_string(ch, COLOR_SPEECH, VT102_BOLD));	
	
	if (!IS_PLR(ch, PLR_HOLYLIGHT))
		makedrunk(ch, text2);

	sprintf(say_buf, "You shout %s\"%s\"\n\r", ClrSpch, text2);
	send_to_char(justify(say_buf, get_page_width(ch)), ch);


	for (text1[0] = '\0', d = mud->f_desc ; d ; d = d->next)
	{
		if (!d->character || d->connected < 0)
		{
			continue;
		}
		if (ch == d->character || d->character->position <= POS_SLEEPING)
		{
			continue;
		}

		range = 10; /* will make function to determine by room flag and sector */

		if (findpath_search_victim(ch, d->character, range) == -1)
		{
			continue;
		}
		if (blocking(d->character, ch))
		{
			continue;
		}

		if (IS_AFFECTED(ch, AFF2_CONFUSION))
		{
			sprintf(say_buf, "%s shouts out in random babblings.\n\r", knows_char(d->character, ch) ? capitalize(get_name(ch)) : "Someone");
			continue;
		}

		sprintf(text1, "%s\"%s\"", ClrSpch, translate_to_char(ch, d->character, text2));

		sprintf(say_buf, "%s shouts, %s\n\r", in_same_room(ch, d->character) ? PERS(ch, d->character) : knows_char(d->character, ch) ? capitalize(get_name(ch)) : "Someone", text1);

		send_to_char(justify(say_buf, get_page_width(d->character)), d->character);
	}
	
	if (tongue_old)
		ch->speak = tongue_old;

	if (IS_NPC(ch))
	{
		pop_call();
		return;
	}
	mprog_speech_trigger(argument, ch);
	
	pop_call();
	return;
}

/*
 * OOC say command - Kregor
 */
void do_osay (CHAR_DATA * ch, char *argument)
{
	CHAR_DATA *fch;
	char text1[MAX_STRING_LENGTH];
	char text2[MAX_STRING_LENGTH];
	char say_buf[MAX_STRING_LENGTH];
	char ClrSpch[10];

	push_call("do_osay(%p,%p)",ch,argument);

	if (argument[0] == '\0')
	{
		send_to_char ("Osay what?\n\r", ch);
		pop_call();
		return;
	}

	if (IS_SILENCED(ch))
	{
		send_to_char ("You cannot talk.\n\r", ch);
		pop_call();
		return;
	}

	if (strstr(argument, "{"))
	{
		send_to_char("No color codes in speech commands!\n\r", ch);
		pop_call();
		return;
	}

	if (strstr(argument, "<"))
	{
		send_to_char("No language codes in OOC speech.\n\r", ch);
		pop_call();
		return;
	}

	strcpy(ClrSpch, get_color_string(ch, COLOR_SPEECH, VT102_DIM));	
	strcpy(text2, argument);

	for (text1[0] = '\0', fch = ch->in_room->first_person ; fch ; fch = fch->next_in_room)
	{
		if (fch->position <= POS_SLEEPING)
		{
			continue;
		}
		if (blocking (fch, ch))
		{
			continue;
		}
		sprintf(text1, "%s%s", ClrSpch, text2);
		
		sprintf(say_buf, "[OOC] %s: %s\n\r", get_name(ch), text1);
		send_to_char(justify(say_buf, get_page_width(fch)), fch);
	}
	
	/* clear out the static buffers to free up the mem */
	text2[0] = '\0';
	text1[0] = '\0';
	
	pop_call();
	return;
}

/*
 * improved MIMIC command, can alternately target
 * victim, or an imaginary subject. Each listener
 * gets a wisdom save against the perform roll of ch
 * Kregor 6/24/07
 */
void do_mimic (CHAR_DATA * ch, char *argument)
{
	char speaker[MAX_INPUT_LENGTH];
	char text[MAX_STRING_LENGTH];
	char say_buf[MAX_STRING_LENGTH];
	char ClrSpch[10];
	CHAR_DATA *vch, *victim;
	PLAYER_GAME *fpl;
	int diceroll, tongue, tongue_old;

	push_call("do_mimic(%p,%p)",ch,argument);

	if (!CAN_TALK(ch))
	{
		send_to_char("You cannot currently speak.\n\r", ch);
		pop_call();
		return;
	}

	if (!learned(ch, gsn_perform))
	{
		send_to_char("You need ranks in Perform to mimic someone.\n\r", ch);
		pop_call();
		return;
	}
	
	if (ch->speak == LANG_THIEVESCANT)
	{
		send_to_char("You can't throw gestures.\n\r", ch);
		pop_call();
		return;
	}

	argument = one_argument_nolower(argument, speaker);

	victim = get_char_room (ch, speaker);

	if (victim && victim->level >= LEVEL_IMMORTAL)
	{
		send_to_char("You cannot victimize that person\n\r.", ch);
	}
	
	if (argument[0] == '\0')
	{
		send_to_char ("What do you want someone to say?\n\r", ch);
		pop_call();
		return;
	}

	if (strstr(argument, "{"))
	{
		send_to_char("No color codes in speech commands!\n\r", ch);
		pop_call();
		return;
	}

	//command uses parse_tongue to speak language in <>'s
	tongue_old = 0;

	if ((tongue = parse_tongue(ch, argument, text)) != LANG_NONE)
	{
		if (tongue == -1)
		{
			pop_call();
			return;
		}
		tongue_old = ch->speak;
		ch->speak = 1 << tongue;
	}
	
	if (!IS_PLR(ch, PLR_HOLYLIGHT))
		makedrunk(ch, text);

	sprintf(ClrSpch, "%s", get_color_string(ch, COLOR_SPEECH, VT102_DIM));
	sprintf(say_buf, "You try to make %s say, %s%s\n\r", victim != NULL ? PERS(victim, ch) : speaker, ClrSpch, text);
	send_to_char_color(justify(say_buf, get_page_width(ch)), ch);

	if (victim)
	{
		sprintf(say_buf, "Someone that sounds like you says, %s%s\n\r", ClrSpch, translate_to_char(ch, victim, text));
		send_to_char_color(justify(say_buf, get_page_width(victim)), victim);
	}

	diceroll = perform_roll(ch);

	for (vch = ch->in_room->first_person ; vch ; vch = vch->next_in_room)
	{
		if (vch == ch)
		{
			continue;
		}
		if (victim && vch == victim)
		{
			continue;
		}
		if (!vch->desc || perception_check(vch, ch, perception_roll(vch, SENSE_SOUND), diceroll))
		{
			sprintf(ClrSpch, "%s", get_color_string(ch, COLOR_SPEECH, VT102_DIM));
			sprintf(say_buf, "Someone nearby says, %s%s\n\r", ClrSpch, translate_to_char(ch, vch, text));
			send_to_char_color(justify(say_buf, get_page_width(vch)), vch);
		}
		else if (!victim)
		{
			sprintf(ClrSpch, "%s", get_color_string(vch, COLOR_SPEECH, VT102_DIM));
			sprintf(say_buf, "%s says, %s%s\n\r", capitalize(speaker), ClrSpch, translate_to_char(ch, vch, text));
			send_to_char_color(justify(say_buf, get_page_width(vch)), vch);
		}
		else
		{
			sprintf(ClrSpch, "%s", get_color_string(victim, COLOR_SPEECH, VT102_DIM));
			sprintf(say_buf, "%s says, %s%s\n\r", capitalize(PERS(victim, vch)), ClrSpch, translate_to_char(ch, vch, text));
			send_to_char_color(justify(say_buf, get_page_width(vch)), vch);
		}
	}

	// adds broadcast of roleplay to audience in arena - Kregor 3/17/12
	if (in_area(ch, ROOM_VNUM_ARENA))
	{
		for (fpl = mud->f_player ; fpl ; fpl = fpl->next)
		{
			if (blocking(fpl->ch, ch))
				continue;
			if (fpl->ch == ch)
				continue;
			if (fpl->ch->in_room == NULL)
				continue;
			if (in_same_room(ch, fpl->ch))
				continue;
			if (!IS_SET(fpl->ch->in_room->room_flags, ROOM_AUDIENCE))
				continue;
	
			if (perception_check(fpl->ch, ch, perception_roll(fpl->ch, SENSE_SOUND), diceroll))
			{
				sprintf(ClrSpch, "%s", get_color_string(ch, COLOR_SPEECH, VT102_DIM));
				sprintf(say_buf, "Someone nearby says, %s%s\n\r", ClrSpch, translate_to_char(ch, fpl->ch, text));
				send_to_char_color(justify(say_buf, get_page_width(fpl->ch)), fpl->ch);
			}
			else if (!victim)
			{
				sprintf(ClrSpch, "%s", get_color_string(fpl->ch, COLOR_SPEECH, VT102_DIM));
				sprintf(say_buf, "%s says, %s%s\n\r", capitalize(speaker), ClrSpch, translate_to_char(ch, fpl->ch, text));
				send_to_char_color(justify(say_buf, get_page_width(fpl->ch)), fpl->ch);
			}
			else
			{
				sprintf(ClrSpch, "%s", get_color_string(victim, COLOR_SPEECH, VT102_DIM));
				sprintf(say_buf, "%s says, %s%s\n\r", capitalize(PERS(victim, fpl->ch)), ClrSpch, translate_to_char(ch, fpl->ch, text));
				send_to_char_color(justify(say_buf, get_page_width(fpl->ch)), fpl->ch);
			}
		}
	}
	if (tongue_old)
		ch->speak = tongue_old;

	pop_call();
	return;
}


/*
 * do_emote was borrowed heavily from the source of
 * RPIMud, and revamped to work with greets, pets and language
 * by Kregor for the Mud20 project 6/29/07
 */
void emote(CHAR_DATA *ch, CHAR_DATA *pet, char *argument)
{
	char buf [MAX_STRING_LENGTH] = { '\0' };
	char copy[MAX_STRING_LENGTH] = { '\0' };
	char key [MAX_STRING_LENGTH] = { '\0' };
	char lang[MAX_STRING_LENGTH];
	bool tochar = FALSE;
	bool is_imote = FALSE;
	bool fSay = FALSE;
	bool fSaid = FALSE;
	OBJ_DATA	*obj = NULL;
	CHAR_DATA *victim, *tch;
	PLAYER_GAME *fpl;
	int  key_e = 0;
	char *p = '\0';
	int tongue = 0;
	int tongue_old = 0;

	push_call("do_emote(%p,%p)",ch,argument);

	if (argument[0] == '$')
	{
		send_to_char ("You cannot start a emote with a target.\n\r", ch);
		pop_call();
		return;
	}

	while (isspace(*argument))
	{
		argument++;
	}
	
	if (*argument == '<')
	{
		argument++;
		argument = grab_token(argument, lang, '>');
		if (!is_string(argument))
		{
			send_to_char ("Emote what?\n\r", ch);
			pop_call();
			return;
		}
		while(isspace(*argument))
		{
			argument++;
		}
		if ((tongue = lookup_tongue(lang)) == -1 || !IS_SET(ch->language, 1 << tongue))
		{
			ch_printf (ch, "You cannot speak in %s.\n\r", lang);
			pop_call();
			return;
		}
	}

	p = copy;

	while(*argument)
	{
		if(*argument == '{')
		{
			send_to_char("No color codes in speech commands!\n\r", ch);
			pop_call();
			return;
		}

		if ( *argument == '@' )
		{
			is_imote = TRUE;
			sprintf (p, "$n");
			p += strlen(p);
			argument++;
		}

		if(*argument == '*') //add in chat/nwn-style emote delimiters
		{
			if ( !fSay )
			{
				if (pet && !CAN_TALK(pet))
				{
					send_to_char("You cannot currently speak.\n\r", ch);
					pop_call();
					return;
				}
				if (!pet && !CAN_TALK(ch))
				{
					send_to_char("You cannot currently speak.\n\r", ch);
					pop_call();
					return;
				}
				if (!pet && IS_AFFECTED(ch, AFF2_CONFUSION))
				{
					send_to_char("You babble incoherently in confusion.\n\r", ch);
					act("$n babbles incohrently.", ch, NULL, NULL, TO_ROOM);
					pop_call();
					return;
				}
				fSay = TRUE;
				fSaid = TRUE;
				sprintf (p, ",%s \"", get_color_string(ch, COLOR_SPEECH, VT102_DIM));
				p += strlen(p);
				argument += 2;
			}
			else
			{
				fSay = FALSE;
				sprintf (p, "\"{300} ");
				p += strlen(p);
				argument++;
			}
		}

		if (*argument == '\"' || *argument == '\'')
		{
			if ( !fSay )
			{
				if (pet && !CAN_TALK(pet))
				{
					send_to_char("Your pet cannot speak.\n\r", ch);
					pop_call();
					return;
				}
				if (!pet && !CAN_TALK(ch))
				{
					send_to_char("You cannot currently speak.\n\r", ch);
					pop_call();
					return;
				}
				if (!pet && IS_AFFECTED(ch, AFF2_CONFUSION))
				{
					send_to_char("You babble incoherently in confusion.\n\r", ch);
					act("$n babbles incohrently.", ch, NULL, NULL, TO_ROOM);
					pop_call();
					return;
				}
				fSay = TRUE;
				fSaid = TRUE;
				sprintf (p, "%s\"", get_color_string(ch, COLOR_SPEECH, VT102_DIM));
				p += strlen(p);
				argument++;
			}
			else
			{
				fSay = FALSE;
				sprintf (p, "\"{300}");
				p += strlen(p);
				argument++;
			}
		}
		
		if(*argument == '$')
		{
			argument++;
			while(*argument>='0' && *argument<='9')
			{
				key[key_e++] = *(argument++);
			}
			if(*argument=='.')
			{
				key[key_e++] = *(argument++);
			}
			while(isalpha(*argument) || *argument=='-')
			{
				key[key_e++] = *(argument++);
			}
			
			key[key_e] = '\0';
			key_e = 0;
			
			if ((victim = get_char_room(ch, key)) == NULL )
			{
				if((obj = get_obj_here(ch, key)) == NULL )
				{
					sprintf(buf,"I don't see any %s here.\n",key);
					send_to_char(buf,ch);
					pop_call();
					return;
				}
				sprintf (p, "%s{300}", obj->short_descr);
				p += strlen(p);
			}
			else
			{
				if (pet && victim == pet)
				{
					send_to_char ("Type '@' to refer to your pet.\n", ch);
					pop_call();
					return;
				}
				if (!pet && victim == ch)
				{
					send_to_char ("Type '@' to refer to yourself.\n", ch);
					pop_call();
					return;
				}
				if (IS_NPC(victim))
					sprintf (p, "%s", get_name(victim));
				else
					sprintf (p, "#5%s#0", get_name(victim));
				p += strlen(p);
				tochar = TRUE;
			}
		}
		else
			*(p++) = *(argument++);
	}
	*p = '\0';
	
	if ( copy [0] == '\'' )
	{
		if ( !is_imote )
		{
			sprintf (buf, "$n %s", copy);
		}
		else
		{
			sprintf (buf, "%s", copy);
			capitalize(buf);
		}
	}
	else
	{
		if ( !is_imote )
		{
			sprintf(buf,"$n %s", copy);
		}
		else
		{
			sprintf (buf, "%s", copy);
		}
	}

	//add punct for those who leave it off the end - Kregor
	if ( buf[strlen(buf)-1] != '\"' && buf[strlen(buf)-1] != '.' && buf[strlen(buf)-1] != '!' && buf[strlen(buf)-1] != '?' )
		strcat (buf, ".");

	// and quotes for those who leave it off the end as well - Kregor
	if ( fSay && buf[strlen(buf)-1] != '\"' )
		strcat (buf, "\"");

	if (tongue)
	{
		if (ch->speak != 1 << tongue)
		{
			tongue_old = ch->speak;
			ch->speak = 1 << tongue;
			ch_printf(ch, "You speak in %s.\n\r", lang_names[tongue]);
		}
	}
	for ( tch = ch->in_room->first_person; tch; tch = tch->next_in_room )
	{
		if (pet && tch == pet)
			continue;
		personalize_string (pet != NULL ? pet : ch, tch, buf);
	}

	// adds broadcast of roleplay to audience in arena - Kregor 3/17/12
	if (in_area(ch, ROOM_VNUM_ARENA))
	{
		for (fpl = mud->f_player ; fpl ; fpl = fpl->next)
		{
			if (blocking(fpl->ch, ch))
				continue;
			if (fpl->ch == ch)
				continue;
			if (fpl->ch->in_room == NULL)
				continue;
			if (in_same_room(ch, fpl->ch))
				continue;
			if (!IS_SET(fpl->ch->in_room->room_flags, ROOM_AUDIENCE))
				continue;
			if (pet && fpl->ch == pet)
				continue;
	
			personalize_string (pet != NULL ? pet : ch, fpl->ch, buf);
		}
	}

	if (tongue_old)
	{
		ch->speak = tongue_old;
	}
	if (pet || IS_NPC(ch))
	{
		pop_call();
		return;
	}

	if (fSaid)
	{
		mprog_speech_trigger (buf, ch);	
		oprog_speech_trigger (buf, ch);
		rprog_speech_trigger (buf, ch);
	}
	pop_call();
	return;
}

void do_emote(CHAR_DATA *ch, char *argument)
{
	push_call("do_emote(%p,%p)",ch,argument);

	if (argument[0] == '\0')
	{
		send_to_char ("Emote what?\n\r", ch);
		pop_call();
		return;
	}

	if (IS_SILENCED(ch))
	{
		send_to_char ("You cannot emote.\n\r", ch);
		pop_call();
		return;
	}

	emote (ch, NULL, argument);
	
	pop_call();
	return;
}

void do_pemote(CHAR_DATA *ch, char *argument)
{
	CHAR_DATA *pet;
	char arg[MAX_INPUT_LENGTH];

	push_call("do_emote(%p,%p)",ch,argument);

	if (argument[0] == '\0')
	{
		send_to_char ("Syntax: pemote <pet> <message>\n\r", ch);
		pop_call();
		return;
	}
	
	argument = one_argument(argument, arg);
	
	if (argument[0] == '\0')
	{
		send_to_char ("Make your pet mote what?\n\r", ch);
		pop_call();
		return;
	}

	if ((pet = get_char_room(ch, arg)) == NULL)
	{
		send_to_char ("That pet doesn't seem to be here.\n\r", ch);
		pop_call();
		return;
	}
	if (!IS_NPC(pet) || !IS_ACT(pet, ACT_PET) || pet->master != ch)
	{
		act( "$N is not your pet.", ch, NULL, pet, TO_CHAR);
		pop_call();
		return;
	}

	if (IS_SILENCED(ch))
	{
		send_to_char ("You cannot emote.\n\r", ch);
		pop_call();
		return;
	}

	emote (ch, pet, argument);
	
	pop_call();
	return;
}

void personalize_string (CHAR_DATA *src, CHAR_DATA *tar, char *emote)
{
	char	desc [MAX_STRING_LENGTH];
	char	output [MAX_STRING_LENGTH];
	char	buf [MAX_STRING_LENGTH];
	CHAR_DATA	*tch, *target;
	bool fSay = FALSE;

	push_call("personalize_string(%p,%p,%p)",src,tar,emote);

	srand( (unsigned int) time(NULL));
	
	*output = '\0';

	while ( *emote )
	{
		*desc = '\0';
		if ( *emote == '#' )
		{
			emote++;
			if ( *emote == '5' )
			{
				emote++;
				
				while ( *emote != '#' )
				{
					sprintf (desc + strlen(desc), "%c", *emote);
					emote++;					
				}
				for (tch = tar->in_room->first_person; tch; tch = tch->next_in_room)
				{
					if (!strcasecmp(get_name(tch), desc))
					{
						break;
					}
				}
				emote++;
				emote++;
				if (*emote == '\'')
				{
					strcat (desc, "\'");
					emote++;
					if (*emote == 's')
					{
						strcat (desc, "s");
					}
					else
					{
						emote--;
					}
				}
				else
				{
					emote--;
					emote--;
				}
				if (!tch)
				{
					continue;
				}
				if (tch == tar)
				{
					sprintf (buf, "%c", desc[strlen(desc)-1]);
					if ( desc[strlen(desc)-1] == '\'' || desc[strlen(desc)-2] == '\'' )
					{
						strcat (output, "your");
						emote--;
					}
					else strcat (output, "you");
					target = tch;
					emote++;
				}
				else
				{
					sprintf (buf, "%s", PERS(tch, tar));
					strcat (output, buf);
					emote--;
					if ( *emote == '\'' )
						emote--;
				}
			}
		}
		else if (*emote == '\"')
		{
			if (!fSay)
			{
				fSay = TRUE;
				sprintf (output + strlen(output), "%c", *emote);
			}
			else
			{
				fSay = FALSE;
				sprintf (output + strlen(output), "%c", *emote);
			}
		}
		else if (fSay)
		{
			if (*emote >= 'a' && *emote <= 'z' && !can_understand(tar, src, FALSE))
			{
				sprintf (output + strlen(output), "%c", (rand() % 26) + 'a');
			}
			else
			{
				sprintf (output + strlen(output), "%c", *emote);
			}
		}
		else
		{
			sprintf (output + strlen(output), "%c", *emote);
		}
		emote++;
	}

	if (tar == src)
		act (output, src, NULL, tar, TO_CHAR);
	else
		act (output, src, NULL, tar, TO_VICT);
	pop_call();
	return;
	
	pop_call();
	return;
}


void do_dump (CHAR_DATA * ch, char *argument)
{
	push_call("do_dump(%p,%p)",ch,argument);

	if (IS_NPC(ch))
	{
		pop_call();
		return;
	}

	if (IS_SILENCED(ch))
	{
		send_to_char ("You cannot talk.\n\r", ch);
		pop_call();
		return;
	}

	if (!strcasecmp(argument, "buffer"))
	{
		FILE *dumpFile;
		char dump_dir[200], buf[MAX_BUFFER_LENGTH];

		ch_printf_color(ch, "Saving buffer..\n\r");

		if (ch->pcdata->scroll_start == 0)
		{
			strcpy(buf, ch->pcdata->scroll_buf);
		}
		else
		{
			strncpy(&buf[0], &ch->pcdata->scroll_buf[ch->pcdata->scroll_end], MAX_BUFFER_LENGTH - ch->pcdata->scroll_end);
			strncpy(&buf[MAX_BUFFER_LENGTH - ch->pcdata->scroll_end], &ch->pcdata->scroll_buf[0], ch->pcdata->scroll_end);
			buf[MAX_BUFFER_LENGTH-1] = '\0';
		}
		sprintf(dump_dir, "%s/%c/dmp/%s.dmp", PLAYER_DIR, tolower(ch->name[0]), ch->name);

		close_reserve();

		if ((dumpFile = my_fopen(dump_dir, "wt", FALSE)) != NULL)
		{
			fprintf(dumpFile, "%s", fixer(ansi_strip(buf)));

			my_fclose(dumpFile);

			log_printf("%s has dumped their buffer", get_name(ch));

			wait_state(ch, PULSE_VIOLENCE);
		}

		open_reserve();

		pop_call();
		return;
	}

	send_to_char ("Waiting for Dump.  Issue 'stop' to cancel.\n\r", ch);

	SET_BIT(ch->pcdata->interp, INTERP_DUMP);

	pop_call();
	return;
}

/*
 * Hack of do_dump to create a report against
 * a player for something offensive. Saves time stamped
 * buffer to a file for later review - Kregor
 */
void log_complaint(CHAR_DATA *ch, CHAR_DATA *victim, char *argument)
{
	FILE *dumpFile;
	char buf[MAX_BUFFER_LENGTH];
	char name[MAX_INPUT_LENGTH];

	push_call("log_complaint(%p,%p)",ch,victim);

	if (victim != NULL)
	{
		strcpy(name, victim->name);
	}
	else
	{
		strcpy(name, "Unknown");
	}

	log_god_printf("COMPLAINT [%u] %s against %s: %s", ch->in_room->vnum, ch->name, name, argument);
	
	if (ch->pcdata->scroll_start == 0)
	{
		strcpy(buf, ch->pcdata->scroll_buf);
	}
	else
	{
		strncpy(&buf[0], &ch->pcdata->scroll_buf[ch->pcdata->scroll_end], MAX_BUFFER_LENGTH - ch->pcdata->scroll_end);
		strncpy(&buf[MAX_BUFFER_LENGTH - ch->pcdata->scroll_end], &ch->pcdata->scroll_buf[0], ch->pcdata->scroll_end);
		buf[MAX_BUFFER_LENGTH-1] = '\0';
	}

	close_reserve();

	if ((dumpFile = my_fopen(COMPLAIN_FILE, "a", FALSE)) != NULL)
	{
		time_t rawtime;
	
		time ( &rawtime );

		fprintf(dumpFile, "COMPLAINT [%s] %s against %s: %s\n", get_time_string(mud->current_time), ch->name, name, argument);
		fprintf(dumpFile, "%s\n\r", fixer(ansi_strip(buf)));

		my_fclose(dumpFile);

		send_to_char("Your complaint has been recorded.\n\r", ch);
	}
	else
		bug("do_complaint: NULL file", 0);

	open_reserve();

	pop_call();
	return;
}

void do_complaint(CHAR_DATA * ch, char *argument)
{
	CHAR_DATA *victim = NULL;
	char name[MAX_INPUT_LENGTH];
	bool fLoaded = FALSE;

	push_call("do_complaint(%p,%p)",ch,argument);

	if (IS_NPC(ch))
	{
		pop_call();
		return;
	}
			
	argument = one_argument(argument, name);
	
	if (*name == '\0')
	{
		send_to_char("Syntax: complaint <player name|unknown> <complaint>\n\r", ch);
		pop_call();
		return;
	}
	if (strcasecmp(name, "unknown") && (victim = get_player_world(ch, name)) == NULL)
	{
		if ((victim = start_partial_load(ch, name)) == NULL)
		{
			send_to_char("There is no player by that name.\n\r", ch);
			send_to_char("Syntax: complaint <player name|unknown> <complaint>\n\r", ch);
			pop_call();
			return;
		}
		else
		{
			fLoaded = TRUE;
		}
	}
	
	log_complaint(ch, victim, argument);

	if (fLoaded)
		clear_partial_load(victim);

	pop_call();
	return;
}


/* 
 * save lines and reuse functions - Kregor
 */
bool tell( CHAR_DATA *ch, CHAR_DATA *victim, char *argument )
{
	char say_buf[MAX_STRING_LENGTH];
	CHAR_DATA *fch;
	PLAYER_GAME *fpl;

	push_call("tell(%p,%p)",ch,victim,argument);

	if (!IS_GOD(ch) && in_area(victim, ROOM_VNUM_ABYSS))
	{
		act("You cannot currently send a tell to $N.", ch, NULL, victim, TO_CHAR);
		pop_call();
		return FALSE;
	}
	if (!IS_GOD(ch) && in_area(ch, ROOM_VNUM_ABYSS))
	{
		act("You cannot send tells while in the abyss.", ch, NULL, NULL, TO_CHAR);
		pop_call();
		return FALSE;
	}

	if (IS_AFFECTED(ch, AFF_MIND_BLANK))
	{
		send_to_char("Your mind is shut off to anyone else.\n\r", ch);
		pop_call();
		return FALSE;
	}
	
	if (IS_PLR(ch, PLR_DEAD) && !is_affected(victim, gsn_speak_with_dead))
	{
		send_to_char("Your thoughts cannot reach the mortal plane.\n\r", ch);
		pop_call();
		return FALSE;
	}
	
	if (strstr(argument, "{"))
	{
		send_to_char("No color codes in speech commands!\n\r", ch);
		pop_call();
		return FALSE;
	}

	if (IS_AFFECTED(ch, AFF2_CONFUSION) && number_bits(2) != 0)
	{
		send_to_char("You are too confused to send thoughts to anyone!\n\r", ch);
		pop_call();
		return FALSE;
	}
	
	if (IS_AFFECTED(victim, AFF_MIND_BLANK) || (IS_PLR(victim, PLR_DEAD) && !is_affected(ch, gsn_speak_with_dead)))
	{
		send_to_char("Your subject's mind cannot be reached.\n\r", ch);
		pop_call();
		return FALSE;
	}
	
	if (victim->desc && victim->desc->connected == CON_EDITING && get_trust(ch) < LEVEL_IMMORTAL)
	{
		ch_printf_color(ch, "They are currently in a writing buffer.  Please try again in a few minutes.\n\r");
		pop_call();
		return FALSE;
	}

	if (IS_SET(victim->act, PLR_AFK))
	{
		ch_printf_color(ch, "That player is afk and may not see your message.\n\r");
	}

	sprintf(say_buf, "You telepath to %s %s\"%s\"\n\r", 
		PERS(victim,ch), get_color_string(ch, COLOR_SPEECH, VT102_BOLD), argument);
	send_to_char(justify(say_buf, get_page_width(ch)), ch);

	sprintf(say_buf, "%s telepaths to you %s\"%s\"\n\r", 
		capitalize(PERS(ch,victim)), get_color_string(ch, COLOR_SPEECH, VT102_BOLD), argument);
	send_to_char(justify(say_buf, get_page_width(victim)), victim);

	for (fch = ch->in_room->first_person ; fch ; fch = fch->next_in_room)
	{
		if (ch == fch || fch == victim || fch->position <= POS_SLEEPING)
			continue;

		if (IS_GOD(ch))
			continue;

		if (blocking (fch, ch))
			continue;

		if (!is_affected(fch, gsn_detect_thoughts))
			continue;
			
		if (check_nondetection(fch, ch, gsn_detect_thoughts))
			continue;
			
		if (will_save(ch, fch, get_affect_level(fch, gsn_detect_thoughts), gsn_detect_thoughts))
		{
			affect_strip(fch, gsn_detect_thoughts);
			send_to_char("You sense someone trying to intrude upon your thoughts.\n\r", ch);
			continue;
		}

		sprintf(say_buf, "You hear %s telepathing to %s, %s\"%s\"\n\r", 
			capitalize(PERS(ch,fch)), PERS(victim,fch), get_color_string(ch, COLOR_SPEECH, VT102_DIM), argument);
		send_to_char(justify(say_buf, get_page_width(fch)), fch);
	}

	for (fpl = mud->f_player ; fpl ; fpl = fpl->next)
	{
		if (!IS_GOD(fpl->ch))
			continue;
		if (fpl->ch == ch || fpl->ch == victim)
			continue;
		ch_printf_color(fpl->ch, "%s telepaths to %s, %s\"%s\"\n\r", get_name(ch), get_name(victim), get_color_string(ch, COLOR_SPEECH, VT102_BOLD), argument);
	}
	
	TAKE_ACTION(ch, ACTION_SWIFT);

	pop_call();
	return TRUE;
}

void do_tell (CHAR_DATA * ch, char *argument)
{
	char arg[MAX_INPUT_LENGTH];
	CHAR_DATA *victim;

	push_call("do_tell(%p,%p)",ch,argument);

	if (IS_SILENCED(ch))
	{
		send_to_char ("Your message didn't get through.\n\r", ch);
		pop_call();
		return;
	}

	if (NEW_AUTH(ch)) /* new auth */    
	{
		send_to_char( "You cannot send tells until you advance.\r\n", ch);
		pop_call();
		return;
	}
	
	argument = one_argument(argument, arg);

	if (arg[0] == '\0' || argument[0] == '\0')
	{
		send_to_char ("Tell whom what?\n\r", ch);
		pop_call();
		return;
	}

	if ((victim = get_player_world(ch, arg)) == NULL)
	{
		send_to_char ("They aren't here.\n\r", ch);
		pop_call();
		return;
	}

	if (victim->desc == NULL)
	{
		ch_printf_color(ch, "That player is link-dead.\n\r");
		pop_call();
		return;
	}

	/* mortals cannot tell without some form of telepathy */
	if (!IS_NPC(ch) && !IS_GOD(ch))
	{
		if (!IS_PLR(victim, PLR_DEAD))
		{
			if (!IS_AFFECTED(ch, AFF_TELEPATHY))
			{
				act( "You cannot establish a mental link with $N.", ch, NULL, victim, TO_CHAR);
				pop_call();
				return;
			}
		}
		else if (!is_affected(ch, gsn_speak_with_dead))
		{
			act( "You cannot establish a mental link with $N.", ch, NULL, victim, TO_CHAR);
			pop_call();
			return;
		}
	}
		
	if (blocking(victim, ch) || IS_MUTED(victim))
	{
		ch_printf_color(ch, "That person refuses to hear you.\n\r");
		pop_call();
		return;
	}

	if (strstr(argument, "{"))
	{
		send_to_char("No color codes in speech commands!\n\r", ch);
		pop_call();
		return;
	}

	if (!tell(ch, victim, argument))
	{
		pop_call();
		return;
	}

	if (!IS_NPC(ch) && !IS_NPC(victim) && !IS_PLR(ch, PLR_DEAD))
	{
		victim->pcdata->reply = ch;
	}

	pop_call();
	return;
}

void do_reply (CHAR_DATA * ch, char *argument)
{
	CHAR_DATA *victim;

	push_call("do_reply(%p,%p)",ch,argument);

	if (IS_NPC(ch))
	{
		send_to_char("You cannot reply.\n\r", ch);
		pop_call();
		return;
	}

	if (IS_SILENCED(ch))
	{
		send_to_char ("Your message didn't get through.\n\r", ch);
		pop_call();
		return;
	}

	if ((victim = ch->pcdata->reply) == NULL || IS_NPC(ch->pcdata->reply))
	{
		send_to_char ("You have no message to reply to.\n\r", ch);
		pop_call();
		return;
	}

	if (argument[0] == '\0')
	{
		send_to_char ("Reply what?\n\r", ch);
		pop_call();
		return;
	}

	if (IS_SET(victim->act, PLR_AFK))
	{
		ch_printf_color(ch, "They are afk and may not see your message.\n\r");
	}

	if (victim->desc == NULL)
	{
		ch_printf_color(ch, "That player is link-dead.\n\r");
		pop_call();
		return;
	}

	if (strstr(argument, "{"))
	{
		send_to_char("No color codes in speech commands!\n\r", ch);
		pop_call();
		return;
	}

	if (tell(ch, victim, argument))
	{
		ch->pcdata->reply = NULL;
	}

	pop_call();
	return;
}

void do_otell (CHAR_DATA * ch, char *argument)
{
	char arg[MAX_INPUT_LENGTH];
	char text2[MAX_STRING_LENGTH];
	char say_buf[MAX_STRING_LENGTH];
	CHAR_DATA *victim;

	push_call("do_tell(%p,%p)",ch,argument);

	if (IS_SILENCED(ch))
	{
		send_to_char ("Your message didn't get through.\n\r", ch);
		pop_call();
		return;
	}

	if (NEW_AUTH(ch)) /* new auth */    
	{
		send_to_char( "You cannot send tells until you advance.\r\n", ch);
		pop_call();
		return;
	}
	
	argument = one_argument(argument, arg);

	if (arg[0] == '\0' || argument[0] == '\0')
	{
		send_to_char ("Tell whom what?\n\r", ch);
		pop_call();
		return;
	}

	if ((victim = get_player_world(ch, arg)) == NULL)
	{
		send_to_char ("They aren't here.\n\r", ch);
		pop_call();
		return;
	}

	if (victim->desc == NULL)
	{
		ch_printf_color(ch, "That player is link-dead.\n\r");
		pop_call();
		return;
	}

	if (blocking(victim, ch) || IS_MUTED(victim))
	{
		ch_printf_color(ch, "That person refuses to hear you.\n\r");
		pop_call();
		return;
	}

	if (victim->desc && victim->desc->connected == CON_EDITING && get_trust (ch) < LEVEL_IMMORTAL)
	{
		ch_printf_color(ch, "They are currently in a writing buffer.  Please try again in a few minutes.\n\r");
		pop_call();
		return;
	}

	if (!IS_GOD(ch) && strstr(argument, "{"))
	{
		send_to_char("No color codes in speech commands!\n\r", ch);
		pop_call();
		return;
	}

	strcpy(text2, argument);
	
	sprintf(say_buf, "[OTELL] to %s: %s%s\n\r", 
		victim->name, get_color_string(ch, COLOR_SPEECH, VT102_BOLD), text2);
	send_to_char(justify(say_buf, get_page_width(ch)), ch);

	sprintf(say_buf, "[OTELL] %s: %s%s\n\r", 
		ch->name, get_color_string(ch, COLOR_SPEECH, VT102_BOLD), text2);
	send_to_char(justify(say_buf, get_page_width(victim)), victim);

	pop_call();
	return;
}

/*
 * THINK command, sends a mental thought to one's self,
 * and echoes it to any imm online - Kregor 6/24/07
 */
void do_think (CHAR_DATA * ch, char *argument)
{
	PLAYER_GAME *fpl;
	char text2[MAX_STRING_LENGTH];
	char buf[MAX_STRING_LENGTH];
	CHAR_DATA *fch;

	push_call("do_think(%p,%p)",ch,argument);

	if (IS_NPC(ch))
	{
		send_to_char("You cannot think for yourself.\n\r", ch);
		pop_call();
		return;
	}

	if (argument[0] == '\0')
	{
		send_to_char ("What are you thinking?\n\r", ch);
		pop_call();
		return;
	}

	if (strstr(argument, "{"))
	{
		send_to_char("No color codes in speech commands!\n\r", ch);
		pop_call();
		return;
	}

	strcpy(text2, argument);
	
	ch_printf_color(ch, "You think to yourself, %s\"%s\"\n\r", get_color_string(ch, COLOR_SPEECH, VT102_BOLD), text2);

	for (buf[0] = '\0', fch = ch->in_room->first_person ; fch ; fch = fch->next_in_room)
	{
		if (ch == fch || fch->position <= POS_SLEEPING)
			continue;

		if (IS_GOD(fch))
			continue;

		if (blocking (fch, ch))
			continue;

		if (!is_affected(fch, gsn_detect_thoughts))
			continue;
							
		if (check_nondetection(fch, ch, gsn_detect_thoughts))
			continue;
			
		if (!will_save(ch, fch, get_affect_level(fch, gsn_detect_thoughts), gsn_detect_thoughts))
		{
			if (!can_understand(fch, ch, FALSE))
			{
				act( "$n thinks to $mself, in a tongue you cannot understand.", ch, NULL, fch, TO_VICT);
				continue;
			}
			else
			{
				sprintf(buf, "%s\"%s\"", get_color_string(ch, COLOR_SPEECH, VT102_DIM), text2);
				act( "You hear $n thinking to $mself, $t", ch, buf, fch, TO_VICT);
				continue;
			}
		}
	}

	for (fpl = mud->f_player ; fpl ; fpl = fpl->next)
	{
		if (!IS_GOD(fpl->ch))
			continue;
		if (fpl->ch == ch)
			continue;
		ch_printf_color(fpl->ch, "THINK: %s thinks, %s\"%s\"\n\r", get_name(ch), get_color_string(ch, COLOR_SPEECH, VT102_BOLD), text2);
	}
	pop_call();
	return;
}

/*
 * New do_pose, sets the end of the long desc for a PC
 * to show them in whatever pose they wish to be in.
 * echoes the pose to the room and to char when set - Kregor 3/3/07
 */
void do_pose( CHAR_DATA *ch, char *argument )
{
	push_call("do_pose(%p,%p)",ch,argument);

	if (IS_NPC(ch))
	{
		send_to_char("NPCs cannot pose.\n\r", ch);
		pop_call();
		return;
	}
	if (!IS_AWAKE(ch))
	{
		send_to_char( "In your dreams, or what?\n\r", ch );
		pop_call();
		return;
	}
	if (in_combat(ch))
	{
		send_to_char( "You can't pose while fighting.\n\r", ch );
		pop_call();
		return;
	}
	
	if (argument[0] == '\0')
	{
		if (is_string(ch->pcdata->pose))
		{
			ch_printf_color(ch, "Your pose says {178}'%s is %s.'\n\r", capitalize(get_name(ch)), ch->pcdata->pose);
			pop_call();
			return;
		}
		else
		{
			send_to_char("Set your pose to what?\n\r", ch);
			pop_call();
			return;
		}
	}
	if (!str_prefix(argument, "remove"))
	{
		STRFREE (ch->pcdata->pose);
		ch->pcdata->pose = STRALLOC("");
		send_to_char( "Your pose has been removed.\n\r", ch );
		pop_call();
		return;
	}

	if (ansi_strlen(argument) > 60 || strlen(argument) > 480)
	{
		send_to_char("Your pose must be set to 60 characters or less.\n\r", ch);
		pop_call();
		return;
	}
	if (strstr(argument, "{"))
	{
		send_to_char("Your you may not use color codes in a pose.\n\r", ch);
		pop_call();
		return;
	}
	smash_tilde( argument );

	if (is_string(ch->pcdata->pose))
		STRFREE(ch->pcdata->pose);
	ch->pcdata->pose = STRALLOC(argument);
	ch_printf_color(ch, "You are %s.\n\r", ch->pcdata->pose);
	act( "$n is $t.", ch, argument, NULL, TO_ROOM);
	pop_call();
	return;
}


/*
 * Change the language you are speaking
 * for all following successive speech commnds - Kregor
 */
void do_speak( CHAR_DATA *ch, char *argument )
{
	int cnt;

	push_call("do_speak(%p,%p)",ch,argument);

	if (IS_NPC(ch))
	{
		pop_call();
		return;
	}

	if (argument[0] == '\0')
	{
		send_to_char("Speak what language?\n\r", ch);
		pop_call();
		return;
	}

	if ((cnt = lookup_tongue(argument)) != -1)
	{
		if (IS_SET(ch->language, 1 << cnt))
		{
			ch_printf_color(ch, "Language set to %s.\n\r", lang_names[cnt]);
			ch->speak = 1 << cnt;
			pop_call();
			return;
		}
	}
	send_to_char("You cannot speak that language.\n\r", ch);
	pop_call();
	return;
}


void send_goodbye(DESCRIPTOR_DATA *d)
{
	CHAR_DATA *ch;
	char buf1[MAX_INPUT_LENGTH];
	char buf2[MAX_INPUT_LENGTH];

	push_call("send_goodbye(%p)",d);
	
	if ((ch = CH(d)) == NULL)
	{
		pop_call();
		return;
	}

	strcpy(buf1, god_table[which_god(ch)].logout_msg);

	sprintf(buf2, "\n\r%s\n\r", center(buf1, get_page_width(ch)));
	cat_sprintf(buf2, "\n\r%s\n\r", center("Thank you for playing", get_page_width(ch)));
	cat_sprintf(buf2, "\n\r%s\n\r", "{088}");

	write_to_descriptor(d, ansi_translate(buf2), 0);

	pop_call();
	return;
}


/*
 * Checks opposed roll for commanding a pet
 * or dominated creature with do_order - Kregor
 */
bool command_check(CHAR_DATA *ch, CHAR_DATA *victim, int cmd)
{
	int roll;

	push_call("command_check(%p,%p,%p)",ch,victim,cmd);
	
	// crash proofing!
	if (!is_string(cmd_table[cmd].name) || !IS_NPC(victim))
	{
		pop_call();
		return FALSE;
	}
	
	if (cmd < 0)
	{
		pop_call();
		return FALSE;
	}
	
	if (!is_master(victim, ch))
	{
		act ("$N refuses to obey your orders.", ch, NULL, victim, TO_CHAR);
		pop_call();
		return FALSE;
	}
	
	if (cmd_table[cmd].level > get_trust(victim))
	{
		act("You cannot order $N to $t.", ch, cmd_table[cmd].name, victim, TO_CHAR);
		pop_call();
		return FALSE;
	}
	
	if (CAN_TALK(victim) && !can_understand(victim, ch, TRUE))
	{
		act ("$N cannot understand your command.", ch, NULL, victim, TO_CHAR);
		pop_call();
		return FALSE;
	}

	//mindless minions always obey
	if (get_curr_int(victim) == -1)
	{
		pop_call();
		return TRUE;
	}

	//only companions, warhorses and familiars will fight to the death
	if (IS_SET(cmd_table[cmd].flags, CMD_ATTACK) && !IS_ACT(victim, ACT_WARHORSE|ACT_COMPANION|ACT_FAMILIAR))
	{
		if (IS_ACT(victim, ACT_WIMPY) || victim->hit <= get_max_hit(victim)/3)
		{
			act ("$N refuses to enter into combat.", ch, NULL, victim, TO_CHAR);
			pop_call();
			return FALSE;
		}
	}

	if (IS_AFFECTED(victim, AFF_DOMINATE))
	{
		roll = dice(1,20) + get_AFFECT_level(victim, AFF_DOMINATE);
		roll += UMAX(0, stat_bonus(TRUE, ch, APPLY_WIS));		
		if (IS_ANIMAL(victim) && learned(ch, gsn_wild_empathy))
			roll += UMAX(0, stat_bonus(TRUE, ch, APPLY_WIS));
		roll -= get_apply(victim, APPLY_TURN_RESIST);
		
		if (will_save(victim, ch, roll, -1))
		{
			act ("$N breaks your hold upon $M.", ch, NULL, victim, TO_CHAR);
			AFFECT_STRIP(victim, AFF_DOMINATE);
			pop_call();
			return FALSE;
		}
	}
	else
	{
		roll = handle_animal_roll(ch);

		if (IS_ACT(victim, ACT_COMPANION))
			roll += multi_skill_level(ch, gsn_companion) / 4;

		if (IS_PET(victim, ch) && !IS_ACT(victim, ACT_FAMILIAR|ACT_WARHORSE) 
		&& !handle_animal_check(ch, victim, roll, victim->level / 2 + 10))
		{
			act ("$N refuses to obey your order.", ch, NULL, victim, TO_CHAR);
			pop_call();
			return FALSE;
		}
	}
	
	pop_call();
	return TRUE;
}


/*
 * Gutted and rewrote to address selective minions or "all".
 * Added commands to set lethal combat and nonassist for minions.
 * Uses function above to check willingness to obey - Kregor 2/20/12
 */
void do_order (CHAR_DATA * ch, char *argument)
{
	char arg[MAX_INPUT_LENGTH];
	char say_buf[MAX_INPUT_LENGTH];
	char buf[MAX_STRING_LENGTH];
	int cmd, col;
	CHAR_DATA *victim = NULL;
	CHAR_DATA *vch, *vch_next;
	bool found = FALSE;
	bool fAll = FALSE;

	push_call("do_order(%p,%p)",ch,argument);

	if (argument[0] == '\0')
	{
		send_to_char ("Order whom to do what?\n\r", ch);
		pop_call();
		return;
	}

	if (IS_AFFECTED (ch, AFF_DOMINATE))
	{
		send_to_char ("You feel like taking, not giving, orders.\n\r", ch);
		pop_call();
		return;
	}

	argument = one_argument(argument, arg);

	if (!strcasecmp(arg, "all"))
	{
		fAll = TRUE;
	}
	else if ((victim = get_char_room (ch, arg)) == NULL)
	{
		send_to_char ("Order whom to do what?\n\r", ch);
		pop_call();
		return;
	}
	else if (victim == ch)
	{
		send_to_char ("Order yourself??\n\r", ch);
		pop_call();
		return;
	}
	
	if (*argument == '\0')
	{
		cmd = -1;
	}
	else if (!strcasecmp(argument, "nofight"))
	{
		cmd = -2;
	}
	else if (!strcasecmp(argument, "nonlethal"))
	{
		cmd = -3;
	}
	else
	{
		one_argument(argument, say_buf);
		cmd = find_command(say_buf, LEVEL_IMMORTAL - 1);
	}
	
	if (victim)
	{
		if (argument[0] == '\0')
		{
			sprintf(buf, "%sOrder %s to:\n\r             ", get_color_string(ch, COLOR_ACCENT, VT102_DIM), get_name(victim));

			for (cmd = col = 0 ; cmd_table[cmd].name[0] != '\0' ; cmd++)
			{
				if (cmd_table[cmd].level < MAX_LEVEL && cmd_table[cmd].level <= get_trust(victim))
				{
					if (col % 5 == 0)
					{
						strcat(buf, get_color_string(ch, COLOR_TEXT, VT102_DIM));
					}
					cat_snprintf(buf, 13, "%-13s", cmd_table[cmd].name);
					if (++col % 5 == 0)
					{
						strcat(buf, "\n\r             ");
					}
				}
			}
			if (col % 5 != 0)
			{
				strcat(buf, "\n\r");
			}
			send_to_char(buf, ch);
			pop_call();
			return;
		}
		if (cmd == -1)
		{
			act("Order $N to do what?", ch, NULL, victim, TO_CHAR);
			pop_call();
			return;
		}
		if (cmd >= 0)
		{
			sprintf(say_buf, "%s'%s'", get_color_string(ch, COLOR_SPEECH, VT102_DIM), capitalize(cmd_table[cmd].name));
			act ("$n orders you, $t.", ch, say_buf, victim, TO_VICT);
			act ("You order $N, $t.", ch, say_buf, victim, TO_CHAR);
			act ("$n orders $N, $t.", ch, say_buf, victim, TO_NOTVICT);
		}
	}
	else
	{
		if (argument[0] == '\0' || cmd == -1)
		{
			send_to_char("Order your minions to do what?\n\r", ch);
			pop_call();
			return;
		}
		if (cmd >= 0)
		{
			sprintf(say_buf, "%s'%s'", get_color_string(ch, COLOR_SPEECH, VT102_DIM), capitalize(cmd_table[cmd].name));
			act ("You order your minions, $t.", ch, say_buf, NULL, TO_CHAR);
			act ("$n orders $s minions, $t.", ch, say_buf, NULL, TO_ROOM);
		}
	}

	for (vch = ch->in_room->first_person; vch != NULL; vch = vch_next)
	{
		vch_next = vch->next_in_room;

		if (!IS_NPC(vch) || !is_master(vch, ch))
			continue;
			
		if (victim && vch != victim)
			continue;
		
		found = TRUE;

		if (cmd == -2)
		{
			TOGGLE_BIT(vch->act, ACT_NOASSIST);
			if (!IS_ACT(vch, ACT_NOASSIST))
			{
				act("$N will now join into combat automatically with you.", ch, NULL, vch, TO_CHAR);
			}
			else
			{
				act("$N will no longer fight with you unless you order $M to.", ch, NULL, vch, TO_CHAR);
			}
			continue;
		}
		if (cmd == -3)
		{
			TOGGLE_BIT(vch->combatmode, COMBAT_NONLETHAL);
			if (COMBATMODE(vch, COMBAT_NONLETHAL))
			{
				act("$N will now deal nonlethal damage when $E fights.", ch, NULL, vch, TO_CHAR);
			}
			else
			{
				act("$N will now deal lethal damage when $E fights.", ch, NULL, vch, TO_CHAR);
			}
			continue;
		}

		if (!command_check(ch, vch, cmd))
			continue;
	
		interpret (vch, argument);
	}
	if (!found)
	{
		send_to_char ("You have no followers to order here.\n\r", ch);
	}
	pop_call();
	return;
}


void do_group (CHAR_DATA * ch, char *argument)
{
	char buf[MAX_STRING_LENGTH];
	char buf2[MAX_STRING_LENGTH];
	char arg[MAX_INPUT_LENGTH];
	char dim[10], bld[10];
	CHAR_DATA *victim, *leader, *gch, *gch_next;
	PET_DATA	 *pch;
	int count;

	push_call("do_group(%p,%p)",ch,argument);

	argument = one_argument (argument, arg);

	strcpy(dim, get_color_string(ch, COLOR_PROMPT, VT102_DIM));
	strcpy(bld, get_color_string(ch, COLOR_PROMPT, VT102_BOLD));

	leader = (ch->leader != NULL) ? ch->leader : ch;

	if (arg[0] == '\0')
	{
		ch_printf_color(ch, "{078}%s's group:\n\r", PERS(leader, ch));

		if (ch != leader && ch->in_room != leader->in_room && !IS_PLR(ch, PLR_HOLYLIGHT) && (!is_affected(ch, gsn_status) || !is_affected(leader, gsn_status)))
		{
			ch_printf_color(ch, " {200}[{078}%2d{200}]{178} %-16s             {128}<<location unknown>>\n\r", 
				1, str_resize(IS_NPC(leader) ? capitalize(leader->short_descr) : leader->name, buf, -16));
		}
		else
		{
			ch_printf_color(ch, " {200}[{078}%2d{200}]{178} %-16s {078}HitPts: %s%3d%%  {078}Move: %s%3d%%  {078}Location: {178}%-20s\n\r",
				1, str_resize(IS_NPC(leader) ? capitalize(leader->short_descr) : leader->name, buf, -16),
				get_gradient(TRUE, leader->hit, get_max_hit(leader)),
				leader->hit * 100 / get_max_hit(leader),
				get_gradient(TRUE, leader->move, get_max_move(leader)),
				leader->move * 100 / get_max_move(leader),
				str_resize(leader->in_room->name, buf2, -19));
		}

		for (count = 1, pch = mud->f_pet ; pch ; pch = pch->next)
		{
			if (pch->ch == leader)
				continue;
			count++;
			if (ch != pch->ch && ch->in_room != pch->ch->in_room && !IS_PLR(ch, PLR_HOLYLIGHT) && (!is_affected(ch, gsn_status) || !is_affected(pch->ch, gsn_status)))
			{
				ch_printf_color(ch, " {200}[{078}%2d{200}]{178} %-16s             {128}<<location unknown>>\n\r",
					count, str_resize(IS_NPC(pch->ch) ? capitalize(pch->ch->short_descr) : pch->ch->name, buf, -16));
			}
			else if (is_same_group(pch->ch, ch))
			{
				ch_printf_color(ch, " {200}[{078}%2d{200}]{178} %-16s {078}HitPts: %s%3d%%  {078}Move: %s%3d%%  {078}Location: {178}%-20s\n\r",
					count, str_resize(IS_NPC(pch->ch) ? capitalize(pch->ch->short_descr) : pch->ch->name, buf, -16),
					get_gradient(TRUE, pch->ch->hit, get_max_hit(pch->ch)),
					pch->ch->hit * 100 / get_max_hit(pch->ch),
					get_gradient(TRUE, pch->ch->move, get_max_move(pch->ch)),
					pch->ch->move * 100 / get_max_move(pch->ch),
					str_resize(pch->ch->in_room->name, buf2, -19));
			}
		}
		pop_call();
		return;
	}

	for (gch = ch->in_room->first_person ; gch != NULL ; gch = gch_next)
	{
		gch_next = gch->next_in_room;

		if (is_same_group(ch, gch) && in_combat(gch))
		{
			send_to_char ("You cannot modify the group while a member is fighting.\n\r", ch);
			pop_call();
			return;
		}
	}

	if (!strcmp (arg, "disband"))
	{
		int count = 0;

		if (ch->master)
		{
			send_to_char ("You cannot disband a group if you're following someone.\n\r ", ch);
			pop_call();
			return;
		}

		for (pch = mud->f_pet ; pch ; pch = pch->next)
		{
			if (ch != pch->ch && is_same_group(ch, pch->ch))
			{
				pch->ch->leader = NULL;
				count++;
				send_to_char("Your group is disbanded.\n\r", pch->ch);
			}
		}

		if (count == 0)
		{
			send_to_char ("You have no group members to disband.\n\r", ch);
		}
		else
		{
			send_to_char ("You disband your group.\n\r", ch);
		}
		pop_call();
		return;
	}

	if (!strcmp (arg, "all"))
	{
		CHAR_DATA *rch;
		int count = 0;

		for (rch = ch->in_room->first_person ; rch ; rch = rch->next_in_room)
		{
			if (ch != rch && can_see(ch, rch) && rch->master == ch && !in_combat(rch) && !ch->master && !ch->leader && !is_same_group(rch, ch))
			{
				rch->leader = ch;
				count++;
			}
		}

		if (count == 0)
		{
			send_to_char ("You have no eligible group members.\n\r", ch);
		}
		else
		{
			act ("$n groups $s followers.", ch, NULL, NULL, TO_ROOM);
			send_to_char ("You group your followers.\n\r", ch);
		}
		pop_call();
		return;
	}
	
	if (!strcmp (arg, "eject"))
	{
		if (argument[0] == '\0')
		{
			act ("Who do you want to eject from your group?", ch, NULL, NULL, TO_CHAR);
			pop_call();
			return;
		}
			
		if ((victim = get_char_room(ch, argument)) == NULL)
		{
			if ((victim = get_player_world(ch, argument)) == NULL)
			{
				send_to_char ("Hmmm...where did they go?\n\r", ch);
				pop_call();
				return;
			}
		}
	
		if (victim->master != ch)
		{
			act ("$N isn't following you.", ch, NULL, victim, TO_CHAR);
			pop_call();
			return;
		}
	
		if (is_same_group (victim, ch))
		{
			victim->leader = NULL;
	
			act ("$n removes $N from $s group.", ch, NULL, victim, TO_NOTVICT);
			act ("$n removes you from $s group.", ch, NULL, victim, TO_VICT);
			act ("You remove $N from your group.", ch, NULL, victim, TO_CHAR);
	
			pop_call();
			return;
		}
		pop_call();
		return;
	}
	
	if (ch->master)
	{
		send_to_char ("But you are following someone else!\n\r", ch);
		pop_call();
		return;
	}

	if ((victim = get_char_room(ch, arg)) == NULL)
	{
		if ((victim = get_player_world(ch, arg)) == NULL)
		{
			send_to_char ("Hmmm...where did they go?\n\r", ch);
			pop_call();
			return;
		}
	}

	if (victim->master != ch)
	{
		act ("$N isn't following you.", ch, NULL, victim, TO_CHAR);
		pop_call();
		return;
 	}

	if (is_same_group (victim, ch))
	{
		act ("$N is already a member of your group.", ch, NULL, victim, TO_CHAR);
		pop_call();
		return;
 	}

	if (in_combat(victim))
	{
		send_to_char ("You cannot group someone that is fighting.\n\r", ch);
		pop_call();
		return;
	}

	victim->leader = ch;

	act ("$N joins $n's group.", ch, NULL, victim, TO_NOTVICT);
	act ("You join $n's group.", ch, NULL, victim, TO_VICT);
	act ("$N joins your group.", ch, NULL, victim, TO_CHAR);

	pop_call();
	return;
}

/*
	'Split' originally by Gnort, God of Chaos.
*/
void do_split (CHAR_DATA * ch, char *argument)
{
	char buf[MAX_STRING_LENGTH];
	char buf2[MAX_STRING_LENGTH];
	char arg[MAX_INPUT_LENGTH];
	CHAR_DATA *gch;
	int members;
	int amount;
	int share;
	int extra;

	push_call("do_split(%p,%p)",ch,argument);

	argument = one_argument (argument, arg);

	if (arg[0] == '\0')
	{
		send_to_char ("How much gold do you want to split ?\n\r", ch);
		pop_call();
		return;
	}

	amount = atol(arg);
	if (!str_prefix(argument, "gold"))
		amount *= 100;
	else if (!str_prefix(argument, "silver"))
		amount *= 10;
	else if (!str_prefix(argument, "copper"))
		amount *= 1;
	else
	{
		ch_printf_color(ch, "{078}Split %d what?\n\r", amount);
		pop_call();
		return;
	}

	if (amount < 0)
	{
		send_to_char ("Your group wouldn't like that.\n\r", ch);
		pop_call();
		return;
	}

	if (amount == 0)
	{
		send_to_char ("You hand out zero coins, but no one notices.\n\r", ch);
		pop_call();
		return;
	}

	if (ch->gold < amount)
	{
		send_to_char ("You don't have that much gold.\n\r", ch);
		pop_call();
		return;
	}

	members = 0;
	for (gch = ch->in_room->first_person ; gch != NULL ; gch = gch->next_in_room)
	{
		if (is_same_group (gch, ch) && !IS_NPC (gch))
		{
			members++;
		}
	}

	if (members < 2)
	{
		send_to_char ("Just keep it all.\n\r", ch);
		pop_call();
		return;
	}

	share = amount / members;
	extra = amount % members;

	if (share == 0)
	{
		send_to_char ("Generousity is an art.\n\r", ch);
		pop_call();
		return;
	}

	ch->gold -= amount;
	ch->gold += share + extra;
	ch->carry_weight = get_carry_w(ch);
	
	sprintf(buf2, "%s", format_coins(amount, FALSE));

	ch_printf_color(ch, "You split %s; your share is %s.\n\r", buf2, format_coins(share + extra, FALSE));

	sprintf (buf, "$n splits %s; your share is %s.", buf2, format_coins(share, FALSE));

	for (gch = ch->in_room->first_person; gch != NULL; gch = gch->next_in_room)
	{
		if (gch != ch && is_same_group (gch, ch) && !IS_NPC (gch))
		{
			act (buf, ch, NULL, gch, TO_VICT);
			gch->gold += share;
			gch->carry_weight = get_carry_w(gch);
		}
	}
	pop_call();
	return;
}

/*
	It is very important that this be an equivalence relation:
	(1) A ~ A
	(2) if A ~ B then B ~ A
	(3) if A ~ B  and B ~ C, then A ~ C
*/

bool is_same_group (CHAR_DATA * ach, CHAR_DATA * bch)
{
	push_call("is_same_group(%p,%p)",ach,bch);
	/*
		the idea is this :
		if my leader is your leader, then we are in the same
		group, else we're in different groups, if at all in a group
	*/

	if (ach->leader != NULL)
	{
		ach = ach->leader;
	}
	if (bch->leader != NULL)
	{
		bch = bch->leader;
	}
	if (IS_NPC(ach) && ach->master && ach->master == bch)
	{
		pop_call();
		return TRUE;
	}
	if (IS_NPC(bch) && bch->master && bch->master == ach)
	{
		pop_call();
		return TRUE;
	}	
	if (ach == bch)
	{
		pop_call();
		return TRUE;
	}
	
	// thwart fighting ungrouped as tanking tactic. Enemy of my enemy is my friend. - Kregor
	if (who_fighting(ach) && who_fighting(bch) && who_fighting(ach) == who_fighting(bch))
	{
		pop_call();
		return TRUE;
	}
	
	//NPC handling routines, since NPCs can't group with each other
	if (IS_NPC(ach) && IS_NPC(bch))
	{
		if (ach->pIndexData == bch->pIndexData)
		{
			pop_call();
			return TRUE;
		}
		if (ach->pIndexData->area == bch->pIndexData->area)
		{
			if (ach->pIndexData->spec_fun && ach->pIndexData->spec_fun == bch->pIndexData->spec_fun)
			{
				pop_call();
				return TRUE;
			}
		}
	}
	
	pop_call();
	return FALSE;
}

bool vnum_in_group (CHAR_DATA * ach, int mobvnum)
{
	CHAR_DATA *gch;

	push_call("vnum_in_group(%p,%p)",ach,mobvnum);

	for (gch = ach->in_room->first_person ; gch ; gch = gch->next_in_room)
	{
		if (IS_NPC(gch) && gch->pIndexData->vnum == mobvnum && is_same_group(ach, gch))
		{
			pop_call();
			return TRUE;
		}
	}
	pop_call();
	return FALSE;
}

/*
 * Translate a document to the reader - Kregor
 * Uses Decipher Script skill to translate words
 */
char *translate_document(CHAR_DATA *ch, OBJ_DATA *obj, char *argument)
{
	char arg[MAX_STRING_LENGTH];
	static char text[MAX_STRING_LENGTH];
	int dc = 0;
	
	push_call("translate_document(%p)",text);
	
	if (obj->item_type != ITEM_BOOK && obj->item_type != ITEM_PAPER)
	{
		pop_call();
		return "<Error: Not a written document>";
	}
	if (!obj->value[3] || IS_SET(obj->value[3], ch->language))
	{
		pop_call();
		return capitalize(argument);
	}
	else if (obj->value[3])
	{
		dc = 25;
	}
	
	dc = UMAX(dc, obj->value[4]);
	
	if (dc && !learned(ch, gsn_decipher_script))
	{
		pop_call();
		return capitalize(translate(argument));
	}
	
	text[0] = '\0';

	while (*argument != '\0')
	{
		argument = one_argument(argument, arg);
		if (strlen(text))
		{
			strcat(text, " ");
		}
		if (decipher_script_check(ch, NULL, decipher_script_roll(ch), dc))
		{
			strcat(text, arg);
		}
		else
		{
			strcat(text, translate(arg));
		}
	}
	pop_call();
	return capitalize(text);
}

/*
 * Translate a language from speaker to listener - Kregor
 * Open variant of d20 rules uses the Linguist feat as a
 * way to recognize words in an unknown language based on
 * a skill roll with the number of ranks (tongues known)
 * in Speak Language
 */
char *translate_to_char(CHAR_DATA *ch, CHAR_DATA *victim, char *argument)
{
	char arg[MAX_STRING_LENGTH];
	static char text[MAX_STRING_LENGTH];
	int roll, adj;
	
	push_call("translate_to_char(%p)",text);
	
	if (can_understand(victim, ch, FALSE))
	{
		pop_call();
		return capitalize(argument);
	}

	if (IS_NPC(victim) || !learned(victim, gsn_linguist))
	{
		pop_call();
		return capitalize(translate(argument));
	}
	
	text[0] = '\0';
	adj = count_tongues(victim) + 2;
	adj += stat_bonus(TRUE, victim, APPLY_INT);

	while (*argument != '\0')
	{
		argument = one_argument(argument, arg);
		if (strlen(text))
		{
			strcat(text, " ");
		}
		if ((roll = dice(1,20)) != 1 && (roll + adj) >= 20)
		{
			strcat(text, arg);
		}
		else
		{
			strcat(text, translate(arg));
		}
	}
	pop_call();
	return capitalize(text);
}

char *translate (char *text)
{
	static char text2[MAX_STRING_LENGTH];
	char *pName;
	int iSyl;
	int length;

	struct syl_type
	{
		char *old;
		char *new;
	};

	static const struct syl_type syl_table[] =
	{
		{	" ",		" "		},
		{	"ar",	"ug"		},
		{	"au",	"ja"		},
		{	"le",	"fi"		},
		{	"li",	"ni"		},
		{	"ur",	"ir"		},
		{	"cu",	"je"		},
		{	"de",	"ca"		},
		{	"en",	"un"		},
		{	"li",	"fu"		},
		{	"lo",	"hi"		},
		{	"mo",	"za"		},
		{	"ma",	"do"		},
		{	"ne",	"la"		},
		{	"ni",	"ta"		},
		{	"pe",	"da"		},
		{	"ra",	"ru"		},
		{	"re",	"ca"		},
		{	"so",	"sa"		},
		{	"ec",	"in"		},
		{	"ri",	"lu"		},
		{	"en",	"of"		},
		{	"a",		"i"		},
		{	"b",		"t"		},
		{	"c",		"f"		},
		{	"d",		"p"		},
		{	"e",		"u"		},
		{	"f",		"l"		},
		{	"g",		"j"		},
		{	"h",		"s"		},
		{	"i",		"e"		},
		{	"j",		"n"		},
		{	"k",		"b"		},
		{	"l",		"k"		},
		{	"m",		"g"		},
		{	"n",		"r"		},
		{	"o",		"a"		},
		{	"p",		"y"		},
		{	"q",		"d"		},
		{	"r",		"m"		},
		{	"s",		"h"		},
		{	"t",		"w"		},
		{	"u",		"o"		},
		{	"v",		"x"		},
		{	"w",		"q"		},
		{	"x",		"z"		},
		{	"y",		"c"		},
		{	"z",		"v"		},
		{	"?",		"?"		},
		{	"!",		"!"		},
		{	".",		"."		},
		{	")",		")"		},
		{	"(",		"("		},
		{	":",		":"		},
		{	"'",		"'"		},
		{	"-",		"-"		},
		{	"=",		"="		},
		{	"*",		"*"		},
		{	"%",		"%"		},
		{	",",		","		},
		{	"<",		"<"		},
		{	">",		">"		},
		{	"",		""		}
	};

	push_call("translate(%p)",text);

	text2[0] = '\0';

	for (pName = text ; *pName != '\0' ; pName += length)
	{
		for (iSyl = 0 ; (length = strlen (syl_table[iSyl].old)) != 0 ; iSyl++)
		{
			if (!str_prefix(syl_table[iSyl].old, pName))
			{
				strcat(text2, syl_table[iSyl].new);
				break;
			}
		}
		if (length == 0)
		{
			length = 1;
		}
	}
	pop_call();
	return text2;
}

/* 
 * New drunk function for figuring out
 * intoxication, based on OG content from
 * Natural Press - Kregor
 */
int alcohol_threshold( CHAR_DATA *ch )
{
	int value;
	
	value = get_curr_con(ch);
	value += get_apply(ch, APPLY_SAVE_POISON);

	switch (get_size(ch))
	{
		case SIZE_FINE:
			value /= 16;
			break;
		case SIZE_DIMINUTIVE:
			value /= 8;
			break;
		case SIZE_TINY:
			value /= 4;
			break;
		case SIZE_SMALL:
			value /= 2;
			break;
		case SIZE_LARGE:
			value *= 2;
			break;
		case SIZE_HUGE:
			value *= 4;
			break;
		case SIZE_GARGANTUAN:
			value *= 8;
			break;
		case SIZE_COLOSSAL:
			value *= 16;
			break;
	}
	return value;
}

/*
 * Snatched from Forsaken Lands mud as a much
 * better drunkify command - Kregor
 */
struct drunk_type
{
    sh_int  min_drunk_level; 
    sh_int  number_of_rep;
    char    *replacement[11];
};

int drunk_level( CHAR_DATA *ch )
{
	int drunklevel = 0;
	
	if (IS_NPC(ch))
		return 0;
		
	if (is_affected(ch, gsn_delay_poison))
		return 0;
	
	if ((drunklevel = ch->pcdata->condition[COND_DRUNK]) > 0)
		drunklevel /= alcohol_threshold(ch);
		
	return drunklevel;
}

void makedrunk( CHAR_DATA *ch, char *string )
{
	struct drunk_type drunk[] =
	{
		{2, 9,	{"a", "a", "a", "A", "aa", "ah", "Ah", "ao", "aw", "ahh"}},
		{4, 5,	{"b", "b", "b", "B", "B", "vb"}},
		{2, 5,	{"c", "c", "C", "cj", "sj", "zj"}},
		{3, 2,	{"d", "d", "D"}},
		{2, 4,	{"e", "e", "eh", "E", "Eh"}},
		{2, 4,	{"f", "f", "F", "ff", "fF"}},
		{4, 3,	{"g", "g", "G", "gh"}},
		{5, 5,	{"h", "h", "H", "hh", "Hh", "hH"}},
		{4, 6,	{"i", "i", "I", "ii", "ii", "iI", "I-yEe"}},
		{5, 5,	{"j", "j", "J", "jj", "Jj", "jJ"}},
		{4, 4,	{"k", "k", "K", "ck", "K-kk"}},
		{2, 2,	{"l", "l", "L"}},
		{3, 6,	{"m", "m", "M", "mm", "mM", "mmm", "MmM"}},
		{3, 6,	{"n", "n", "N", "nn", "Nn", "nnn", "nNn"}},
		{2, 6,	{"o", "o", "O", "ao", "oo", "Oo", "oOo"}},
		{2, 2,	{"p", "p", "P"}},
		{3, 4,	{"q", "q", "Q", "qu", "ku"}},
		{2, 2,	{"r", "r", "R"}},
		{1, 6,	{"s", "ss", "sSs", "szs", "ZSs", "sSz", "Zsz"}},
		{3, 2,	{"t", "t", "T"}},
		{2, 6,	{"u", "u", "uh", "U", "Uh", "uhh", "uhH"}},
		{2, 2,	{"v", "v", "V"}},
		{2, 2,	{"w", "ww", "W"}},
		{3, 6,	{"x", "x", "X", "xz", "kz", "kx", "iks"}},
		{2, 2,	{"y", "y", "Y"}},
		{1, 7,	{"z", "z", "Z", "zZ", "szz", "ZSz", "Zzz", "sZz"}}
	};

	char buf[MAX_STRING_LENGTH];	
	char* str; /* current place in the string */
	char* point; /* current place inthe buffer */	
	int drunklevel, randomnum, count;
	
	push_call("makedrunk(%p,%p)",ch,string);

	if (IS_NPC(ch))
		drunklevel = 0;
	else if (IS_PLR(ch, PLR_HOLYLIGHT))
		drunklevel = 0;
	else
		drunklevel = drunk_level(ch);

	if (drunklevel > 0)
	{
	
		/* begin converting string to drunk talk. */
		str = string;
		point = buf; /* we need buffer as the result might be longer then original. */
	
		while (*str != '\0')
		{
			if (UPPER(*str) >= 'A' && UPPER(*str) <= 'Z')
			{
				if (drunklevel > drunk[*str - 'A'].min_drunk_level)
				{
					randomnum = number_range (0, drunk[UPPER(*str) - 'A'].number_of_rep);
					strcpy(point, drunk[UPPER(*str) - 'A'].replacement[randomnum]);
					point += strlen(drunk[UPPER(*str) - 'A'].replacement[randomnum]);
				}
				else
					*point++ = *str;
			}
			else if (*str == '.')
			{
				randomnum = number_range(1,drunklevel/5);
				for (count = 0; count < 3 && count < randomnum; count++)
					*point++ = *str;
			}
			else if (*str == '%')
				*point++ = *str;
			else if (*str >= '0' && *str <= '9')
				*point++ = '0' + number_range (0, 9);
			else
				*point++ = *str;
			str++;
		}
		*point = '\0';
		strncpy(string, buf, MAX_INPUT_LENGTH);
	}
	pop_call();
	return;
}


/*
 * is player with pvnum in another player's group?
 */
bool pvnum_in_group (CHAR_DATA * ch, int pvnum)
{
	CHAR_DATA *fch;

	push_call("pvnum_in_group(%p,%p)",ch,pvnum);

	if ((fch = get_char_pvnum(pvnum)) == NULL)
	{
		pop_call();
		return FALSE;
	}
	pop_call();
	return is_same_group(fch, ch);
}


/*
 * Handles repeat numbers given before a command
 */
void do_repeat (CHAR_DATA * ch, char *argument)
{
	char buf1[MAX_INPUT_LENGTH], buf2[MAX_INPUT_LENGTH];
	int amount, cnt;
	char bbuf[MAX_INPUT_LENGTH], *bptr;

	push_call("do_repeat(%p,%p)",ch,argument);

	if (ch->desc == NULL)
	{
		pop_call();
		return;
	}

	argument = one_argument(argument, buf1);

	amount = atol(buf1);

	if (amount < 2 || amount > 99 || isdigit(argument[0]))
	{
		send_to_char ("Syntax: <amount> <command string>\n\r", ch);
		send_to_char ("Amount range is 2 to 99, or whatever the input line can hold.\n\r", ch);
		pop_call();
		return;
	}

	if (argument[0] == '.' || argument[0] == '!')
	{
		send_to_char("That would be a bad idea.\n\r", ch);
		pop_call();
		return;
	}

	if ((bptr = strstr(argument, "&")) != NULL)
	{
		sprintf(bbuf, "%s\r", bptr+1);
		*bptr = '\0';
	}
	else
	{
		strcpy(bbuf, ch->desc->inbuf);
	}
	sprintf(buf2, "%s\r", argument);

	*ch->desc->inbuf = 0;
	 ch->desc->intop = 0;

	for (cnt = 0 ; cnt < amount ; cnt++)
	{
		if (ch->desc->intop >= MAX_INPUT_LENGTH - 6)
		{
			break;
		}
		ch->desc->intop = str_apd_max(ch->desc->inbuf, buf2, ch->desc->intop, MAX_INPUT_LENGTH - 5);
	}
	ch->desc->intop = str_apd_max(ch->desc->inbuf, bbuf, ch->desc->intop, MAX_INPUT_LENGTH - 6);

	pop_call();
	return;
}

/*
void do_toggle (CHAR_DATA * ch, char *argument)
{
	push_call("do_toggle(%p,%p)",ch,argument);

	if (!is_desc_valid (ch) || IS_NPC (ch))
	{
		pop_call();
		return;
	}

	if (argument[0] == '\0')
	{
		send_to_char("Syntax: request < c+ | c- | v+ | v- >\n\r", ch);
	}
	else if (!str_prefix(argument, "v+") && IS_SET(ch->pcdata->request, REQUEST_VT_SAVE_ON))
	{
		if (ch->pcdata->vt100 == 0)
		{
			REMOVE_BIT(ch->pcdata->request, REQUEST_VT_SAVE_ON);
			vt100on (ch);
		}
	}
	else if (!str_prefix(argument, "v-") && !IS_SET(ch->pcdata->request, REQUEST_VT_SAVE_ON))
	{
		if (ch->pcdata->vt100 == 1)
		{
			SET_BIT (ch->pcdata->request, REQUEST_VT_SAVE_ON);
			vt100off (ch);
		}
	}
	else if (!str_prefix(argument, "c+") && IS_SET(ch->pcdata->request, REQUEST_ANSI_SAVE_ON))
	{
		if (ch->pcdata->ansi == FALSE)
		{
			REMOVE_BIT(ch->pcdata->request, REQUEST_ANSI_SAVE_ON);
			ch->pcdata->ansi = TRUE;
		}
	}
	else if (!str_prefix(argument, "c-") && !IS_SET(ch->pcdata->request, REQUEST_ANSI_SAVE_ON))
	{
		if (ch->pcdata->ansi == TRUE)
		{
			SET_BIT(ch->pcdata->request, REQUEST_ANSI_SAVE_ON);
			ch->pcdata->ansi = FALSE;
		}
	}
	pop_call();
	return;
}
*/

/*
 * Block another player from communicating with you
 * or seeing his text strings.
 */
void do_block (CHAR_DATA * ch, char *argument)
{
	char buf1[MAX_STRING_LENGTH];

	push_call("do_block(%p,%p)",ch,argument);

	if (!is_desc_valid (ch) || IS_NPC (ch))
	{
		pop_call();
		return;
	}


	argument = one_argument(argument, buf1);

	if (buf1[0] == '\0')
	{
		ch_printf_color(ch, "Your block-list is:\n\r > %s\n\r", ch->pcdata->block_list);
		pop_call();
		return;
	}
	smash_tilde (buf1);

	if (!strcasecmp ("clear", buf1))
	{
		STRFREE(ch->pcdata->block_list);
		ch->pcdata->block_list = STRALLOC("");
		send_to_char("Your block-list is now empty.\n\r", ch);
		pop_call();
		return;
	}

	if (!strcasecmp ("del", buf1))
	{
		char *block_list,
			block_names	[MAX_INPUT_LENGTH],
			name_to_delete	[MAX_INPUT_LENGTH],
			cur_name		[MAX_INPUT_LENGTH],
			new_block		[MAX_INPUT_LENGTH];

		argument = one_argument(argument, name_to_delete);

		strcpy (block_names, ch->pcdata->block_list);

		block_list = &(block_names[0]);
		block_list = one_argument(block_list, cur_name);

		for (new_block[0] = '\0' ; cur_name[0] != '\0' ; block_list = one_argument(block_list, cur_name))
		{
			if (!strcasecmp (cur_name, name_to_delete))
			{
				strcat(new_block, block_list);
				STRFREE(ch->pcdata->block_list);
				ch->pcdata->block_list = STRALLOC (new_block);

				ch_printf_color(ch, "Deleted '%s' from your block-list.\n\r", name_to_delete);
				ch_printf_color(ch, "Your block-list is now:\n\r > %s\n\r", ch->pcdata->block_list);
				pop_call();
				return;
			}
			else
			{
				cat_sprintf(new_block, "%s ", cur_name);
			}
		}
		ch_printf_color(ch, "I was unable to find '%s' in your block-list.\n\r", name_to_delete);
		pop_call();
		return;
	}

	if (!strcasecmp ("add", buf1))
	{
		if (strlen(ch->pcdata->block_list) > 150)
		{
			send_to_char ("Your block-list is too long to add a new name.\n\r", ch);
			pop_call();
			return;
		}
		sprintf(buf1, "%s %s", ch->pcdata->block_list, argument);

		STRFREE (ch->pcdata->block_list);
		ch->pcdata->block_list = STRALLOC(buf1);

		ch_printf_color(ch, "Your block-list is now:\n\r > %s\n\r", ch->pcdata->block_list);
		pop_call();
		return;
	}
	send_to_char ("Incorrect blocking format.\n\rUse: block [<add/clear/del>] [namelist]\n\r", ch);
	pop_call();
	return;
}

/*
 * Sets your death room (if game is not perm death - Kregor
 */
void do_death (CHAR_DATA * ch, char *arg)
{
	push_call("do_death(%p,%p)",ch,arg);

	if (PERM_DEATH)
	{
		send_to_char("There is no death room in this game, perm death is enforced.\n\r", ch);
		pop_call();
		return;
	}
	if (IS_NPC(ch))
	{
		pop_call();
		return;
	}

	if (IS_SET(ch->in_room->room_flags, ROOM_SAFE))
	{
		ch->pcdata->death_room = ch->in_room->vnum;
		send_to_char ("Death room set.\n\r", ch);
		pop_call();
		return;
	}
	ch_printf_color(ch, "You may not set your death room here.\n\rYour death room is currently set at: %s\n\r", room_index[ch->pcdata->death_room]->name);

	pop_call();
	return;
}

/* 
 * Respawn command for players to return from
 * perm death on their own. Note this is the harshest
 * penalty method over getting a prayer for return.
 * Penalty is 1 level, plus a CON point. - Kregor
 */
void do_respawn( CHAR_DATA *ch, char *argument )
{
	OBJ_DATA *corpse, *obj_content;
	char arg[MAX_INPUT_LENGTH];
	int room;

	push_call("do_respawn(%p,%p,%p,%p)",ch,argument);

	if (IS_NPC(ch))
	{
		send_to_char("Only PCs can respawn.\n\r", ch);
		pop_call();
		return;
	}
	
	if (!IS_PLR(ch, PLR_DEAD))
	{
		send_to_char("Only the dead can respawn!\n\r", ch);
		pop_call();
		return;
	}

	argument = one_argument(argument, arg);
	corpse = NULL;
	
	if (*arg == '\0')
	{
		send_to_char("Respawn at corpse or temple?\n\r", ch);
		pop_call();
		return;
	}
	else if (!strcasecmp(arg, "corpse"))
	{
		if((corpse = find_char_corpse(ch, TRUE)) == NULL || !corpse->in_room)
		{
			send_to_char("Your spirit can't locate your corpse.\n\r", ch);
			pop_call();
			return;
		}
		room = corpse->in_room->vnum;
	}
	else
		room = ch->pcdata->death_room;
	
	if (ch->perm_con < 2)
	{
		send_to_char("Your spirit is too weak to respawn.\n\r", ch);
		pop_call();
		return;
	}

	act("{138}$n's spirit departs from the beyond!", ch, NULL, NULL, TO_ROOM);
	char_from_room(ch);
	char_to_room(ch, room, TRUE);
	
	bool GodSave = FALSE;
	
	if (ch->pcdata->god_favor <= 0)
	{
		GodSave = FALSE;
	}
	else if (domain_apotheosis(ch, DOMAIN_RENEWAL))
	{
		GodSave = TRUE;
		ch->pcdata->god_favor -= 500;
	}
	else if (ch->god && ch->pcdata->god_favor > 0 && (number_percent() <= ch->pcdata->god_favor/1000))
	{
		GodSave = TRUE;
		ch->pcdata->god_favor -= 500;
	}
	
	if (ch->level > 1 && !GodSave)
	{
		AFFECT_DATA af;
		
		ch->perm_con--;

		af.type      = gsn_drain;
		af.duration  = -1;
		af.modifier  = 0 - UMIN(2, ch->perm_str - 1);
		af.location  = APPLY_STR_DRAIN;
		af.bittype   = AFFECT_TO_NONE;
		af.bitvector = AFF_NONE;
		af.level		 = ch->level;	
		affect_join( ch, ch, &af );
	
		af.location  = APPLY_CON_DRAIN;
		af.modifier  = 0 - UMIN(2, ch->perm_con - 1);
		affect_join( ch, ch, &af );
	
		af.location  = APPLY_DEX_DRAIN;
		af.modifier  = 0 - UMIN(2, ch->perm_dex - 1);
		affect_join( ch, ch, &af );
	
		af.location  = APPLY_LEVEL;
		af.modifier  = (ch->level == 2) ? (0-1) : (0-2);
		affect_join( ch, ch, &af );
	}

	if (GodSave)
	{
		ch->hit = get_max_hit(ch);
		ch->move = get_max_move(ch);
	}
	else
	{
		destroy_mana(ch);
		ch->hit = ch->level;
		ch->move = ch->level;
	}

	act("{138}You have returned from the dead!", ch, NULL, NULL, TO_CHAR);
	act("{138}$n has returned from the dead!", ch, NULL, NULL, TO_ROOM);
	REMOVE_BIT(ch->act, PLR_DEAD);
	
	if (corpse)
	{
		while ((obj_content = corpse->last_content) != NULL)
		{
			obj_from_obj(obj_content);
			obj_to_char(obj_content, ch);
		}
		extract_obj(corpse);
	}

	do_look(ch, "");

	pop_call();
	return;
}


/*
 * Can a character/mob actually talk? - Kregor
 */
bool CAN_TALK(CHAR_DATA *ch)
{
	push_call("CAN_TALK(%p)",ch);

	if (ch->perm_int < 3)
	{
		pop_call();
		return FALSE;
	}
	if (IS_AFFECTED(ch, AFF_TELEPATHY))
	{
		pop_call();
		return TRUE;
	}
	if (IS_AFFECTED(ch, AFF2_SILENCE))
	{
		pop_call();
		return FALSE;
	}
	if (!race_table[get_race(ch)].speaks)
	{
		pop_call();
		return FALSE;
	}
	switch (race_type(ch))
	{
		default:
			break;
		case RTYPE_ANIMAL:
		case RTYPE_OOZE:
		case RTYPE_VERMIN:
		case RTYPE_CONSTRUCT:
			pop_call();
			return FALSE;
	}
	if (ch->in_room->sector_type == SECT_UNDER_WATER
	&& !rspec_req(ch, RSPEC_AQUATIC))
	{
		pop_call();
		return FALSE;
	}
	pop_call();
	return TRUE;
}


/*
 * determine whether victim can understand victim - revised by Kregor
 * Passing TRUE will reset the speaker's language to one that
 * the listener can understand.
 */
bool can_understand (CHAR_DATA * victim, CHAR_DATA * speaker, bool fChange)
{
	int understand, speak, cnt;

	push_call("can_understand(%p,%p)",victim,speaker);

	if (speaker == victim)
	{
		pop_call();
		return TRUE;
	}

	if (IS_AFFECTED(speaker, AFF_TONGUES) || IS_AFFECTED(victim, AFF_TONGUES) || IS_AFFECTED(victim, AFF_UNDERSTAND))
	{
		pop_call();
		return TRUE;
	}

	if ((IS_AFFECTED(speaker, AFF_TELEPATHY) && !IS_AFFECTED(victim, AFF_MIND_BLANK)) || IS_AFFECTED(victim, AFF_TELEPATHY))
	{
		pop_call();
		return TRUE;
	}

	if (IS_PLR(speaker, PLR_HOLYLIGHT) || IS_PLR(victim, PLR_HOLYLIGHT))
	{
		pop_call();
		return TRUE;
	}

	if (learned(speaker, gsn_universal_tongue) || learned(victim, gsn_universal_tongue))
	{
		pop_call();
		return TRUE;
	}
	
	if ((understand = victim->language) == 0)
	{
		if (get_race(victim) == RACE_NONE)
		{
			if (IS_NPC(victim) && victim->pIndexData->understands)
			{
				understand = victim->pIndexData->understands;
			}
			else
			{
				pop_call();
				return TRUE;
			}
		}
		else
		{
			understand = race_table[victim->race].understands;
		}
	}

	if ((speak = speaker->speak) == 0)
	{
		if (get_race(speaker) == RACE_NONE)
		{
			if (IS_NPC(victim) && victim->pIndexData->speaks)
			{
				speak = victim->pIndexData->speaks;
			}
			else
			{
				pop_call();
				return TRUE;
			}
		}
		else
		{
			speak	= race_table[speaker->race].speaks;
		}
	}

	if (!IS_SET(understand, speak))
	{
		if (fChange && IS_NPC(speaker))
		{
			for (cnt = 0 ; cnt < MAX_LANG ; cnt++)
			{
				if (IS_SHIFT(cnt, speaker->language) && IS_SHIFT(cnt, understand))
				{
					speaker->speak = 1 << cnt;
					break;
				}
			}
			if (speaker->speak == speak)
			{
				pop_call();
				return FALSE;
			}
		}
		else
		{
			pop_call();
			return FALSE;
		}
	}
	pop_call();
	return TRUE;
}

/*
 * Roll dice and display to room.
 * Options for GM to roll in secret, also rolls
 * for all skill checks and saves - Kregor
 */
void do_diceroll( CHAR_DATA * ch, char *argument )
{
	CHAR_DATA * fch;
	char arg1[MAX_STRING_LENGTH];
	char arg2[MAX_STRING_LENGTH];
	char buf[MAX_STRING_LENGTH];
	char name[MAX_STRING_LENGTH];
	char char1;
	int value, numdice, sizedice, total;
	
	push_call("diceroll(%p,%p)", ch, argument);
	
	argument = one_argument( argument, arg1 );
	argument = one_argument( argument, arg2 );

	if ( arg1[0] == '\0' )
	{
		if (IS_IMMORTAL(ch))
		{
			ch_printf_color(ch, "Syntax: diceroll <?d?> [secret]\n\r");
			ch_printf_color(ch, "        diceroll <skill name|will|fort|refl>\n\r");
		}
		else
		{
			ch_printf_color(ch, "Syntax: diceroll ?d?\n\r");
			ch_printf_color(ch, "        diceroll <skill name|will|fort|refl>\n\r");
		}
		pop_call();
		return;
	}
		
	value = sscanf(arg1, "%d %c %d", &numdice, &char1, &sizedice);

	if (char1 != 'd' || value != 3)
	{
		if (!strcasecmp(arg1, "appraise"))
		{
			total = appraise_roll(ch, NULL);
			strcpy( name, "Appraise Roll" );
		}
		else if (!strcasecmp(arg1, "bluff"))
		{
			total = bluff_roll(ch);
			strcpy( name, "Bluff Roll" );
		}
		else if (!strcasecmp(arg1, "climb"))
		{
			total = climb_roll(ch);
			strcpy( name, "Climb Roll" );
		}
		else if (!strcasecmp(arg1, "concentration"))
		{
			total = concentration_roll(ch);
			strcpy( name, "Concentration Roll" );
		}
		else if (!strcasecmp(arg1, "decipher"))
		{
			total = decipher_script_roll(ch);
			strcpy( name, "Decipher Script Roll" );
		}
		else if (!strcasecmp(arg1, "diplomacy"))
		{
			total = diplomacy_roll(ch);
			strcpy( name, "Diplomacy Roll" );
		}
		else if (!strcasecmp(arg1, "disable") && !str_prefix(arg2, "device"))
		{
			total = disable_device_roll(ch);
			strcpy( name, "Disable Device Roll" );
		}
		else if (!strcasecmp(arg1, "disguise"))
		{
			total = disguise_roll(ch);
			strcpy( name, "Disguise Roll" );
		}
		else if (!strcasecmp(arg1, "escape"))
		{
			total = escape_artist_roll(ch);
			strcpy( name, "Escape Artist Roll" );
		}
		else if (!strcasecmp(arg1, "first") && !str_prefix(arg2, "aid"))
		{
			total = first_aid_roll(ch);
			strcpy( name, "First Aid Roll" );
		}
		else if (!strcasecmp(arg1, "streetwise"))
		{
			total = streetwise_roll(ch);
			strcpy( name, "Streetwise Roll" );
		}
		else if (!strcasecmp(arg1, "haggle"))
		{
			total = haggle_roll(ch);
			strcpy( name, "Haggle Roll" );
		}
		else if (!strcasecmp(arg1, "intimidate"))
		{
			total = intimidate_roll(ch);
			strcpy( name, "Intimidate Roll" );
		}
		else if (!strcasecmp(arg1, "jump"))
		{
			total = jump_roll(ch);
			strcpy( name, "Jump Roll" );
		}
		else if (!strcasecmp(arg1, "mount"))
		{
			total = mount_roll(ch);
			strcpy( name, "Mount Roll" );
		}
		else if (!strcasecmp(arg1, "pick") && !strcasecmp(arg2, "lock"))
		{
			total = pick_lock_roll(ch);
			strcpy( name, "Pick Lock Roll" );
		}
		else if (!strcasecmp(arg1, "perception"))
		{
			if (!strcasecmp(arg2, "sight"))
			{
				total = perception_roll(ch, SENSE_SIGHT);
			}
			else if (!strcasecmp(arg2, "sound"))
			{
				total = perception_roll(ch, SENSE_SOUND);
			}
			else if (!strcasecmp(arg2, "smell"))
			{
				total = perception_roll(ch, SENSE_SMELL);
			}
			else if (!strcasecmp(arg2, "touch"))
			{
				total = perception_roll(ch, SENSE_TOUCH);
			}
			else if (!strcasecmp(arg2, "taste"))
			{
				total = perception_roll(ch, SENSE_TASTE);
			}
			else
			{
				send_to_char_color( "Which perception? <sight|sound|smell|touch|taste>.\n\r", ch );
				pop_call();
				return;
			}
			strcpy( name, "Perception Roll" );
		}
		else if (!strcasecmp(arg1, "perform"))
		{
			total = perform_roll(ch);
			strcpy( name, "Perform Roll" );
		}
		else if (!strcasecmp(arg1, "search"))
		{
			total = search_roll(ch);
			strcpy( name, "Search Roll" );
		}
		else if (!strcasecmp(arg1, "sense") && !strcasecmp(arg2, "motive"))
		{
			total = sense_motive_roll(ch);
			strcpy( name, "Sense Motive Roll" );
		}
		else if (!strcasecmp(arg1, "sleight"))
		{
			total = sleight_of_hand_roll(ch);
			strcpy( name, "Sleight of Hand Roll" );
		}
		else if (!strcasecmp(arg1, "stealth"))
		{
			total = hide_roll(ch);
			strcpy( name, "Stealth Roll" );
		}
		else if (!strcasecmp(arg1, "spellcraft"))
		{
			total = spellcraft_roll(ch);
			strcpy( name, "Spellcraft Roll" );
		}
		else if (!strcasecmp(arg1, "survival"))
		{
			total = survival_roll(ch);
			strcpy( name, "Survival Roll" );
		}
		else if (!strcasecmp(arg1, "swim"))
		{
			total = swim_roll(ch);
			strcpy( name, "Swim Roll" );
		}
		else if (!strcasecmp(arg1, "tumble"))
		{
			total = tumble_roll(ch);
			strcpy( name, "Tumble Roll" );
		}
		else if (!strcasecmp(arg1, "use") && !strcasecmp(arg2, "magic"))
		{
			total = use_magic_roll(ch);
			strcpy( name, "Use Magic Roll" );
		}
		else if (!strcasecmp(arg1, "will"))
		{
			total = dice(1,20) + get_will_save(ch);
			strcpy( name, "WILL Save" );
		}
		else if (!strcasecmp(arg1, "fort"))
		{
			total = dice(1,20) + get_fort_save(ch);
			strcpy( name, "FORT Save" );
		}
		else if (!strcasecmp(arg1, "refl"))
		{
			total = dice(1,20) + get_refl_save(ch);
			strcpy( name, "REFL Save" );
		}
		else
		{
			ch_printf_color(ch, "Syntax: diceroll ?d?\n\r");
			pop_call();
			return;
		}		
		sprintf( buf, "%d", total );
		act( "{200}$t: {078}You roll: {178}$T", ch, name, buf, TO_CHAR );
		act( "{200}$t: {078}$n rolls: {178}$T", ch, name, buf, TO_CAN_SEE );
	}
	else
	{
		strcpy(name, arg1);

		if (numdice < 1)
		{
			send_to_char_color( "That is not a valid number of dice.\n\r", ch );
			pop_call();
			return;
		}
	
		total = dice( numdice, sizedice );
		
		ch_printf_color( ch, "{200}OOC: {078}You roll %dd%d and get: {178}%d\n\r", numdice, sizedice, total );
		
		if (IS_IMMORTAL(ch) && !strcasecmp(arg2, "secret"))
		{
			pop_call();
			return;
		}
		
		for (fch = ch->in_room->first_person ; fch ; fch = fch->next_in_room)
		{
			if (blocking (fch, ch))
				continue;
	
			if (fch == ch)
				continue;
	
			ch_printf_color( fch, "{200}OOC: {078}%s rolls %dd%d gets: {178}%d\n\r", capitalize(PERS(ch, fch)), numdice, sizedice, total );
		}
	}

	sprintf(buf, "%s = %d", name, total);
	append_file(ch, DICE_FILE, NULL, buf);

	pop_call();
	return;
}

/*****************************************************
 * Letter writing/sealing/reading 
 * inspired by John Patrick (j.s.patrick@ieee.org)
 * Functions rewritten extensively - Kregor
 *****************************************************/
/*
 * Write a note
 * rewritten to use edit buffer
 */
void do_write(CHAR_DATA *ch, char *argument)
{
	OBJ_DATA *letter, *pen;
	char buf[MAX_INPUT_LENGTH];

	push_call("do_write(%p,%p)",ch,argument);

	CHECK_EDITMODE( ch );

	if (!ch->desc)
	{
		bug( "do_write: no descriptor", 0 );
		pop_call();
		return;
	}

	if (!check_blind(ch))
	{
		pop_call();
		return;
	}

	if (!can_see_in_room(ch, ch->in_room))
	{
		send_to_char("You can't see here to write.\n\r",ch);
		pop_call();
		return;
	}

	if ((letter = get_obj_carry_type(ch, ITEM_PAPER)) == NULL)
	{
		send_to_char("You have no parchment to write on.\n\r",ch);
		pop_call();
		return;
	}

	if ((pen = get_obj_wear_type(ch, ITEM_TOOLS)) == NULL || !TOOL_TYPE(pen, TOOL_PEN))
	{
		send_to_char("You need to be holding a pen to write with.\n\r",ch);
		pop_call();
		return;
	}
	if (pen->value[1] == 0)
	{
		act( "$p is out of ink.", ch, pen, NULL, TO_CHAR);
		junk_obj(pen);
		pop_call();
		return;
	}

	/* Check to see if it's blank.	*/
	if (letter->value[1] != 0)
	{
		act( "$p has already been written on.", ch, letter, NULL, TO_CHAR);
		pop_call();
		return;
	}

	/* Write the note.	*/
	switch( ch->pcdata->editmode )
	{
		default:
			bug( "do_write: illegal editmode", 0 );
			pop_call();
			return;

		case MODE_RESTRICTED:
			send_to_char( "You cannot use this command from while editing something else.\n\r",ch);
			pop_call();
			return;

		case MODE_NONE:
			act( "You set to writing on $p.", ch, letter, NULL, TO_CHAR);
			act( "$n sets to writing on $p.", ch, letter, NULL, TO_CAN_SEE);
			ch->pcdata->editmode = MODE_EDIT_NOTE;
			ch->pcdata->tempmode  = MODE_NONE;
			start_editing( ch, letter->description );
			pop_call();
			return;

		case MODE_EDIT_NOTE:
			STRFREE ( letter->description );
			letter->description = copy_buffer( ch );
			stop_editing( ch );

			/* Flag the letter as being written.	*/
			letter->value[1] = 1;
		
			/* Make sure it's not flagged sealed.	*/
			letter->value[2] = 0;
		
			/* And now, set the language of the note.	*/
			letter->value[3] = ch->speak;
		
			/* Set the short description to say a written letter.	*/
			sprintf(buf, "handwritten note %s", letter->name);
			letter->name = STRALLOC(buf);
		
			/* Set the short description to say a written letter.	*/
			sprintf(buf, "a handwritten note on %s", letter->short_descr);
			STRFREE(letter->short_descr);
			letter->short_descr = STRALLOC(buf);
		
			/* Change the long description. */
			STRFREE(letter->long_descr);
			strcat(buf," lies here.");
			letter->long_descr = STRALLOC(buf);
		 
			/* Let the characters know.	*/
			act( "$n finishes writing $s note.",ch, NULL, NULL, TO_CAN_SEE);
			
			if (pen->value[1] > 0)
			{
				if (--pen->value[1] == 0)
				{
					act( "$p runs out of ink.", ch, pen, NULL, TO_CHAR);
					junk_obj(pen);
				}
			}
			pop_call();
			return;
	}
	pop_call();
	return; 
}

/*
 * READ a note or book
 * NOTE: Make sure to intercept do_look for books and parchment for this!
 */
void do_read(CHAR_DATA *ch, char *argument)
{
	OBJ_DATA *obj;
	char *pdesc;
	char arg[MAX_STRING_LENGTH];
	char buf[MAX_STRING_LENGTH];
	int num;

	push_call("do_read(%p,%p)",ch,argument);
	
	if (IS_NPC(ch))
	{
		send_to_char("Mobiles don't need to read.\n\r",ch);
		pop_call();
		return;
	}

	argument = one_argument(argument, arg);

	if (arg[0] == '\0')
	{
		send_to_char("What do you want to read?\n\r", ch);
		pop_call();
		return;
	}

	if ((obj = get_obj_list(ch, arg, ch->first_carrying)) == NULL)
	{
		if (!is_number(arg) && strcasecmp(arg, "next") && strcasecmp(arg, "prev"))
		{
			send_to_char("What do you want to read?\n\r",ch);
			pop_call();
			return;
		}
	}

	if (is_number(arg) || !strcasecmp(arg, "next") || !strcasecmp(arg, "prev"))
	{
		if ((obj = get_obj_carry_type(ch, ITEM_BOOK)) == NULL)
		{
			send_to_char("You are not carrying a book.\n\r",ch);
			pop_call();
			return;
		}
	}
		
	if (!IS_OBJ_TYPE(obj, ITEM_PAPER) && !IS_OBJ_TYPE(obj, ITEM_BOOK))
	{
		send_to_char("That is not a book or parchment.\n\r",ch);
		pop_call();
		return;
	}

	if (!check_blind(ch))
	{
		pop_call();
		return;
	}
	
	if (!can_see_in_room(ch, ch->in_room))
	{
		send_to_char("You can't see here to read.\n\r",ch);
		pop_call();
		return;
	}

	if (IS_OBJ_TYPE(obj, ITEM_PAPER))
	{
		/* Check to see if it's sealed.	*/
		if ((obj->value[2] != 0))
		{
			act( "$p is sealed. You have to UNSEAL it to read it.", ch, obj, NULL, TO_CHAR);
			if (!IS_PLR(ch, PLR_HOLYLIGHT))
			{
				pop_call();
				return;
			}
		}
	
		/* Check to see if it's blank.	*/
		if (obj->value[1] == 0)
		{
			send_to_char("It is blank.\n\r",ch);
			pop_call();
			return;
		}

		ch_printf_color(ch, "{300}The note reads:\n\r%s", translate_document(ch, obj, obj->description));	
		act( "$n reads $p.", ch, obj, NULL, TO_ROOM);
	}

	if (IS_OBJ_TYPE(obj, ITEM_BOOK))
	{
		if (!is_number(arg) && strcasecmp(arg, "next") && strcasecmp(arg, "prev"))
		{
			send_to_char("Syntax: read <page number|next|prev>\n\r",ch);
			pop_call();
			return;
		}
		if (is_number(arg))
		{
			sprintf(buf, "_p%d_", atoi(arg));
			if ((pdesc = get_extra_descr(buf, obj->pIndexData->first_extradesc)) == NULL)
			{
				act( "$p doesn't seem to have a chapter $T.", ch, obj, arg, TO_CHAR);
				pop_call();
				return;
			}
			if (obj->value[1] == 0)
				act( "$n opens $p to read.", ch, obj, NULL, TO_CAN_SEE);
			else
				act( "$n reads $p.", ch, obj, NULL, TO_CAN_SEE);
			ch_printf_color(ch, "{300}%s - Page %d\n\r%s", obj->short_descr, atoi(arg), translate_document(ch, obj, pdesc));
			obj->value[1] = atoi(arg);
		}
		else if (!strcasecmp(arg, "next"))
		{
			if ((num = obj->value[1] + 1) > obj->value[2])
			{
				send_to_char("You're already on the last chapter.", ch);
				pop_call();
				return;
			}
			sprintf(buf, "_p%d_", num);
			if ((pdesc = get_extra_descr(buf, obj->pIndexData->first_extradesc)) == NULL)
			{
				act( "$p doesn't seem to have a next chapter.", ch, obj, NULL, TO_CHAR);
				pop_call();
				return;
			}
			act( "$n flips a page in $p.", ch, obj, NULL, TO_CAN_SEE);
			ch_printf_color(ch, "{300}%s - Page %d\n\r%s", obj->short_descr, num, translate_document(ch, obj, pdesc));
			obj->value[1]++;
		}
		else if (!strcasecmp(arg, "prev"))
		{
			if ((num = obj->value[1] - 1) <= 0)
			{
				act( "You're already on the first chapter of $p.", ch, obj, NULL, TO_CHAR);
				pop_call();
				return;
			}
			sprintf(buf, "_p%d_", num);
			if ((pdesc = get_extra_descr(buf, obj->pIndexData->first_extradesc)) == NULL)
			{
				act( "$p doesn't seem to have a previous chapter.", ch, obj, NULL, TO_CHAR);
				pop_call();
				return;
			}
			act( "$n flips a page in $p.", ch, obj, NULL, TO_CAN_SEE);
			ch_printf_color(ch, "{300}%s - Page %d\n\r%s", obj->short_descr, num, translate_document(ch, obj, pdesc));
			obj->value[1]--;
		}
	}	
	pop_call();
	return;
}

/*
 * Seal a note: renders it unreadable unless seal broken
 * altered to add custom seal description - Kregor
 */
void do_seal(CHAR_DATA *ch, char *argument)
{
	OBJ_DATA *letter, *seal;
	char buf[MAX_STRING_LENGTH];
	char arg[MAX_INPUT_LENGTH];

	push_call("do_seal(%p,%p)",ch,argument);

	if (argument[0] == '\0')
	{
		send_to_char("Syntax: seal <note> [description of seal]\n\r",ch);
		pop_call();
		return;
	}
	
	argument = one_argument(argument, arg);

	/* Look for an item matching argument.	*/
	if ((letter = get_obj_list(ch, arg, ch->first_carrying)) == NULL
	|| !IS_OBJ_TYPE(letter, ITEM_PAPER))
	{
		send_to_char("You have no message to seal.\n\r",ch);
		pop_call();
		return;
	}

	if (!check_blind(ch))
	{
		pop_call();
		return;
	}
	
	if (!can_see_in_room(ch, ch->in_room))
	{
		send_to_char("You can't see here to seal anything.\n\r",ch);
		pop_call();
		return;
	}

	/* Check to see if it's sealed.	*/
	if (letter->value[2] != 0)
	{
		act( "$p is already sealed.", ch, letter, NULL, TO_CHAR);
		pop_call();
		return;
	}

	/* OK, seal it, and let people know who sealed it.	*/
	letter->value[2] = 1;
	
	act( "You seal $p.", ch, letter, NULL, TO_CHAR);
	act( "$n seals $p.", ch, letter, NULL, TO_CAN_SEE);
	
	/* optional seal description if ch chooses a seal obj */
	if (argument[0] != '\0')
	{
		if ((seal = get_obj_list(ch, argument, ch->first_carrying)) == NULL)
		{
			send_to_char("You are not carrying that seal.\n\r", ch);
			pop_call();
			return;
		}
		if (!TOOL_TYPE(seal, TOOL_SEAL))
		{
			act( "$p is not a seal.", ch, letter, NULL, TO_CHAR);
			pop_call();
			return;
		}
		STRFREE(letter->long_descr);
		sprintf(buf, "A note is here, sealed with %s.", seal->short_descr);
		letter->long_descr = STRALLOC(buf);

		STRFREE(letter->short_descr);
		sprintf(buf, "A note sealed with %s.", seal->short_descr);
		letter->short_descr = STRALLOC(buf);
	}
	else
	{
		STRFREE(letter->long_descr);
		sprintf(buf, "A note is here, sealed with the signet of %s.", ch->name);
		letter->long_descr = STRALLOC(buf);

		STRFREE(letter->short_descr);
		letter->short_descr = STRALLOC("a sealed note");
	}
 	pop_call();
	return;
}

void do_unseal(CHAR_DATA *ch, char *argument)
{
	OBJ_DATA *letter;
	char buf[MAX_STRING_LENGTH];
	char arg[MAX_STRING_LENGTH];

	push_call("do_seal(%p,%p)",ch,argument);

	if (argument[0] == '\0')
	{
		send_to_char("Unseal what?\n\r",ch);
		pop_call();
		return;
	}
	
	argument = one_argument(argument, arg);

	/* Look for an item matching argument.	*/
	if ((letter = get_obj_list(ch, arg, ch->first_carrying)) == NULL
	|| !IS_OBJ_TYPE(letter, ITEM_PAPER))
	{
		send_to_char("You have no message to unseal.\n\r",ch);
		pop_call();
		return;
	}

	if (!check_blind(ch))
	{
		pop_call();
		return;
	}
	
	if (!can_see_in_room(ch, ch->in_room))
	{
		send_to_char("You can't see here to unseal anything.\n\r",ch);
		pop_call();
		return;
	}

	/* Check to see if it's sealed.	*/
	if (letter->value[2] == 0)
	{
		send_to_char("It is already unsealed.\n\r",ch);
		pop_call();
		return;
	}

	/* OK, unseal it and let people know.	*/
	letter->value[2] = 0;

	act( "You unseal $p.", ch, letter, NULL, TO_CHAR);
	act( "$n unseals $p.", ch, letter, NULL, TO_CAN_SEE);

	/* Change the descs to remove sealed. */
	sprintf(buf, "handwritten note %s", letter->pIndexData->name);
	STRFREE(letter->short_descr);
	letter->name = STRALLOC(buf);

	sprintf(buf, "a handwritten note on %s", letter->pIndexData->short_descr);
	STRFREE(letter->short_descr);
	letter->short_descr = STRALLOC(buf);

	STRFREE(letter->long_descr);
	strcat(buf," lies here.");
	letter->long_descr = STRALLOC(buf);
 
	pop_call();
	return;
}