/* ....[@@@..[@@@..............[@.................. 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_info.cc */ #include "config.h" #include "string.h" #include "llist.h" #include "hash.h" #include "room.h" #include "server.h" #include "area.h" #include "help.h" #include "screen.h" #include "env.h" #include "spell.h" #include "pc.h" #include "social.h" #include "shop.h" #include "edit.h" #include "global.h" // Need to add lookupPCBit() to make toggle more extensive // This is just temporary. void PC::do_toggle( const String & arg ) { if( arg == "wiznet" ) TOGGLE_BIT( pc_bits, PC_WIZNET ); else return; out( "Ok.\n\r" ); } void PC::do_autoeat( const String & ) { if( IS_SET( pc_bits, PC_AUTOEAT ) ) out( "Autoeat OFF.\n\r" ); else out( "Autoeat ON.\n\r" ); TOGGLE_BIT( pc_bits, PC_AUTOEAT ); } void PC::do_autodrink( const String & ) { if( IS_SET( pc_bits, PC_AUTODRINK ) ) out( "Autodrink OFF.\n\r" ); else out( "Autodrink ON.\n\r" ); TOGGLE_BIT( pc_bits, PC_AUTODRINK ); } void PC::do_autogold( const String & ) { if( IS_SET( pc_bits, PC_AUTOGOLD ) ) out( "Autogold OFF.\n\r" ); else out( "Autogold ON.\n\r" ); TOGGLE_BIT( pc_bits, PC_AUTOGOLD ); } void PC::do_autoloot( const String & ) { if( IS_SET( pc_bits, PC_AUTOLOOT ) ) out( "Autoloot OFF.\n\r" ); else out( "Autoloot ON.\n\r" ); TOGGLE_BIT( pc_bits, PC_AUTOLOOT ); } void PC::do_autodir( const String & ) { if( IS_SET( pc_bits, PC_AUTODIR ) ) out( "Autodir OFF.\n\r" ); else out( "Autodir ON.\n\r" ); TOGGLE_BIT( pc_bits, PC_AUTODIR ); } void PC::do_autoexits( const String & ) { if( IS_SET( pc_bits, PC_AUTOEXITS ) ) out( "Autoexits OFF.\n\r" ); else out( "Autoexits ON.\n\r" ); TOGGLE_BIT( pc_bits, PC_AUTOEXITS ); } void PC::do_uptime( const String & ) { extern long max_login; extern long cur_login; String str; time_t now = time(0); str << ctime( & now ); str << "\r" << server->getUpTime() << ", " << cur_login << (cur_login != 1 ? " users (max was " : " user (max was " ) << max_login << ")\n\n\r"; out( str ); } void PC::do_afk( const String & arg ) { String str; if( isAFK() ) { CLR_BIT( pc_bits, PC_AFK ); afk_messg = ""; out( "Back in action.\n\r" ); str << getShort() << " is back at the keyboard.\n\r"; inRoom()->outAllCharExcept( str, this, 0 ); return; } SET_BIT( pc_bits, PC_AFK); str << getShort() << " just went into afk mode.\n\r"; if( (bool)arg ) { afk_messg = arg; str << "Message: " << afk_messg << "\n\r"; } inRoom()->outAllCharExcept( str, this, 0 ); out( "AFK mode on.\n\r" ); } void PC::do_title( const String & arg ) { if( !(bool)arg ) { out( "Your title is: " ); out( title ); out( "\n\rType 'title clear' to clear your title.\n\r" ); return; } if( arg == "clear" ) { title = ""; out( "Title cleared.\n\r" ); return; } if( !arg.hasChar( '~' ) ) { title = arg; if( title.len() > 45 ) { title[45] = '\0'; out( "Title truncated to 45 characters.\n\r" ); } out( "Title set to: " ); out( title ); out( "\n\r" ); return; } out( "Illegal character '~'\n\r" ); } void PC::do_list( const String & arg ) { Char * ch; Object * obj; String str; int cost; ShopKeeper * sk; String arg1; String arg2; int type = 0; bool nameFilter = false; arg.startArgs(); arg1 = arg.getArg(); if ( !arg1 ) { } else if ( arg1 == "type" ) { arg1 = arg.getArg(); type = lookupObjType(arg1); if ( !type ) { out("No such object type.\n\r"); return; } } else if ( arg1 == "all" ) { } else { nameFilter = true; } arg2 = arg.getArg(); for_each( in_room->chars, ch ) { if( ch == this ) continue; if( ch->isShopKeeper() ) { if ( !arg2 || ch->isName(arg2) ) break; } } if( ! ch ) { if ( !arg2 ) out( "No merchant is here.\n\r" ); else out( "No merchant with that name here" ); return; } sk = (ShopKeeper *)ch; str << "Goods for sale:\n"; for_each( sk->inv, obj ) { if( obj->worn() ) continue; if ( type && !obj->isType(type) ) continue; if ( nameFilter && ( !obj->isName(arg1) ) ) continue; cost = sk->sellValue(obj); if ( cost != SHOPKEEPER_NOT_TRADING ) str << "[ " << cost << " ] " << obj->getShort() << endl; } out( str ); } void PC::do_commands( const String & ) { String str( BUF * 2 ); int count=1; int i; str << "MUD++ player commands.\n\n\r"; for( int ihash = 0; ihash < 27; ihash++ ) { i = 0; while( cmdlist[ihash][i].commd ) { str.sprintfAdd( "%11s", cmdlist[ihash][i].commd ); if( !( count++ % 5 ) ) str += "\n\r"; i++; } } out( str ); } void PC::do_skills( const String & ) { } void PC::do_where( const String & ) { String str( "Players in this part of the country:\n\r" ); Area * area = inRoom()->getArea(); PC *pc; LList<PC> tlist = pcs; pcs.reset(); while( ( pc = tlist.peek() ) ) { if( pc->inRoom() && pc->inRoom()->getArea() == area ) str.sprintfAdd( "%20s - %s\n\r", (const char*)pc->getShort(), (const char*)pc->inRoom()->getName() ); tlist.next(); } out( str ); } void PC::do_socials( const String & ) { String str( BUF * 2 ); Social * social; int count=1; str << "MUD++ socials.\n\n\r"; for( int ihash = 0; ihash < 26; ihash++ ) { social_table[ihash].reset(); while( ( social = social_table[ihash].peek() ) ) { social_table[ihash].next(); str.sprintfAdd( "%11s", social->getName().chars() ); if( !( count++ % 5 ) ) str += "\n\r"; } } out( str ); out( "\n\r" ); } void PC::do_wizhelp( const String & ) { String str( BUF * 2 ); int count=1; int i; str << "MUD++ Wizard Commands.\n\n\r"; for( int ihash = 0; ihash < 27; ihash++ ) { i = 0; while( immcmdlist[ihash][i].commd ) { if( authorized( immcmdlist[ihash][i].bit ) ) { str.sprintfAdd( "%11s", immcmdlist[ihash][i].commd ); if( !( count++ % 5 ) ) str += "\n\r"; } i++; } } out( str ); } void PC::do_help( const String & arg ) { if( ! (bool) arg ) { // Simple help view( STANDARD_HELP ); } else { // Lookup help in helps hashtable // View( 'HELP_DIR+filename' ) // View is very efficient at reading in files and helps can be // modified while the game is running. // Memory usage is also decreased. String fname; Help * help; help = helps_ht.lookup(arg); if ( !help ) { out("\n\rNo help on that.\n\r"); return; } if ( !help->isAllowed(this) ) { out("\n\rYou are not authorized to read that.\n\r"); return; } if ( help->haveTitle() ) out ( help->getName() ); fname << HELP_DIR << '/' << help->getFileName(); view( fname.chars() ); return; } } void PC::do_levels( const String & ) { String str( BUF * 2 ); str << "Exp is per level, not cumalative.\n\r-===================-\n\r"; for( int i = 1; i<=MAX_PC_LEVEL; i++ ) { str.sprintf( " %2d %11d\n\r", i, exp_table[ i ] ); } str += "\n\r"; out( str ); } void PC::do_prompt( const String & arg ) { String str; if( (bool)arg ) { if( arg.len() > 80 ) { out( "Prompt string too long. Must be < 80 characters.\n\r" ); return; } else if( arg.hasChar( '~' ) ) { out( "Illegal character '~' in prompt string.\n\r" ); return; } else if ( arg == "default" ) { prompt = "[ %h/%Hh %m/%Mm ] "; out( "Ok, prompt set to default.\n\r" ); putPrompt(); return; } prompt = arg; } else { str << "Your prompt string is currently: \'" << prompt << "\'\n\r"; out( str ); return; } out( "Ok, new prompt has been set.\n\r" ); putPrompt(); } void PC::do_clear( const String & ) { out( "\x1B[2J" ); } void PC::do_areas( const String & ) { String str( BUF * 2 ); Area *pArea; areas.reset( ); str.sprintf( "%-13s %-15s %-35s\n\r", "Scope", "Builder(s)", "Title" ); while( ( pArea = areas.peek( ) ) ) { areas.next(); str.sprintfAdd( "%-13s %-15s %-35s\n\r", pArea->getKey().chars(), pArea->getBuilder().chars(), pArea->getTitle().chars() ); } out( str ); } void PC::do_equipment( const String & ) { String str( BUF ); bool anything = false; 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() ); anything = true; } else str.sprintfAdd( "%25s - %s\n\r", getWearPosName( wear_pos ), "BUG!! NULL OBJECT EQ POSITION!!" ); } } if ( ! anything ) str << "Nothing! " << BWHITE << "GACK!!!" << NORMAL << "\n\r"; out( str ); } void PC::do_inventory( const 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 ); } // Little ugly code here, have to make sure if the player is // disoriented, we still print the exits in order of direction // that he expects, else it would be possible to beat the system. void PC::do_exits( const String & ) { String str = "Obvious Exits:"; for( int door=orientation; door < DIR_UP + orientation; door++ ) { if( !inRoom()->getExit( door % DIR_UP ) ) continue; str.sprintfAdd( "\n\r%-5s - %s%s%s", lookupDirName( door - orientation ), WHITE, in_room->toRoom( door % DIR_UP )->getName().chars(), NORMAL ); } // Always do up and down last, this is seperate so dis-orientation // still lists exits as player expects if( in_room->getExit( DIR_UP ) ) str.sprintfAdd( "\n\r%-5s - %s%s%s", lookupDirName( DIR_UP ), WHITE, in_room->toRoom( DIR_UP )->getName().chars(), NORMAL ); if( in_room->getExit( DIR_DOWN ) ) str.sprintfAdd( "\n\r%-5s - %s%s%s", lookupDirName( DIR_DOWN ), WHITE, in_room->toRoom( DIR_DOWN )->getName().chars(), NORMAL ); str += "\n\r"; out( str ); } void PC::do_look( const String & arg ) { Object *obj; Object *obj2; Char *ch; int dir; String str( 4096 ); String strDirs; String arg1; String arg2; arg.startArgs(); arg1 = arg.getArg(); arg2 = arg.getArg(); if( !(bool)arg1 ) { str << "\n\r" << BCYAN << in_room->getName() << NORMAL << "\n\r"; // No arg so do standard look if( IS_SET( pc_bits, PC_AUTODIR ) ) { str << BWHITE << "[Exits:"; for( dir=orientation; dir < DIR_UP + orientation; dir++ ) { if( !inRoom()->getExit( dir % DIR_UP ) ) continue; str << ' ' << lookupDirName( dir - orientation ); } if( in_room->getExit( DIR_UP ) ) str << ' ' << lookupDirName( DIR_UP ); if( in_room->getExit( DIR_DOWN ) ) str << ' ' << lookupDirName( DIR_DOWN ); str << ']' << NORMAL << "\n\r"; } // Room::desc has it own \n\r so don't add str << CYAN << in_room->getDesc() << NORMAL; // show the items in_room->inv.reset(); while( ( obj = in_room->inv.peek() ) ) { in_room->inv.next(); if( obj->isInvis() ) { if( affectedBy( AFF_DET_INVIS ) ) str << " (Invis) " << obj->getLong() << "\n\r"; } else str << " " << obj->getLong() << "\n\r"; } in_room->chars.reset(); while( ( ch = in_room->chars.peek() ) ) { in_room->chars.next(); if( ch == this ) continue; str << ch->getLong() << "\n\r"; } out( str ); if( IS_SET( pc_bits, PC_AUTOEXITS ) ) do_exits( "" ); return; } else if( arg1 == "in" || arg1 == "inside" ) { // look at contents of a container (arg2) if( !(bool)arg2 ) { out( "Look inside what?\n\r" ); return; } countedThing cI(arg2); obj = getObjInv( cI ); if ( obj == NULL ) { obj = in_room->getObj( cI ); if( !obj ) { out( "Nothing by that name here.\n\r" ); return; } } if( !obj->isContainer() && !obj->isCorpse() ) { out( "Not much to see.\n\r" ); return; } // replace resetInv/getNexObjInv with a const LList & // access member function and assign to a temp readonly list // for iterating. // Reset object inventory list pointer obj->inv.reset(); out( obj->getShort() ); out( " contains:\n\r-------------\n\r" ); if( !( obj2 = obj->inv.peek() ) ) { out( "Nothing.\n\r" ); return; } // Iterate while( ( obj2 = obj->inv.peek() ) ) { out( obj2->getShort() ); out( "\n\r" ); obj->inv.next(); } return; } countedThing cI(arg1); // Find object/mob/player to look at ch = in_room->getChar( cI ); if( ch ) { look( ch ); return; } // Look at object in inventory and equipment obj = getObjInvWear( cI ); if( obj ) { look( obj ); return; } // Look at an object in room obj = in_room->getObj( cI ); if( obj ) { look( obj ); return; } // Extra description keywords String ed = in_room->getExtraDesc( arg1 ); if( ed ) { out( ed ); out( "\n\r" ); return; } // Look in a direction if( ( dir = getDir( arg1[0] ) ) != DIR_UNDEFINED ) { const Exit *ex = in_room->getExit( dir ); if( !ex ) { out( "Nothing to see in that direction.\n\r" ); return; } if( (bool) ex->getDesc() ) out( ex->getDesc() ); else out( "You see nothing special in that direction." ); out( "\n\r" ); return; } out( "I see no " ); out( arg1 ); out( " here.\n\r" ); } void PC::do_save( const String & ) { if( !save() ) out( "There was an error saving your character. Report.\n\r" ); else out( "\n\rOk. Saved.\n\r" ); } void PC::do_password( const String & arg ) { String orig; String newp; arg.startArgs(); orig = arg.getArg(); newp = arg.getArg(); if( ! (bool) orig || ! (bool) newp ) { out( "Usage: password <old> <new>\n\r" ); return; } orig.crypt( getName() ); if( (bool) getPasswd() && orig != getPasswd() ) { out( "Old password doesn't match. Password not changed.\n\r" ); return; } if( newp.hasChar( '~' ) ) { out( "Illegal character '~'. Password not changed.\n\r" ); return; } newp.crypt( getName() ); password = newp; out( "Ok. Password changed.\n\r" ); } void PC::do_score( const String & ) { String str( 8192 ); str.sprintf( "You are %s.\n\rLevel: %d\n\r", name.chars(), levels[classnow]); str.sprintfAdd( "Security: %d\n\r", getSecurity() ); if( classnow != CLASS_WIZARD ) { str.sprintfAdd( "You have %ld experience points.\n\r", exp ); str.sprintfAdd( "You need %ld exp to level %d.\n\r", exp_table[levels[classnow]]-exp, levels[classnow]+1 ); } str.sprintfAdd( "Str: %d Int: %d Wis: %d Con: %d Spd: %d\n\r", getStr(), getInt(), getWis(), getCon(), getSpd() ); str.sprintfAdd( "%d(%d) hp %d(%d) mana\n\r", getHP(), getMaxHP(), getMana(), getMaxMana() ); str.sprintfAdd( "You are carrying %d items weighing %d stones.\n\r", getCarriedCount(), getCarriedWeight() ); str.sprintfAdd( "You have %ld gold coins.\n\r", getGold() ); str.sprintfAdd( "Damroll: %d Hitroll: %d Armor: %d\n\r", getDamRoll(), getHitRoll(), getArmor() ); str.sprintfAdd( "Flags: " ); str += "\n\r"; if( fighting ) { str << "You are fighting " << fighting->getShort() << ".\n\r"; } if( getHunger() < -10 ) str << "You are starving.\n\r"; else if( getHunger() < 0 ) str << "You could use something to eat.\n\r"; if( getThirst() < -10 ) str << "You are dying of thirst.\n\r"; else if( getThirst() < 0 ) str << "You throat is slightly parched.\n\r"; str << "Affected by:\n\r"; Affect *paf; //aff_list.reset(); for_each( aff_list, paf ) { if( paf->getSpellType() ) str.sprintfAdd( "Spell '%s' for %d hours.\n\r", paf->getSpellType()->getName(), paf->getTimer() ); } out( str ); } void PC::do_time( const String & ) { out( envir.getTime() ); } void PC::do_weather( const String & ) { if( in_room->isIndoors() ) { out( "You can't see the weather indoors.\n\r" ); return; } out( envir.getWeather() ); } // Who improvements by Klepto void PC::do_who( const String & ) { extern long max_login; PC *ch; int count = 0; String str( BUF * 4 ); LList<PC> tlist; str += " Lost Realms II\n\r"; str += "[]=============*=========*===================*==========*====================[]\n\r"; str += " | Name | Level | Race/ Class/ Sex | Kills | Flags |\n\r"; str += "[]=============*=========*===================*==========*====================[]\n\r"; if( authorized( WIZARD ) ) { tlist = shellpcs; tlist.reset(); // reset the list while( ( ch = tlist.peek() ) ) { str.sprintfAdd( " | %-11s | %2d(%3d) | %-12s%3s-%c | 0000/000 | %s%s\n\r", ch->getName().chars(), ch->getLevel(), ch->getLevel(), lookupRaceName( ch->getRace() ), ch->className(), ch->getSex() == 1 ? 'M' : 'F', "[*OS SHELL*]", ch->getEditor() ? "[*EDITOR*]" : "" ); tlist.next(); } } tlist = pcs; for( int i = MAX_PC_LEVEL; i >= 0; i-- ) { tlist.reset(); // reset the list while( ( ch = tlist.peek() ) ) { if( ch->getLevel() != i || !ch->inRoom() ) { tlist.next(); continue; } count++; str.sprintfAdd( " | %-11s | %2d(%3d) | %-12s%3s-%c | 0000/000 | %s%s%s\n\r", ch->getName().chars(), ch->getLevel(), ch->getLevel(), lookupRaceName( ch->getRace() ), ch->className(), ch->getSex() == 1 ? 'M' : 'F', ch->getSocket() ? "" : "[*LOST LINK*]", ch->isAFK() ? "[*AFK*]" : "", ch->getEditor() ? "[*EDITOR*]" : "" ); tlist.next(); } } str += "[]-------------*---------*-------------------*----------*--------------------[]\n\r"; str.sprintfAdd( "Total - %d players (max was %d)\n\r", count, max_login ); out( str ); } void PC::pc_report( const char * fname, const String & comment ) { OutputFile outf(fname, Append); String frm; if ( !outf ) { Cout << "Problem with opening " << fname <<endl; return; } /* frm.sprintf("[%10.10s@%-15.15s] %s\n\r", getName().chars(), (inRoom()->getScope() + ':' + inRoom()->getKey()).chars() , comment.chars()); outf << frm; */ outf << '[' << getName() << '@' << inRoom()->getScope() << ':' << inRoom()->getKey() << "] " << comment << "\n"; outf.close(); } #define BUG_FILE "../etc/bugs.txt" #define IDEA_FILE "../etc/ideas.txt" #define TYPO_FILE "../etc/typos.txt" void PC::do_bug( const String & arg ) { pc_report(BUG_FILE, arg); out("Your bug report has been noted.\n\r"); } void PC::do_idea( const String & arg ) { pc_report(IDEA_FILE, arg); out("Your idea report has been noted.\n\r"); } void PC::do_typo( const String & arg ) { pc_report(TYPO_FILE, arg); out("Your typo report has been noted.\n\r"); } void PC::do_checkreport( const String & args ) { String arg1; String arg2; const char * filename; args.startArgs(); arg1 = args.getArg(); arg2 = args.getArgRest(); if ( arg1 == "bug" ) filename = BUG_FILE; else if ( arg1 == "idea" ) filename = IDEA_FILE; else if ( arg1 == "typo" ) filename = TYPO_FILE; else { out( "Usage: checkreport <bug/idea/typo> [clean/view]\n\r" ); return; } if ( !arg2 || (arg2 == "view") ) { view(filename); return; } if (arg2 == "clean" ) { // I'm guessing as about this privilages. Somebody (Fusion?) has // to outline logic behind each priv bit - Artur if (authorized(SUPERUSER) || authorized(ADMIN) ) { unlink(filename); out("Report file deleted.\n\r"); return; } else { out( "You are not allowed to delete entries.\n\r"); return; } } out( "Usage: checkreport <bug/idea/typo> [clean/view]\n\r" ); } void PC::do_hint( const String & arg ) { int i; String str; if ( (!arg) ) { last_hint++; if ( last_hint >= hints.length() ) last_hint = 0; out(hints[last_hint]); } else if ( (arg == "help") || (arg == "?") ) { view( HINTS_HELP ); } else if ( arg.isNumber() ) { i = arg.asInt(); if ( (i >= hints.length()) || (i<0) ) { out ( "No hint with such number.\n\r"); return; } out( hints[i] ); last_hint = i; } else if ( arg == "info" ) { str << "You can access " << hints.length() << " hints." << endl; if ( last_hint >= 0 ) str << "Last hint you read has number " << last_hint << endl; else str << "You have hints turned off.\n\r"; out(str); } else if ( arg == "reload" ) { // AUTHORIZATION CHECK // ... str << "Hints file reloaded with " << loadHints() << " hints." <<endl; out(str); } else if ( arg == "off" ) { out( "You turned automatic hints off."); last_hint = -1; } else if ( arg == "on" ) { out ( "You have turned automatic hints on." ); if ( last_hint < 0 ) last_hint = 0; } else if ( arg == "edit" ) { // AUTHORIZATION CHECK // ... editor = new TextfileEditor(this); editor->command( HINTS_FILE ); return; } else { out ( "Unknown command.\n\r" ); view(HINTS_HELP); } }