SmaugWizard/Backup/
SmaugWizard/Backup/L/
SmaugWizard/Boards/
SmaugWizard/Building/
SmaugWizard/Corpses/
SmaugWizard/Councils/
SmaugWizard/Deity/
SmaugWizard/Gods/
SmaugWizard/MudProgs/
SmaugWizard/Player/L/
SmaugWizard/Src/
SmaugWizard/Src/res/
/****************************************************************************
 * [S]imulated [M]edieval [A]dventure multi[U]ser [G]ame      |				*
 * -----------------------------------------------------------|   \\._.//	*
 * SmaugWiz (C) 1998 by Russ Pillsbury (Windows NT version)   |   (0...0)	*
 * -----------------------------------------------------------|    ).:.(	*
 * SMAUG (C) 1994, 1995, 1996 by Derek Snider                 |    {o o}	*
 * -----------------------------------------------------------|   / ' ' \	*
 * SMAUG code team: Thoric, Altrag, Blodkai, Narn, Haus,      |~'~.VxvxV.~'~*
 * Scryn, Swordbearer, Rennard, Tricops, and Gorog.           |				*
 * ------------------------------------------------------------------------ *
 * 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 Staerfeldt, Tom Madsen, and Katja Nyboe.    *
 ****************************************************************************/

#include	"stdafx.h"

#include	"smaug.h"
#include	"SysData.h"
#include	"language.h"
#include	"skill.h"
#include	"mobiles.h"
#include	"objects.h"
#include	"rooms.h"
#include	"deity.h"
#include	"auction.h"
#include	"objectmenu.h"
#include	"roommenu.h"
#include	"mobmenu.h"
#include	"sysmenu.h"
#include	"playermenu.h"
#include	"races.h"
#include	"class.h"
#include	"SmaugWizDoc.h"
#include	"SmaugFiles.h"
#include	"descriptor.h"
#include	"character.h"
#include	<ctype.h>


//  Externals
void send_obj_page_to_char (CCharacter * ch, CObjIndexData * idx, char page);
void send_room_page_to_char (CCharacter * ch, CRoomIndexData * idx, char page);
void send_page_to_char (CCharacter * ch, CMobIndexData * idx, char page);
void send_control_page_to_char (CCharacter * ch, char page);

// Local functions.
void	talk_channel (CCharacter *ch, char *argument,
			    int channel, const char *verb);

char	*scramble (const char *argument, int modifier);			    
char	*drunk_speech (const char *argument, CCharacter *ch); 
int		countlangs (const CBitVector& languages);

/* Text scrambler -- Altrag */
char *scramble (const char *argument, int modifier)
{
    static char arg[MAX_INPUT_LENGTH];
    short position;
    short conversion = 0;
    
	modifier %= number_range (80, 300); /* Bitvectors get way too large #s */
    for (position = 0; position < MAX_INPUT_LENGTH; position++)
    {
    	if (argument[position] == '\0')
    	{
    		arg[position] = '\0';
    		return arg;
    	}
    	else if (argument[position] >= 'A' && argument[position] <= 'Z')
	    {
	    	conversion = -conversion + position - modifier + argument[position] - 'A';
	    	conversion = number_range (conversion - 5, conversion + 5);
	    	while (conversion > 25)
	    		conversion -= 26;
	    	while (conversion < 0)
	    		conversion += 26;
	    	arg[position] = conversion + 'A';
	    }
	    else if (argument[position] >= 'a' && argument[position] <= 'z')
	    {
	    	conversion = -conversion + position - modifier + argument[position] - 'a';
	    	conversion = number_range (conversion - 5, conversion + 5);
	    	while (conversion > 25)
	    		conversion -= 26;
	    	while (conversion < 0)
	    		conversion += 26;
	    	arg[position] = conversion + 'a';
	    }
	    else if (argument[position] >= '0' && argument[position] <= '9')
	    {
	    	conversion = -conversion + position - modifier + argument[position] - '0';
	    	conversion = number_range (conversion - 2, conversion + 2);
	    	while (conversion > 9)
	    		conversion -= 10;
	    	while (conversion < 0)
	    		conversion += 10;
	    	arg[position] = conversion + '0';
	    }
	    else
	    	arg[position] = argument[position];
	}
	arg[position] = '\0';
	return arg;	     
}

/* I'll rewrite this later if its still needed.. -- Altrag */
char *translate (CCharacter *ch, CCharacter *victim, const char *argument)
{
	return "";
}

char *drunk_speech (const char *argument, CCharacter *ch)
{
  const char *arg = argument;
  static char buf[MAX_INPUT_LENGTH*2];
  char buf1[MAX_INPUT_LENGTH*2];
  short drunk;
  char *txt;
  char *txt1;  

  if (ch->IsNpc () || !ch->GetPcData ()) return (char *) argument;

  drunk = ch->GetPcData ()->condition[COND_DRUNK];

  if (drunk <= 0)
    return (char *) argument;

  buf[0] = '\0';
  buf1[0] = '\0';

  if (!argument)
  {
     bug ("Drunk_speech: NULL argument", 0);
     return "";
  }

  /*
  if (*arg == '\0')
    return (char *) argument;
  */

  txt = buf;
  txt1 = buf1;

  while (*arg != '\0')
  {
    if (toupper (*arg) == 'S')
    {
	if (number_percent () < (drunk * 2))		/* add 'h' after an 's' */
	{
	   *txt++ = *arg;
	   *txt++ = 'h';
	}
       else
	*txt++ = *arg;
    }
   else if (toupper (*arg) == 'X')
    {
	if (number_percent () < (drunk * 2 / 2))
	{
	  *txt++ = 'c', *txt++ = 's', *txt++ = 'h';
	}
       else
	*txt++ = *arg;
    }
   else if (number_percent () < (drunk * 2 / 5))  /* slurred letters */
    {
      short slurn = number_range (1, 2);
      short currslur = 0;	

      while (currslur < slurn)
	*txt++ = *arg, currslur++;
    }
   else
    *txt++ = *arg;

    arg++;
  };

  *txt = '\0';

  txt = buf;

  while (*txt != '\0')   /* Let's mess with the string's caps */
  {
    if (number_percent () < (2 * drunk / 2.5))
    {
      if (isupper (*txt))
        *txt1 = tolower (*txt);
      else
      if (islower (*txt))
        *txt1 = toupper (*txt);
      else
        *txt1 = *txt;
    }
    else
      *txt1 = *txt;

    txt1++, txt++;
  };

  *txt1 = '\0';
  txt1 = buf1;
  txt = buf;

  while (*txt1 != '\0')   /* Let's make them stutter */
  {
    if (*txt1 == ' ')  /* If there's a space, then there's gotta be a */
    {			 /* along there somewhere soon */

      while (*txt1 == ' ')  /* Don't stutter on spaces */
        *txt++ = *txt1++;

      if ((number_percent () < (2 * drunk / 4)) && *txt1 != '\0')
      {
	short offset = number_range (0, 2);
	short pos = 0;

	while (*txt1 != '\0' && pos < offset)
	  *txt++ = *txt1++, pos++;

	if (*txt1 == ' ')  /* Make sure not to stutter a space after */
	{		     /* the initial offset into the word */
	  *txt++ = *txt1++;
	  continue;
	}

	pos = 0;
	offset = number_range (2, 4);	
	while (*txt1 != '\0' && pos < offset)
	{
	  *txt++ = *txt1;
	  pos++;
	  if (*txt1 == ' ' || pos == offset)  /* Make sure we don't stick */ 
	  {		               /* A hyphen right before a space	*/
	    txt1--;
	    break;
	  }
	  *txt++ = '-';
	}
	if (*txt1 != '\0')
	  txt1++;
      }     
    }
   else
    *txt++ = *txt1++;
  }

  *txt = '\0';

  return buf;
}


