mud++0.35/etc/
mud++0.35/etc/guilds/
mud++0.35/help/propert/
mud++0.35/mudC/
mud++0.35/player/
mud++0.35/src/interface/
mud++0.35/src/os/cygwin32/
mud++0.35/src/os/win32/
mud++0.35/src/os/win32/bcppbuilder/
mud++0.35/src/osaddon/
mud++0.35/src/util/
/*
....[@@@..[@@@..............[@.................. MUD++ is a written from
....[@..[@..[@..[@..[@..[@@@@@....[@......[@.... scratch multi-user swords and
....[@..[@..[@..[@..[@..[@..[@..[@@@@@..[@@@@@.. sorcery game written in C++.
....[@......[@..[@..[@..[@..[@....[@......[@.... This server is an ongoing
....[@......[@..[@@@@@..[@@@@@.................. development project.  All 
................................................ contributions are welcome. 
....Copyright(C).1995.Melvin.Smith.............. Enjoy. 
------------------------------------------------------------------------------
Melvin Smith (aka Fusion)         msmith@hom.net 
MUD++ development mailing list    mudpp@van.ml.org
------------------------------------------------------------------------------
pc.cc
*/

#include "config.h"
#include "string.h"
#include "llist.h"
#include "hash.h"
#include "server.h"
#include "room.h"
#include "bit.h"
#include "affect.h"
#include "screen.h"
#include "npc.h"
#include "pc.h"
#include "edit.h"
#include "area.h"
#include "social.h"

#include "global.h"

bool Nanny( PC *, const String & str="" );

int PC::total_count = 0;


PC::PC( Server *serv, Socket *s, char * )
:	Char(), server(serv), socket(s), snooper(0), snoopvictim(0),
	state(STATE_INIT),state_last(STATE_INIT),
	task(0),idletime(0),
	incommd(256),args(256),
	inptr(inbuf),intop(inbuf),inceiling(inbuf+MAX_INBUF_SIZE-1),
	outbuf(new char[2048]),outptr(outbuf),outsize(2048),
	pagebuf(0),pageptr(0),editor(0),
	messages(0), exp(0),
	energy(100), security(0), last_hint(0)
{
	total_count++;
	*inbuf = '\0';
	*outbuf = '\0';
	classnow=0;
	max_hp=max_mana=hp=mana=12;
	memset( levels, 0, sizeof( levels[0] ) * MAX_CLASS );
	memset( pc_bits, 0, sizeof( pc_bits[0] ) * MAX_PC_BIT_FIELDS );
	priv =0;
	skills = new unsigned char[ MAX_SKILL ];
	memset( skills, 0, MAX_SKILL );
	strength=intel=wis=con=dex=speed=13;
}


PC::~PC()
{
	total_count--;
	if( socket ) delete socket;
	if( outbuf ) delete [] outbuf;
	if( pagebuf ) delete [] pagebuf;
	if( skills ) delete [] skills;
}



const bitType priv_bit_list[] =
{
	{0,					0				},
	{"superuser",		N_SUPERUSER		},
	{"director",		N_DIRECTOR		},
	{"admin",			N_ADMIN			},
	{"operator",		N_OPERATOR		},
	{"masterbuilder",	N_MASTERBUILDER	},
	{"builder",			N_BUILDER			},
	{"quester",			N_QUESTER			},
	{"wizard",			N_WIZARD			},
	{0,					0				}
};


const command_type PC::cmdlist[27][32] =
{
// A
{
	{"areas",		&PC::do_areas,		false	},
	{"afk",			&PC::do_afk,			false	},
	{"autodir",		&PC::do_autodir,		false	},
	{"autodrink",	&PC::do_autodrink,	false	},
	{"autoeat",		&PC::do_autoeat,		false	},
	{"autoexits",	&PC::do_autoexits,	false	},
	{"autogold",	&PC::do_autogold,	false	},
	{"autoloot",	&PC::do_autoloot,	false	},
	{0,	0 }
},
// B
{
	{"break",		&PC::do_break,		false   },
	{"bug",			&PC::do_bug,			false   },
	{"buy",			&PC::do_buy,			true	},
	{0,	0 }
},
// C
{
	{"cast",		&PC::do_cast,	true	},
	{"chat",		&PC::do_chat,	true	},
	{"clear",		&PC::do_clear,	false	},
	{"close",		&PC::do_close,	true	},
	{"commands",	&PC::do_commands,	false	},
	{"collect",		&PC::do_collect,	true	},
	{0,	0 }
},
// D
{
	{"down",		&PC::do_down,	true	},
	{"drink",		&PC::do_quaff,	true	},
	{"drop",		&PC::do_drop,	true	},
	{0,	0 }
},
// E
{
	{"east",		&PC::do_east,	true	},
	{"eat",			&PC::do_eat,		true	},
	{"equipment",		&PC::do_equipment,	true	},
	{"exits",		&PC::do_exits,	true	},
	{0,	0 }
},
// F
{
	{0,	0 }
},
// G
{
	{"get",			&PC::do_get,	true	},
	{"give",		&PC::do_give,true	},
	{0,	0 }
},
// H
{
	{"help",		&PC::do_help,	false	},
	{"hint",		&PC::do_hint,	false	},
	{"hide",		&PC::do_hide,	true	},
	{0,	0 }
},
// I
{
	{"inventory",	&PC::do_inventory,	true	},
	{"idea",		&PC::do_idea,		false	},
	{0,	0 }
},
// J
{
	{0,	0 }
},
// K
{
	{"kill",		&PC::do_kill,	true	},
	{0,	0 }
},
// L
{
	{"look",		&PC::do_look,	false	},
	{"list",		&PC::do_list,	true	},
	{"levels",		&PC::do_levels,	false	},
	{0,	0 }
},
// M
{
	//{"mail",		&PC::do_mail,	false	},
	{0,	0 }
},
// N
{
	{"north",		&PC::do_north,	true	},
	{0,	0 }
},
// O
{
	{"open",		&PC::do_open,	true	},
	{0,	0 }
},
// P
{
	{"password",		&PC::do_password,	false	},
	{"put",			&PC::do_put,	true	},
	{"practice",		&PC::do_practice,	true	},
	{"prompt",		&PC::do_prompt,	false	},
	{"price",		&PC::do_price,	true	},
	{0,	0 }
},
// Q
{
	{"quaff",		&PC::do_quaff,	true	},
	{"quit",		&PC::do_quit,	false	},
	{0,	0 }
},
// R
{
	{"remove",		&PC::do_remove,	true	},
	{0,	0 }
},
// S
{
	{"south",		&PC::do_south,	true	},
	{"say",			&PC::do_say,	true	},
	{"sell",		&PC::do_sell,	true	},
	{"skills",		&PC::do_skills,	false	},
	{"socials",		&PC::do_socials,	false	},
	{"save",		&PC::do_save,	false	},
	{"score",		&PC::do_score,	false	},
	{"sneak",		&PC::do_sneak,	true	},
	{"study",		&PC::do_study,	true	},
	{0,	0 }
},
// T
{
	{"tell",		&PC::do_tell,	true	},
	{"time",		&PC::do_time,	false	},
	{"toggle",		&PC::do_toggle,	false	},
	{"title",		&PC::do_title,	false	},
	{"trade",		&PC::do_trade,	true	},
	{"typo",		&PC::do_typo, false	},
	{0,	0 }
},
// U
{
	{"up",			&PC::do_up,	true	},
	{"use",			&PC::do_use,	true	},
	{"uptime",		&PC::do_uptime,	false	},
	{0,	0 }
},
// V
{
	{"value",		&PC::do_price,	true	},
	{0,	0 }
},
// W
{
	{"west",		&PC::do_west,	true	},
	{"wear",		&PC::do_wear,	true	},
	{"weather",		&PC::do_weather,	false	},
	{"where",		&PC::do_where,	true	},
	{"who",			&PC::do_who,	false	},
	{"wield",		&PC::do_wield,	true	},
	{0,	0 }
},
// X
{
	{0,	0 }
},
// Y
{
	{0,	0 }
},
// Z
{
	{0,	0 }
},
// Other
{
	{"'",		&PC::do_say,	true	},
	{".",		&PC::do_chat,	true	},
	{":",		&PC::do_immtalk,	false	},
	{0,	0 }
}
};
	

