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      |				*
 * -----------------------------------------------------------|   \\._.//	*
 * SmaugWizard (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.    *
 ****************************************************************************/
// SmaugWizDoc.cpp : implementation of the CSmaugWizDoc class

#include	"stdafx.h"
#include	"SmaugWiz.h"

#define	SMAUGSERVER_CPP

#include	"swtime.h"
#include	"smaug.h"
#include	"SmaugSocket.h"
#include	"SysData.h"
#include	"language.h"
#include	"skill.h"
#include	"social.h"
#include	"commands.h"
#include	"area.h"
#include	"mobiles.h"
#include	"objects.h"
#include	"rooms.h"
#include	"boards.h"
#include	"help.h"
#include	"log.h"
#include	"races.h"
#include	"class.h"
#include	"ActFlags.h"
#include	"Affect.h"
#include	"Exits.h"
#include	"SmaugWizDoc.h"
#include	"SmaugFiles.h"
#include	"character.h"
#include	"descriptor.h"
#include	"Smaugx.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif


char		*ProgVersion = "2.03";

void	SetHelpFilePath ();
CString MakeKeyFromPath (CString s);


/////////////////////////////////////////////////////////////////////////////
// CSmaugWizDoc

IMPLEMENT_DYNCREATE(CSmaugWizDoc, CDocument)

BEGIN_MESSAGE_MAP(CSmaugWizDoc, CDocument)
	//{{AFX_MSG_MAP(CSmaugWizDoc)
		// NOTE - the ClassWizard will add and remove mapping macros here.
		//    DO NOT EDIT what you see in these blocks of generated code!
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CSmaugWizDoc construction/destruction

CSmaugWizDoc::CSmaugWizDoc ()
{
#ifdef	_DEBUG
	afxDump.SetDepth (1);				// Specifies deep dump
#endif

	gpDoc = this;						// Set up global pointer
	m_pLog = NULL;
	m_pControl = NULL;
	m_bInLoop = FALSE;
	m_bShuttingDown = m_bDenyNewPlayers = FALSE;
	m_ScrPos = 0;
	bSmaugDown = TRUE;
}


CSmaugWizDoc::~CSmaugWizDoc()
{
	delete m_pControl;
	delete m_pLog;
}


BOOL CSmaugWizDoc::OnNewDocument()
{
	FileTable.m_FileRoot = GetRegisteredSmaugWizPath ("SmaugWiz");

	SetHelpFilePath ();

	m_StartTime.SetCurrentTime ();
	SetTitle ();						// set the window title string

	SysData.Load ();
	if (SysData.IsAutoStart ()) {
		m_TimeTilStart = SysData.GetStartDelay ();
		POSITION	pos = GetFirstViewPosition ();
		GetNextView (pos)->SetTimer (3, 1000, NULL);	// 1 Second
	}

	AllAreasList.RemoveAll ();
	AllAreasList.AddTail ((void*) &AreaList);
	AllAreasList.AddTail ((void*) &BuildList);

	return CDocument::OnNewDocument ();
}


BOOL CSmaugWizDoc::BootAndStart ()
{
	CString Msg;

	m_bReboot = FALSE;
	m_bInLoop = FALSE;
	m_bShuttingDown = FALSE;
	m_bDenyNewPlayers = FALSE;
	m_ScrPos = 0;
	bSmaugDown = TRUE;
	m_ExceptionCount = 0;

	if (! FileTable.Exists (FileTable.GetDir (SD_SYSTEM_DIR))) {
		CString	s = "No System Directory. The SmaugWizard system directory\n"
			"does not exist or it is incorrectly set.\n"
			"(it is currently set to " + FileTable.GetDir (SD_SYSTEM_DIR) + ").";
		MessageBox (0, s, "Serv", MB_ICONHAND | MB_OK);
		return FALSE;
	}

    // Reserve two channels for our use.
    if ((fpReserve = fopen (FileTable.GetName (SM_NULL_FILE), "a")) == NULL) {
		MessageBox (0, "Open Failure on Reserve File", "Serv",
			MB_ICONHAND | MB_OK);
		return FALSE;
	}
    if ((fpLOG = fopen (FileTable.GetName (SM_NULL_FILE), "a")) == NULL) {
		MessageBox (0, "Open Failure on Log File", "Serv",
			MB_ICONHAND | MB_OK);
		return FALSE;
	}

	SysData.Load ();
	if (! m_pLog)
		m_pLog = new CLog (STDERR_FILE);
	m_pLog->SetFromSysData ();
	m_pLog->SetType (LOG_NORMAL);		// always log normal messages
	if (m_pLog->GetSize () > (SysData.GetMaxStdErrSize () * 0x100000))
		m_pLog->Archive ();

	SetBootTime ();						// must do before boot_db
	LogStringf (LOG_NORMAL, LEVEL_LOG,
		"SmaugWizard %s, Booting Database", ProgVersion);

	TRY boot_db ();
	CATCH (CException, ex) return FALSE;
	END_CATCH

	m_pControl = new CSmaugSocket (this);
	if (! m_pControl->Create (SysData.GetPort ())) {
		Msg.Format ("Create Failure %d on Control Socket",
			GetLastError ());
		MessageBox (0, Msg, "Serv", MB_ICONHAND | MB_OK);
		return FALSE;
	}
	if (! m_pControl->Listen ()) {
		Msg.Format ("Listen Failure %d on Control Socket",
			GetLastError ());
		MessageBox (0, Msg, "Serv", MB_ICONHAND | MB_OK);
		return FALSE;
	}

    // Run the game.
	LogString ("Initializing sockets");

	// Tell Winsock we are listening for accepted sockets
	if (! m_pControl->AsyncSelect (FD_ACCEPT)) {
		Msg.Format ("AsyncSelect failure %d on FD_ACCEPT",
			GetLastError ());
		MessageBox (0, Msg, "Serv", MB_ICONHAND | MB_OK);
		m_pControl->Close ();
		return FALSE;
	}

	Msg.Format ("SmaugWizard Ready:  port %d.", SysData.GetPort ());
	LogString (Msg);

	return TRUE;
}


// Remove this define to test exceptions in debug mode.
#ifdef _DEBUG
	#define NOEXCEPTIONS
#endif

void CSmaugWizDoc::GameLoop (CDC* pDC)
{
	static int	count = 0;
	CDescriptor	*pCurDes = NULL;

	if (m_bInLoop || bSmaugDown)
		return;

#ifndef NOEXCEPTIONS
	try {
#endif
		m_bInLoop = TRUE;
		CurrentTime.SetCurrentTime ();

		// This loop is entered 4 times per second.
		if (! m_bShuttingDown) {
			// For each Descriptor, kick out descriptors which have been
			// idle. Then process input.
			POSITION	pos = DList.GetHeadPosition ();
			while (pos) {
				pCurDes = DList.GetNext (pos);
				CDescriptor	&Ds = *pCurDes;
				CCharacter	&Ch = *Ds.m_pCharacter;

				Ds.m_Idle++;
				if ((! Ds.m_pCharacter && Ds.m_Idle > 360)		// 1.5 mins
				  || (Ds.m_Connected != CON_PLAYING && Ds.m_Idle > 1200) // 5 mins
				  || Ds.m_Idle > 28800) {						// 2 hrs
					Ds.WriteToDescriptor ("Idle timeout... disconnecting.\n\r");
					RemoveCharacter (Ds);
					Ds.m_Outtop = 0;
					continue;
				}

				// Don't process any more input if char shutting down.
				if (Ds.IsDisconnecting ())
					continue;
				Ds.m_bFcommand = FALSE;

				if (&Ch && Ch.GetWait () > 0) {
					Ch.AddWait (-1);
					continue;
				}

				Ds.ReadFromBuffer ();
				if (Ds.IsCommandReady ()) {
					Ds.m_bFcommand = TRUE;
					Ds.m_Idle = 0;
					if (&Ch)
						Ch.StopIdling ();

					if (Ds.pagepoint)
						Ds.GetPagerInput ();
					else switch (Ds.m_Connected) {
					  case CON_PLAYING:
						interpret (&Ch, Ds.m_Incomm);
						break;
					  case CON_EDITING:
						Ch.Edit (Ds.m_Incomm);
						break;
					  default:
						nanny (&Ds, Ds.m_Incomm);
						break;
					}
					Ds.m_Incomm [0] = 0;
				}
			}

			update_handler ();
			check_requests ();		// Check REQUESTS pipe
		}

		// Output.
		POSITION	pos = DList.GetHeadPosition ();
		while (pos) {
			POSITION	CurPos = pos;
			pCurDes = DList.GetNext (pos);
			CDescriptor	&Ds = *pCurDes;

			if ((Ds.m_bFcommand || Ds.m_Outtop > 0)) {
				if (! Ds.ProcessOutput (TRUE) &&
				  WSAGetLastError () != WSAEWOULDBLOCK) {
					if (Ds.m_pCharacter != NULL)
						save_char_obj (Ds.m_pCharacter);
					Ds.m_Outtop = 0;
					RemoveCharacter (Ds);
				}
			}
			// Note that at this point everything having to do with
			// the char has been saved and freed.  We kept the
			// descriptor around long enough to output the final
			// messages.
			if (Ds.IsDisconnecting () && Ds.m_Outtop == 0) {
				Ds.Shutdown ();		// close socket & free pointers
				// Now remove the descriptor from the descriptor list
				// and add it to the free list.
				DList.RemoveAt (CurPos);
				DFreeList.AddHead (&Ds);
			}
		}

		if (m_bShuttingDown)
			if (CloseAllDescriptors ()) {
				SysData.LogBytes ();
				LogString ("Normal termination of game.\n\n");
				fclose (fpReserve);
				fclose (fpLOG);
				bSmaugDown = TRUE;
			}
		m_bInLoop = FALSE;
#ifndef NOEXCEPTIONS
	}

	#ifdef _DEBUG
		#undef MAX_EXCEPTIONS
		#define	MAX_EXCEPTIONS 10
	#endif

	catch (CSwException ex) {
		if (m_ExceptionCount < MAX_EXCEPTIONS) {
			if (pCurDes) {
				pCurDes->m_Incomm [0] = 0;
				pCurDes->m_Outtop = 0;
			}
			LogString (ex.m_buf, LOG_COMM, 61);
			LogStringf (LOG_COMM, 61, "Exception %d. Attempting to continue\n",
				++m_ExceptionCount);
		}
		else DoEmergencyReboot ();
	}
	
	catch (CException* ex) {
		char	buf [128];
		UINT	id;

		if (ex->GetErrorMessage (buf, sizeof (buf), &id)) {
			LogStringf (LOG_COMM, 61, "CException %u:%s\n", id, buf);
			DoEmergencyReboot ();
		}
		else throw ex;
	}
	
	catch (...) {
		LogString ("Unknown CException.\n", LOG_COMM, 61);
		DoEmergencyReboot ();
	}
#endif

	m_bInLoop = FALSE;
}


void CSmaugWizDoc::DoEmergencyReboot ()
{
	LogString ("Unable to continue. Attempting Reboot.\n",
		LOG_COMM, 61);
	if (! bSmaugDown) {
		do_shutdown (supermob, "mud now");
		CloseAllDescriptors (TRUE);		// TRUE means force close
		SysData.LogBytes ();
		fclose (fpReserve);
		fclose (fpLOG);
		bSmaugDown = TRUE;
		SetReboot ();
	}
}


void CSmaugWizDoc::LogToScreen (LogTypes type, const char* msg)
{
	if (! SysData.IsLoggingToScreen (type))
		return;

	CString	s = msg;
	while (! s.IsEmpty ()) {
		if (m_ScrPos >= MAX_LINES) {
			memmove (&m_ScrBuf [0], &m_ScrBuf [1], LINE_LEN * (MAX_LINES - 1));
			--m_ScrPos;
		}

		int		len = s.Find ('\n');
		if (len < 0)
			len = s.GetLength ();

		strcpy (m_ScrBuf [m_ScrPos], s.Left (len));

		if (len < s.GetLength ())
			s = s.Mid (len+1);
		else
			s.Empty ();

		int	NewLine = m_ScrPos++;
		UpdateAllViews (NULL, NewLine);
	}
}


char *CSmaugWizDoc::GetData (int row)
{
	char	*s = "";

	if (row < m_ScrPos)
		s = (char*) &m_ScrBuf [row];

	return s;
}


void CSmaugWizDoc::LogStringf (LogTypes type, short level, char *fmt, ...)
{
	char	buf [MAX_STRING_LENGTH*2];	// better safe than sorry
	va_list	args;

	va_start (args, fmt);
	vsprintf (buf, fmt, args);
	va_end (args);

	LogString (buf, type, level);
}


void CSmaugWizDoc::LogString (const char *str, LogTypes type, short level)
{
	LogToScreen (type, str);
	
	if (m_pLog)
		m_pLog->Printf (type, "%s :: %s\n", NCCP CurrentTime.GetString (),
			str);


	int		offset = (strncmp (str, "Log ", 4)) ? 0 : 4;

	if (! bSmaugDown) switch (type) {
	  case LOG_NORMAL:
	  case LOG_ALWAYS:
	  case LOG_HIGH:
		to_channel (str + offset, CHANNEL_LOG, "Log", level);
		break;
	  case LOG_BUILD:
		if (SysData.IsLogBuildToChannel ())
			to_channel (str + offset, CHANNEL_BUILD, "Build", level);
		break;
	  case LOG_COMM:
		if (SysData.IsLogCommToChannel ())
			to_channel (str + offset, CHANNEL_COMM, "Comm", level);
		break;
	  case LOG_BUG:
		if (SysData.IsLogBugsToChannel ())
			to_channel (str + offset, CHANNEL_LOG, "Bug", level);
		break;
	  case LOG_PLAYER:
		if (SysData.IsLogPlayersToChannel ())
			to_channel (str + offset, CHANNEL_LOG, "Player", level);
		break;
	}
}


void CSmaugWizDoc::SetBootTime ()
{

	// Init time.
	CurrentTime.SetCurrentTime ();
	m_BootTime = CurrentTime;

    // Init boot time.
	m_RebootTime.SetCurrentTime ();
	m_RebootTime.SetSecond (0);
	m_RebootTime.SetMinute (0);
	m_RebootTime.SetHour (SysData.GetRebootHour ());
	m_RebootTime += SysData.GetRebootInterval ();
	m_RebootTime.SetManual (FALSE);
}

#ifdef XXXX
	new_boot_time = update_time (localtime (&current_time));
	// Copies *new_boot_time to new_boot_struct, and then points
	// new_boot_time to new_boot_struct again. -- Alty
	// RCP: This is because update_time returns a pointer to a static
	// structure which may get overwritten, thus we need to copy the data
	// into new_boot_struct and then point to it.  It would be much simpler
	// to only use the new_boot_struct (or a SYSTEMTIME struct), and get it's
	// address when a pointer is needed.  I'll fix this up someday :) RCP
	new_boot_struct = *new_boot_time;
	new_boot_time = &new_boot_struct;

	new_boot_time->tm_sec = 0;
	new_boot_time->tm_min = 0;
	new_boot_time->tm_hour = SysData.GetRebootHour ();
	new_boot_time->tm_mday += SysData.GetRebootInterval ();
//	++new_boot_time->tm_mday;
//	if (new_boot_time->tm_hour > 12)
//		++new_boot_time->tm_mday;
//	new_boot_time->tm_sec = 0;
//	new_boot_time->tm_min = 0;
//	new_boot_time->tm_hour = 6;

	// Update new_boot_time (due to day increment)
	new_boot_time = update_time (new_boot_time);
	new_boot_struct = *new_boot_time;
	new_boot_time = &new_boot_struct;
	new_boot_time_t = mktime (new_boot_time);

	// Set reboot time string for do_time
	SetRebootTime (*new_boot_time);
}
#endif


