/*
....[@@@..[@@@..............[@.................. MUD++ is a written from
....[@..[@..[@..[@..[@..[@@@@@....[@......[@.... scratch multi-user swords and
....[@..[@..[@..[@..[@..[@..[@..[@@@@@..[@@@@@.. sorcery game written in C++.
....[@......[@..[@..[@..[@..[@....[@......[@.... This server is an ongoing
....[@......[@..[@@@@@..[@@@@@.................. development project. All
................................................ contributions are welcome.
....Copyright(C).1995.Melvin.Smith.............. Enjoy.
------------------------------------------------------------------------------
Melvin Smith (aka Fusion) msmith@falcon.mercer.peachnet.edu
MUD++ development mailing list mudpp-list@spice.com
------------------------------------------------------------------------------
main.cc
*/
// The game loop is here.
// Also the main objects are created here: server, players, mobs, objs
#include "io.h"
#include "string.h"
#include "server.h"
#include "room.h"
#include "indexable.h"
#include "llist.h"
#include "area.h"
#include "edit.h"
#include "help.h"
#include "env.h"
#include "global.h"
#include <sys/time.h>
#include <sys/resource.h>
const char * version = "\n\r\
MUD++ Cyberspace Server version 0.1\n\r\
Copyright(C) 1995, 1996 Melvin Smith\n\n\r";
#if defined( ultrix )
extern "C"
{
int getrlimit( int, struct rlimit * );
int setrlimit( int, struct rlimit * );
}
#endif
Server server( 4000 );
LList<PC> pcs;
LList<NPC> npcs;
LList<Area> areas;
LList<Object> objects;
void loadAreas();
void buildWorld();
void Nanny( PC *, String & str = "" );
int DOWN;
int REBOOT;
int pulse = PULSE_PER_SEC;
int main( int argc, char *argv[] )
{
// pid_t pid;
Cout << version;
int thePort = 4000;
if( argc > 1 )
{
thePort = atoi( argv[1] );
if( thePort <= 0 )
thePort = 4000;
}
chdir( AREA_DIR );
umask( ~( S_IRUSR | S_IWUSR ) );
// Raise the soft limit on coredump size to the max (hard limit)
// if it isn't already. This isn't POSIX but it is SYSV and 4.3+BSD
// so it should compile. If it doesn't, delete these lines and
// invoke mud++ through a csh or ksh after giving the command
// 'unlimit coredumpsize'
// otherwise you may not get a core for debugging.
rlimit rl;
getrlimit( RLIMIT_CORE, &rl );
rl.rlim_cur = rl.rlim_max;
setrlimit( RLIMIT_CORE, &rl );
/* Zap off into daemon land.
if( ( pid = fork() ) < 0 )
{
perror( "fork" );
exit( 0 );
}
else if( pid != 0 )
{
// parent
exit(0);
}
*/
// Create a new session.
// setsid();
// See if we have been spawned by another instance.
// If so, inherit all open descriptors.
PC *pc;
String str;
Cout << "Looking for reboot.tab\n";
InFile tab( "./reboot.tab" );
if( !tab )
{
Cout << "No reboot.tab, this is a fresh boot.\n\r";
server.boot( thePort );
}
else
{
char name[ 256 ];
char host[ 256 ];
int desc;
int port;
Cout << "Live reboot, loading descriptor data.\n\r";
// For now I assume that first entry is the socket control
// entry so I dont examine it.
tab.getstring( name ); // name
tab.getstring( host ); // hostname ignored for this entry
port = tab.getnum();
desc = tab.getnum();
server.boot( port, desc );
for( ; ; )
{
if( tab.eof() )
break;
Cout << "Restoring old connection.\n";
tab.getstring( name );
if( tab.eof() )
break;
tab.getstring( host );
if( tab.eof() )
break;
port = tab.getnum();
desc = tab.getnum();
Cout << "\n" << name << ":" << host << ":" << port << ":" << desc << endl;
Socket *newSock = new Socket( host, port, desc );
server.addSock( newSock );
pc = new PC( &server, newSock, name );
pcs.add( pc );
newSock->write( "\n\rSuccessful Reboot.\n\r" );
// Here I got lazy. The potential is here to go ahead
// and load the player -and- the mob he was fighting
// if there was one, along with mobs old hp/mana
// For now this is still better than closing connection.
pc->setState( STATE_GET_NAME );
str = name;
Nanny( pc, str );
}
Cout << "reboot.tab loaded.\n";
tab.close();
remove( "./reboot.tab" );
}
server.useNameServer();
Cout << "Loading help Index.\n";
loadHelps();
Cout << "Loading areas.\n";
loadAreas();
Cout << "Linking world.\n";
buildWorld();
// OK here goes the game loop
while( !DOWN )
{
// High precision timer
server.sleep( PULSE );
// Pulse
if( !pulse-- )
{
heartbeat();
pulse = PULSE_PER_SEC;
}
// Check for new connections
if( server.newConnection() )
{
Socket * sock = server.accept();
if( sock )
{
pc = new PC( &server, sock, "" );
printf("New connection from %s\n", sock->getHostName() );
pc->setState( STATE_INIT );
pc->view( "./title" );
pcs.addTop( pc );
Nanny( pc );
pc->flush();
}
}
// Poll all active descriptors
server.poll();
// Set the current pointer back to head of player list
pcs.reset();
while( ( pc = pcs.peek() ) )
{
String commd( 256 );
// Keep this at top of loop, since continue is used
pcs.next();
// Check for guys to boot.
if( pc->getState() == STATE_BOOT )
{
// Make sure he gets any pending text
// This could be a disconnect, slay, etc. so inform him
pc->flush();
server.remove( pc->getSock() );
pc->getSock()->close();
if( pc->inRoom() )
pc->inRoom()->rmCharInv( pc );
pcs.remove( pc );
delete pc;
continue;
}
// Boot sockets with exceptional condition
// Need to add handler instead of booting.
if( server.error( pc->getSock() ) )
{
Cout << "Closing link to " << pc->getName() <<endl;
server.remove( pc->getSock() );
pc->getSock()->close();
if( pc->inRoom() )
pc->inRoom()->rmCharInv( pc );
pcs.remove( pc );
delete pc;
continue;
}
// Read socket if ready
if( server.canRead( pc->getSock() ) )
{
pc->readInput();
if( pc->getSock()->eof() )
{
pc->setState( STATE_BOOT );
Cout << "EOF Read: Closing link to " << pc->getName() << endl;
server.remove( pc->getSock() );
pc->getSock()->close();
pcs.remove( pc );
delete pc;
continue;
}
}
if( !pc->inBuf() && !pc->outBuf() )
continue;
else if( pc->pagePending() )
pc->page( "" );
else if( pc->getEditor() )
{
if( !pc->getNextCommand() )
continue;
commd = pc->getCommand();
if( commd == "quit" )
{
pc->quitEditor();
}
else
{
// Need to check return status and pass command on
// to regular interpreter if not recognized
pc->getEditor()->command( commd );
}
pc->flush();
pc->putPrompt();
continue;
}
else if( pc->inBuf() )
{
if( pc->getState() >= STATE_INIT
&& pc->getState() < STATE_PLAYING )
{
if( !pc->getNextCommand() )
continue;
commd = pc->getCommand();
Nanny( pc, commd );
}
else if( pc->inBuf() && pc->getState() > STATE_PLAYING )
{
switch( pc->getState() )
{
default : pc->setState( STATE_PLAYING );
break;
//case STATE_EMAIL : pc->do_mail( buf ); break;
//case STATE_EDIT_TEXT : pc->Edit( buf ); break;
}
// pc->flush();
// pc->putPrompt();
// continue;
}
else
{
pc->getNextCommand();
pc->command();
if( pc->outBuf() )
pc->flush( );
if( pc->pagePending( ) )
pc->page( "" );
else
pc->putPrompt( );
}
}
// Process some buffered output
if( pc->outBuf( ) )
{
pc->flush();
if( pc->getState() == STATE_PLAYING )
pc->putPrompt();
}
}
}
// Bottom of game loop
if( REBOOT )
{
OutFile out( "reboot.tab" );
// Write the control socket descriptor so the Server object can
// pick it up on way back in after execl()
out << "mudpp.1~" << "localhost~"
<< server.getPort() << " "
<< server.getDesc();
// Write each player name and descriptor so mud++ can reload
// the player files after execl()
pcs.reset();
while( ( pc = pcs.peek() ) )
{
out << "\n"
<< pc->getName() << '~'
<< pc->getSock()->getHostName() << '~'
<< pc->getSock()->getPort() << " "
<< pc->getSock()->getDesc();
pcs.next();
}
out.close();
execl( "../src/mud++", "mud++", (char*)0 );
}
exit( 0 );
}
void loadAreas()
{
String areaKey;
char filename[256];
char buf[256];
Area *area;
InFile in( AREA_FILE );
while( in )
{
in.getline( filename );
Cout << "Area-[" << filename << "]" << endl;
if( !*filename )
break;
InFile inArea( filename );
if( inArea )
{
inArea.getword( buf );
if( *buf == 'A' )
{
areaKey = inArea.getword( buf );
area = new Area( areaKey );
area->readFrom( inArea );
areas.addTop( area );
}
else
{
Cout << "Area file [" << filename << "] has no area header.\n";
exit(0);
}
}
else
{
Cout << "Error opening area file [" << filename << "]\n";
exit(0);
}
}
Cout <<"\nDatabase booted sucessfully.\n";
}
// Link rooms, do initial repop
void buildWorld()
{
Area *area;
areas.reset();
while( ( area = areas.peek() ) )
{
areas.next();
area->hardLink();
}
// 2 seperate loops in case I decide to do something funky with doors.
areas.reset();
while( ( area = areas.peek() ) )
{
areas.next();
area->repop();
}
}