const immcmd_type PC::immcmdlist[27][12] =
{
// A
{
	{"advance",		&PC::do_advance,		1,	DIRECTOR		},
	{"aedit",		&PC::do_aedit,		1,	MASTERBUILDER	},
	{0,	0,	0,	UNDEFINED	}
},
// B
{
	{0,	0,	0,  UNDEFINED	}
},
// C
{
	{"cat",			&PC::do_cat,			1,	SUPERUSER		},
	{"checkreport",	&PC::do_checkreport,	1,	BUILDER },
	{"cset",		&PC::do_cset,		1,	ADMIN			},
	{0,	0,	0,	UNDEFINED	}
},
// D
{
	{"debug",		&PC::do_debug,		1,	SUPERUSER		},
	{"dbsave",		&PC::do_dbsave,		1,	MASTERBUILDER	},
	{0,	0,	0,	UNDEFINED	}
},
// E
{
	{"echo",		&PC::do_echo,		1,	WIZARD			},
	{0,	0,	0,	UNDEFINED	}
},
// F
{
	{"force",		&PC::do_force,		1,	ADMIN			},
	{0,	0,	0,	UNDEFINED	}
},
// G
{
	{"grant",		&PC::do_grant,		1,	SUPERUSER		},
	{"goto",		&PC::do_goto,		1,	WIZARD			},
	{"gc",  		&PC::do_gc,  		1,	OPERATOR		},
	{0,	0,	0,	UNDEFINED	}
},
// H
{
	{"hedit",		&PC::do_hedit,		1,	BUILDER		},
	{0,	0,	0,	UNDEFINED	}
},
// I
{
	{"immtalk",		&PC::do_immtalk,		1,	WIZARD			},
	{"invis",		&PC::do_invis,		1,	WIZARD			},
	{"ident",		&PC::do_ident,		1,	ADMIN			},
	{0,	0,	0,  UNDEFINED	}
},
// J
{
	{0,	0,	0,  UNDEFINED	}
},
// K
{
	{0,	0,	0,  UNDEFINED	}
},
// L
{
	{0,	0,	0,  UNDEFINED	}
},
// M
{
	{"mfind",		&PC::do_mfind,		1,	BUILDER		},
	{"medit",		&PC::do_medit,		1,	BUILDER		},
	{"mload",		&PC::do_mload,		1,	OPERATOR    },
	{"mstat",		&PC::do_mstat,		1,	OPERATOR	},
	{"mwhere",		&PC::do_mwhere,		1,	OPERATOR	},
	{"memory",		&PC::do_memory,		1,	ADMIN	},
	{0,	0,	0,	UNDEFINED	}
},
// N
{
	{0,	0,	0,	UNDEFINED	}
},
// O
{
	{"ofind",		&PC::do_ofind,		1,	BUILDER		},
	{"oedit",		&PC::do_oedit,		1,	BUILDER		},
	{"oload",		&PC::do_oload,		1,	OPERATOR	},
	{"owhere",		&PC::do_owhere,		1,	OPERATOR	},
	{"ostat",		&PC::do_ostat,		1,	OPERATOR	},
	{0,	0,	0,	UNDEFINED	}
},
// P
{
	{"page",		&PC::do_page,		1,	WIZARD		},
	{"peace",		&PC::do_peace,		1,	WIZARD		},
	{"privileges",	&PC::do_privileges,	1,	WIZARD		},
	{"purge",		&PC::do_purge,		1,	BUILDER		},
	{"prodisplay",	&PC::do_prodisplay,	1,	ADMIN		},
	{"proset",  	&PC::do_proset,  	1,	ADMIN		},
	{"prosave", 	&PC::do_prosave, 	1,	ADMIN		},
	{"proload", 	&PC::do_proload, 	1,	ADMIN		},
	{"probackup",	&PC::do_probackup,	1,	ADMIN		},
	{"prorestore",	&PC::do_prorestore,	1,	ADMIN		},
	{0,	0,	0,	UNDEFINED	}
},
// Q
{
	{0,	0,	0,	UNDEFINED	}
},
// R
{
	{"reboot",		&PC::do_reboot,		1,	DIRECTOR	},
	{"revoke",		&PC::do_revoke,		1,	SUPERUSER	},
	{"rfind",		&PC::do_rfind,		1,	BUILDER		},
	{"repops",		&PC::do_repops,		1,	BUILDER		},
	{"redit",		&PC::do_redit,		1,	BUILDER		},
	{"rstat",		&PC::do_rstat,		1,	OPERATOR	},
	{0,	0,	0,	UNDEFINED	}
},
// S
{
	{"shell",		&PC::do_shell,		1,	DIRECTOR	},
	{"show",		&PC::do_show,		1,	ADMIN		},
	{"shutdown",	&PC::do_shutdown,	1,	DIRECTOR	},
	{"slay",		&PC::do_slay,		1,	ADMIN		},
	{"snoop",		&PC::do_snoop,		1,	ADMIN		},
	{"smite",		&PC::do_smite,		1,	WIZARD		},
	{0,	0,	0,	UNDEFINED	}
},
// T
{
	{"transfer",	&PC::do_transfer,	1,	ADMIN		},
	{0,	0,	0,	UNDEFINED	}
},
// U
{
	{"users",		&PC::do_users,		1,	OPERATOR	},
	{0,	0,	0,	UNDEFINED	}
},
// V
{
    {"vm",		&PC::do_vm,		1,	WIZARD		},
	{0,	0,	0,	UNDEFINED	}
},
// W
{
	{"wizhelp",		&PC::do_wizhelp,		1,	WIZARD		},
	{0,	0,	0,	UNDEFINED	}
},
// X
{
	{0,	0,	0,	UNDEFINED	}
},
// Y
{
	{0,	0,	0,	UNDEFINED	}
},
// Z
{
	{0,	0,	0,	UNDEFINED	}
},
// Other
{
	{0,	0,	0,	UNDEFINED	}
}
};


