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