// Generic channel function.
void talk_channel (CCharacter *ch, char *argument, int channel, const char *verb)
{
	char	buf [MAX_STRING_LENGTH];
	char	buf2 [MAX_STRING_LENGTH];
	int		position;

	if (ch->IsNpc () && channel == CHANNEL_CLAN) {
		ch->SendText ("Mobs can't be in clans.\n\r");
		return;
	}

	if (ch->IsNpc () && channel == CHANNEL_ORDER) {
		ch->SendText ("Mobs can't be in orders.\n\r");
		return;
	}

	if (ch->IsNpc () && channel == CHANNEL_COUNCIL) {
		ch->SendText ("Mobs can't be in councils.\n\r");
		return;
	}

	if (ch->IsNpc () && channel == CHANNEL_GUILD) {
		ch->SendText ("Mobs can't be in guilds.\n\r");
		return;
	}

	if (! ch->IsPkiller () && channel == CHANNEL_WARTALK) {
		if (ch->IsMortal ()) {
			ch->SendText ("Peacefuls have no need to use wartalk.\n\r");
			return;
		}
	}

	if (ch->GetInRoom ()->IsSilent ()) {
		ch->SendText ("You can't do that here.\n\r");
		return;
	}

	if (ch->IsNpc () && ch->IsCharmed ()) {
		if (ch->GetMaster ())
			ch->GetMaster ()->SendText ("I don't think so...\n\r");
		return;
	}

	if (argument [0] == '\0') {
		sprintf (buf, "%s what?\n\r", verb);
		buf [0] = UPPER (buf [0]);
		ch->SendText (buf);
		return;
	}

	if (! ch->IsNpc () && ch->IsAction (PLR_SILENCE)) {
		ch->SendTextf ("You can't %s.\n\r", verb);
		return;
	}

	REMOVE_BIT (ch->deaf, channel);

	switch (channel) {
	  default:
		set_char_color (AT_GOSSIP, ch);
		ch->SendTextf ("You %s '%s'\n\r", verb, argument);
		sprintf (buf, "$n %ss '$t'", verb);
		break;
	  case CHANNEL_RACETALK:
		set_char_color (AT_RACETALK, ch);
		ch->SendTextf ("You %s '%s'\n\r", verb, argument);
		sprintf (buf, "$n %ss '$t'", verb);
		break;
	  case CHANNEL_WARTALK:
		set_char_color (AT_WARTALK, ch);
		ch->SendTextf ("You %s '%s'\n\r", verb, argument);
		sprintf (buf, "$n %ss '$t'", verb);
		break;
	  case CHANNEL_IMMTALK:
	  case CHANNEL_AVTALK:
		sprintf (buf, "$n%c $t", channel == CHANNEL_IMMTALK ? '>' : ':');
		position = ch->GetPosition ();
		ch->SetPosition (POS_STANDING);
		act (AT_IMMORT, buf, ch, argument, NULL, TO_CHAR);
		ch->SetPosition (position);
		break;
	}

	if (ch->GetInRoom ()->IsLogSpeech ()) {
		sprintf (buf2, "%s: %s (%s)",
			ch->IsNpc () ? ch->GetShortDescr () : ch->GetName (),
			argument, verb);
		append_to_file (FileTable.GetName (SM_LOG_FILE), buf2);
	}

	POSITION	pos = DList.GetHeadPosition ();
	while (pos) {
		CDescriptor	&Ds = *DList.GetNext (pos);
		if (Ds.IsDisconnecting ())
			continue;

		CCharacter *och = Ds.m_pOriginal ? Ds.m_pOriginal : Ds.m_pCharacter;
		CCharacter *vch = Ds.m_pCharacter;

		if (Ds.m_Connected == CON_PLAYING && vch != ch
		  && !IS_SET (och->deaf, channel)) {
			char *sbuf = argument;

			if (channel != CHANNEL_NEWBIE && !och->IsAuthed ())
				continue;		
			if (channel == CHANNEL_IMMTALK && och->IsMortal ())
				continue;
			if (channel == CHANNEL_WARTALK && !och->IsAuthed ())
				continue;
			if (channel == CHANNEL_AVTALK && ! och->IsHero ())
				continue;
			if (channel == CHANNEL_HIGHGOD
			  && och->GetTrustLevel () < SysData.MuseLevel)
				continue;
			if (channel == CHANNEL_HIGH &&
			  och->GetTrustLevel () < SysData.ThinkLevel)
				continue;

			/* Fix by Narn to let newbie council members see the newbie channel. */
			if (channel == CHANNEL_NEWBIE && 
				(och->IsMortal () && och->IsAuthed () 
				&& ! (och->GetPcData ()->council && 
				!str_cmp (och->GetPcData ()->council->GetName (),
				"Newbie Council"))))
					continue;
			if (vch->GetInRoom ()->IsSilent ())
				continue;
			if (channel == CHANNEL_YELL
				&& vch->GetInRoom ()->GetArea () != ch->GetInRoom ()->GetArea ())
					continue;

			if (channel == CHANNEL_CLAN || channel == CHANNEL_ORDER
			  || channel == CHANNEL_GUILD) {
				if (vch->IsNpc ())
					continue;
				if (vch->GetPcData ()->GetClan () != ch->GetPcData ()->GetClan ())
					continue;
			}

			if (channel == CHANNEL_COUNCIL) {
				if (vch->IsNpc ())
					continue;
				if (vch->GetPcData ()->council != ch->GetPcData ()->council)
					continue;
			}

			if (channel == CHANNEL_RACETALK)
				if (vch->GetRace () != ch->GetRace ())
			continue;

			CString	WizStr;
			if (ch->IsWizInvis () && can_see (vch, ch)
			  && vch->IsImmortal ()) {
				WizStr.Format ("(%d) ", (! ch->IsNpc ()) ?
					ch->GetPcData ()->wizinvis : ch->GetMobInvisLevel ());
			}
			CString	OutStr = WizStr + buf;
	    
			position = vch->GetPosition ();
			if (channel != CHANNEL_SHOUT && channel != CHANNEL_YELL)
				vch->SetPosition (POS_STANDING);
			if (! knows_language (vch, ch->GetSpeaking (), ch) &&
				! ch->IsNpc ())
					sbuf = scramble (argument, ch->GetSpeaking ());
			MOBtrigger = FALSE;
			if (channel == CHANNEL_IMMTALK || channel == CHANNEL_AVTALK)
				act (AT_IMMORT, OutStr, ch, sbuf, vch, TO_VICT);
			else if (channel == CHANNEL_WARTALK)
				act (AT_WARTALK, OutStr, ch, sbuf, vch, TO_VICT);
			else if (channel == CHANNEL_RACETALK)
				act (AT_RACETALK, OutStr, ch, sbuf, vch, TO_VICT);
			else
				act (AT_GOSSIP, buf, ch, sbuf, vch, TO_VICT);
			vch->SetPosition (position);
		}
	}
}


void to_channel (const char *argument, int channel, const char *verb, short level)
{
	char	buf [MAX_STRING_LENGTH];

	if (argument [0] == '\0' || DList.IsEmpty ())
		return;

    sprintf (buf, "%s: %s\r\n", verb, argument);

	POSITION	pos = DList.GetHeadPosition ();
	while (pos) {
		CDescriptor	&Ds = *DList.GetNext (pos);
		if (Ds.IsDisconnecting ())
			continue;

		CCharacter *och = Ds.m_pOriginal ? Ds.m_pOriginal : Ds.m_pCharacter;
		CCharacter *vch = Ds.m_pCharacter;

		if (!och || !vch)
			continue;
		if (vch->IsMortal ()
		  || (vch->GetTrustLevel () < SysData.BuildLevel
		      && channel == CHANNEL_BUILD)
		  || (vch->GetTrustLevel () < SysData.LogLevel
		      && (channel == CHANNEL_LOG
		      || channel == CHANNEL_HIGH || channel == CHANNEL_COMM)))
				continue;

		if (Ds.m_Connected == CON_PLAYING
			&&  !IS_SET (och->deaf, channel)
			&&   vch->GetTrustLevel () >= level) {
				set_char_color (AT_LOG, vch);
				vch->SendText (buf);
		}
    }
}


void do_chat (CCharacter *ch, char *argument)
{
    if (!ch->IsAuthed ())
    {
      ch->SendText ("Huh?\n\r");
      return;
    }
    talk_channel (ch, argument, CHANNEL_CHAT, "chat");
    return;
}


void do_clantalk (CCharacter *ch, char *argument)
{
    if (!ch->IsAuthed ())
    {
      ch->SendText ("Huh?\n\r");
      return;
    }

    if (ch->IsNpc () || !ch->GetPcData ()->GetClan () 
    ||   ch->GetPcData ()->GetClan ()->GetType () == CLAN_ORDER
    ||   ch->GetPcData ()->GetClan ()->GetType () == CLAN_GUILD)
    {
	ch->SendText ("Huh?\n\r");
	return;
    }
    talk_channel (ch, argument, CHANNEL_CLAN, "clantalk");
    return;
}

void do_newbiechat (CCharacter *ch, char *argument)
{
    if (ch->IsNpc ()
       || (ch->IsAuthed () && ch->IsMortal () 
       && ! (ch->GetPcData ()->council && 
          !str_cmp (ch->GetPcData ()->council->GetName (), "Newbie Council"))))
    {
        ch->SendText ("Huh?\n\r");
        return;
    }
    talk_channel (ch, argument, CHANNEL_NEWBIE, "newbiechat");
    return;
}

void do_ot (CCharacter *ch, char *argument)
{
  do_ordertalk (ch, argument);
}

void do_ordertalk (CCharacter *ch, char *argument)
{
    if (!ch->IsAuthed ())
    {
      ch->SendText ("Huh?\n\r");
      return;
    }

    if (ch->IsNpc () || !ch->GetPcData ()->GetClan () 
         || ch->GetPcData ()->GetClan ()->GetType () != CLAN_ORDER)
    {
	ch->SendText ("Huh?\n\r");
	return;
    }
    talk_channel (ch, argument, CHANNEL_ORDER, "ordertalk");
    return;
}