/////////////////////////////////////////////////////////////////////////////
// CSmaugWizDoc serialization

void CSmaugWizDoc::Serialize(CArchive& ar)
{
	if (ar.IsStoring())
	{
		// TODO: add storing code here
	}
	else
	{
		// TODO: add loading code here
	}
}

/////////////////////////////////////////////////////////////////////////////
// CSmaugWizDoc diagnostics

#ifdef _DEBUG
void CSmaugWizDoc::AssertValid() const
{
	CDocument::AssertValid();
}

void CSmaugWizDoc::Dump(CDumpContext& dc) const
{
	CDocument::Dump(dc);
}
#endif //_DEBUG

/////////////////////////////////////////////////////////////////////////////
// CSmaugWizDoc commands


void CSmaugWizDoc::SetTitle ()
{
	CString		Tit;

	Tit.Format ("Smaug Wizard v%s", ProgVersion);
	
	CDocument::SetTitle (Tit);
}


void CSmaugWizDoc::ShutSmaugDown () 
{
    CString	Msg = "You are forced from " + SysData.GetShortDesc () +
		" by a strong magical presence\n\ras life here is reconstructed.";
	echo_to_all (AT_YELLOW, Msg, ECHOTAR_ALL);

	m_bShuttingDown = TRUE;
}


