/*
....[@@@..[@@@..............[@.................. 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 ???";
}