void do_counciltalk (CCharacter *ch, char *argument)
{
    if (!ch->IsAuthed ())
    {
      ch->SendText ("Huh?\n\r");
      return;
    }

    if (ch->IsNpc () || !ch->GetPcData ()->council)
    {
	ch->SendText ("Huh?\n\r");
	return;
    }
    talk_channel (ch, argument, CHANNEL_COUNCIL, "counciltalk");
    return;
}


void do_guildtalk (CCharacter *ch, char *argument)
{
	if (! ch->IsAuthed ()) {
		ch->SendText ("Huh?\n\r");
		return;
	}

	if (ch->IsNpc () || !ch->GetPcData ()->GetClan ()
	  || ch->GetPcData ()->GetClan ()->GetType () != CLAN_GUILD) {
		ch->SendText ("Huh?\n\r");
		return;
	}
	talk_channel (ch, argument, CHANNEL_GUILD, "guildtalk");
}


void do_music (CCharacter *ch, char *argument)
{
    if (!ch->IsAuthed ())
    {
      ch->SendText ("Huh?\n\r");
      return;
    }
    talk_channel (ch, argument, CHANNEL_MUSIC, "music");
    return;
}


void do_quest (CCharacter *ch, char *argument)
{
    if (!ch->IsAuthed ())
    {
      ch->SendText ("Huh?\n\r");
      return;
    }
    talk_channel (ch, argument, CHANNEL_QUEST, "quest");
    return;
}

void do_ask (CCharacter *ch, char *argument)
{
    if (!ch->IsAuthed ())
    {
      ch->SendText ("Huh?\n\r");
      return;
    }
    talk_channel (ch, argument, CHANNEL_ASK, "ask");
    return;
}



void do_answer (CCharacter *ch, char *argument)
{
    if (!ch->IsAuthed ())
    {
      ch->SendText ("Huh?\n\r");
      return;
    }
    talk_channel (ch, argument, CHANNEL_ASK, "answer");
    return;
}


void do_shout (CCharacter *ch, char *argument)
{
	if (! ch->IsAuthed ()) {
		ch->SendText ("Huh?\n\r");
		return;
	}
	talk_channel (ch, drunk_speech (argument, ch), CHANNEL_SHOUT, "shout");
	WAIT_STATE (ch, 12);
}


void do_yell (CCharacter *ch, char *argument)
{
    if (!ch->IsAuthed ())
    {
      ch->SendText ("Huh?\n\r");
      return;
    }
  talk_channel (ch, drunk_speech (argument, ch), CHANNEL_YELL, "yell");
  return;
}



void do_immtalk (CCharacter *ch, char *argument)
{
    if (!ch->IsAuthed ())
    {
      ch->SendText ("Huh?\n\r");
      return;
    }
    talk_channel (ch, argument, CHANNEL_IMMTALK, "immtalk");
    return;
}


void do_muse (CCharacter *ch, char *argument)
{
    if (!ch->IsAuthed ())
    {
      ch->SendText ("Huh?\n\r");
      return;
    }
    talk_channel (ch, argument, CHANNEL_HIGHGOD, "muse");
    return;
}


void do_think (CCharacter *ch, char *argument)
{
    if (!ch->IsAuthed ())
    {
      ch->SendText ("Huh?\n\r");
      return;
    }
    talk_channel (ch, argument, CHANNEL_HIGH, "think");
    return;
}


void do_avtalk (CCharacter *ch, char *argument)
{
    if (!ch->IsAuthed ())
    {
      ch->SendText ("Huh?\n\r");
      return;
    }
    talk_channel (ch, drunk_speech (argument, ch), CHANNEL_AVTALK, "avtalk");
    return;
}


void do_say (CCharacter *ch, char *argument)
{
	char	buf [MAX_STRING_LENGTH];

	if (argument [0] == '\0') {
		ch->SendText ("Say what?\n\r");
		return;
	}

	if (ch->GetInRoom ()->IsSilent ()) {
		ch->SendText ("You can't do that here.\n\r");
		return;
	}

	CActFlags	actflags = ch->GetActFlags ();		// save act flags
	if (ch->IsNpc ())
		ch->ClrActBit (ACT_SECRETIVE);

	CCharacter	*vch = ch->GetInRoom ()->first_person;
	for (; vch; vch = vch->GetNextInRoom ()) {
		char	*sbuf = argument;

		if (vch == ch)
			continue;

		// Check to see if character is ignoring speaker
		if (vch->IsIgnoring (ch)) {
			// continue unless speaker is an immortal
			if (ch->IsMortal ()
				|| vch->GetTrustLevel () > ch->GetTrustLevel ())
					continue;
			else {
				set_char_color (AT_IGNORE, vch);
				vch->SendTextf (
					"You attempt to ignore %s, but are unable to do so.\n\r",
					ch->GetName ());
			}
		}

		if (! knows_language (vch, ch->GetSpeaking (), ch) &&
			(! ch->IsNpc () || ! ch->IsSpeaking (LANG_ALL)))
				sbuf = scramble (argument, ch->GetSpeaking ());

		sbuf = drunk_speech (sbuf, ch);

		MOBtrigger = FALSE;
		act (AT_SAY, "$n says '$t'", ch, sbuf, vch, TO_VICT);
	}

	// MOBtrigger = FALSE;
	// act (AT_SAY, "$n says '$T'", ch, NULL, argument, TO_ROOM);
	ch->SetActFlags (actflags);		// restore act flags

	MOBtrigger = FALSE;
	act (AT_SAY, "You say '$T'", ch, NULL, drunk_speech (argument, ch), TO_CHAR); 

	if (ch->GetInRoom ()->IsLogSpeech ()) {
		sprintf (buf, "%s: %s",
			ch->IsNpc () ? ch->GetShortDescr () : ch->GetName (), argument);
		append_to_file (FileTable.GetName (SM_LOG_FILE), buf);
	}
	mprog_speech_trigger (argument, ch);

	if (char_died (ch))
		return;
	oprog_speech_trigger (argument, ch); 
	if (char_died (ch))
		return;

	rprog_speech_trigger (argument, ch); 
}


void do_tell (CCharacter* ch, char* argument)
{
	char		arg [MAX_INPUT_LENGTH];
	char		buf [MAX_INPUT_LENGTH];
	CCharacter	*victim;
	int			position;
	CCharacter	*switched_victim = NULL;

	REMOVE_BIT (ch->deaf, CHANNEL_TELLS);
	if (ch->GetInRoom ()->IsSilent ()) {
		ch->SendText ("You can't do that here.\n\r");
		return;
	}

	if (! ch->IsNpc () && (ch->IsSilent ()) || ch->IsNoTell ()) {
		ch->SendText ("You can't do that.\n\r");
		return;
	}

	argument = one_argument (argument, arg);

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

	if ((victim = get_char_world (ch, arg)) == NULL 
	  || (victim->IsNpc () && victim->GetInRoom () != ch->GetInRoom ()) 
	  || (ch->IsAuthed () && !victim->IsAuthed () && ch->IsMortal ())) {
		ch->SendText ("They aren't here.\n\r");
		return;
	}

	if (ch == victim) {
		ch->SendText ("You have a nice little chat with yourself.\n\r");
		return;
	}

	if (! ch->IsAuthed () && ! victim->IsAuthed () && victim->IsMortal ()) {
		ch->SendText ("They can't hear you because you are not authorized.\n\r");
		return;
	}

	if (! victim->IsNpc () && (victim->switched) 
	  && (ch->GetTrustLevel () > LEVEL_AVATAR) 
	  && ! victim->switched->IsPolymorphed ()
	  && ! victim->switched->IsPossessed ()) {
		ch->SendText ("That player is switched.\n\r");
		return;
	}

	else if (!victim->IsNpc () && (victim->switched) 
		&& (victim->switched->IsPolymorphed ()
		|| victim->switched->IsPossessed ()))
			switched_victim = victim->switched;

	else if (! victim->IsNpc () && (! victim->GetDesc ())) {
		ch->SendText ("That player is link-dead.\n\r");
		return;
	}

	if (! victim->IsNpc () && victim->IsAfk ()) {
		ch->SendText ("That player is afk.\n\r");
		return;
	}

	if (IS_SET (victim->deaf, CHANNEL_TELLS)
	  && (ch->IsMortal ()
	  || (ch->GetTrustLevel () < victim->GetTrustLevel ()))) {
		act (AT_PLAIN, "$E has $S tells turned off.", ch, NULL, victim,
			TO_CHAR);
		return;
	}

	if (! victim->IsNpc () && victim->IsSilent ()) {
		ch->SendText ("That player is silenced.  They will receive your "
			"message but cannot respond.\n\r");
	}   

	if ((ch->IsMortal () && ! victim->IsAwake ())
	  || (! victim->IsNpc () && victim->GetInRoom ()->IsSilent ())) {
		act (AT_PLAIN, "$E can't hear you.", ch, 0, victim, TO_CHAR);
		return;
	}

	if (victim->GetDesc ()		// make sure desc exists first  -Thoric
	  && victim->GetDesc ()->m_Connected == CON_EDITING 
	  && ch->GetTrustLevel () < LEVEL_GOD) {
		act (AT_PLAIN, "$E is currently in a writing buffer.  Please try "
			"again in a few minutes.", ch, 0, victim, TO_CHAR);
		return;
	}

	// Check to see if target of tell is ignoring the sender
	if (victim->IsIgnoring (ch)) {
		// If the sender is an imm then they cannot be ignored
		if (ch->IsMortal ()
		  || victim->GetTrustLevel () > ch->GetTrustLevel ()) {
			set_char_color (AT_IGNORE, ch);
			ch->SendTextf ("%s is ignoring you.\n\r", victim->GetName ());
			return;
		} else {
			set_char_color (AT_IGNORE, victim);
			victim->SendTextf (
				"You attempt to ignore %s, but are unable to do so.\n\r",
				ch->GetName ());
		}
	}

	if (switched_victim)
		victim = switched_victim;

	act (AT_TELL, "You tell $N '$t'", ch, argument, victim, TO_CHAR);
	position = victim->GetPosition ();
	victim->SetPosition (POS_STANDING);
	if (knows_language (victim, ch->GetSpeaking (), ch))
		act (AT_TELL, "$n tells you '$t'", ch, argument, victim, TO_VICT);
	else
		act (AT_TELL, "$n tells you '$t'", ch,

	scramble (argument, ch->GetSpeaking ()), victim, TO_VICT);
	victim->SetPosition (position);
	victim->SetReplier (ch);
	if (ch->GetInRoom ()->IsLogSpeech ()) {
		sprintf (buf, "%s: %s (tell to) %s.",
			ch->IsNpc () ? ch->GetShortDescr () : ch->GetName (), argument,
			victim->IsNpc () ? victim->GetShortDescr () : victim->GetName ());
		append_to_file (FileTable.GetName (SM_LOG_FILE), buf);
	}
	mprog_speech_trigger (argument, ch);
}