BOOL CSmaugWizDoc::CloseAllDescriptors (BOOL bForce /* = FALSE */)
{
	if (! DList.IsEmpty ()) {
		POSITION	pos = DList.GetHeadPosition ();
		while (pos) {
			POSITION	CurPos = pos;
			CDescriptor	*d = DList.GetNext (pos);

			if (d->m_pCharacter)
				RemoveCharacter (*d);
			if (d->m_Outtop == 0 || bForce) {
				d->Shutdown ();				// close socket & free pointers
				DList.RemoveAt (CurPos);	// Remove from DList
				delete d;					// kill it
			}
		}
	}
	
	if (DList.IsEmpty ()) {
		DList.RemoveAll ();

		// Clear out the free list
		while (! DFreeList.IsEmpty ())
			delete (CDescriptor*) DFreeList.RemoveTail ();
		DFreeList.RemoveAll ();
	}

	return DList.IsEmpty ();
}



void RemoveCharacter (CDescriptor& Dclose)
{
	if (Dclose.m_pSnoopBy) {
		Dclose.m_pSnoopBy->WriteToBuffer (
			"Your victim has left the game.\n\r", 0);
	}

	// stop snooping everyone else
	POSITION	pos = DList.GetHeadPosition ();
	while (pos) {
		CDescriptor	&Ds = * (CDescriptor*) DList.GetNext (pos);
		if (Ds.m_pSnoopBy == &Dclose)
			Ds.m_pSnoopBy = NULL;
	}


	CCharacter *ch = Dclose.m_pCharacter;

	// Check for switched people who go link-dead. -- Altrag
	if (Dclose.m_pOriginal) {
		if (ch)
			do_return (ch, "");
		else {
			bug ("RemoveCharacter: dclose->original without character %s",
				(Dclose.m_pOriginal->GetName () ?
				Dclose.m_pOriginal->GetName () : "unknown"));
			Dclose.m_pCharacter = Dclose.m_pOriginal;
			Dclose.m_pOriginal = NULL;
		}
	}

	ch = Dclose.m_pCharacter;		// in case it got changed above!
	if (ch) {
		sprintf (log_buf, "Closing link to %s.", ch->GetName ());
		gpDoc->LogString (log_buf, LOG_COMM,
			UMAX ((int)SysData.LogLevel, (int)ch->GetLevel ()));
		if (Dclose.m_Connected == CON_PLAYING
		  || Dclose.m_Connected == CON_EDITING) {
			act (AT_ACTION, "$n has lost $s link.", ch, NULL, NULL, TO_ROOM);
			ch->SetDesc (NULL);
		} else {
			delete ch;
			Dclose.m_pCharacter = NULL;
		}
	}

	Dclose.SetDisconnecting ();
}