void PC::fromWorld()
{
	pcs.remove( this );
}

void PC::toWorld()
{
	pcs.add( this );
}

int PC::lastState() const
{
	return state_last;
}
 
bool PC::isPlaying() const
{
	if( editor )
		return false;
	else if( state == STATE_PLAYING )
		return true;
	return false;
}
 

void PC::setWindow( int t, int b )
{
	char buf[ 256 ];
	sprintf( buf, WINDOW( t, b ) );
	out( buf );
}


void PC::out( const char *str )
{ 
	char *newbuf;
	int addsize;
	if( socket ) // if PC lost link skip output
	{
		addsize = strlen( str );
		if( count_lines( str ) > 23 )
		{
			// Put it on the pager
			// Clobber the pagebuf if it exists ( it IS possible ) 
			if( pagebuf )
				delete [] pagebuf;
			pagebuf = new char[ addsize+1 ];
			strcpy( pagebuf, str );
			pageptr = pagelast = pagebuf;
			return;
		}
		else
		{
			// OK - just concatenate this chunk onto the outbuf which
			// will get serviced every pulse.
			long totsize = addsize + outptr - outbuf + 2;
   	 
			// See if it exceeds outbuf size, if so reallocate,
			// else strcat and return
			if( totsize < outsize )
			{
				strcpy( outptr, str );
				outptr += addsize;
				return;
			}

			while( outsize <= totsize )
				outsize <<= 1;

			newbuf = new char[ outsize ];

			// Copy the old outbuf then free it and replace
			strcpy( newbuf, outbuf );
			delete [] outbuf;
			outbuf = newbuf;
			strcpy( (outbuf + totsize - addsize), str );
			outptr = outbuf + totsize;
		}	
	}
}


void PC::putPrompt()
{
	Socket * snoopSock;
	char buf[BUF];
	const char *token = prompt.chars();
	char *ptr = buf;
	*ptr++ = '\n'; *ptr++ = '\r';

	if( !snooper )
		snoopSock = 0;
	else
		snoopSock = snooper->getSocket();
 
	if( pagebuf )
	{
		socket->write( INVERSE );
		socket->write( "[ Type (q)uit (r)efresh (b)ack or RETURN for more ]" );
		socket->write( NTEXT );
		if( snoopSock )
		{
			snoopSock->write( INVERSE );
			snoopSock->write( "[ Type (q)uit (r)efresh (b)ack or RETURN for more ]" );
			snoopSock->write( NTEXT );
		}
		return;
	}
	else
	if( editor )
	{
		socket->write( editor->getPrompt() );
		if( snoopSock )
			snoopSock->write( editor->getPrompt() );	
		return;
 	}
	else
	if( state == STATE_PLAYING )
	{
		while( *token )
		{  
			if( *token == '%' )
			{
				switch( *(++token) )
				{
					case '\0': break;
					case 'B':
						strcpy( ptr, BBLUE );
						while( *ptr ) ptr++;
						token++;
						continue;
					case 'C':
						strcpy( ptr, BCYAN );
						while( *ptr ) ptr++;
						token++;
						continue;
					case 'G':
						strcpy( ptr, BGREEN );
						while( *ptr ) ptr++;
						token++;
						continue;
					case 'P':
						strcpy( ptr, BPURPLE );
						while( *ptr ) ptr++;
						token++;
						continue;
					case 'R':
						strcpy( ptr, BRED );
						while( *ptr ) ptr++;
						token++;
						continue;
					case 'Y':
						strcpy( ptr, BYELLOW );
						while( *ptr ) ptr++;
						token++;
						continue;
					case 'h':
						strcpy( ptr, itoa( hp ) );
						while( *ptr ) ptr++;
						token++;
						continue; 
					case 'H':
						strcpy( ptr, itoa( max_hp ) );
						while( *ptr ) ptr++;
						token++;
						continue; 
					case 'm':
						strcpy( ptr, itoa( mana ) );
						while( *ptr ) ptr++;
						token++;
						continue; 
					case 'M':
						strcpy( ptr, itoa( max_mana ) );
						while( *ptr ) ptr++;
						token++;
						continue; 
					case 'n':
						*(ptr++) = '\n';
						*(ptr++) = '\r'; 
						token++;
						continue;
					case 'd':
						if ( isBusy() )
						{
							strcpy( ptr, doing->getTag().chars() );
							while ( *ptr ) ptr++;
						}
						token++;
						continue;
					default:
						*(ptr++) = *(token++);
						continue; 
				}
			}
			else
			{
				*(ptr++) = *(token++);
				continue;
			}
		}

		*ptr='\0';
		sprintf( buf+strlen( buf ), "%s", NTEXT );
		if( messages )
			strcat( buf, "[EMAIL]" );
	}
	else if( state == STATE_EMAIL )
		sprintf( buf, "\n\rEMAIL >" );
	else if( state == STATE_BOOT )
		sprintf( buf, "\n\r[ ** Seeya!! ** ]\n\r" );
	else
		return;

	// short circuit the buffering - go straight to the socket 
	socket->write( buf );
	if( snoopSock )
		snoopSock->write( buf );
}


