/*
....[@@@..[@@@..............[@.................. 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@falcon.mercer.peachnet.edu 
MUD++ development mailing list    mudpp-list@spice.com
------------------------------------------------------------------------------
pc.cc
*/

#include "string.h"
#include "llist.h"
#include "indexable.h"
#include "server.h"
#include "room.h"
#include "bit.h"
#include "affect.h"
#include "screen.h"
#include "npc.h"
#include "pc.h"

#include "global.h"

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


const bitType priv_bit_list[] =
{
	{"undefined",		NULLBIT			},
	{"superuser",		SUPERUSER		},
	{"director",		DIRECTOR		},
	{"admin",			ADMIN			},
	{"operator",		OPERATOR		},
	{"masterbuilder",	MASTERBUILDER	},
	{"builder",			BUILDER			},
	{"quester",			QUESTER			},
	{"wizard",			WIZARD			}
};


const command_type PC::cmdlist[27][32] =
{
// A
{
	{"areas",		do_areas     },
	{0,	0 }
},
// B
{
	{0,	0 }
},
// C
{
	{"cast",		do_cast      },
	{"chat",		do_chat      },
	{"clear",		do_clear     },
	{"close",		do_close     },
	{"commands",	do_commands  },
	{0,	0 }
},
// D
{
	{"down",		do_down      },
	{"drop",		do_drop      },
	{0,	0 }
},
// E
{
	{"east",		do_east      },
	{"equipment",	do_equipment },
	{"exits",		do_exits     },
	{0,	0 }
},
// F
{
	{0,	0 }
},
// G
{
	{"get",			do_get       },
	{0,	0 }
},
// H
{
	{"help",		do_help      },
	{"hide",		do_hide      },
	{0,	0 }
},
// I
{
	{"inventory",	do_inventory },
	{0,	0 }
},
// J
{
	{0,	0 }
},
// K
{
	{"kill",		do_kill      },
	{0,	0 }
},
// L
{
	{"look",		do_look      },
	{"list",		do_list      },
	{"levels",		do_levels    },
	{0,	0 }
},
// M
{
	//{"mail",		do_mail      },
	{0,	0 }
},
// N
{
	{"north",		do_north     },
	{0,	0 }
},
// O
{
	{"open",		do_open     },
	{0,	0 }
},
// P
{
	{"password",	do_password  },
	{"put",			do_put       },
	{"practice",	do_practice  },
	{"prompt",		do_prompt    },
	{0,	0 }
},
// Q
{
	{"quaff",		do_quaff     },
	{"quit",		do_quit      },
	{0,	0 }
},
// R
{
	{"remove",		do_remove    },
	{0,	0 }
},
// S
{
	{"south",		do_south     },
	{"say",			do_say       },
	{"save",		do_save      },
	{"score",		do_score     },
	{"sneak",		do_sneak     },
	{"study",		do_study     },
	{0,	0 }
},
// T
{
	{"tell",		do_tell      },
	{"time",		do_time      },
	{0,	0 }
},
// U
{
	{"up",			do_up        },
	{0,	0 }
},
// V
{
	{0,	0 }
},
// W
{
	{"west",		do_west      },
	{"wear",		do_wear      },
	{"weather",		do_weather   },
	{"who",			do_who       },
	{"wield",		do_wield     },
	{0,	0 }
},
// X
{
	{0,	0 }
},
// Y
{
	{0,	0 }
},
// Z
{
	{0,	0 }
},
// Other
{
	{".",		do_chat      },
	{":",		do_immtalk   },
	{0,	0 }
}
};
	

