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