BOOL is_reserved_name (CString Name)
{
	Name.MakeLower ();
	POSITION	pos = ReservedNamesList.GetHeadPosition ();
	while (pos) {
		CString	Rname = ReservedNamesList.GetNext (pos);
		if ((Rname [0] == '*' && Name.Find (Rname.Mid (1)) > -1)
			|| ! Name.CompareNoCase (Rname))
				return TRUE;
	}

	return FALSE;
}


// Parse a name for acceptability.
BOOL check_parse_name (const char* name, BOOL bNewchar /* = TRUE */)
{
// Names checking should really only be done on new characters, otherwise
// we could end up with people who can't access their characters.  Would
// have also provided for that new area havoc mentioned below, while still
// disallowing current area mobnames.  I personally think that if we can
// have more than one mob with the same keyword, then may as well have
// players too though, so I don't mind that removal.  -- Alty

	if (bNewchar)
		if (is_reserved_name (name))
			return FALSE;

	// Length restrictions.
	if (strlen (name) <  3)
		return FALSE;

	if (strlen (name) > 12)
		return FALSE;

	// Alphanumerics only.
	// Lock out IllIll twits.
	const char	*pc;
	BOOL		bOk = FALSE;

	for (pc = name; *pc != '\0'; pc++) {
		if (! isalpha (*pc))
			return FALSE;
		if (LOWER (*pc) != 'i' && LOWER (*pc) != 'l')
			bOk = TRUE;
	}

	return bOk;
}


void write_to_pager (CDescriptor *d, const char *txt, int length)
{

	if (length <= 0)
		length = strlen (txt);
	if (length == 0)
		return;
	if (!d->pagebuf) {
		d->pagesize = MAX_STRING_LENGTH;
		d->pagebuf = (char*) malloc (d->pagesize);
	}
	if (!d->pagepoint) {
		d->pagepoint = d->pagebuf;
		d->pagetop = 0;
		d->pagecmd = '\0';
	}
	if (d->pagetop == 0 && !d->m_bFcommand) {
		d->pagebuf[0] = '\n';
		d->pagebuf[1] = '\r';
		d->pagetop = 2;
	}
	while ((ULONG)d->pagetop + (ULONG)length >= d->pagesize) {
		if (d->pagesize >= MAX_PAGER_BUFFER) {
			bug ("Pager overflow.  Ignoring.\n\r");
			d->pagetop = 0;
			d->pagepoint = NULL;
			free (d->pagebuf);
			d->pagebuf = NULL;
			d->pagesize = MAX_STRING_LENGTH;
			return;
		}
		d->pagesize *= 2;
		d->pagebuf = (char*) realloc (d->pagebuf, d->pagesize);
		if (! d->pagebuf) {
			perror ("realloc failure in pager");
			abort ();
		}
	}
	strncpy (d->pagebuf + d->pagetop, txt, length);
	d->pagetop += length;
	d->pagebuf [d->pagetop] = '\0';
}