void do_reply (CCharacter *ch, char *argument)
{
	char		buf [MAX_STRING_LENGTH];
	CCharacter	*victim;
	int			position;


	REMOVE_BIT (ch->deaf, CHANNEL_TELLS);
	if (ch->GetInRoom ()->IsSilent ()) {
		ch->SendText ("You can't do that here.\n\r");
		return;
	}

	if (! ch->IsNpc () && ch->IsSilent ()) {
		ch->SendText ("Your message didn't get through.\n\r");
		return;
	}

	if ((victim = ch->GetReplier ()) == NULL) {
		ch->SendText ("They aren't here.\n\r");
		return;
	}

	if (! victim->IsNpc () && victim->switched
	  && can_see (ch, victim) && (ch->GetTrustLevel () > LEVEL_AVATAR)) {
		ch->SendText ("That player is switched.\n\r");
		return;
	}
	else if (! victim->IsNpc () && (! victim->GetDesc ())) {
		ch->SendText ("That player is link-dead.\n\r");
		return;
	}

	if (! victim->IsNpc () && victim->IsAfk ()) {
		ch->SendText ("That player is afk.\n\r");
		return;
	}

	if (IS_SET (victim->deaf, CHANNEL_TELLS) 
	  && (ch->IsMortal ()
	  || ch->GetTrustLevel () < victim->GetTrustLevel ())) {
		act (AT_PLAIN, "$E has $S tells turned off.", ch, NULL, victim,
			TO_CHAR);
		return;
	}

	if ((ch->IsMortal () && ! victim->IsAwake ())
	  || (!victim->IsNpc () && victim->GetInRoom ()->IsSilent ())) {
		act (AT_PLAIN, "$E can't hear you.", ch, 0, victim, TO_CHAR);
		return;
	}

	if (victim->GetDesc ()		// make sure desc exists first  -Thoric
	  && victim->GetDesc ()->m_Connected == CON_EDITING 
	  && ch->GetTrustLevel () < LEVEL_GOD) {
		act (AT_PLAIN, "$E is currently in a writing buffer.  Please try "
			"again in a few minutes.", ch, 0, victim, TO_CHAR);
		return;
	}

	// Check to see if target of tell is ignoring the sender
	if (victim->IsIgnoring (ch)) {
		// If the sender is an imm then they cannot be ignored
		if (ch->IsMortal ()
		  || victim->GetTrustLevel () > ch->GetTrustLevel ()) {
			set_char_color (AT_IGNORE, ch);
			ch->SendTextf ("%s is ignoring you.\n\r", victim->GetName ());
			return;
		} else {
			set_char_color (AT_IGNORE, victim);
			victim->SendTextf (
				"You attempt to ignore %s, but are unable to do so.\n\r",
				ch->GetName ());
		}
	}

	act (AT_TELL, "You tell $N '$t'", ch, argument, victim, TO_CHAR);
	position = victim->GetPosition ();
	victim->SetPosition (POS_STANDING);

	if (knows_language (victim, ch->GetSpeaking (), ch))
		act (AT_TELL, "$n tells you '$t'", ch, argument, victim, TO_VICT);
	else
		act (AT_TELL, "$n tells you '$t'", ch,

	scramble (argument, ch->GetSpeaking ()), victim, TO_VICT);
	victim->SetPosition (position);
	victim->SetReplier (ch);
	if (ch->GetInRoom ()->IsLogSpeech ()) {
		sprintf (buf, "%s: %s (reply to) %s.",
			ch->IsNpc () ? ch->GetShortDescr () : ch->GetName (), argument,
			victim->IsNpc () ? victim->GetShortDescr () : victim->GetName ());
		append_to_file (FileTable.GetName (SM_LOG_FILE), buf);
	}
}


void do_emote (CCharacter *ch, char *argument)
{
	char	buf [MAX_STRING_LENGTH];
	char	*plast;

	if (! ch->IsNpc () && ch->IsNoEmote ()) {
		ch->SendText ("You can't show your emotions.\n\r");
		return;
	}

	if (argument [0] == '\0') {
		ch->SendText ("Emote what?\n\r");
		return;
	}

	CActFlags	actflags = ch->GetActFlags ();		// save act flags
	if (ch->IsNpc ())
		ch->ClrActBit (ACT_SECRETIVE);
	for (plast = argument; *plast != '\0'; plast++)
	;

	strcpy (buf, argument);
	if (isalpha (plast [-1]))
		strcat (buf, ".");

	CCharacter	*vch = ch->GetInRoom ()->first_person;
	for (; vch; vch = vch->GetNextInRoom ()) {
		char	*sbuf = buf;

		// Check to see if character is ignoring emoter
		if (vch->IsIgnoring (ch)) {
			// continue unless emoter is an immortal
			if (ch->IsMortal ()
				|| vch->GetTrustLevel () > ch->GetTrustLevel ())
					continue;
			else {
				set_char_color (AT_IGNORE, vch);
				vch->SendTextf (
					"You attempt to ignore %s, but are unable to do so.\n\r",
					ch->GetName ());
			}
		}

		if (! knows_language (vch, ch->GetSpeaking (), ch))
			sbuf = scramble (buf, ch->GetSpeaking ());
		MOBtrigger = FALSE;
		act (AT_ACTION, "$n $t", ch, sbuf, vch,
			(vch == ch ? TO_CHAR : TO_VICT));
	}
	ch->SetActFlags (actflags);			// restore act flags

	if (ch->GetInRoom ()->IsLogSpeech ()) {
		sprintf (buf, "%s %s (emote)",
			ch->IsNpc () ? ch->GetShortDescr () : ch->GetName (), argument);
		append_to_file (FileTable.GetName (SM_LOG_FILE), buf);
	}
}


void do_bug (CCharacter *ch, char *argument)
{
    append_file (ch, FileTable.GetName (SM_BUG_FILE), argument);
    ch->SendText ("Ok.  Thanks.\n\r");
    return;
}


void do_ide (CCharacter *ch, char *argument)
{
    ch->SendText ("If you want to send an idea, type 'idea <message>'.\n\r");
    ch->SendText ("If you want to identify an object and have the identify spell,\n\r");
    ch->SendText ("Type 'cast identify <object>'.\n\r");
    return;
}

void do_idea (CCharacter *ch, char *argument)
{
    append_file (ch, FileTable.GetName (SM_IDEA_FILE), argument);
    ch->SendText ("Ok.  Thanks.\n\r");
    return;
}



void do_typo (CCharacter *ch, char *argument)
{
    append_file (ch, FileTable.GetName (SM_TYPO_FILE), argument);
    ch->SendText ("Ok.  Thanks.\n\r");
    return;
}