const immcmd_type PC::immcmdlist[27][10] =
{
// A
{
	{"advance",		do_advance,		1,	DIRECTOR		},
	{"aedit",		do_aedit,		1,	MASTERBUILDER	},
	{0,	0,	0,	NULLBIT	}
},
// B
{
	{0,	0,	0,  NULLBIT	}
},
// C
{
	{"cat",			do_cat,			1,	SUPERUSER		},
	{"cset",		do_cset,		1,	ADMIN			},
	{0,	0,	0,	NULLBIT	}
},
// D
{
	{"debug",		do_debug,		1,	SUPERUSER		},
	//{"dbsave",	do_dbsave,		1,	MASTERBUILDER	},
	{0,	0,	0,	NULLBIT	}
},
// E
{
	{"echo",		do_echo,		1,	WIZARD			},
	{0,	0,	0,	NULLBIT	}
},
// F
{
	{0,	0,	0,	NULLBIT	}
},
// G
{
	{"grant",		do_grant,		1,	SUPERUSER		},
	{"goto",		do_goto,		1,	WIZARD			},
	{0,	0,	0,	NULLBIT	}
},
// H
{
	{0,	0,	0,	NULLBIT	}
},
// I
{
	{"immtalk",		do_immtalk,		1,	WIZARD			},
	{"invis",		do_invis,		1,	WIZARD			},
	{"ident",		do_ident,		1,	ADMIN			},
	{0,	0,	0,  NULLBIT	}
},
// J
{
	{0,	0,	0,  NULLBIT	}
},
// K
{
	{0,	0,	0,  NULLBIT	}
},
// L
{
	{0,	0,	0,  NULLBIT	}
},
// M
{
	{"mfind",		do_mfind,		1,	BUILDER		},
	//{"medit",		do_medit,		1,	BUILDER		},
	{"mload",		do_mload,		1,	OPERATOR    },
	//{"mstat",		do_mstat,		1,	OPERATOR	},
	{0,	0,	0,	NULLBIT	}
},
// N
{
	{0,	0,	0,	NULLBIT	}
},
// O
{
	{"ofind",		do_ofind,		1,	BUILDER		},
	{"oedit",		do_oedit,		1,	BUILDER		},
	{"oload",		do_oload,		1,	OPERATOR	},
	{"owhere",		do_owhere,		1,	OPERATOR	},
	//{"ostat",		do_ostat,		1,	OPERATOR	},
	{0,	0,	0,	NULLBIT	}
},
// P
{
	{"page",		do_page,		1,	WIZARD		},
	{"purge",		do_purge,		1,	BUILDER		},
	{0,	0,	0,	NULLBIT	}
},
// Q
{
	{0,	0,	0,	NULLBIT	}
},
// R
{
	{"reboot",		do_reboot,		1,	DIRECTOR	},
	{"revoke",		do_revoke,		1,	SUPERUSER	},
	{"rfind",		do_rfind,		1,	BUILDER		},
	{"repops",		do_repops,		1,	BUILDER		},
	{"redit",		do_redit,		1,	BUILDER		},
	{0,	0,	0,	NULLBIT	}
},
// S
{
	{"shutdown",	do_shutdown,	1,	DIRECTOR	},
	{"slay",		do_slay,		1,	ADMIN		},
	{0,	0,	0,	NULLBIT	}
},
// T
{
	{"transfer",	do_transfer,	1,	ADMIN		},
	{0,	0,	0,	NULLBIT	}
},
// U
{
	{"users",		do_users,		1,	OPERATOR	},
	{0,	0,	0,	NULLBIT	}
},
// V
{
	{0,	0,	0,	NULLBIT	}
},
// W
{
	{"wizhelp",		do_wizhelp,		1,	WIZARD		},
	{0,	0,	0,	NULLBIT	}
},
// X
{
	{0,	0,	0,	NULLBIT	}
},
// Y
{
	{0,	0,	0,	NULLBIT	}
},
// Z
{
	{0,	0,	0,	NULLBIT	}
},
// Other
{
	{0,	0,	0,	NULLBIT	}
}
};


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


PC::~PC()
{
	if( socket ) delete socket;
	if( prompt ) delete [] prompt;
	if( outbuf ) delete [] outbuf;
	if( pagebuf ) delete [] pagebuf;
}


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


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


int PC::getState() const
{
	return state;
}


void PC::setState( int s )
{
	state_last = state;
	state = s;
}

int PC::lastState() const
{
	return state_last;
}
 