void send_to_pager (const char *txt, CCharacter *ch)
{
	ASSERT (ch);
	CDescriptor *d = ch->GetDesc ();
	if (txt && d) {
		ch = d->m_pOriginal ? d->m_pOriginal : d->m_pCharacter;
		if (ch->IsNpc () || ! ch->IsPagerOn ()) {
			d->m_pCharacter->SendText (txt);
			return;
		}
		write_to_pager (d, txt, 0);
	}
}


UCHAR ConvertATypeToColor (int AType);

void set_char_color (short AType, CCharacter *ch)
{
	CDescriptor *d = ch->GetDesc ();
	char		buf [32];

	if (!ch || !d)
		return;

	int len = MakeColorSequence (ConvertATypeToColor (AType), buf, d);
	if (len)
		d->WriteToBuffer (buf);
}


void set_pager_color (short AType, CCharacter *ch)
{
	CDescriptor *d = ch->GetDesc ();
    char		buf [32];
    
	if (!ch || !d)
		return;

	int len = MakeColorSequence (ConvertATypeToColor (AType), buf, d);
	if (len)
		send_to_pager (buf, ch);
}


void pager_printf (CCharacter *ch, char *fmt, ...)
{
	char	buf [MAX_STRING_LENGTH*2];
	va_list	args;

	va_start (args, fmt);
	vsprintf (buf, fmt, args);
	va_end (args);

	send_to_pager (buf, ch);
}


char *obj_short (CObjData *obj)
{
	static char buf [MAX_STRING_LENGTH];

	if (obj->count > 1) {
		sprintf (buf, "%s (%d)", obj->GetShortDescr (), obj->count);
		return buf;
	}
	return NCCP obj->GetShortDescr ();
}


// The primary output interface for formatted output.
// Major overhaul. -- Alty
#define NAME(ch)	 (ch->IsNpc () ? ch->GetShortDescr () : ch->GetName ())
char *act_string (const char *format, CCharacter *to, CCharacter *ch,
		 const void *arg1, const void *arg2)
{
	static char		*const he_she  [] = { "it",  "he",  "she" };
	static char		*const him_her [] = { "it",  "him", "her" };
	static char		*const his_her [] = { "its", "his", "her" };
	static char		buf [MAX_STRING_LENGTH];
	char			fname [MAX_INPUT_LENGTH];
	char			*point = buf;
	const char		*str = format;
	const char		*i;

	CCharacter *vch = (CCharacter*) arg2;
	CObjData *obj1 = (CObjData*) arg1;
	CObjData *obj2 = (CObjData*) arg2;

	while (*str != '\0') {
		if (*str != '$') {
			*point++ = *str++;
			continue;
		}
		++str;
		if (!arg2 && *str >= 'A' && *str <= 'Z') {
			bug ("Act: missing arg2 for code %c:", *str);
			bug (format);
		i = " <@@@> ";
		} else {
			switch (*str) {
			  default:
				bug ("Act: bad code %c.", *str);
				i = " <@@@> ";
				break;
			  case 't':
				i = (char*) arg1;
				break;
			  case 'T':
				i = (char*) arg2;
				break;
			  case 'n':
				i = (to ? PERS (ch, to) : NAME (ch));
				break;
			  case 'N':
				i = (to ? PERS (vch, to) : NAME (vch));
				break;
			  case 'e':
				if (ch->GetSex () > 2 || ch->GetSex () < 0) {
					bug ("act_string: player %s has sex set at %d!",
						ch->GetName (), ch->GetSex ());
					i = "it";
				}
				else i = he_she [URANGE (0,  ch->GetSex (), 2)];
				break;
			  case 'E':
				if (vch->GetSex () > 2 || vch->GetSex () < 0) {
					bug ("act_string: player %s has sex set at %d!",
						vch->GetName (), vch->GetSex ());
					i = "it";
				}
				else i = he_she [URANGE (0, vch->GetSex (), 2)];
				break;
			  case 'm':
				if (ch->GetSex () > 2 || ch->GetSex () < 0) {
					bug ("act_string: player %s has sex set at %d!",
						ch->GetName (), ch->GetSex ());
					i = "it";
				}
				else i = him_her [URANGE (0,  ch->GetSex (), 2)];
				break;
			  case 'M':
				if (vch->GetSex () > 2 || vch->GetSex () < 0) {
					bug ("act_string: player %s has sex set at %d!",
						vch->GetName (), vch->GetSex ());
					i = "it";
				}
				else i = him_her [URANGE (0, vch->GetSex (), 2)];
				break;
			  case 's':
				if (ch->GetSex () > 2 || ch->GetSex () < 0) {
					bug ("act_string: player %s has sex set at %d!",
						ch->GetName (), ch->GetSex ());
					i = "its";
				}
				else i = his_her [URANGE (0, ch->GetSex (), 2)];
				break;
			  case 'S':
				if (vch->GetSex () > 2 || vch->GetSex () < 0) {
					bug ("act_string: player %s has sex set at %d!",
						vch->GetName (), vch->GetSex ());
					i = "its";
				}
				else i = his_her [URANGE (0, vch->GetSex (), 2)];
				break;
			  case 'q':
				i = (to == ch) ? "" : "s";
				break;
			  case 'Q':
				i = (to == ch) ?
					"your" : his_her [URANGE (0,  ch->GetSex (), 2)];
				break;
			  case 'p':
				i = (!to || can_see_obj (to, *obj1)
					? obj_short (obj1) : "something");
				break;
			  case 'P':
				i = (!to || can_see_obj (to, *obj2)
					? obj_short (obj2) : "something");
				break;
			  case 'd':
				if (!arg2 || ((char*) arg2) [0] == '\0')
					i = "door";
				else {
					one_argument ((char*) arg2, fname);
					i = fname;
				}
				break;
			}
		}
		++str;
		while ((*point = *i) != '\0')
			++point, ++i;
	}
	strcpy (point, "\n\r");
	buf [0] = UPPER (buf [0]);
	return buf;
}
#undef NAME