void do_rent (CCharacter *ch, char *argument)
{
    set_char_color (AT_WHITE, ch);
    ch->SendText ("There is no rent here.  Just save and quit.\n\r");
    return;
}



void do_qui (CCharacter *ch, char *argument)
{
    set_char_color (AT_RED, ch);
    ch->SendText ("If you want to QUIT, you have to spell it out.\n\r");
    return;
}


void do_quit (CCharacter *ch, char *argument)
{
	int		x, y;
	int		level;

	if (ch->IsNpc () && ch->IsPolymorphed ()) {
		ch->SendText ("You can't quit while polymorphed.\n\r");
		return;
	}

	if (ch->IsNpc ())
		return;

	if (ch->IsFightPosition ()) {
		set_char_color (AT_RED, ch);
		ch->SendText ("No way! You are fighting.\n\r");
		return;
	}

	if (ch->GetPosition () < POS_STUNNED) {
		set_char_color (AT_BLOOD, ch);
		ch->SendText ("You're not DEAD yet.\n\r");
		return;
	}

	if (get_timer (ch, TIMER_RECENTFIGHT) > 0 && ch->IsMortal ()) {
		set_char_color (AT_RED, ch);
		ch->SendText ("Your adrenaline is pumping too hard to quit now!\n\r");
		return;
	}

	if (auction->IsActive () && ((ch == auction->GetBuyer ())
	  || (ch == auction->GetSeller ()))) {
		ch->SendText ("Wait until you have bought/sold the item on auction.\n\r");
		return;
	}

	if (ch->IsPkiller ()
	  && ch->GetWimpLevel () > (int) ch->GetMaxHp () / 2.25) {
		ch->SendText ("Your wimpy has been adjusted to the maximum level"
			" for deadlies.\n\r");
		do_wimpy (ch, "max");
	}

	// Get 'em dismounted until we finish mount saving -- Blodkai, 4/97
	if (ch->IsMounted ())
		do_dismount (ch, "");

	set_char_color (AT_WHITE, ch);
	ch->SendText ("Your surroundings begin to fade as a mystical swirling "
		"vortex of colors\n\renvelops your body... When you come to, things"
		" are not as they were.\n\r\n\r");
	act (AT_SAY, "A strange voice says, 'We await your return, $n...'", ch, NULL, NULL, TO_CHAR);
	act (AT_BYE, "$n has left the game.", ch, NULL, NULL, TO_ROOM);
	set_char_color (AT_GREY, ch);

	sprintf (log_buf, "%s has quit.", ch->GetName ());
	quitting_char = ch;
	save_char_obj (ch);

	if (ch->HasPet ()) {
		act (AT_BYE, "$N follows $S master into the Void.", ch, NULL, 
			ch->GetPet (), TO_ROOM);
		extract_char (ch->GetPet (), TRUE);
	}

	// Synch clandata up only when clan member quits now. --Shaddai
	if (ch->GetPcData ()->GetClan ())
		ch->GetPcData ()->GetClan ()->Save ();     

	saving_char = NULL;

	level = ch->GetTrustLevel ();
	// After extract_char the ch is no longer valid!
	extract_char (ch, TRUE);
	for (x = 0; x < MAX_WEAR; ++x)
		for (y = 0; y < MAX_LAYERS; ++y)
			save_equipment [x][y] = NULL;

	gpDoc->LogString (log_buf, LOG_COMM, level);
}


void send_rip_screen (CCharacter *ch)
{
    FILE *rpfile;
    int num=0;
    char BUFF[MAX_STRING_LENGTH*2];

    if ((rpfile = fopen (FileTable.GetName (SM_RIPSCREEN_FILE),"r")) !=NULL) {
      while ((BUFF[num]=fgetc (rpfile)) != EOF)
	 num++;
      fclose (rpfile);
      BUFF[num] = 0;
      ch->GetDesc ()->WriteToBuffer (BUFF, num);
    }
}

void send_rip_title (CCharacter *ch)
{
    FILE *rpfile;
    int num=0;
    char BUFF[MAX_STRING_LENGTH*2];

    if ((rpfile = fopen (FileTable.GetName (SM_RIPTITLE_FILE),"r")) !=NULL) {
      while ((BUFF[num]=fgetc (rpfile)) != EOF)
	 num++;
      fclose (rpfile);
      BUFF[num] = 0;
      ch->GetDesc ()->WriteToBuffer (BUFF,num);
    }
}

void send_ansi_title (CCharacter *ch)
{
	FILE	*rpfile;
	int		num=0;
	char	buf [MAX_STRING_LENGTH*2];

	if (rpfile = fopen (FileTable.GetName (SM_ANSITITLE_FILE), "r")) {
		while ((buf [num] = fgetc (rpfile)) != EOF)
			++num;

		fclose (rpfile);
		buf [num] = 0;
		ch->SendColor (buf);
	}
}

void send_ascii_title (CCharacter *ch)
{
    FILE *rpfile;
    int num=0;
    char BUFF[MAX_STRING_LENGTH];

    if ((rpfile = fopen (FileTable.GetName (SM_ASCTITLE_FILE),"r")) !=NULL) {
      while ((BUFF[num]=fgetc (rpfile)) != EOF)
	 num++;
      fclose (rpfile);
      BUFF[num] = 0;
      ch->GetDesc ()->WriteToBuffer (BUFF,num);
    }
}


void do_omenu (CCharacter *ch, char *argument)
{
	CObjData	*obj;
	char		arg1 [MAX_INPUT_LENGTH];
	char		arg2 [MAX_INPUT_LENGTH];

	smash_tilde (argument);
	argument = one_argument (argument, arg1);
	argument = one_argument (argument, arg2);

	if (arg1 [0] == '\0') {
		ch->SendText ("Syntax: omenu <object> [page]  \n\r");
		ch->SendText ("      Where:    <object> is a prototype object  \n\r");
		ch->SendText ("            and  <page>  is an optional letter to select menu-pages\n\r");
		return;
	}

	if ((obj = get_obj_world (ch, arg1)) == NULL) {
		ch->SendText ("It isn't here.\n\r");
		return;
	}

	if (ch->IsMenuActive ())
		ch->RemoveMenu ();

	CObjectMenu		*pOmenu = new CObjectMenu (*obj);
	ch->SetMenu (pOmenu);
	ch->DisplayMenu (arg2 [0]);
}


void do_rmenu (CCharacter *ch, char *argument)
{
	char	arg1 [MAX_INPUT_LENGTH];

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

	if (ch->IsMenuActive ())
		ch->RemoveMenu ();

	CRoomIndexData	*idx = ch->GetInRoom ();
	CRoomMenu		*pRmenu = new CRoomMenu (*idx);

	ch->SetMenu (pRmenu);
	ch->DisplayMenu (arg1 [0]);
}


void do_cmenu (CCharacter *ch, char *argument)
{
	char	arg1 [MAX_INPUT_LENGTH];

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


	if (ch->IsMenuActive ())
		ch->RemoveMenu ();

	CSystemMenu	*pCmenu = new CSystemMenu (SysData);
	ch->SetMenu (pCmenu);
	ch->DisplayMenu (arg1 [0]);
}


void do_mmenu (CCharacter *ch, char *argument)
{
	CCharacter	*victim;
	char		arg1 [MAX_INPUT_LENGTH];
	char		arg2 [MAX_INPUT_LENGTH];

	smash_tilde (argument);
	argument = one_argument (argument, arg1);
	argument = one_argument (argument, arg2);

	if (arg1 [0] == '\0') {
		ch->SendText ("Syntax: mmenu <victim> [page]  \n\r");
		ch->SendText ("      Where:    <victim> is a prototype mob  \n\r");
		ch->SendText ("            and  <page>  is an optional letter to select menu-pages\n\r");
		return;
	}


	if ((victim = get_char_world (ch, arg1)) == NULL) {
		ch->SendText ("They aren't here.\n\r");
		return;
	}

	if (! victim->IsNpc ()) {
		ch->SendText ("Not on players.\n\r");
		return;
	}

	if (ch->GetTrustLevel () < victim->GetLevel ()) {
		set_char_color (AT_IMMORT, ch);
		ch->SendText ("Their godly glow prevents you from getting a good look .\n\r");
		return;
	}

	if (ch->IsMenuActive ())
		ch->RemoveMenu ();

	CMobMenu	*pMmenu = new CMobMenu (*victim);
	ch->SetMenu (pMmenu);
	ch->DisplayMenu (arg2 [0]);
}