bool PC::outBuf() const
{
	if( !*outbuf )
		return false;
	return true;
}


bool PC::inBuf() const
{
	if( !*inptr )
		return false;
	return true;
}


void PC::readInput()
{
	int err;

	// If pending data, return.
	if( intop < inceiling )
	{
		err = socket->read( intop, inceiling - intop );
		if( err <= 0 )
			return;
		else
		{
			intop += err;
			*intop = '\0';
		}
	}
	else
	{
		// Overflow

	}
}


void PC::flush()
{
	// Ack, we really don't need to blow the whole chunk out
	// at once. I'll clean this up later.
	if( socket->write( outbuf ) < 0 )
	{
		Cout << "PC::flush: write error\n";
		state = STATE_BOOT;
	}

	if( snooper && snooper->getSocket() )
		snooper->getSocket()->write( outbuf );

	*outbuf = '\0';
	outptr = outbuf;
}


char *PC::pagePending() const
{
	return pagebuf;
}


void PC::page( const String & arg )
{
	if( !pagebuf )
		return;

	int lines = 0;
	char *ptr = pageptr;
 
	switch( toupper(arg[0]) )
	{
		default :  break;
		case 'Q':
			delete [] pagebuf;
			pagebuf = pageptr = pagelast = 0;
//			putPrompt();
			return;
		case 'R':
			ptr = pageptr = pagelast; 
			break;
		case 'B':
			ptr = pagelast;

			while( ptr != pagebuf && (lines < 23) )
				if( *(ptr--) == '\n' )
					lines++;

			while( ptr != pagebuf && *(ptr-1) != '\n' && *(ptr-1) != '\r' )
				ptr--;

			pageptr = ptr;
			break;   
	}

	lines = 0;
	pagelast = ptr;

	while( *ptr && (lines < 23) )
	{
		if( *(ptr++) == '\n' )
			lines++;
	}

	if( *ptr )
	{
		if( *(ptr+1) == '\r' )
			ptr++;

		if( *(ptr+1) == '\0' )
			ptr++;
	}

	socket->write( pageptr, (ptr - pageptr) );
	if( snooper && snooper->getSocket() )
		snooper->getSocket()->write( pageptr, (ptr - pageptr) );

	if( *ptr )
	{
	}
	else if( state > STATE_INIT && state < STATE_PLAYING )
		Nanny( this, "" );

	pageptr = ptr;

	if( !*pageptr )
	{
		delete [] pagebuf;
		pagebuf = pageptr = 0;
	}

	return;
}


char *PC::className() const
{
	static char *buf_mort[ MAX_CLASS ] =
	{
		"WIZ",
		"HER", "MAG", "CLE", "THI", "WAR", "MNK", "PSI"
	};

	return buf_mort[ classnow ]; 
}


int PC::readFrom( StaticInput &in )
{
	int numfields;
	char buf[ BUF ];
	SkillType *skill;
	int learned;

	if( !in )
		return 0;

	while( in && *in.getword( buf ) != '#' )
	{
		//Cout << "Loading stat [" << buf << "]\n";
		switch( *buf )
		{
			default : break;

			case 'C':
				if( !strcmp( "CharBits", buf ) )
					in.getbitfield( char_bits );
				else if( !strcmp( "Class", buf ) )
					classnow = in.getnum();
			break;

			case 'E':
				if( !strcmp( "EqBits", buf ) )
				{
					numfields = in.getnum();
					for( int i = 0; i < numfields; i++ )
						eq_bits[i] = in.getlong();
				}
				else if( !strcmp( "Exp", buf ) )
				{
					exp = in.getnum();
				}
				break;

			case 'G':
				if( !strcmp( "GSC", buf ) )
				{
					gold = in.getlong();
					silver = in.getlong();
					copper = in.getlong();
				}
				break;

			case 'H':
				if( !strcmp( "HgrThirst", buf ) )
				{
					hunger = in.getnum();
					thirst = in.getnum();
				}
				break;

			case 'I':
				break;

			case 'L':
				if( !strcmp( "LangBits", buf ) )
					in.getbitfield( language_bits );
				else if( !strcmp( "Levels", buf ) )
				{
					levels[0] = in.getnum();
					levels[1] = in.getnum();
					levels[2] = in.getnum();
					levels[3] = in.getnum();
					levels[4] = in.getnum();
					levels[5] = in.getnum();
					break;
				}
				else if( !strcmp( "Long", buf ) )
				{
					in.getstring( buf );
					setLong( buf );
					break;
				}
				else if ( !strcmp( "LastHint", buf ) )
				{
					last_hint = in.getnum();
					break;
				}
				break;

			case 'M':
				if( !strcmp( "Messages", buf ) )
				{  
					messages = in.getnum();
				}
				break;

			case 'N':
				if( !strcmp( "Name", buf ) )
				{
					in.getword( buf );
					setName( buf );
				}
				break;

			case 'P':
				if( !strcmp( "Password", buf ) )
				{
					in.getstring( buf );
					setPasswd( buf );
				}
				else if( !strcmp( "PCBits", buf ) )
				{
					numfields = in.getnum();
					for( int i = 0; i < numfields; i++ )
						pc_bits[i] = in.getlong();
				}
				else if( !strcmp( "PrivBits", buf ) )
				{
					numfields = in.getnum(); // compatibilty
					priv = in.getnum();
				}
				else if( !strcmp( "Prompt", buf ) )
				{
					in.getstring( buf );
					setPrompt( buf );
				}
				break;

			case 'R':
				if( !strcmp( "Room", buf ) )
				{
					in.getstring( buf );
					room_index = buf;
				}
				else if( !strcmp( "Race", buf ) )
				{
					race = in.getnum();
				}
				break;

			case 'S':
				if( !strcmp( "Sk", buf ) )
				{
					in.getstring( buf );
					learned = in.getnum();
					if( ( skill = lookupSkill( buf ) ) )
						setSkill( skill->getIndex(), learned );
					else
						Cout << getShort() << " has invalid skill " << buf << endl;
				}
				else if( !strcmp( "Short", buf ) )
				{
					in.getstring( buf );
					setShort( buf );
				}
				else if( !strcmp( "Sex", buf ) )
					setSex( in.getnum() );
				break;

			case 'T':
				if( !strcmp( "Title", buf ) )
				{
					in.getstring( buf );
					setTitle( buf );
					break;
				}
				else if( !strcmp( "TextEd", buf ) )
				{
					in.getstring( buf );
					setTextEditor( buf );
					break;
				}
				break;
		} 
	}

	Object *obj;
	NPC *npc;

	// ITEMS, NPCS, etc.
	while( in && *in.getword( buf ) != '#' )
	{
		switch( toupper( *buf ) )
		{
			case 'A':
				{
					Affect *aff = new Affect;
					if( ( aff->readFrom( in ) != -1 ) )
						addAffect( aff );
					else
						aff->fordelete();
				}
				break;

			case 'O':
				{
					obj = Object::createObject(lookupObjType(in.getword(buf)));
					if ( obj == NULL )
						in.error("Wrong obj type.\n\r");
					// later we will add recover, now crash
					obj->readFrom( in );
					obj->toWorld();
					addObjInv( obj );
				}
				break;
				
			case 'N':
				{
					npc = new NPC;
					npc->readFrom( in );
					out( "Loading NPC " );
					out( npc->getShort() );
					out( "\n\r" );
					npc->fordelete();
				}
				break;
		}

		continue;
	}


	return 1;
}