void act (short AType, const char *format, CCharacter *ch,
			const void *arg1, const void *arg2, int type)
{
	ASSERT (ch);
	char		*txt;
	CCharacter *to;
	CCharacter *vch = (CCharacter*) arg2;

	// Discard null and zero-length messages.
	if (!format || format [0] == '\0')
		return;

	if (!ch->GetInRoom ())
		to = NULL;
	else if (type == TO_CHAR)
		to = ch;
	else to = ch->GetInRoom ()->first_person;

	// ACT_SECRETIVE handling
	if (ch->IsNpc () && ch->IsSecretive () && type != TO_CHAR)
		return;

	if (type == TO_VICT) {
		if (!vch) {
			bug ("Act: null vch with TO_VICT.");
			bug ("%s (%s)", ch->GetName (), format);
			return;
		}
		if (!vch->GetInRoom ()) {
			bug ("Act: vch in NULL room!");
			bug ("%s -> %s (%s)", ch->GetName (), vch->GetName (), format);
			return;
		}
		to = vch;
		//	to = vch->GetInRoom ()->first_person;
	}

	if (MOBtrigger && type != TO_CHAR && type != TO_VICT && to) {
		CObjData	*to_obj;

		txt = act_string (format, NULL, ch, arg1, arg2);
		if (to->GetInRoom ()->m_Progtypes.IsSet (ACT_PROG))
			rprog_act_trigger (txt, to->GetInRoom (), ch, (CObjData*) arg1,
				 (void*) arg2);
		POSITION	pos = to->GetInRoom ()->GetHeadContentPos ();
		while (to_obj = to->GetInRoom ()->GetNextContent (pos))
			if (to_obj->pIndexData->HasActProg ())
				oprog_act_trigger (txt, to_obj, ch, (CObjData*) arg1,
					 (void*) arg2);
	}

	/* Anyone feel like telling me the point of looping through the whole
	room when we're only sending to one char anyways..? -- Alty */
	for (; to; to = (type == TO_CHAR || type == TO_VICT) ?
	  NULL : to->GetNextInRoom ()) {
		if ((!to->GetDesc () 
		  && (to->IsNpc ()
		  && ! to->GetMobIndex ()->m_Progtypes.IsSet (ACT_PROG)))
		  || ! to->IsAwake ())
			continue;

		if (type == TO_CHAR && to != ch)
			continue;
		if (type == TO_VICT && (to != vch || to == ch))
			continue;
		if (type == TO_ROOM && to == ch)
			continue;
		if (type == TO_NOTVICT && (to == ch || to == vch))
			continue;

		txt = act_string (format, to, ch, arg1, arg2);
		if (to->GetDesc ()) {
			set_char_color (AType, to);
			to->SendColor (txt);
//			to->GetDesc ()->WriteToBuffer (txt);
		}
		if (MOBtrigger) {
			// Note: use original string, not string with ANSI. -- Alty
			mprog_act_trigger (txt, to, ch, (CObjData*) arg1, (void*) arg2);
		}
	}
	MOBtrigger = TRUE;
}


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

	if (ch->IsNpc ())
		return;

	if (ch->IsAuthed () || ch->GetPcData ()->auth_state != 2) {
		ch->SendText ("Huh?\n\r");
		return;
	}

	// Can't use one_argument here because it smashes case.
	// So we just steal all its code.  Bleagh.
	pArg = arg1;
	while  (isspace (*argument))
		argument++;

	char	cEnd = ' ';
	if (*argument == '\'' || *argument == '"')
		cEnd = *argument++;

	while (*argument != '\0') {
		if (*argument == cEnd) {
			argument++;
			break;
		}
		*pArg++ = *argument++;
	}
	*pArg = '\0';

	pArg = arg2;
	while (isspace (*argument))
		argument++;

	cEnd = ' ';
	if (*argument == '\'' || *argument == '"')
		cEnd = *argument++;

	while (*argument != '\0') {
		if (*argument == cEnd) {
			argument++;
			break;
		}
		*pArg++ = *argument++;
	}
	*pArg = '\0';

	if (arg1 [0] == '\0' || arg2 [0] == '\0') {
		ch->SendText ("Syntax: name <new> <password>.\n\r");
		return;
	}

	arg1 [0] = toupper (arg1 [0]);

	if (! check_parse_name (arg1)) {
		ch->SendText ("Illegal name, try another.\n\r");
		return;
	}

	if (! stricmp (ch->GetName (), arg1)) {
		ch->SendText ("That's already your name!\n\r");
		return;
	}

	CCharacter	*tmp;
	for (tmp = first_char; tmp; tmp = tmp->GetNext ()) {
		if (! stricmp (argument, tmp->GetName ()))
		break;
	}

	if (tmp) {
		ch->SendText ("That name is already taken.  Please choose another.\n\r");
		return;
	}
 
	CString Fname = FileTable.PlayerName (arg1);
	if (FileTable.Exists (Fname)) {
		ch->SendText ("That name is already taken.  Please choose another.\n\r");
		return;
	}

	if (strcmp (Crypt (arg2, ch->GetName ()), ch->GetPcData ()->GetPassWord ())) {
		ch->SendText ("Wrong password.\n\r");
		return;
	}

	ch->SetName (arg1);

	CString NewPwd = Crypt (arg2, ch->GetName ());
	delete ch->GetPcData ()->GetPassWord ();
	ch->GetPcData ()->SetPassWord (str_dup (NewPwd));

	ch->SendText ("Your name has been changed.  Please apply again.\n\r");
	ch->GetPcData ()->auth_state = 0;
}