void do_pmenu (CCharacter *ch, char *argument)
{
	char		arg1 [MAX_INPUT_LENGTH];
	char		arg2 [MAX_INPUT_LENGTH];

	smash_tilde (argument);
	argument = one_argument (argument, arg1);
	argument = one_argument (argument, arg2);

	if (arg1 [0] == '\0') {
		ch->SendText ("Syntax: pmenu <victim> [page]  \n\r");
		ch->SendText ("      Where:    <victim> is an off-line player  \n\r");
		ch->SendText ("            and  <page>  is an optional letter to select menu-pages\n\r");
		return;
	}

	// check the world for an exact match
	CCharacter	*vict;
	for (vict = first_char; vict; vict = vict->GetNext ())
		if (! stricmp (vict->GetName (), arg1)) {
			ch->SendText ("Sorry, That player is on-line.\r\n");
			return;
		}

	CPlayerMenu	*pMenu = new CPlayerMenu;

	if (! pMenu->Load (*ch, arg1)) {
		ch->SendTextf ("No Player File for %s.\r\n", arg1);
		delete	pMenu;
		return;
	}

	if (ch->IsMenuActive ())
		ch->RemoveMenu ();
	ch->SetMenu (pMenu);
	ch->DisplayMenu (arg2 [0]);
}


void do_rip (CCharacter *ch, char *argument)
{
    char arg[MAX_INPUT_LENGTH];

    one_argument (argument, arg);

    if (arg[0] == '\0')
    {
	ch->SendText ("Rip ON or OFF?\n\r");
	return;
    }
    if (! str_cmp (arg,"on")) {
	send_rip_screen (ch);
	ch->SetActBit (PLR_RIP);
	ch->SetActBit (PLR_ANSI);
	return;
    }

    if (! str_cmp (arg,"off")) {
	ch->ClrActBit (PLR_RIP);
	ch->SendText ("!|*\n\rRIP now off...\n\r");
	return;
    }
}

void do_ansi (CCharacter *ch, char *argument)
{
    char arg[MAX_INPUT_LENGTH];

    one_argument (argument, arg);

    if (arg[0] == '\0')
    {
	ch->SendText ("ANSI ON or OFF?\n\r");
	return;
    }
    if ((str_cmp (arg,"on")==0) || (str_cmp (arg,"ON") == 0)) {
	ch->SetActBit (PLR_ANSI);
	set_char_color (AT_WHITE + AT_BLINK, ch);
	ch->SendText ("ANSI ON!!!\n\r");
	return;
    }

    if ((str_cmp (arg,"off")==0) || (str_cmp (arg,"OFF") == 0)) {
	ch->ClrActBit (PLR_ANSI);
	ch->SendText ("Okay... ANSI support is now off\n\r");
	return;
    }
}

void do_save (CCharacter *ch, char *argument)
{
	if (ch->IsNpc () && ch->IsPolymorphed ()) {
		ch->SendText ("You can't save while polymorphed.\n\r");
		return;
	}

	if (ch->IsNpc ())
		return;

	if (ch->GetLevel () < 2) {
		set_char_color (AT_BLUE, ch);
		ch->SendColor ("&BYou must be at least second level to save.\n\r");
		return;
	}

	CRaceData	&Ra = *RaceTable.GetRaceData (ch->GetRace ());

	ch->SetAffectBits (Ra.GetAffectFlags ());
	ch->SetResist (Ra.resist);
	ch->SetSusceptible (Ra.suscept);	   

	if (ch->GetPcData ()->deity) {
		ch->SetAffectBits (ch->GetPcData ()->deity->m_Affected);
		ch->SetResist (ch->GetPcData ()->deity->element);
		ch->SetSusceptible (ch->GetPcData ()->deity->suscept);
	}
	save_char_obj (ch);
	saving_char = NULL;
	ch->SendText ("Ok.\n\r");
}


/*
 * Something from original DikuMUD that Merc yanked out.
 * Used to prevent following loops, which can cause problems if people
 * follow in a loop through an exit leading back into the same room
 * (Which exists in many maze areas)			-Thoric
 */
BOOL circle_follow (CCharacter *ch, CCharacter *victim)
{
    CCharacter *tmp;

    for (tmp = victim; tmp; tmp = tmp->GetMaster ())
	if (tmp == ch)
	  return TRUE;
    return FALSE;
}


void do_dismiss (CCharacter* ch, char* argument)
{
	char		arg [MAX_INPUT_LENGTH];
	CCharacter	*victim;

	one_argument (argument, arg);     

	if (arg [0] == '\0') {
		ch->SendText ("Dismiss whom?\n\r");
		return;
	}

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

	if (victim->IsCharmed () && victim->IsNpc ()
	  && victim->GetMaster () == ch) {
		stop_follower (victim);
		stop_hating (victim);
		stop_hunting (victim);
		stop_fearing (victim);
		act (AT_ACTION, "$n dismisses $N.", ch, NULL, victim, TO_NOTVICT);
		act (AT_ACTION, "You dismiss $N.", ch, NULL, victim, TO_CHAR);
	}
	else ch->SendText ("You cannot dismiss them.\n\r");
}


void do_follow (CCharacter *ch, char *argument)
{
    char arg[MAX_INPUT_LENGTH];
    CCharacter *victim;

    one_argument (argument, arg);

    if (arg[0] == '\0')
    {
	ch->SendText ("Follow whom?\n\r");
	return;
    }

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

    if (ch->IsCharmed () && ch->GetMaster ())
    {
	act (AT_PLAIN, "But you'd rather follow $N!", ch, NULL, ch->GetMaster (), TO_CHAR);
	return;
    }

    if (victim == ch)
    {
	if (!ch->GetMaster ())
	{
	    ch->SendText ("You already follow yourself.\n\r");
	    return;
	}
	stop_follower (ch);
	return;
    }

    if ((ch->GetLevel () - victim->GetLevel () < -10 || ch->GetLevel () - victim->GetLevel () >  10)
    &&   ! ch->IsHero ())
    {
	ch->SendText ("You are not of the right caliber to follow.\n\r");
	return;
    }

    if (circle_follow (ch, victim))
    {
	ch->SendText ("Following in loops is not allowed... sorry.\n\r");
	return;
    }

    if (ch->GetMaster ())
	stop_follower (ch);

    add_follower (ch, victim);
    return;
}


void add_follower (CCharacter *ch, CCharacter *master)
{
	if (ch->GetMaster ()) {
		bug ("Add_follower: non-null master.");
		return;
	}

	ch->SetMaster (master);
	ch->SetLeader (NULL);

	// Support for saving pets --Shaddai
	if (ch->IsNpc () && ch->IsPet () && ! master->IsNpc ())
		master->SetPet (ch);

	if (can_see (master, ch))
		act (AT_ACTION, "$n now follows you.", ch, NULL, master, TO_VICT);

	act (AT_ACTION, "You now follow $N.",  ch, NULL, master, TO_CHAR);
}


void stop_follower (CCharacter *ch)
{
	if (! ch->GetMaster ()) {
		bug ("Stop_follower: null master.");
		return;
	}

	CCharacter	&Master = *ch->GetMaster ();
	if (ch->IsNpc () && ! Master.IsNpc () && Master.GetPet () == ch)
		Master.SetPet (NULL);

	if (ch->IsCharmed ()) {
		ch->ClrCharmed ();
		affect_strip (ch, gsn_charm_person);
	}

	if (can_see (&Master, ch))
		act (AT_ACTION, "$n stops following you.", ch, NULL, &Master, TO_VICT);
	act (AT_ACTION, "You stop following $N.", ch, NULL, &Master, TO_CHAR);

	ch->SetMaster (NULL);
	ch->SetLeader (NULL);
}


void die_follower (CCharacter *ch)
{
	CCharacter	*fch;

	if (ch->GetMaster ())
		stop_follower (ch);

	ch->SetLeader (NULL);

	for (fch = first_char; fch; fch = fch->GetNext ()) {
		if (fch->GetMaster () == ch)
			stop_follower (fch);
		if (fch->GetLeader () == ch)
			fch->SetLeader (fch);
	}
}


void do_order (CCharacter *ch, char *argument)
{
	char		arg [MAX_INPUT_LENGTH];
	char		argbuf [MAX_INPUT_LENGTH];
	CCharacter	*victim;
	CCharacter	*och;
	CCharacter	*och_next;
	BOOL		found;
	BOOL		fAll;

	strcpy (argbuf, argument);
	argument = one_argument (argument, arg);

	if (arg [0] == '\0' || argument [0] == '\0') {
		ch->SendText ("Order whom to do what?\n\r");
		return;
	}

	if (ch->IsCharmed ()) {
		ch->SendText ("You feel like taking, not giving, orders.\n\r");
		return;
	}

	if (! str_cmp (arg, "all")) {
		fAll   = TRUE;
		victim = NULL;
	} else {
		fAll = FALSE;
		if ((victim = get_char_room (ch, arg)) == NULL) {
			ch->SendText ("They aren't here.\n\r");
			return;
		}

		if (victim == ch) {
			ch->SendText ("Aye aye, right away!\n\r");
			return;
		}

		if (! victim->IsCharmed () || victim->GetMaster () != ch) {
			ch->SendText ("Do it yourself!\n\r");
			return;
		}
	}

	found = FALSE;
	for (och = ch->GetInRoom ()->first_person; och; och = och_next) {
		och_next = och->GetNextInRoom ();

		if (och->IsCharmed () && och->GetMaster () == ch
		  && (fAll || och == victim)) {
			found = TRUE;
			act (AT_ACTION, "$n orders you to '$t'.", ch, argument, och, TO_VICT);
			interpret (och, argument);
		}
	}

	if (found) {
		sprintf (log_buf, "%s: order %s.", ch->GetName (), argbuf);
		gpDoc->LogString (log_buf, LOG_PLAYER, ch->GetLevel ());
		ch->SendText ("Ok.\n\r");
		WAIT_STATE (ch, 12);
	}
	else
		ch->SendText ("You have no followers here.\n\r");
}