void PC::checkMail()
{
	char buf[BUF];

	sprintf( buf, "%s/%s", EMAIL_DIR, name.chars() );
	InputFile in( buf );
	if( in ) 
	{
		messages = 1;
		out( "\n\rYou have email.\n\r" );
	}
	else
		messages = 0;
}


int PC::writeTo( Output &outf ) const
{
	int i;

	if( ! outf )
		return 0;

	outf << "Name " << getName() << endl; 
	outf << "Short " << getShort() << "~\n";
	outf << "Long " << getLong() << "~\n";
	outf << "Password " << getPasswd() << "~\n";
	outf << "TextEd " << getTextEditor() << "~\n";
	outf << "Title " << getTitle() << "~\n";
	outf << "Messages " << messages  << endl;
	outf << "LastHint " << last_hint << endl;
	outf << "Race " << race << endl;
	outf << "HgrThirst " << hunger << ' ' << thirst << endl;
	outf << "Class " << classnow  << endl;
	outf << "Sex " << sex << endl;
	outf << "Exp " << exp << endl;
	outf << "Levels " << levels[0] << " " << levels[1] << " "
			<< levels[2] << " " << levels[3] << " "
			<< levels[4] << " " << levels[5] << endl;
	outf << "Stats " << strength << " " << intel << " " << wis   << " "
			<< con << " " << dex   << " " << speed << endl;
	outf << "MaxHpMana " << max_hp << " " << max_mana << endl;
	outf << "HpMana " << hp << " " << mana << endl;
	outf << "GSC " << gold << ' ' << silver << ' ' << copper << endl;
	outf << "Energy " << energy << endl;
	outf << "Prompt " << prompt << "~\n"; 
	outf << "Room " << room_index.asString() << "~\n";
	outf << "CharBits ";
	outf.putbitfield( char_bits, MAX_CHAR_BIT_FIELDS );
	outf << endl;
	outf << "LangBits ";
	outf.putbitfield( language_bits, MAX_LANG_BIT_FIELDS );
	outf << endl;
	outf << "PCBits ";
	outf.putbitfield( pc_bits, MAX_PC_BIT_FIELDS );
	outf << endl;
	outf << "PrivBits 1 " << priv << endl;
	outf << "EqBits ";
	outf.putbitfield( eq_bits, MAX_EQ_BIT_FIELDS );
	outf << endl;
	outf << "#\n";

	SkillType *skill;
//	for( i = 1; i < top_skill; i++ )
	for( i = 1; i < MAX_SKILL; i++ )
	{
		if( skills[i]
			&& ( skill = lookupSkill( i ) ) )
		{
			outf << "Sk " << skill->getName() << '~' << (int)skills[i] << endl;
		} 
	}	

	Affect *aff;
	for_each( aff_list, aff )
	{
		outf << 'A';
		aff->writeTo( outf );
	}

	Object *obj;
	for_each( inv, obj )
	{
		outf << "O " << obj->typeName() << endl;
		obj->writeTo( outf );
	}

	outf << "#";
	return 1;
}


void PC::look( Char *ch )
{
	String str ( BUF );
	Object *obj;
	int i;

	if ( ch->TgLookedAt(this) )
		return;

	str << ch->getLong() << "\n\r";

	// Add check for thief peek skill or imm.
	ch->inv.reset();
	i = 0;
	if( ch->inv.peek() )
	{
		str << "Wearing:\n\r";

		while( ( obj = ch->inv.peek() ) )
		{
			ch->inv.next();
			if( !obj->wearPos() )
				continue;
			i++;
			str.sprintfAdd( "%25s %-s\n\r", getWearPosName( obj->wearPos() ),
										obj->getShort().chars() );
		}

		if( ch->isPC() && i == 0 )
			str << ch->getShort() << " is naked.  EEK!\n\r";

		str << "\n\rYou peek at the inventory:\n\r";
		ch->inv.reset();
		while( ( obj = ch->inv.peek() ) )
		{
			ch->inv.next();
			if( obj->wearPos() )
				continue;
			str << obj->getShort() << "\n\r";
		}
	}

	out( str );
}

void PC::look( Object * obj)
{
	if ( obj->TgLookedAt(this) )
		return;
	out ("You look at ");
	out ( obj->getShort() );
	out (".\n\r" );
}

void PC::advance( int increment )
{
	String str;

	if( !increment )
		return;

	if( increment < 0 )
	{
		if( levels[classnow] + increment < 1 )
			increment = ( -1 - levels[classnow] ); // min level 1

		str	<< "*** You lose " << increment << " level"
			<< ( increment < -1 ? "s !!! ***\n\r" : "!!! ***\n\r" );

		out( str );

		// Add stat mods here.
		levels[classnow] -= increment;
		level -= increment;
	}
	else if( increment > 0 )
	{
		str	<< "*** You raise " << increment << " level"
			<< ( increment > 1 ? "s !!! ***\n\r" : "!!! ***\n\r" );

		out( str );
		levels[classnow] += increment;
		level += increment;

	}

	exp = 0;
}


int open_pty_master( char * );
int open_pty_slave( int, const char * );
int route_io( int, int );

