Logo Search packages:      
Sourcecode: lbreakout2 version File versions  Download package

server.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 to LBreakout2 online!"; /* 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 = 0; /* 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[] = {
      "MAIN"
};

/***** 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 );
                  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 ) );
      }
      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 not found: %s\n", 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();
      
      printf( "server halted\n" );
}
static void init( int argc, char **argv )
{
    char name[16];
    int  id = 1, j;
      /* initiate sdl timer */
      SDL_Init( SDL_INIT_TIMER );
      
      /* set signal handler to cleanly shutdown by CTRL-C */
      signal( SIGINT, signal_handler );
      
      /* 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;
}


Generated by  Doxygen 1.6.0   Back to index