void do_group (CCharacter *ch, char *argument)
{
	char		arg [MAX_INPUT_LENGTH];
	CCharacter	*victim;

	one_argument (argument, arg);

	if (arg[0] == '\0') {
		CCharacter	*gch;
		CCharacter	*leader;

		leader = ch->GetLeader () ? ch->GetLeader () : ch;
		set_char_color (AT_GREEN, ch);
		ch->SendTextf ("%s's group:\n\r", PERS (leader, ch));

		// Changed so that no info revealed on possess
		for (gch = first_char; gch; gch = gch->GetNext ()) {
			if (is_same_group (gch, ch)) {
				set_char_color (AT_DGREEN, ch);
				if (gch->IsPossessed ())
				  ch->SendTextf (
					"[%2d %s] %-16s %4s/%4s hp %4s/%4s %s %4s/%4s "
					"mv %5s xp\n\r", gch->GetLevel (), gch->IsNpc () ?
					"Mob" : ClassTable.GetName (gch->GetClass ()),
					capitalize (PERS (gch, ch)),
					"????", "????", "????", "????",
					gch->IsVampire () ? "bp" : "mana",
					"????", "????", "?????");

				else
				  ch->SendTextf (
					"[%2d %s] %-16s %4d/%4d hp %4d/%4d %s %4d/%4d "
					"mv %5d xp\n\r", gch->GetLevel (), gch->IsNpc () ?
					"Mob" : ClassTable.GetName (gch->GetClass ()),
					capitalize (PERS (gch, ch)), gch->GetHp (),   
					gch->GetMaxHp (), gch->IsVampire () ?
						gch->GetPcData ()->condition[COND_BLOODTHIRST]
						: gch->GetMana (),
					gch->IsVampire () ?
						10 + gch->GetLevel () : gch->GetMaxMana (),
					gch->IsVampire () ? "bp" : "mana",
					gch->GetMove (), gch->GetMaxMove (), gch->GetExp ());
			}
		}
		return;
	}

	if (!str_cmp (arg, "disband")) {
		CCharacter	*gch;
		int			count = 0;

		if (ch->GetLeader () || ch->GetMaster ()) {
			ch->SendText ("You cannot disband a group if you're "
				"following someone.\n\r");
			return;
		}
		
		for (gch = first_char; gch; gch = gch->GetNext ()) {
			if (is_same_group (ch, gch) && (ch != gch)) {
				gch->SetLeader (NULL);
				gch->SetMaster (NULL);
				count++;
				gch->SendText ("Your group is disbanded.\n\r");
			}
		}

		if (count == 0)
			ch->SendText ("You have no group members to disband.\n\r");
		else
			ch->SendText ("You disband your group.\n\r");

		return;
	}

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

		for (rch = ch->GetInRoom ()->first_person; rch;
		  rch = rch->GetNextInRoom ()) {
			if (ch != rch && !rch->IsNpc () && can_see (ch, rch)
			  && rch->GetMaster () == ch && !ch->GetMaster ()
			  && !ch->GetLeader ()
			  && abs (ch->GetLevel () - rch->GetLevel ()) < 8
			  && !is_same_group (rch, ch)
			  && ch->IsPkiller () == rch->IsPkiller ()) {
				rch->SetLeader (ch);
				count++;
			}
		}

		if (count == 0)
			ch->SendText ("You have no eligible group members.\n\r");
		else {
			act (AT_ACTION, "$n groups $s followers.", ch, NULL,NULL,TO_ROOM);
			ch->SendText ("You group your followers.\n\r");
		}
		return;
	}

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

	if (ch->GetMaster () || (ch->GetLeader () && ch->GetLeader () != ch)) {
		ch->SendText ("But you are following someone else!\n\r");
		return;
	}

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

	if (is_same_group (victim, ch) && ch != victim) {
		victim->SetLeader (NULL);
		act (AT_ACTION, "$n removes $N from $s group.",   ch, NULL, victim, TO_NOTVICT);
		act (AT_ACTION, "$n removes you from $s group.",  ch, NULL, victim, TO_VICT   );
		act (AT_ACTION, "You remove $N from your group.", ch, NULL, victim, TO_CHAR   );
		return;
	}

	if (ch->GetLevel () - victim->GetLevel () < -8
	  || ch->GetLevel () - victim->GetLevel () >  8 
	  || (ch->IsPkiller () != victim->IsPkiller ())) {
		act (AT_PLAIN, "$N cannot join $n's group.",     ch, NULL, victim, TO_NOTVICT);
		act (AT_PLAIN, "You cannot join $n's group.",    ch, NULL, victim, TO_VICT   );
		act (AT_PLAIN, "$N cannot join your group.",     ch, NULL, victim, TO_CHAR   );
		return;
	}

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



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

    one_argument (argument, arg);

    if (arg[0] == '\0')
    {
	ch->SendText ("Split how much?\n\r");
	return;
    }

    amount = atoi (arg);

    if (amount < 0)
    {
	ch->SendText ("Your group wouldn't like that.\n\r");
	return;
    }

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

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

    members = 0;
    for (gch = ch->GetInRoom ()->first_person; gch; gch = gch->GetNextInRoom ())
    {
	if (is_same_group (gch, ch))
	    members++;
    }

    
    if ((ch->IsAction (PLR_AUTOGOLD)) && (members < 2))
    return;

    if (members < 2)
    {
	ch->SendText ("Just keep it all.\n\r");
	return;
    }

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

    if (share == 0)
    {
	ch->SendText ("Don't even bother, cheapskate.\n\r");
	return;
    }

    ch->AddGold (-amount);
    ch->AddGold (share + extra);

    set_char_color (AT_GOLD, ch);
    ch->SendTextf (
	"You split %d gold coins.  Your share is %d gold coins.\n\r",
	amount, share + extra);

    sprintf (buf, "$n splits %d gold coins.  Your share is %d gold coins.",
	amount, share);

    for (gch = ch->GetInRoom ()->first_person; gch; gch = gch->GetNextInRoom ())
    {
	if (gch != ch && is_same_group (gch, ch))
	{
	    act (AT_GOLD, buf, ch, NULL, gch, TO_VICT);
	    gch->AddGold (share);
	}
    }
}



void do_gtell (CCharacter *ch, char *argument)
{
    CCharacter *gch;

    if (argument[0] == '\0')
    {
	ch->SendText ("Tell your group what?\n\r");
	return;
    }

    if (ch->IsAction (PLR_NO_TELL))
    {
	ch->SendText ("Your message didn't get through!\n\r");
	return;
    }

	// Note use of CCharacter.SendText, so gtell works on sleepers.
	// sprintf (buf, "%s tells the group '%s'.\n\r", ch->GetName (), argument);
	for (gch = first_char; gch; gch = gch->GetNext ()) {
		if (is_same_group (gch, ch)) {
			set_char_color (AT_GTELL, gch);
			// Groups unscrambled regardless of clan language.
			// Other languages still garble though. -- Altrag
			if (knows_language (gch, ch->GetSpeaking (), gch))
				gch->SendTextf ("%s tells the group '%s'.\n\r",
					ch->GetName (), argument);
			else
				gch->SendTextf ("%s tells the group '%s'.\n\r",
					ch->GetName (),
			scramble (argument, ch->GetSpeaking ()));
		}
	}
}