int PC::startShell( const String & arg1, const String & arg2 )
{
	PC * pc;
	Descriptor master_fd;
	Descriptor slave_fd;
	int temp_fds[2];
	char pty[12];
	int pid;

	if( !getSocket() )
		return -1;

#if !defined(WIN32) && !defined(__CYGWIN32__)
	if( pipe( temp_fds ) < 0 )
	{
		perror( "pipe" );
		return -1;
	}

	setPipeIn( temp_fds[0] );
	getPipeIn()->nonBlock();
	setPipeOut( temp_fds[1] );
	// Fork a server off to take care of shell and IO
	if( ( pid = fork() ) > 0 )
	{
		closePipeOut();
		getPipeIn()->nonBlock();
		return 0;
	}
	else if( pid < 0 )
	{
		perror( "fork" );
		getSocket()->write( "system error forking shell.\n\r" );
		closePipeIn();
		closePipeOut();
		return -1;
	}

	::server.close();
	closePipeIn();
	for_each( pcs, pc )
	{
		if( pc == this )
			continue;
		if( pc->getSocket() )
			pc->getSocket()->close();
	}

	if( (int)( master_fd = open_pty_master( pty ) ) < 0 )
	{
		getSocket()->write( "Failed to open pty master for shell.\n\r" );
		return -1;
	}

	// Now fork/exec shell process
	pid = fork();

	// Child, get slave pty then close master_fd
	if( pid == 0 )
	{
		// Must not have a sig-handler for child because grantpt()
		// might fail so remove it.
		struct sigaction sa;
		sa.sa_flags = SA_RESETHAND;
		sa.sa_handler = 0;
		if( sigaction( SIGCHLD, &sa, 0 ) < 0 )
		{
			perror( "sigaction:can't reset SIGCHLD" );
//			exit(0);
		}

		if( (int)( slave_fd = open_pty_slave( (int)master_fd, pty ) ) < 0 )
		{
			perror( "open_pty_slave:" );
			exit(0);	
		}

#ifdef DEBUG
		char buf[64];
		sprintf( buf, "child [%d] got slave pty\n\r", getpid() );
		getSocket()->write( buf );
#endif
		master_fd.close();
		::close(0);
		::close(1);
		::close(2);
		if( dup((int)slave_fd) != 0 || dup((int)slave_fd) != 1
				|| dup((int)slave_fd) != 2 )
		{
			getSocket()->write( "dup failed\n\r" );
			exit(0);
		}

		slave_fd.close();
		if( arg1 )
			execlp( arg1.chars(), arg1.chars(), arg2.chars(), (char*)0 );
		else
			execl( "/bin/sh", "sh", (char*)0 );
		perror( "execl" );
		exit(0);
	}
	else if( pid < 0 )
	{
		getSocket()->write( "fork slave pty failed" );
		return -1;
	}

	// Kick pty into non-blocking mode. Careful here because there
	// is a race condition between this process and the slave side.
	// I haven't studied it close enough but if the pty is blocking
	// then most of the time route_io blocks permanently on first
	// read from the pty.
	master_fd.nonBlock();
	getSocket()->willEcho();
	getSocket()->willSuppressGA();

	// Run mini-telnetd between pty and network
	route_io( getSocket()->getDescriptor(), (int)master_fd );

	// Kick net socket back to normal local-echo, linemode
	getSocket()->wontEcho();
	getSocket()->wontSuppressGA();

	// Done, now we can close pipe so main process can know
	// which pc to put back into game list
 	closePipeOut();
	exit(0);
#endif /* WIN32 && CYGWIN32 */
	getSocket()->write( "Sorry...not supported in v0.16 jwo@netcom.com WIN32 port.\n\r" );
	return 0;
}


// Now rewrite using the new memory mapped file class
void PC::view( const char *filename )
{
	if( !*filename )
		return;

	char buf[BUF*8];
	char tbuf[ 1024 ];
	int fd;

	fd = ::open( filename , O_RDONLY );

	if( fd < 0 )
		return;

	int bytes = 0;
	int tot = 0;
	int i;
	char ch;

	while( (bytes = read( fd, tbuf, 1024 )) > 0 )
	{
		i = 0;
		while( i < bytes )
		{
			ch = tbuf[i++];
			if( ch == '\n' )
				buf[tot++] = '\r';
			if( ch != '\r' )
				buf[tot++] = ch;
		}

		if( BUF*8 - tot <= 1024 )
		{
			sprintf( buf+tot, "\n[** BUF EXCEEDED - TRUNCATED **]\n" );
			tot = strlen( buf );
			break;
		}
	}

	*(buf+tot) = '\0';
	out( buf );
	::close( fd );
}


bool PC::save()
{
	String str;
	Index rIndex;

	if( inRoom() )
	{
		rIndex.setScope( inRoom()->getArea()->getKey() );
		rIndex.setKey( inRoom()->getKey() );
		setWasInRoom( rIndex );
	}

	if( name[0] )
		str << PLAYER_DIR << '/' << name[0] << '/' << name.asProper();
	else
	{
		Cout << "BUG: Player with NULL name calling do_quit.\n";
		return false;
	}

	OutputFile of( str.chars() );
	if( !of )
#ifdef DEBUG
		abort();
#else
		return false;
#endif
	writeTo( of );
	return true;
}


void PC::do_break( const String & )
{
	if ( isBusy() )
	{
		doing->interrupt();
		out( "Interrupted!\n\r" );
		return;
	}

	out( "You're not doing anything at the moment.\n\r" );

	return;
}


void PC::do_tell( const String & arg )
{
	PC *ch;
	String str;
	String victimName; 
	String messg;

	arg.startArgs();
	victimName	= arg.getArg(); 
	messg		= arg.getArgRest();

	victimName.toProper(); 
	ch = getPCWorld( victimName );
	if( !ch )
	{
		out("Couldn't find anyone by that name.\n\r");
		return; 
	}

	if( ch == this )
	{
		out( "You mumble something to yourself.\n\r" );
		return;
	}

	str << "\n\n\r" << name << " tells you '" << messg << "'\n\r";
	ch->out( str );

	str.clr();
	str << "\n\rYou tell " << victimName << " '" << messg << "'\n\r";
	if( ch->isAFK() )
		str << victimName << " is AFK and may not see your tell.\n\r"
			<< "Message: " << ch->getAFKMessage() << "\n\r";

	out( str );

	ch->TgTold( this, messg.chars() );
}