char *default_prompt (CCharacter *ch)
{
	static char	buf [60];

	strcpy (buf, "&w<&Y%hhp ");
	if (ch->IsVampire ())
		strcat (buf, "&R%bbp");
	else strcat (buf, "&C%mm");

	strcat (buf, " &G%vmv&w> ");
	if (ch->IsNpc () || ch->IsImmortal ())
		strcat (buf, "%i%R");
	return buf;
}


int getcolor (char clr)
{
	static const char colors [] = "xrgObpcwzRGYBPCW";
	int		r;

	for (r = 0; r < sizeof (colors); r++)
		if (clr == colors [r])
			return r;
	return -1;
}


UCHAR ConvertATypeToColor (int AType)
{
	UCHAR	col = AType & 0x0F;

	if (AType & 0x10)
		col |= 0x80;		// set blink

	return col;
}


bool IsColor (const char* pCol)
{
	if (pCol [0] != '&')
		return FALSE;

	if (pCol [1] == ']' || pCol [1] == '['
		|| getcolor (pCol [1]) > -1)
			return TRUE;

	return FALSE;
}


bool HasColor (const char* str)
{
	int	len = strlen (str);
	for (int i=0; i < len; ++i)
		if (str [i] == '&' && IsColor (&str [i]))
			return TRUE;

	return FALSE;
}


int ColorLen (const char* str)
{
	int	len = strlen (str);
	int j, i;
	if (! HasColor (str))
		return len;

	for (j=0,i=0; i < len; ++i) {
		if (str [i] == '&' && IsColor (&str [i]))
			++i;
		else
			++j;
	}

	return j;
}


int GetColorSize (const char* str)
{
	if (! HasColor (str))
		return 0;

	int	len = strlen (str);
	int j, i;
	for (j=0,i=0; i < len; ++i) {
		if (str [i] == '&' && IsColor (&str [i])) {
			j += 2;
			++i;
		}
	}

	return j;
}


CString StripColor (const char* str)
{
	if (! HasColor (str))
		return str;

	int		len = strlen (str);
	char	*pBuf = new char [len + 1];
	char	*p1 = pBuf;

	for (int i=0; i < len; ++i) {
		if (*str == '&' && IsColor (str))
			str += 2;
		else
			*p1++ = *str++;
	}
	*p1 = 0;

	CString s = pBuf;
	delete [] pBuf;

	return s;
}


int MakeColorSequence (UCHAR Col, char *buf, CDescriptor *d)
{
	ASSERT (d);
	int				ln;
	CCharacter		*och;
	bool			bAnsi;

	och = d->m_pOriginal ? d->m_pOriginal : d->m_pCharacter;
	bAnsi = ! och->IsNpc () && och->IsAnsi ();

	*buf = 0;
	if (! bAnsi)
		return 0;

	strcpy (buf, "\x1B[");				// Set up escape sequence

	// if bold or blink are not the same as before
	if ((Col & 0x88) != (d->m_PrevColor & 0x88)) {
		strcat (buf, "0m\x1B[");		// clear all text characteristics & set up new escape seq.
		if ((Col & 0x08))
			strcat (buf, "1;");			// set BOLD
		if ((Col & 0x80))
			strcat (buf, "5;");			// set BLINK
		ln = strlen (buf);
	}
	else ln = 2;

	// Set foreground and background colors
	sprintf (buf+ln, "3%d;4%dm", Col & 0x07, (Col & 0x70) >> 4);

	d->m_PrevColor = Col;

	return strlen (buf);
}


int MakeColorSequence (const char *col, char *buf, CDescriptor *d)
{
	ASSERT (d);
	int				ln;
	char			ctype = *col;
	unsigned char	cl;
	CCharacter		*och;
	bool			bAnsi;

	och = d->m_pOriginal ? d->m_pOriginal : d->m_pCharacter;
	bAnsi = ! och->IsNpc () && och->IsAnsi ();

	*buf = 0;
	++col;
	if (! *col)
		return -1;
	
	if (ctype != '&' && ctype != '^') {
		bug ("MakeColorSequence: command '%c' not '&' or '^'.", ctype);
		return -1;
	}

	if (*col == ctype) {
		buf [0] = *col;
		buf [1] = '\0';
		return 1;
	}

	if (! bAnsi)
		return 0;

	cl = d->m_PrevColor;

	if (ctype == '&' && *col == '-') {
		buf [0] = '~';
		buf [1] = '\0';
		return 1;
	}

	int newcol = getcolor (*col);
	if (newcol < 0)
		return 0;

	if (ctype == '&')
		cl = (cl & 0xF0) | newcol;
	else cl = (cl & 0x0F) | (newcol << 4);

	strcpy (buf, "\x1B[");				// Set up escape sequence

	// if bold or blink are not the same as before
	if ((cl & 0x88) != (d->m_PrevColor & 0x88)) {
		strcat (buf, "0m\x1B[");		// clear all text characteristics & set up new escape seq.
		if ((cl & 0x08))
			strcat (buf, "1;");			// set BOLD
		if ((cl & 0x80))
			strcat (buf, "5;");			// set BLINK
		ln = strlen (buf);
	}
	else ln = 2;

	// Set foreground and background colors
	sprintf (buf+ln, "3%d;4%dm", cl & 0x07, (cl & 0x70) >> 4);

	d->m_PrevColor = cl;

	return strlen (buf);
}