/*
 * 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 (CCharacter *ach, CCharacter *bch)
{
    if (ach->GetLeader ()) ach = ach->GetLeader ();
    if (bch->GetLeader ()) bch = bch->GetLeader ();
    return ach == bch;
}


// this function sends raw argument over the AUCTION: channel
// I am not too sure if this method is right..
void talk_auction (const char *argument)
{
	char	buf [MAX_STRING_LENGTH];

	sprintf (buf, "Auction: %s", argument);		// last %s to reset color

	POSITION	pos = DList.GetHeadPosition ();
	while (pos) {
		CDescriptor	&Ds = *DList.GetNext (pos);
		if (Ds.IsDisconnecting ())
			continue;

        CCharacter *ch = Ds.m_pOriginal ? Ds.m_pOriginal : Ds.m_pCharacter;		// if switched
        if (Ds.m_Connected == CON_PLAYING
			&& !IS_SET (ch->deaf, CHANNEL_AUCTION) 
			&& ! ch->GetInRoom ()->IsSilent ()
			&& ch->IsAuthed ())
				act (AT_GOSSIP, buf, ch, NULL, NULL, TO_CHAR);
    }
}


// Language support functions. -- Altrag
// 07/01/96
BOOL knows_language (CCharacter *victim, int language, CCharacter *ch)
{
	short sn;

	// everyone KNOWS common tongue
	if (language == LanguageTable.GetCommon () || ch->IsSpeaking (LANG_ALL))
		return TRUE;

	if (! victim->IsNpc () && victim->IsImmortal ())
		return TRUE;

	// SpeaksLanguage returns TRUE if speaking all.
	if (victim->IsNpc () && victim->SpeaksLanguage (language))
		return TRUE;

	if (language == LanguageTable.GetClan ()) {
		// Clan = common for mobs.. snicker.. -- Altrag
		if (victim->IsNpc () || ch->IsNpc ())
			return TRUE;
		if (victim->GetPcData ()->GetClan () == ch->GetPcData ()->GetClan () &&
			 victim->GetPcData ()->GetClan () != NULL)
			return TRUE;
	}
	if (! victim->IsNpc ()) {
		// Racial languages for PCs
	    if (RaceTable.CanSpeak (victim->GetRace (), language))
	    	return TRUE;

		if (victim->SpeaksLanguage (language)) {
			sn = SkillTable.Lookup (LanguageTable.GetName (language));
			if (sn != -1 && victim->GetPcData ()->learned [sn] >= 60)
				return TRUE;
		}
	}
	return FALSE;
}


// This function is unused and may not be functional
BOOL can_learn_lang (CCharacter *ch, int language)
{
	if (language == LanguageTable.GetClan ())
		return FALSE;
	if (ch->IsNpc () || ch->IsImmortal ())
		return FALSE;
	if (RaceTable.CanSpeak (ch->GetRace (), language))
		return FALSE;

	CLanguage	&La = *LanguageTable.GetLanguageData (language);

	if (ch->SpeaksLanguage (language)) {
		if (La.CanLearn ()) {
			int	sn = SkillTable.Lookup (La.GetName ());
			if (sn < 0)
				bug ("Can_learn_lang: valid language without sn: %s:%d",
					NCCP La.GetName (), language);
			if (ch->GetPcData ()->learned [sn] >= 99)
				return FALSE;
		}
	}

	return La.CanLearn ();
}


int countlangs (const CBitVector& languages)
{
	int numlangs = 0;

	for (int lang=1; lang < LanguageTable.GetCount (); ++lang) {
		if (lang != LanguageTable.GetClan () && languages.IsSet (lang))
			++numlangs;
	}

	return numlangs;
}


void do_speak (CCharacter *ch, char *argument)
{
	char arg [MAX_INPUT_LENGTH];
	
	argument = one_argument (argument, arg);
	
	if (! str_cmp (arg, "all") && ch->IsImmortal ()) {
		set_char_color (AT_SAY, ch);
		ch->SetSpeaking (LANG_ALL);
		ch->SendText ("Now speaking all languages.\n\r");
		return;
	}

	BOOL	bKnow = FALSE;
	CLanguage	&La = *LanguageTable.Find (arg);
	if (&La)
		bKnow = knows_language (ch, La.GetLanguage (), ch);

	if (bKnow) {
		ch->SetSpeaking (La.GetLanguage ());
		set_char_color (AT_SAY, ch);
		ch->SendTextf ("You now speak %s.\n\r", NCCP La.GetName ());
	} else {
		set_char_color (AT_SAY, ch);
		ch->SendText ("You do not know that language.\n\r");
	}
}


void do_languages (CCharacter *ch, char *argument)
{
	char	arg [MAX_INPUT_LENGTH];
	
	argument = one_argument (argument, arg);
	if (arg [0] != '\0' && !str_prefix (arg, "learn") &&
	  ch->IsMortal () && !ch->IsNpc ()) {
		CCharacter	*sch;
		char		arg2 [MAX_INPUT_LENGTH];
		int			sn;
		int			prct;
		int			prac;
		
		argument = one_argument (argument, arg2);
		if (arg2 [0] == '\0') {
			ch->SendText ("Learn which language?\n\r");
			return;
		}

		CLanguage	&La = *LanguageTable.Find (arg2);

		if (! &La) {
			ch->SendText ("That is not a language.\n\r");
			return;
		}

		if (! La.CanLearn ()) {
			ch->SendText ("You may not learn that language.\n\r");
			return;
		}

		if ((sn = SkillTable.Lookup (La.GetName ())) < 0) {
			ch->SendText ("That is not a language.\n\r");
			return;
		}

		int	lang = La.GetLanguage ();
		if (RaceTable.CanSpeak (ch->GetRace (), lang) ||
		  lang == LanguageTable.GetCommon () ||
		  ch->GetPcData ()->learned [sn] >= 99) {
			act (AT_PLAIN, "You are already fluent in $t.", ch,
				 NCCP La.GetName (), NULL, TO_CHAR);
			return;
		}

		for (sch = ch->GetInRoom ()->first_person; sch; sch = sch->GetNextInRoom ())
			if (sch->IsNpc () && sch->IsAction (ACT_SCHOLAR) &&
				// if the mob knows the language you are speaking, and
				// he knows the language you want to learn, and if you
				// know the language the mob is speaking...
				knows_language (sch, ch->GetSpeaking (), ch) &&
				knows_language (sch, lang, sch) &&
				knows_language (ch, sch->GetSpeaking (), sch))
					break;

		if (! sch) {
			ch->SendText (
				"There is no one who can teach that language here.\n\r");
			return;
		}

		if (countlangs (ch->GetSpeaksFlags ()) >= (ch->GetLevel () / 10) &&
		  ch->GetPcData ()->learned [sn] <= 0) {
			act (AT_TELL, "$n tells you 'You may not learn a new language yet.'",
				 sch, NULL, ch, TO_VICT);
			return;
		}

		// 0..16 cha = 2 pracs, 17..25 = 1 prac. -- Altrag
		prac = 2 - (get_curr_cha (ch) / 17);
		if (ch->GetPractices () < prac) {
			act (AT_TELL, "$n tells you 'You do not have enough practices.'",
				 sch, NULL, ch, TO_VICT);
			return;
		}

		ch->AddPractices (-prac);
		// Max 12% (5 + 4 + 3) at 24+ int and 21+ wis. -- Altrag
		prct = 5 + (ch->GetIntelligence () / 6) + (ch->GetWisdom () / 7);
		ch->GetPcData ()->learned [sn] += prct;
		ch->GetPcData ()->learned [sn] =
			UMIN (ch->GetPcData ()->learned [sn], 99);
		ch->SetSpeaks (lang);

		if (ch->GetPcData ()->learned [sn] == prct)
			act (AT_PLAIN, "You begin lessons in $t.", ch,
				NCCP La.GetName (), NULL, TO_CHAR);
		else if (ch->GetPcData ()->learned [sn] < 60)
			act (AT_PLAIN, "You continue lessons in $t.", ch,
				NCCP La.GetName (), NULL, TO_CHAR);
		else if (ch->GetPcData ()->learned [sn] < 60 + prct)
			act (AT_PLAIN, "You feel you can start communicating in $t.", ch,
				 NCCP La.GetName (), NULL, TO_CHAR);
		else if (ch->GetPcData ()->learned [sn] < 99)
			act (AT_PLAIN, "You become more fluent in $t.", ch,
				 NCCP La.GetName (), NULL, TO_CHAR);
		else
			act (AT_PLAIN, "You now speak perfect $t.", ch,
				NCCP La.GetName (), NULL, TO_CHAR);
		return;
	}

	for (int lang=0; lang < LanguageTable.GetCount (); ++lang)
		if (knows_language (ch, lang, ch)) {
			if (ch->IsSpeaking (lang)
				|| (ch->IsNpc () && ch->IsSpeaking (LANG_ALL)))
					set_char_color (AT_RED, ch);
			else
				set_char_color (AT_SAY, ch);
			ch->SendText (LanguageTable.GetName (lang));
			ch->SendText (" ");
		}

	ch->SendText ("\n\r");
}


void do_wartalk (CCharacter* ch, char* argument)
{
	if (! ch->IsAuthed ()) {
		ch->SendText ("Huh?\n\r");
		return;
	}
	talk_channel (ch, argument, CHANNEL_WARTALK, "war");
}


void do_racetalk (CCharacter* ch, char* argument)
{
	if (! ch->IsAuthed ()) {
		ch->SendText ("Huh?\n\r");
		return;
	}
	talk_channel (ch, argument, CHANNEL_RACETALK, "racetalk");
}