void PC::say( const String & arg )
{
	String str;
	if ( inRoom()->TgSaidIn( this, arg.chars() ) )
		return;

	str << BCYAN << "\n\r" << name << " says '" << arg << "'\n\r" << NTEXT;
	in_room->outAllCharExcept( str, this, 0 );

	str.clr();
	str << BCYAN << "You say '" << arg << "'\n\r" << NTEXT;
	out( str );
}


void PC::do_say( const String & arg )
{
	String str;
	if( !(bool)arg )
	{
		out( "Say what?\n\r" );
		return;
	}

	say( arg );
}


void PC::do_chat( const String & arg )
{
	String str;

	if( !(bool)arg )
	{
		out( "What do you want to chat?\n\r" );
		return;
	}

	str << BPURPLE << "\n\r" << name << " chats '" << arg << "'\n\r" << NTEXT;
	outAllCharExcept( str, this, 0 );
  
	str.clr();
	str << BPURPLE << "You chat '" << arg << "'\n\r" << NTEXT;
	out( str );
}


void PC::do_quit( const String & )
{
	String str;

	if( fighting )
	{
		out( "No way! Not in the middle of combat.\n\r" );
		return;
	}

	state = STATE_BOOT;
	if( !save() )
		out( "There was an error saving your character. Report.\n\r" );

	if( snoopvictim )
	{
		out( "Snooping stopped.\n\r" );
		snoopvictim->setSnooper( 0 );
	}

	out( "Goodbye, come back soon.\n\r" );
	if( snooper )
		snooper->setSnoopVictim( 0 );

	str.clr();
	str << name << " has left the game.";
	wizLog( str, this );
}


void PC::command( String & arg )
{
	int ihash;
	const Social * social;
	Char *socialTarget;

	if( arg[0] == '\n' || arg[0] == '\r' || ! (bool) arg )
		return;

	arg.startArgs();
	incommd = arg.getArg();
	incommd[0] = tolower( incommd[0] );

	if( incommd[0] == '!' )
	{
		if( !(bool)inlast )
			return;

		// Check for command substitution like 'look Fusion ; ! Klepto'
		// Looks at Fusion then looks at Klepto
		incommd = inlast;
		if( arg.getArgRest() )
			args = arg.getArgRest();
	}
	else
	{
		inlast = arg;
		args = arg.getArgRest();
	}

	ihash = (int) ( incommd[0] - 'a' );
	if( ihash < 0 || ihash > 25 )
		ihash = 26;

	int i;
	for( i = 0; cmdlist[ihash][i].commd; i++ )
	{ 
		if( incommd[0] == *cmdlist[ihash][i].commd )
		{
			if( incommd.isAbbrev( cmdlist[ihash][i].commd ) )
			{
				if ( cmdlist[ihash][i].is_action )
				{
					if ( isBusy() )
					{
						out( "You're already doing something!\n\r" );
						return;
					}
				}

				(this->*(cmdlist[ihash][i].fun))( args );
				return;
			}
		}		
	}

	for( i = 0; immcmdlist[ihash][i].commd; i++ )
	{ 
		if( incommd[0] == *immcmdlist[ihash][i].commd )
		{
			if( incommd.isAbbrev( immcmdlist[ihash][i].commd ) )
			{
				// possible action check

				if( authorized( immcmdlist[ihash][i].bit ) )
					(this->*(immcmdlist[ihash][i].fun))( args );
				else
					out( "You don't have privileges for this command.\r\n" );
 				return;
			}
		}		
	}

	social = lookupSocial( incommd );
	if( social )
	{
		if( (bool) args )
		{
			socialTarget = in_room->getChar( args );
			if( !socialTarget )
			{
				args[0] = toupper( args[0] );
				out( args );
				out( " isn't here.\n\r" );
				return;
			}
		}
		else
			socialTarget = 0;

		if( !socialTarget )
		{
			interp( social->selftargnone, this, 0, 0, 0 );
			inRoom()->interp( social->roomtargnone, this, 0, 0, 0 );
		}
		else if( this != socialTarget )
		{
			interp( social->selftargother, this, socialTarget, 0, 0 );
			socialTarget->interp( social->targother, this, 0, 0, 0 );
			inRoom()->interp( social->roomtargother, this, socialTarget, 0, 0 );
			socialTarget->TgSocialed( this, social );
		}
		else
		{
			interp( social->selftargself, this, 0, 0, 0 );
			inRoom()->interp( social->roomtargself, this, 0, 0, 0 );
		}

		inRoom()->TgSocialedIn( this, socialTarget, social );

		return;
	}

	
	if ( !inRoom()->TgStrangeCmd(this, incommd, args ) )
		out( "Huh?\n\r" );
}


bool PC::getNextCommand()
{
	int i;
	char *save = inptr; // save in case we don't find a new line

	if( !*inptr )
		return false;

	// Skip CRLF and telnet negotiations (response from ECHO ON/OFF for now)
	if( *inptr == '\n' || *inptr == '\r' )
	{
		while( *inptr && isspace( *inptr ) )
			inptr++;

		if( !*inptr )
		{
			inptr = intop = inbuf;
			*inptr = '\0';
		}

		incommd = "\n";		
		return true;
	}
	// This is braindead, I'll fix later. --Melvin
	else if( *inptr == (char)IAC )
	{
		if( *(inptr+1) == (char)IP )
		{
			state = STATE_BOOT;
			return false;
		}

		while( *inptr && *inptr != '\n' )
			inptr++;

		if( !*inptr )
		{
			inptr = intop = inbuf;
			*inptr = '\0';
		}

		return false;
	}

	i = 0;
	while( *inptr && !isspace( *inptr ) )
	{
		incommd[i] = *inptr;
		i++; inptr++;
	}

	// See if there is a complete line
	if( !*inptr )
	{
		inptr = save;
		return false;
	}

	// if PC is in editor, dont break into seperate arg
	if( !getEditor() )
	{
		incommd[i] = '\0';
		i = 0;
		if( *inptr != '\n' && *inptr != '\r' )
		{
			// Skip the seperating ' ' which will always be there if there are args
			inptr++;

			while( *inptr && *inptr != '\n' && *inptr != '\r' )
			{
				args[i] = *inptr;
				i++; inptr++;
			}
		}
		args[i] = '\0';
	}
	else
	{
		// Preserve whitespace, etc. for editor
		while( *inptr && *inptr != '\n' && *inptr != '\r' )
		{
			incommd[i] = *inptr;
			i++; inptr++;
		}			
		incommd[i] = '\0';
	}

	// See if there is a complete line
	if( !*inptr )
	{
		inptr = save;
		return false;
	}

	while( *inptr && isspace( *inptr ) )
		inptr++;

	// If end of data reset input buffer queue and terminate it.
	if( !*inptr )
	{
		inptr = intop = inbuf;
		*inptr = '\0';
	}

	return true;
}