/* This is the old make color sequence code.  It attempts to save a little bandwidth by
   not sending color codes when it detects that the new color has already been set on the
   user's terminal screen.  This has been replaced with MakeColorSequence (above) which
   always sends out the necessary color sequences whenever colors are specified. (RCP)

int make_color_sequence (const char *col, char *buf, CDescriptor *d)
{
	ASSERT (d);
	int				ln;
	const char		*ctype = col;
	unsigned char	cl;
	CCharacter		*och;
	BOOL			ansi;

	och = (d->m_pOriginal ? d->m_pOriginal : d->m_pCharacter);
	ansi = (! och->IsNpc () && och->IsAnsi ());
	col++;
	if (!*col)
		ln = -1;
	else if (*ctype != '&' && *ctype != '^') {
		bug ("Make_color_sequence: command '%c' not '&' or '^'.", *ctype);
		ln = -1;
	}
	else if (*col == *ctype) {
		buf [0] = *col;
		buf [1] = '\0';
		ln = 1;
	}
	else if (!ansi)
		ln = 0;
	else {
		cl = d->m_PrevColor;
		switch (*ctype) {
		  default:
			bug ("Make_color_sequence: bad command char '%c'.", *ctype);
			ln = -1;
			break;
		  case '&':
			if (*col == '-') {
				buf [0] = '~';
				buf [1] = '\0';
				ln = 1;
				break;
			}							// NOTE FALL THRU !!!
		  case '^':
		    {
				int newcol;

				if ((newcol = getcolor (*col)) < 0) {
					ln = 0;
					break;
				}
				else if (*ctype == '&')
					cl = (cl & 0xF0) | newcol;
				else cl = (cl & 0x0F) | (newcol << 4);
			}
			if (cl == d->m_PrevColor) {			// Removed RCP
				ln = 0;
				break;
			}
			strcpy (buf, "\033[");				// clear all text characteristics

			// if bold or blink are not the same as before
			if ((cl & 0x88) != (d->m_PrevColor & 0x88)) {
				strcat (buf, "m\033[");
				if ((cl & 0x08))
					strcat (buf, "1;");					// set BOLD
				if ((cl & 0x80))
					strcat (buf, "5;");					// set BLINK
				d->m_PrevColor = 0x07 | (cl & 0x88);
				ln = strlen (buf);
			}
			else ln = 2;
			if ((cl & 0x07) != (d->m_PrevColor & 0x07)) {
				sprintf (buf+ln, "3%d;", cl & 0x07);	// set foreground color
				ln += 3;
			}
			if ((cl & 0x70) != (d->m_PrevColor & 0x70)) {
				sprintf (buf+ln, "4%d;", (cl & 0x70) >> 4);	// set background color
				ln += 3;
			}

			// make sure it is properly terminated
			if (buf [ln-1] == ';')
				buf [ln-1] = 'm';
			else {
				buf [ln++] = 'm';
				buf [ln] = '\0';
			}
			d->m_PrevColor = cl;
		}
	}
	if (ln <= 0)
		*buf = '\0';
	return ln;
}
*/


void set_pager_input (CDescriptor *d, char *argument)
{
	ASSERT (d);

	while (isspace (*argument))
		argument++;
	d->pagecmd = *argument;
}


void ResendExitMsg ()
{
	// same as double-clicking on main window close box
	ASSERT (AfxGetMainWnd() != NULL);
	AfxGetMainWnd ()->SendMessage (WM_CLOSE);
}


void SetHelpFilePath ()
{
	extern	CSmaugWizApp theApp;
	// This is a public CWinApp member variable named m_pszHelpFilePath that
	// the user can change if desired.
	CString Hlp = FileTable.GetDir (SD_SYSTEM_DIR) + "/SmaugWiz.hlp";
	
	theApp.m_pszHelpFilePath = _strdup (Hlp);
}


CString CSmaugWizDoc::GetRegisteredSmaugWizPath (const char* name)
{
	GetCurrentDirectory (256, m_CurrentDir.GetBuffer (256));
	m_CurrentDir.ReleaseBuffer ();

	CString	key;
	key.Format ("Software\\%s\\%s", name,
		NCCP MakeKeyFromPath (m_CurrentDir));

	HKEY	hk;
	int	rv = RegOpenKeyEx (HKEY_CURRENT_USER, key, 0, KEY_READ, &hk);

	UCHAR	buf [256];
	if (rv == 0) {
		DWORD	Size = sizeof (buf);

		rv = RegQueryValueEx (hk, "Root", 0, 0, buf, &Size);
		RegCloseKey (hk);
	}
	return rv ? m_CurrentDir : buf;
}


void CSmaugWizDoc::SetRegisteredSmaugWizPath (const char* name, CString& path)
{
	HKEY	hk;
	DWORD	Disposition;

	CString	key;
	key.Format ("Software\\%s\\%s", name,
		MakeKeyFromPath (m_CurrentDir));

	int rv = RegCreateKeyEx (HKEY_CURRENT_USER, key, 0, NULL,
		REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hk, &Disposition);

	if (rv == 0) {
		RegSetValueEx (hk, "Root", 0, REG_SZ, (UCHAR*) NCCP path,
			path.GetLength ());
		RegCloseKey (hk);
	}
}


CString MakeKeyFromPath (CString s)
{
	for (int i=0; i < s.GetLength (); ++i) {
		char c = s [i];
		if (c == '/' || c == '\\')
			s.SetAt (i, '_');
	}
	return s;
}