Files
commandergenius/project/jni/application/lbreakout2/server/server.c

1194 lines
40 KiB
C

/***************************************************************************
server.c - description
-------------------
begin : 03/03/11
copyright : (C) 2003 by Michael Speck
email : kulkanie@gmx.net
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
/***** INCLUDES ************************************************************/
#include <dirent.h>
#include "server.h"
#include "server_game.h"
/***** EXTERNAL VARIABLES **************************************************/
extern int net_buffer_cur_size, msg_read_pos;
/***** FORWARDED DECLARATIONS **********************************************/
#ifdef NETWORK_ENABLED
static void signal_handler( int signal );
static void broadcast_all( int len, char *data );
#endif
/***** LOCAL TYPE DEFINITIONS **********************************************/
enum { CHANNEL_MAIN_ID = 1 };
/***** LOCAL VARIABLES *****************************************************/
int server_port = 8000; /* server is listening at this port */
List *channels = 0; /* chat channels */
List *games = 0; /* running games */
List *levelsets = 0; /* loaded levelsets */
int global_id = 1; /* global id counter increased each type an object
(user,channel,game) is added (id:1 is channel MAIN) */
int server_halt = 0; /* when set, server will go down after some seconds */
int server_halt_since = 0; /* global time (in secs) passed since halt command */
char errbuf[128]; /* used to compile error messages */
char msgbuf[MAX_MSG_SIZE]; /* used to compile messages */
int msglen = 0;
int user_limit = 50; /* maximum number of users that may be logged in (0: unlimited)*/
int user_count = 0; /* number of logged in users */
char greetings[256]; /* welcome message send to user on login */
char admin_pwd[20] = ""; /* admin password (if any) */
char datadir[128] = SRC_DIR; /* levels are loaded from here */
int server_fps = 20; /* communication frame rate */
int server_frame_delay = 50; /* delay between server frames */
int server_recv_limit = -1; /* number of packets parsed in one go (-1 == unlimited) */
int server_def_bot_num = 1; /* number of 800's and 1000's bots to be
created on startup */
/* these default channels are known by the client and the only
* ones shown in the list. there id starts at 1 for MAIN increased
* by 1 with each step */
ServerChannel *main_channel = 0; /* pointer to MAIN */
int default_channel_count = 1;
char *default_channels[1];
/***** LOCAL FUNCTIONS *****************************************************/
#ifdef NETWORK_ENABLED
static void server_init_halt()
{
printf( _("server is going down...\n") );
server_halt_since = time(0);
server_halt = 1;
errbuf[0] = MSG_ERROR;
sprintf( errbuf+1, _("SERVER IS GOING DOWN!!!") );
broadcast_all( 2+strlen(errbuf+1), errbuf );
}
void send_info( ServerUser *user, int type, char *format, ... )
{
va_list args;
if ( user->no_comm ) return;
va_start( args, format );
vsnprintf( msgbuf+1, MAX_MSG_SIZE-1, format, args );
va_end( args );
msgbuf[0] = type;
socket_transmit( &user->socket, CODE_BLUE, 2+strlen(msgbuf+1), msgbuf );
}
/* channel_add/delete don't require client updates as the only
* channels that are shown in the list are already known by name
* and id by the client. additional channels can be entered by typing
* in the name. A pointer is returned to simplify transfer of users.
*/
static ServerChannel* channel_add( char *name )
{
ServerChannel *channel = salloc( 1, sizeof( ServerChannel ) );
strncpy(channel->name,name,20);
channel->id = global_id++;
channel->users = list_create( LIST_AUTO_DELETE, LIST_NO_CALLBACK );
printf( _("channel added: %s\n"), channel->name );
list_add( channels, channel );
return channel;
}
static void channel_delete( void *ptr )
{
ServerChannel *channel = (ServerChannel*)ptr;
if ( channel ) {
printf( _("channel deleted: %s (%i users)\n"), channel->name, channel->users->count );
if ( channel->users )
list_delete( channel->users );
free( channel );
}
}
ServerChannel *channel_find_by_name( char *name )
{
ServerChannel *channel = 0;
list_reset( channels );
while ( ( channel = list_next( channels ) ) )
if ( !strcmp( channel->name, name ) )
return channel;
return 0;
}
static void channel_add_user( ServerChannel *channel, ServerUser *user );
static void channel_add_bot( ServerChannel *channel, char *name, int level )
{
NetAddr addr;
ServerUser *user = salloc( 1, sizeof( ServerUser) );
/* add a bot user to channel which can be challenged
* but does nothing else */
strncpy(user->name,name,16);
user->id = global_id++;
user->no_comm = 1;
user->bot = 1;
user->bot_level = level; /* playing level */
net_build_addr( &addr, "localhost", 8000 );
socket_init( &user->socket, &addr );
channel_add_user( channel, user );
}
static void create_default_channels()
{
int i = 0;
for ( i = 0; i < default_channel_count; i++ )
channel_add( default_channels[i] );
main_channel = (ServerChannel*)list_first( channels );
}
static int is_default_channel( ServerChannel *channel )
{
int i = 0;
for ( i = 0; i < default_channel_count; i++ )
if ( !strcmp( default_channels[i], channel->name ) ) return 1;
return 0;
}
static void channel_broadcast( ServerChannel *channel, int len, char *data )
{
int urgent = 0;
ServerUser *user;
/* urgent messages are always sent even to hidden users */
if ( data[0] == MSG_ERROR ||
data[0] == MSG_ADD_USER || data[0] == MSG_REMOVE_USER ||
data[0] == MSG_SET_COMM_DELAY )
urgent = 1;
/* deliver it */
list_reset( channel->users );
while ( ( user = list_next( channel->users ) ) )
if ( !user->no_comm )
if ( urgent || !user->hidden )
socket_transmit( &user->socket, CODE_BLUE, len, data );
}
/* broadcast message to all users in all channels even the hidden ones */
static void broadcast_all( int len, char *data )
{
ServerChannel *channel;
list_reset( channels );
while ( ( channel = list_next( channels ) ) )
channel_broadcast( channel, len, data );
}
static void channel_add_user( ServerChannel *channel, ServerUser *user )
{
if ( channel == 0 ) return;
if ( user == 0 ) return;
list_add( channel->users, user );
printf( _("user added: %s (%i) from %s\n"),
user->name, user->id, net_addr_to_string( &user->socket.remote_addr ) );
/* inform all users in channel (including this one if not hidden) */
msg_begin_writing( msgbuf, &msglen, 128 );
msg_write_int8( MSG_ADD_USER );
msg_write_int32( user->id );
msg_write_string( user->name );
channel_broadcast( channel, msglen, msgbuf );
/* don't count dummies */
if ( !user->bot ) user_count++;
}
void channel_remove_user( ServerChannel *channel, ServerUser *user )
{
if ( channel == 0 ) return;
if ( user == 0 ) return;
user->hidden = 1; /* this user does not require the following update */
/* inform all users in channel */
msg_begin_writing( msgbuf, &msglen, 128 );
msg_write_int8( MSG_REMOVE_USER );
msg_write_int32( user->id );
channel_broadcast( channel, msglen, msgbuf );
/* don't count dummies */
if ( !user->bot ) user_count--;
/* remove */
printf( _("user removed: %s (%i)\n"), user->name, user->id );
list_delete_item( channel->users, user );
/* if empty channel and not default channel delete it */
if ( channel->users->count == 0 && !is_default_channel( channel ) )
list_delete_item( channels, channel );
}
void channel_hide_user( ServerChannel *channel, ServerUser *user, int hide )
{
if ( channel == 0 ) return;
if ( user == 0 ) return;
if ( user->hidden == hide ) return; /* nothing changes */
/* broadcast update to all users in channel */
if ( hide ) {
msg_begin_writing( msgbuf, &msglen, 128 );
msg_write_int8( MSG_REMOVE_USER );
msg_write_int32( user->id );
channel_broadcast( channel, msglen, msgbuf );
user->hidden = hide;
} else {
user->hidden = hide;
msg_begin_writing( msgbuf, &msglen, 128 );
msg_write_int8( MSG_ADD_USER );
msg_write_int32( user->id );
msg_write_string( user->name );
channel_broadcast( channel, msglen, msgbuf );
}
}
static void channel_kick_user( ServerChannel *channel, ServerUser *user, char *reason )
{
if ( channel == 0 ) return;
if ( user == 0 ) return;
snprintf( errbuf, 128, _("You have been kicked! Reason: %s"), reason );
send_info( user, MSG_ERROR, errbuf );
errbuf[0] = MSG_DISCONNECT;
socket_transmit( &user->socket, CODE_BLUE, 1, errbuf );
printf( _("user kicked (%s): %s (%i)\n"), reason, user->name, user->id );
channel_remove_user( channel, user );
}
/* transfer user to new channel and send nescessary updates */
void send_full_update( ServerUser *user, ServerChannel *channel );
void channel_transfer_user( ServerChannel *old, ServerChannel *new, ServerUser *user )
{
/* same channel? */
if ( old == new ) return;
/* mute user as he will receive a complete update
after the transfer */
user->hidden = 1;
/* transfer */
msg_begin_writing( msgbuf, &msglen, 128 );
msg_write_int8( MSG_REMOVE_USER );
msg_write_int32( user->id );
channel_broadcast( old, msglen, msgbuf );
list_transfer( old->users, new->users, user );
if ( old->users->count == 0 && !is_default_channel( old ) )
list_delete_item( channels, old );
msg_begin_writing( msgbuf, &msglen, 128 );
msg_write_int8( MSG_ADD_USER );
msg_write_int32( user->id );
channel_broadcast( new, msglen, msgbuf );
/* update */
user->hidden = 0;
msg_begin_writing( msgbuf, &msglen, 128 );
msg_write_int8( MSG_SERVER_INFO );
msg_printf( _("you have entered a new channel: %s"), new->name );
socket_transmit( &user->socket, CODE_BLUE, msglen, msgbuf );
msg_begin_writing( msgbuf, &msglen, 128 );
msg_write_int8( MSG_SET_CHANNEL );
msg_write_string( new->name );
socket_transmit( &user->socket, CODE_BLUE, msglen, msgbuf );
send_full_update( user, new );
}
/* Send a list of all users in user's channel including itself.
*
* FIXME: Sending each single entry is a not very nice.
*/
void send_full_update( ServerUser *user, ServerChannel *channel )
{
ServerUser *u;
msgbuf[0] = MSG_PREPARE_FULL_UPDATE;
socket_transmit( &user->socket, CODE_BLUE, 1, msgbuf );
/* users */
list_reset( channel->users );
while ( ( u = list_next( channel->users ) ) ) {
msg_begin_writing( msgbuf, &msglen, 32 );
msg_write_int8( MSG_ADD_USER );
msg_write_int32( u->id );
msg_write_string( u->name );
socket_transmit( &user->socket, CODE_BLUE, msglen, msgbuf );
}
}
/* ServerUser *find_user_by_name
* IN char *name
*
* Search all channels for a user by that name.
*/
static ServerUser* find_user_by_name( char *name )
{
ServerUser *user;
ServerChannel *channel;
list_reset( channels );
while ( ( channel = list_next( channels ) ) ) {
list_reset( channel->users );
while ( ( user = list_next( channel->users ) ) )
if ( !strcmp( user->name, name ) )
return user;
}
return 0;
}
/* ServerUser *search_user
* IN char *name
*
* Search all channels for a user by that name and return
* the channel as well.
*/
static ServerUser* search_user( char *name, ServerChannel **channel )
{
ServerUser *user;
list_reset( channels );
while ( ( *channel = list_next( channels ) ) ) {
list_reset( (*channel)->users );
while ( ( user = list_next( (*channel)->users ) ) )
if ( !strcmp( user->name, name ) )
return user;
}
return 0;
}
/* ServerUser *find_user_by_id
* IN int id
*
* Search all channels for a user by that id.
*/
static ServerUser* find_user_by_id( int id )
{
ServerUser *user;
ServerChannel *channel;
list_reset( channels );
while ( ( channel = list_next( channels ) ) ) {
list_reset( channel->users );
while ( ( user = list_next( channel->users ) ) )
if ( user->id == id )
return user;
}
return 0;
}
/* ServerUser *find_user_by_addr
* IN NetAddr *addr
*
* Search wether a user already uses this net address.
*/
static ServerUser* find_user_by_addr( NetAddr *addr )
{
ServerUser *user;
ServerChannel *channel;
list_reset( channels );
while ( ( channel = list_next( channels ) ) ) {
list_reset( channel->users );
while ( ( user = list_next( channel->users ) ) )
if ( net_compare_addr( addr, &user->socket.remote_addr ) )
return user;
}
return 0;
}
/* void handle_connectionless_packet
*
* By now only connection attempts can be found in this category.
* So check wether the packet contains a valid request (or send
* error messages if it doesn't) and add a new user to channel
* MAIN.
*/
static void handle_connectionless_packet( void )
{
char name[20], pwd[20], buf[128];
int protocol;
ServerUser *user = 0;
int i;
msg_begin_connectionless_reading();
if ( msg_read_int8() != MSG_CONNECT ) return;
protocol = msg_read_int8();
strncpy(name,msg_read_string(),20); name[19] = 0;
strncpy(pwd,msg_read_string(),20); pwd[19] = 0;
if ( msg_read_failed() ) {
sprintf( errbuf+1, _("Login data corrupted, please retry.") );
goto failure;
}
if ( !is_alphanum(name) ) {
strcpy(errbuf+1,
_("Your username may only contain letters, digits and underscores.\n") );
goto failure;
}
/* check wether this user already exists. if so the LOGIN_OKAY
* message was dropped. */
if ( (user = find_user_by_addr( &net_sender_addr )) )
if ( strcmp( user->name, name ) )
user = 0; /* somebody else though same box */
/* check data for validity */
if ( protocol != PROTOCOL ) {
if ( protocol < PROTOCOL )
sprintf( errbuf+1, _("Your protocol is out of date, please update.") );
else
sprintf( errbuf+1, _("Server uses an older protocol (%i), sorry."),
PROTOCOL );
goto failure;
}
if ( user_count >= user_limit && user == 0/*else user exists already but wasn't informed*/ ) {
sprintf( errbuf+1, _("Server is full!") );
goto failure;
}
if ( name[0] == 0 ) {
sprintf( errbuf+1, _("Please enter a name!") );
goto failure;
}
if ( strchr( name, ' ' ) ) {
sprintf( errbuf+1, _("Your name must not contain blanks! (But can have underscores.)") );
goto failure;
}
if ( (user==0 && find_user_by_name( name )) || !strcmp( name, _("admin") ) ) {
sprintf( errbuf+1, _("This name is already in use. Please choose another one.") );
goto failure;
}
/* password is currently unused */
/* data successfully extracted and checked. if this is not a
* user whos LOGIN_OKAY was dropped, create a new one. */
if ( user == 0 ) {
user = salloc( 1, sizeof( ServerUser ) );
user->id = global_id++;
if ( admin_pwd[0] != 0 && !strcmp( admin_pwd, name ) ) {
strncpy(user->name,_("admin"),20);
user->admin = 1;
}
else
strncpy(user->name,name,20);
socket_init( &user->socket, &net_sender_addr );
user->hidden = 1; /* don't get the ADD_USER message */
channel_add_user( main_channel, user );
user->hidden = 0;
}
/* tell user that it is accepted */
msg_begin_writing( msgbuf, &msglen, 32 );
msg_write_int8( MSG_LOGIN_OKAY );
msg_write_int32( user->id );
msg_write_string( user->name );
socket_transmit( &user->socket, CODE_BLUE, msglen, msgbuf );
msg_begin_writing( msgbuf, &msglen, 2+strlen(greetings) );
msg_write_int8( MSG_SERVER_INFO );
msg_write_string( greetings );
socket_transmit( &user->socket, CODE_BLUE, msglen, msgbuf );
msg_begin_writing( msgbuf, &msglen, 128 );
msg_write_int8( MSG_SERVER_INFO );
if ( user_count == 1 )
strcpy ( buf , _("1 user online") );
else
snprintf( buf, 128, _("%i users online"), user_count );
msg_write_string( buf );
socket_transmit( &user->socket, CODE_BLUE, msglen, msgbuf );
msg_begin_writing( msgbuf, &msglen, 4 );
msg_write_int8( MSG_SET_COMM_DELAY );
msg_write_int16( server_frame_delay );
socket_transmit( &user->socket, CODE_BLUE, msglen, msgbuf );
/* send default channels */
msg_begin_writing( msgbuf, &msglen, MAX_MSG_SIZE );
msg_write_int8( MSG_CHANNEL_LIST );
msg_write_int8( default_channel_count );
for ( i = 0; i < default_channel_count; i++ )
msg_write_string( default_channels[i] );
socket_transmit( &user->socket, CODE_BLUE, msglen, msgbuf );
/* send levelset names */
msg_begin_writing( msgbuf, &msglen, MAX_MSG_SIZE );
msg_write_int8( MSG_LEVELSET_LIST );
msg_write_int8( levelsets->count );
list_reset( levelsets );
for ( i = 0; i < levelsets->count; i++ )
msg_write_string( list_next( levelsets ) );
socket_transmit( &user->socket, CODE_BLUE, msglen, msgbuf );
send_full_update( user, main_channel );
return;
failure:
/* send error message as connectionless one as we have no
* connection so far */
errbuf[0] = MSG_ERROR;
net_transmit_connectionless( &net_sender_addr, 2+strlen(errbuf+1), errbuf );
}
/* void handle_command
* IN ServerUser *user
* IN char *cmd_line
*
* Handle command send by the user. The admin has more commands available
* the a normal user.
*/
static void handle_command( ServerUser *user, ServerChannel *channel, char *cmd_line )
{
List *args = parser_explode_string( cmd_line, ' ' );
char *cmd = list_first( args );
char *name, *limit, *text;
int val;
ServerUser *target, *remote;
ServerChannel *target_channel;
LevelSet *lset;
char buf[128];
FILE *file;
if ( cmd == 0 ) return;
if ( !strcmp( cmd, _("search") ) ) {
if ( (name = list_next( args )) == 0 ) {
send_info( user, MSG_SERVER_INFO, _("search: specify a user name!") );
return;
}
if ( (target = search_user( name, &target_channel )) == 0 )
send_info( user, MSG_SERVER_INFO, _("search: user is not online.") );
else {
sprintf( buf, _("search: %s: in channel %s: %s"),
target->name, target_channel->name,
user->game?_("playing"):_("chatting") );
send_info( user, MSG_SERVER_INFO, buf );
}
} else
if ( !strcmp( cmd, _("version") ) ) {
sprintf( buf, _("transmission protocol: %i"), PROTOCOL );
send_info( user, MSG_SERVER_INFO, buf );
} else
if ( !strcmp( cmd, _("info") ) ) {
sprintf( buf, _("user limit: %i#frame rate: %i#packet limit: %i"),
user_limit, server_fps, server_recv_limit );
send_info( user, MSG_SERVER_INFO, buf );
} else
if ( !strcmp( cmd, _("addset") ) && user->admin ) {
if ( (name = list_next( args )) == 0 ) {
send_info( user, MSG_SERVER_INFO, _("addset: name missing") );
return;
}
/* can find levelset? */
if ( (file = levelset_open( name, "r" )) ) {
fclose( file );
lset = levelset_load( name, 0 );
if ( lset ) {
list_add( levelsets, lset );
msg_begin_writing( msgbuf, &msglen, MAX_MSG_SIZE );
msg_write_int8( MSG_ADD_LEVELSET );
msg_write_string( name );
if ( !msg_write_failed() )
broadcast_all( msglen, msgbuf );
}
else
send_info( user, MSG_SERVER_INFO, _("addset: file %s corrupted"), name );
}
else
send_info( user, MSG_SERVER_INFO, _("addset: file %s not found"), name );
} else
if ( !strcmp( cmd, _("addbot") ) && user->admin ) {
if ( (name = list_next( args )) == 0 ) {
send_info( user, MSG_SERVER_INFO, _("addbot: name missing") );
return;
}
if ( (target = find_user_by_name( name )) ) {
send_info( user, MSG_SERVER_INFO, _("addbot: bot '%s' exists"), name );
return;
}
if ( (limit = list_next( args )) == 0 ) {
send_info( user, MSG_SERVER_INFO, _("addbot: strength missing") );
return;
}
channel_add_bot( channel, name, atoi(limit) );
} else
if ( !strcmp( cmd, _("delbot") ) && user->admin ) {
if ( (name = list_next( args )) == 0 ) {
send_info( user, MSG_SERVER_INFO, _("delbot: name missing") );
return;
}
if ( (target = find_user_by_name( name )) == 0 ) {
send_info( user, MSG_SERVER_INFO, _("delbot: bot '%s' not found"), name );
return;
}
channel_remove_user( channel, target );
} else
if ( !strcmp( cmd, _("set") ) && user->admin ) {
if ( (name = list_next( args )) == 0 ) {
send_info( user, MSG_SERVER_INFO, _("set: variable missing") );
return;
}
if ( (limit = list_next( args )) == 0 ) {
send_info( user, MSG_SERVER_INFO, _("set: value missing") );
return;
}
val = atoi( limit );
if ( !strcmp( name, _("userlimit") ) ) {
user_limit = val;
sprintf( buf, _("userlimit: set to %i"), user_limit );
}
else
if ( !strcmp( name, _("packetlimit") ) ) {
server_recv_limit = val;
sprintf( buf, _("packetlimit: set to %i"), server_recv_limit );
}
else
if ( !strcmp( name, _("fps") ) ) {
server_fps = val;
server_frame_delay = 1000/val;
sprintf( buf, _("fps: set to %i"), server_fps );
msg_begin_writing( msgbuf, &msglen, MAX_MSG_SIZE );
msg_write_int8( MSG_SET_COMM_DELAY );
msg_write_int16( server_frame_delay );
broadcast_all( msglen, msgbuf );
}
send_info( user, MSG_SERVER_INFO, buf );
} else
if ( !strcmp( cmd, _("kick") ) && user->admin ) {
if ( (name = list_next( args )) == 0 ) {
send_info( user, MSG_SERVER_INFO, _("kick: specify a user name!") );
return;
}
if ( (target = search_user( name, &target_channel )) == 0 )
send_info( user, MSG_SERVER_INFO, _("kick: user is not online.") );
else {
if ( target->game ) {
/* bring em out of the game and tell the remote
* that the game is killed*/
remote = ((ServerGame*)target->game)->users[0];
if ( remote == target )
remote = ((ServerGame*)target->game)->users[1];
errbuf[0] = MSG_ERROR;
sprintf( errbuf+1, _("Sorry, but your opponent has been kicked!") );
socket_transmit(
&remote->socket, CODE_BLUE,
2+strlen(errbuf+1), errbuf );
server_game_remove( (ServerGame*)target->game );
}
msg_begin_writing( msgbuf, &msglen, MAX_MSG_SIZE );
msg_write_int8( MSG_SERVER_INFO );
msg_printf( _("ADMIN has kicked %s."), target->name );
broadcast_all( msglen, msgbuf );
channel_kick_user( target_channel, target, _("admin kick") );
}
} else
if ( !strcmp( cmd, _("admin_says") ) && user->admin ) {
if ( (text = list_next( args )) == 0 ) {
send_info( user, MSG_SERVER_INFO, _("info: a message is required!") );
return;
}
/* don't show just the first word */
if ( (text = strchr( cmd_line, ' ' )) == 0 ) return; /* will never happen */
msg_begin_writing( msgbuf, &msglen, MAX_MSG_SIZE );
msg_write_int8( MSG_SERVER_INFO );
msg_printf( _("ADMIN says: %s"), text+1 /*don't double the space*/ );
broadcast_all( msglen, msgbuf );
} else
if ( !strcmp( cmd, _("halt") ) && user->admin ) {
server_init_halt();
}
else {
send_info( user, MSG_SERVER_INFO, _("unknown command: %s"), cmd );
}
}
/* void parse_packet_channel
* IN ServerChannel *channel
* IN ServerUser *user
*
* Check all messages in packet from user who is located in channel and
* not playing. The header has been successfully processed and the read
* pointer is at the beginning of the first message. If a message occurs
* that is not handled, the rest of the packet is skipped as we don't
* know its further format.
*/
static void parse_packet_channel( ServerUser *user, ServerChannel *channel )
{
int id;
unsigned char type;
char name[16];
ServerUser *recv;
ServerGameCtx ctx;
ServerChannel *newchannel;
while ( 1 ) {
type = (unsigned)msg_read_int8();
if ( msg_read_failed() ) break; /* no more messages */
switch ( type ) {
case MSG_HEARTBEAT:
/* updates the socket information automatically
* so connection is not closed */
break;
case MSG_DISCONNECT:
user->no_comm = 1; /* receive no more messages */
printf( _("%s (%i) disconnected\n"), user->name, user->id );
channel_remove_user( channel, user );
break;
case MSG_QUIT_GAME:
/* if player looks at error message and breaks up
* game (e.g. waiting for stats) it will send this
* message which is simply ignored */
break;
case MSG_COMMAND:
handle_command( user, channel, msg_read_string() );
break;
case MSG_UNHIDE:
if ( user->hidden )
channel_hide_user( channel, user, 0 );
break;
case MSG_CHATTER:
/* if UNHIDE was dropped user can become visible
* again by simply chatting */
if ( user->hidden )
channel_hide_user( channel, user, 0 );
msg_begin_writing( msgbuf, &msglen, 128 );
msg_write_int8( MSG_CHATTER );
msg_printf( "<%s> %s", user->name, msg_read_string() );
if ( !msg_write_failed() )
channel_broadcast( channel, msglen, msgbuf );
break;
case MSG_WHISPER:
id = msg_read_int32();
recv = find_user_by_id( id ); /* all channels */
if ( recv == 0 ) {
sprintf( errbuf, _("There is no user by that name.") );
send_info( user, MSG_ERROR, errbuf );
} else {
msg_begin_writing( msgbuf, &msglen, MAX_MSG_SIZE );
msg_write_int8( MSG_CHATTER );
msg_printf( "<%s> %s", user->name, msg_read_string() );
if ( !msg_write_failed() ) {
socket_transmit(
&recv->socket,
CODE_BLUE, msglen, msgbuf );
socket_transmit(
&user->socket,
CODE_BLUE, msglen, msgbuf );
}
}
break;
case MSG_ENTER_CHANNEL:
strncpy(name,msg_read_string(),16);
if ( strchr( name, ' ' ) ) {
sprintf( errbuf, _("Channel name must not contain blanks!") );
send_info( user, MSG_ERROR, errbuf );
break;
}
newchannel = channel_find_by_name( name );
if ( newchannel == 0 ) newchannel = channel_add( name );
channel_transfer_user( channel, newchannel, user );
break;
case MSG_OPEN_GAME:
ctx.challenger = user;
id = msg_read_int32();
ctx.challenged = find_user_by_id( id );
strncpy(ctx.name,msg_read_string(),20);
ctx.name[19] = 0;
ctx.diff = msg_read_int8();
ctx.rounds = msg_read_int8();
ctx.frags = msg_read_int8();
ctx.balls = msg_read_int8();
errbuf[0] = 0;
if ( msg_read_failed() )
sprintf( errbuf, _("OpenGame message corrupted!") );
if ( ctx.challenged == 0 )
sprintf( errbuf, _("User with id %i does not exist!"), id );
if ( errbuf[0] != 0 )
send_info( user, MSG_ERROR, errbuf );
else
server_game_add( channel, &ctx );
break;
default:
printf( _("channel %i: %s: invalid message %x: skipping %i bytes\n"),
channel->id,
net_addr_to_string( &user->socket.remote_addr), type,
net_buffer_cur_size - msg_read_pos );
msg_read_pos = net_buffer_cur_size;
break;
}
}
}
/* void find_send_user
* OUT ServerUser **user
*
* Check all channels and games for the user who's socket address equals
* net_sender_addr and return a pointer to it or set '*user' 0 else.
*
* This is a linear search and should be improved.
*/
static void find_send_user( ServerUser **user, ServerChannel **channel, ServerGame **game )
{
*user = 0;
*channel = 0;
*game = 0;
list_reset( channels );
while ( ( *channel = list_next( channels ) ) ) {
list_reset( (*channel)->users );
while ( ( *user = list_next( (*channel)->users ) ) )
if ( net_compare_addr( &net_sender_addr, &(*user)->socket.remote_addr ) ) {
/* we have found the user. check if it is playing a game */
if ( (*user)->game )
*game = (ServerGame*)(*user)->game; /* is a void pointer */
return;
}
}
}
/* void remove_zombies
*
* Close overflowed connections and users that were idle for too long.
*/
static void remove_zombies( void )
{
ServerUser *user, *peer;
ServerChannel *channel;
int cur_time = time(0);
list_reset( channels );
while ( ( channel = list_next( channels ) ) ) {
list_reset( channel->users );
while ( ( user = list_next( channel->users ) ) ) {
if ( user->bot ) continue; /* are never removed */
if ( user->socket.fatal_error ||
cur_time >= user->socket.idle_since + 60 ) {
/* either the code red buffer overflowed or the
* user did not send the heartbeat: a zombie! */
/* bring users to channel if playing */
if ( user->game ) {
peer = user->player_id==0?
((ServerGame*)user->game)->users[1]:
((ServerGame*)user->game)->users[0];
send_info( peer, MSG_ERROR,
_("Remote player has disconnected!") );
server_game_remove( (ServerGame*)user->game );
}
channel_kick_user( channel, user, _("zombie") );
}
}
}
}
/* void handle
* IN int ms milliseconds passed since last call
*
* Receive all packets at the single UDP socket and check wether it is
* connectionless (connection requests) or belongs to a connection (socket).
* Find the user and parse all messages in the packet.
*
* Remove any zombies. (no heartbeat, buffer overflow)
*
* Update games.
*/
static void handle( int ms )
{
int recv_limit;
ServerUser *user = 0;
ServerChannel *channel = 0;
ServerGame *game = 0;
recv_limit = server_recv_limit; /* limited number of packets if not -1 */
while ( net_recv_packet() && ( recv_limit==-1 || recv_limit > 0) ) {
/* handle connectionless packets (login requests) */
if ( msg_is_connectionless() ) {
handle_connectionless_packet();
continue;
}
/* find the sending user and its channel by comparing
* net_sender_addr. */
find_send_user( &user, &channel, &game );
if ( user == 0 ) continue;
/* check if this is a valid packet and update the socket */
if ( !socket_process_header( &user->socket ) ) continue;
/* extract the messages */
if ( game )
parse_packet_game( game, user );
else
if ( channel )
parse_packet_channel( user, channel );
if ( recv_limit != -1 ) recv_limit--;
}
remove_zombies();
update_games( ms );
}
/* (re)load all levelsets from the datadir */
static int load_levelsets( void )
{
DIR *hdir;
struct dirent *dirent;
list_clear( levelsets );
hdir = opendir( SRC_DIR "/levels" );
if ( hdir == 0 ) {
printf( _("couldn't open directory %s!\n"), SRC_DIR "/levels" );
return 0;
}
while ( (dirent = readdir( hdir )) ) {
if ( dirent->d_name[0] == '.' ) continue;
if ( dirent->d_name[0] != 'N' || dirent->d_name[1] != '_' ) continue;
list_add( levelsets, levelset_load( dirent->d_name, 0 ) );
}
printf( _("loaded %i levelsets from directory %s\n"),
levelsets->count, SRC_DIR "/levels" );
closedir( hdir );
return 1;
}
/* display help of command line options */
static void display_help()
{
printf( _("Usage:\n lbreakout2server\n") );
printf( _(" [-p <SERVER_PORT>] Bind server to this port (Default: %i).\n"), server_port );
printf( _(" [-l <USER_LIMIT>] Maximum number of users that can login to server.\n") );
printf( _(" [-m <WELCOME_FILE>] The text in this file is send to new users on login.\n") );
printf( _(" [-a <ADMIN_PWD>] The user logging in as <ADMIN_PWD> will become\n") );
printf( _(" the administrator named 'admin'.\n") );
/* printf( _(" [-D <DATADIR>] In this directory the network levelsets are located.\n") );
printf( _(" Note: To upload levelsets as admin the directory\n") );
printf( _(" must be writeable.\n") );*/
printf( _(" [-f <FRAMERATE>] Number of send/recv handlings in a second.\n") );
printf( _(" (Default: 33)\n") );
printf( _(" [-b <BOTNUM>] Number of paddle bots with 800 and 1000 strength\n"));
printf( _(" each. (Default: 0)\n") );
exit( 0 );
}
/* Parse the command line. */
static void parse_args( int argc, char **argv )
{
int i, len;
FILE *file;
for ( i = 0; i < argc; i++ ) {
if ( !strcmp( "-p", argv[i] ) )
if ( argv[i + 1] )
server_port = atoi( argv[i + 1] );
if ( !strcmp( "-l", argv[i] ) )
if ( argv[i + 1] )
user_limit = atoi( argv[i + 1] );
if ( !strcmp( "-f", argv[i] ) )
if ( argv[i + 1] ) {
server_fps = atoi(argv[i + 1]);
server_frame_delay = 1000/server_fps;
}
if ( !strcmp( "-D", argv[i] ) )
if ( argv[i + 1] )
strncpy(datadir,argv[i + 1],128);
if ( !strcmp( "-h", argv[i] ) || !strcmp( "--help", argv[i] ) )
display_help();
if ( !strcmp( "-m", argv[i] ) )
if ( argv[i + 1] ) {
file = fopen( argv[i+1], "r" );
if ( file == 0 )
{
printf( _("greetings file not found, setting directly: %s\n"), argv[i+1] );
snprintf(greetings,256,"%s",argv[i+1]);
}
else {
len = fread( greetings, 1, 255, file );
greetings[len] = 0;
fclose( file );
printf( _("greetings loaded: %s\n"), argv[i+1] );
}
}
if ( !strcmp( "-a", argv[i] ) )
if ( argv[i + 1] )
strncpy(admin_pwd,argv[i + 1],15);
if ( !strcmp( "-b", argv[i] ) )
if ( argv[i + 1] )
server_def_bot_num = atoi(argv[i + 1]);
}
}
/* Initiate network connection and lists. */
static void finalize()
{
/* disconnect all users */
errbuf[0] = MSG_DISCONNECT;
broadcast_all( 1, errbuf );
/* free lists */
if ( channels )
list_delete( channels );
if ( games )
list_delete( games );
if ( levelsets )
list_delete( levelsets );
/* close server socket */
net_shutdown();
/* free default channel title */
free(default_channels[0]);
printf( _("server halted\n") );
}
static void init( int argc, char **argv )
{
char name[16];
int id = 1, j;
/* i18n */
#ifdef ENABLE_NLS
setlocale (LC_ALL, "");
bindtextdomain (PACKAGE, LOCALEDIR);
textdomain (PACKAGE);
#endif
/* initiate sdl timer */
SDL_Init( SDL_INIT_TIMER );
/* set signal handler to cleanly shutdown by CTRL-C */
signal( SIGINT, signal_handler );
/* welcome message */
snprintf(greetings,256,_("Welcome to LBreakout2 online!"));
/* default channel */
default_channels[0] = strdup(_("MAIN"));
/* parse command line options */
parse_args( argc, argv );
/* open single UDP socket */
if ( !net_init( server_port ) ) exit(1);
/* create empty lists */
channels = list_create( LIST_AUTO_DELETE, channel_delete );
games = list_create( LIST_AUTO_DELETE, server_game_delete );
levelsets= list_create( LIST_AUTO_DELETE, levelset_list_delete );
if ( channels == 0 || games == 0 || levelsets == 0 ) exit(1);
/* load levelset names */
load_levelsets();
printf( _("user limit is %i\n"), user_limit );
printf( _("FPS: %i (delay: %i ms)\n"), 1000/server_frame_delay, server_frame_delay );
/* add default channels */
create_default_channels();
/* add default bots */
for ( j = 0; j < server_def_bot_num; j++,id++ )
{
snprintf( name, 16, _("BOT%i-800"), id );
channel_add_bot( main_channel, name, 800 );
}
for ( j = 0; j < server_def_bot_num; j++,id++ )
{
snprintf( name, 16, _("BOT%i-1000"), id );
channel_add_bot( main_channel, name, 1000 );
}
/* build angle table */
init_angles();
}
static void signal_handler( int signal )
{
switch ( signal ) {
case SIGINT:
if ( server_halt ) break;
server_init_halt();
break;
}
}
#endif
/***** PUBLIC FUNCTIONS ****************************************************/
int main( int argc, char **argv )
{
#ifdef NETWORK_ENABLED
int last_ticks, cur_ticks;
int ms = 0;
set_random_seed(); /* set random seed */
init( argc, argv );
/* loop and handle messages until shutdown */
last_ticks = cur_ticks = SDL_GetTicks();
while ( 1 ) {
last_ticks = cur_ticks; cur_ticks = SDL_GetTicks();
ms += cur_ticks - last_ticks;
if ( ms > server_frame_delay ) {
handle( ms );
ms -= server_frame_delay;
}
if ( server_halt && time( 0 ) > server_halt_since + 5 )
break;
SDL_Delay( 5 );
}
finalize();
#else
printf( _("LBreakout2 has been compiled without network support.\n") );
#endif
return 0;
}