bool PC::isPlaying() const
{
	if( editor || text )
		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 = strlen( str );
	if( count_lines( str ) > 23 )
	{
		// Put it on the pager
		// Clobber the pagebuf if it exists - how it could I dont know
		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.
 
		int totsize = addsize + (outbuf ? strlen( outbuf ) : 0 ) +2;
    
		// See if it exceeds outbuf size, if so reallocate,
		// else strcat and return

		if( outbuf && totsize < outsize )
		{
			strcat( outbuf, str );
			return;
		}

		// Start with size 2048 and double until big enough

		if( !outsize )
			outsize = 2048;

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

		newbuf = new char[ outsize ];
		if( !newbuf )
		{
			error( "Can't allocate dynamic memory!" );
		} 
		*newbuf = '\0';

		// Get the old outbuf then free it and replace

		if( outbuf )
		{
			strcpy( newbuf, outbuf );
			delete [] outbuf;
		}

		strcat( newbuf, str );
		outbuf = newbuf;
	}	
}


void PC::putPrompt()
{
	char buf[BUF];
	char *token = prompt;
	char *ptr = buf;
	*ptr++ = '\n'; *ptr++ = '\r';
  
	if( task )
		return;
 
	if( state == STATE_PLAYING )
	{
		if( !*prompt )
			sprintf( buf, "\n\r[set prompt]" );
		else
		{
			while( *token )
			{  
				if( *token == '%' )
					switch( *(++token) )
					{
						case '\0': break;
						case 'B':
							sprintf( ptr, "%s%s",BOLD,BLUE);
							while( *ptr ) ptr++;
							token++;
							continue;
						case 'C':
							sprintf( ptr, "%s%s",BOLD,CYAN);
							while( *ptr ) ptr++;
							token++;
							continue;
						case 'G':
							sprintf( ptr, "%s%s",BOLD,GREEN);
							while( *ptr ) ptr++;
							token++;
							continue;
						case 'P':
							sprintf( ptr, "%s%s",BOLD,PURPLE);
							while( *ptr ) ptr++;
							token++;
							continue;
						case 'R':
							sprintf( ptr, "%s%s",BOLD,RED);
							while( *ptr ) ptr++;
							token++;
							continue;
						case 'Y':
							sprintf( ptr, "%s%s",BOLD,YELLOW);
							while( *ptr ) ptr++;
							token++;
							continue;
						case 'h':
							sprintf( ptr, "%d", hp );
							while( *ptr ) ptr++;
							token++;
							continue; 
						case 'H':
							sprintf( ptr, "%d", 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; 
					}
					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_EDIT )
	{
		if( task == TASK_EDIT_APPEND )
			sprintf( buf, "]" );
		else 
			sprintf( buf, "\n\rEDITOR >" );
	}
	else if( state == STATE_BOOT )
		sprintf( buf, "\n\r[ ** Seeya!! ** ]\n\r" );
	else
		sprintf( buf, "\n\r[Unknown connected state]" );

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


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, close connection

	}
}


void PC::flush()
{
	if( !outbuf || !*outbuf )
		return;

	if( socket->write( outbuf ) < 0 )
		state = STATE_BOOT;

	*outbuf = '\0';
}


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


void PC::page( char *arg )
{
	if( !pagebuf )
		return;

	int lines = 0;
	char *ptr = pageptr;
 
	switch( toupper(*arg) )
	{
		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( *ptr )
	{
		socket->write( INVERSE );
		socket->write( "[ Type (q)uit (r)efresh (b)ack or RETURN for more ]" );
		socket->write( NTEXT );
	}
	else if( state > STATE_INIT && state < STATE_PLAYING )
	{
		Nanny( this, "" );
	}
	else
		putPrompt();

	pageptr = ptr;

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

	return;
}



int PC::setClass( int x )
{
	classnow = x;
	return classnow;
}


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

	if( classnow )
		return buf_mort[ classnow ]; 

	return "WIZARD"; 
}


int PC::readFrom( InFile &in )
{
	char buf[ BUF ];

	if( !in )
		return 0;

	while( in && *in.getword( buf ) != '#' )
	{
		switch( *buf )
		{
			default : break;

			case 'C':
				if( !strcmp( "Class", buf ) )
				{	
					classnow = in.getnum();
				}
			break;

			case 'E':
				if( !strcmp( "EqBits", buf ) )
				{
					int 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 'I':
				break;

			case 'L':
				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;
				}
				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( "PrivBits", buf ) )
				{
					int numfields = in.getnum();
					for( int i = 0; i < numfields; i++ )
						priv[i] = in.getlong();
				}
				else if( !strcmp( "Prompt", buf ) )
				{
					in.getstring( buf );
					prompt = new char[ strlen(buf) + 1 ];
					strcpy( prompt, buf );
				}
				break;

			case 'S':
				if( !strcmp( "Short", buf ) )
				{
					in.getstring( buf );
					setShort( 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
						delete aff;
				}
				break;

			case 'O':
				{
					obj = new Object;
					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" );
					delete npc;
				}
				break;
		}

		continue;
	}


	return 1;
}


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

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


int PC::writeTo( OutFile &out ) const
{
	if( ! out )
		return 0;

	out << "Name " << getName() << endl; 
	out << "Short " << getShort() << "~" << endl;
	out << "Long " << getLong() << "~" << endl;
	out << "Password " << getPasswd() << "~" << endl;
	out << "Messages " << messages  << endl;
	out << "Class " << classnow  << endl;
	out << "Exp " << exp       << endl;
	out << "Levels " << levels[0] << " " << levels[1] << " "
		<< levels[2] << " " << levels[3] << " "
		<< levels[4] << " " << levels[5] << endl;
	out << "Stats " << strength << " " << intel << " " << wis   << " "
		<< con << " " << dex   << " " << speed << endl;
	out << "MaxHpMana " << max_hp << " " << max_mana << endl;
	out << "HpMana " << hp << " " << mana << endl;
	out << "GSC " << gold << ' ' << silver << ' ' << copper << endl;
	out << "Energy " << energy << endl;
	out << "Prompt " << prompt << TERM_CHAR << endl;
	out << "PrivBits ";
	out.putbitfield( priv, MAX_PRIV_BIT_FIELDS );
	out << endl;
	out << "EqBits ";
	out.putbitfield( eq_bits, MAX_EQ_BIT_FIELDS );
	out << endl;
	out << "#\n";

	Affect *aff;
	affects.reset();
	while( ( aff = affects.peek() ) )
	{
		affects.next();
		out << 'A';
		aff->writeTo( out );
	}

	Object *obj;

	inv.reset();
	while( ( obj = inv.peek() ) )
	{
		inv.next();
		out << "O" << endl;
		obj->writeTo( out );
	}

	out << "#";

	return 1;
}


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

	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 * )
{
}


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;
}


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

	char buf[BUF*8];
	int fd;

	fd = ::open( filename , O_RDONLY );

	if( fd < 0 )
	{
		return;
	}

	int bytes = 0;
	int size = 0;

	while( (bytes = read( fd, buf+size, 1024 )) > 0 )
	{
		size += bytes;
		if( BUF*8 - size <= 1024 )
		{
			sprintf( buf+size, "\n[** BUF EXCEEDED - TRUNCATED **]\n" );
			size = strlen( buf );
			break;
		}
	}

	*(buf+size) = '\0';
	out( buf );
}