void PC::command()
{
	const Social * social;
	Char * socialTarget;

	// Check for \n only since I know what I put in incommd
	if( ! (bool) incommd || incommd[0] == '\n' )
		return;

	if( incommd[0] == '!' )
	{
		if( ! (bool) inlast )
			return;

		incommd = inlast;
	}
	else
	{
		inlast = incommd;
	}

	int ihash = (int) ( incommd[0] - 'a' );
	if( ihash < 0 || ihash > 25 )
		ihash = 26;

	int i;

	for( i = 0; cmdlist[ihash][i].commd; i++ )
	{ 
		if( incommd[0] == *cmdlist[ihash][i].commd )
		{
			if( incommd.isAbbrev( cmdlist[ihash][i].commd ) )
			{

				if ( cmdlist[ihash][i].is_action )
				{
					if ( isBusy() )
					{
						out( "You're already doing something!\n\r" );
						return;
					}
				}

				(this->*(cmdlist[ihash][i].fun))( args );
				return;
			}
		}		
	}

	for( i = 0; immcmdlist[ihash][i].commd; i++ )
	{ 
		if( incommd[0] == *immcmdlist[ihash][i].commd )
		{
			if( incommd.isAbbrev( immcmdlist[ihash][i].commd ) )
			{
				// possible action check

				if( authorized( immcmdlist[ihash][i].bit ) )
					(this->*(immcmdlist[ihash][i].fun))( args );
				return;
			}
		}		
	}

	social = lookupSocial( incommd );
	if( social )
	{
		if( (bool) args )
		{
			socialTarget = in_room->getChar( args );
			if( !socialTarget )
			{
				args[0] = toupper( args[0] );
				out( args );
				out( " isn't here.\n\r" );
				return;
			}
		}
		else
			socialTarget = 0;

		if( !socialTarget )
		{
			interp( social->selftargnone, this, 0, 0, 0 );
			inRoom()->interp( social->roomtargnone, this, 0, 0, 0 );
		}
		else if( this != socialTarget )
		{
			interp( social->selftargother, this, socialTarget, 0, 0 );
			socialTarget->interp( social->targother, this, 0, 0, 0 );
			inRoom()->interp( social->roomtargother, this, socialTarget, 0, 0 );
			socialTarget->TgSocialed( this, social );

		}
		else
		{
			interp( social->selftargself, this, 0, 0, 0 );
			inRoom()->interp( social->roomtargself, this, 0, 0, 0 );
		}

		inRoom()->TgSocialedIn( this, socialTarget, social );
		return;
	}

	if ( !inRoom()->TgStrangeCmd(this, incommd, args ) )
		out( "Huh?\n\r" );
}


void PC::setPrivBit( int bit )
{
	priv |= BIT(bit);
}


void PC::rmPrivBit( int bit )
{
	priv &= ~BIT(bit);
}


int PC::authorized( int mask ) const
{
	return (priv & mask);

/*	if( IS_SET( priv, bit ) )
		return 1;

	return 0;
*/
}


bool PC::pulse()
{
	if( !isPlaying() )
		return false;

	Object * obj;
	int hpgain = max_hp / 10;
	int managain = max_mana / 8;

	if( hunger-- < 0 )
	{
		if( config( PC_AUTOEAT ) )
			if( ( obj = getObjInv( ITEM_FOOD ) ) )
				eat( obj );
		if( hunger < -10 )
			out( "You are starving for food!\n\r" );
		else if( hunger < 0 )
			out( "You are hungry.\n\r" );
	}

	if( thirst-- < 0 )
	{
		if( config( PC_AUTODRINK ) )
			if( ( obj = getObjInv( ITEM_LIQUID_CONTAINER ) ) )
				quaff( obj );
		if( thirst < -10 )
			out( "You are dying of thirst!\n\r" );
		else if( thirst < 0 )
			out( "Your throat is a bit parched.\n\r" );
	}
	
	if( !affectedBy( AFF_POISON ) )
	{
		hp += hpgain;
		mana += managain;
	}
	else
	{
		hp -= hpgain;
		mana -= managain;
	}

	// Update conditions
	if( hp > max_hp )
		hp = max_hp;
	else if( hp <= 0 )
	{
		die( 0 );
		return true;
	}
	
	if( mana > max_mana )
		mana = max_mana;
	else if( mana < 0 )
		mana = 0;

	// Update affects
	Affect *paf;

	aff_list.reset();
	while( ( paf = aff_list.peek() ) )
	{
		if( paf->pulse() <= 0 )
		{
			// remove affect
			aff_list.remove();
			Modifier * mod;

			// remove the modifiers (false = negate)
			for_each( paf->mod_list, mod )
				modify( mod, false );

			CLR_BIT( aff_bits, paf->getType() );
			
			if( paf->getSpellType() )
			{
				out( "The affects of " );
				out( paf->getSpellType()->getName() );
				out( " wear off.\n\r" );
			}
			paf->fordelete();
		}

		aff_list.next();
	}

	return false; // still alive
}

// exp_table[i] = exp needed to get to level i+1
const int exp_table[MAX_PC_LEVEL+1] =
{
	0,
	1000,     // 1
	2500,
	5000,
	10000,
	25000,     // 5
	50000,
	100000,
	250000,
	500000,
	400000,     // 10
	800000,
	1500000,
	2000000,
	3000000,
	4000000,     // 15
	5000000,
	7000000,
	10000000,
	15000000,
	10000000,     // 20
	20000000,
	30000000,
	40000000,
	50000000,
	70000000,     // 25
	100000000,
	150000000,
	200000000,
	300000000,
	200000000,     // 30
	0      // HERO
}; 


int PC::gainExp( int x )
{
	// Calc exp caps, etc and return actual.

	exp += x;
	if( exp >= exp_table[ levels[ classnow ] ] )
		advance( 1 );

	return x;
}


void PC::describeItself( String & str )
{
	str << "PC \n\r";
	Char::describeItself(str);
	str << "PC specific data ???";
}