void PC::do_exits( String & )
{
	String str = "[Obvious Exits]";
  
	for( int door=0; door < MAX_DIR; door++ )
	{
		if( in_room->getExit( door ) )
		{
			str << "\n\r" << getDirName( door ) << " - "
				<< WHITE << in_room->toRoom( door )->getName() << NORMAL;
		} 
	}
	str += "\n\r";

	out( str );
}


void PC::do_equipment( String & )
{
	String str( BUF );	
	Object *obj;

	str << "\n\rYou are wearing:\n\r";
	for( int wear_pos = EQ_MIN; wear_pos <= EQ_MAX; wear_pos++ )
	{
		if( IS_SET( eq_bits, wear_pos ) )
		{
			obj = getObjWear( wear_pos );
			if( obj )
				str.sprintfAdd( "%25s - %s\n\r",
							getWearPosName( wear_pos ),
							obj->getShort().chars() );
			else
				str.sprintfAdd( "%25s - %s\n\r",
							getWearPosName( wear_pos ),
							"BUG!! NULL OBJECT EQ POSITION!!" );
				
		}
	}

	out( str );
}


void PC::do_inventory( String & )
{
	String str ( BUF * 2 );
	Object *obj;
	inv.reset();

	str += "\n\rYou are carrying:\n\r";

	while( ( obj = inv.peek() ) )
	{
		inv.next();
		if( obj->wearPos() )
			continue;

		str << obj->getShort() << "\n\r";
	}

	str += "\n\r";
	out( str );
}


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

	arg.startArgs();

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

	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";
	out( str );
}


void PC::do_say( String & arg )
{
	String str;

	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_chat( String & arg )
{
	String str;

	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( String & )
{
	String str;

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

	str << PLAYER_DIR << '/' << name[0] << '/' << name.asProper();
	writeTo( str );
	out( "Goodbye, come back soon.\n\r" );

	str.clr();
	str << "\n\n\r" << name << " has left the game.\n\r";
	in_room->out( str );
	state = STATE_BOOT;
}



void PC::command( String & arg )
{
	int ihash;

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

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

	if( incommd == "!" )
	{
		if( ! inlast )
			return;

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

	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.abbrev( cmdlist[ihash][i].commd ) )
			{
				(cmdlist[ihash][i].fun)( args );
				return;
			}
		}		
	}

	for( i = 0; immcmdlist[ihash][i].commd; i++ )
	{ 
		if( incommd[0] == *immcmdlist[ihash][i].commd )
		{
			if( incommd.abbrev( immcmdlist[ihash][i].commd ) )
			{
				if( authorized( immcmdlist[ihash][i].bit ) )
					(immcmdlist[ihash][i].fun)( args );
				else
					out( "Huh?\n\r" );
				return;
			}
		}		
	}

	out( "Huh?\n\r" );
}


const String & PC::getCommand()
{
	return incommd;
}


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;
	}
	else if( *inptr == (char)IAC )
	{
		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++;
	}

	incommd[i] = '\0';

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

	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';

	// 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()
{
	// Check for \n only since I know what I put in incommd
	if( !incommd || incommd[0] == '\n' )
		return;

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

//	int len  = incommd.len();
	int i;

	for( i = 0; cmdlist[ihash][i].commd; i++ )
	{ 
		if( incommd[0] == *cmdlist[ihash][i].commd )
		{
//			if( !strncmp( cmdlist[ihash][i].commd, incommd, len ) )
			if( incommd.abbrev( cmdlist[ihash][i].commd ) )
			{
				(cmdlist[ihash][i].fun)( args );
				return;
			}
		}		
	}

	for( i = 0; immcmdlist[ihash][i].commd; i++ )
	{ 
		if( incommd[0] == *immcmdlist[ihash][i].commd )
		{
//			if( !strncmp( immcmdlist[ihash][i].commd, incommd, len ) )
			if( incommd.abbrev( immcmdlist[ihash][i].commd ) )
			{
				if( authorized( immcmdlist[ihash][i].bit ) )
					(immcmdlist[ihash][i].fun)( args );
				return;
			}
		}		
	}

	out( "Huh?\n\r" );
}


void PC::setPrivBit( int bit )
{
	SET_BIT( priv, bit );
}


void PC::rmPrivBit( int bit )
{
	CLEAR_BIT( priv, bit );
}


int PC::authorized( int bit ) const
{
	if( IS_SET( priv, bit ) )
		return 1;

	return 0;
}


void PC::pulse()
{
	Char::pulse();
}


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

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

	return x;
}


// 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
};