/*************************************************************************** server_game.c - description ------------------- begin : 03/03/19 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 "server.h" /***** EXTERNAL VARIABLES **************************************************/ extern List *games; extern int global_id; extern char errbuf[128]; /* used to compile error messages */ extern char msgbuf[MAX_MSG_SIZE]; /* used to compile messages */ extern int msglen; extern List *levelsets; extern char net_buffer[MAX_MSG_SIZE + PACKET_HEADER_SIZE]; extern int server_frame_delay; extern int msg_read_pos, net_buffer_cur_size; extern void send_info( ServerUser *user, int type, char *format, ... ); extern void channel_hide_user( ServerChannel *channel, ServerUser *user, int hide ); extern void channel_remove_user( ServerChannel *channel, ServerUser *user ); extern void send_full_update( ServerUser *user, ServerChannel *channel ); /***** EXPORTS *************************************************************/ /***** FORWARDED DECLARATIONS **********************************************/ /***** LOCAL TYPE DEFINITIONS **********************************************/ /***** LOCAL VARIABLES *****************************************************/ /***** LOCAL FUNCTIONS *****************************************************/ #ifdef NETWORK_ENABLED /* update the position of top paddle */ static void update_bot_paddle( Game *game, int ms ) { int src_x, dest_x, dir; Ball *ball, *min_ball = 0; Extra *extra, *min_extra = 0; Paddle *paddle = game->paddles[PADDLE_TOP]; int move = 0; static int entropy = 0; float change; /* always fire */ paddle->fire_left = 1; /* get nearest ball */ list_reset( game->balls ); while ( ( ball = list_next( game->balls ) ) ) { if ( ball->attached ) continue; if ( min_ball == 0 || ball->y < min_ball->y ) min_ball = ball; } /* get nearest extra */ list_reset( game->extras ); while ( ( extra = list_next( game->extras ) ) ) { if ( extra->dir != -1 ) continue; if ( min_extra == 0 || extra->y < min_extra->y ) min_extra = extra; } src_x = paddle->x + paddle->w/2; dest_x = paddle->x + paddle->w/2; if ( min_ball || min_extra ) { if ( min_ball && ( min_extra == 0 || min_ball->y < min_extra->y ) ) { dest_x = min_ball->x + 6; move = 1; } else if ( min_extra && ( min_ball == 0 || min_extra->y < min_ball->y ) ) { dest_x = min_extra->x + 20; move = 1; } } dir = (dest_xsrc_x)?1:0; entropy = (rand() % 17)-8; if ( move && dir != 0 ) { change = paddle->bot_vx * ms; /* due to high 'ms' the change might be so much that * the paddle would start to jump epileptically, so * set position to 'dest' then */ if ( dir < 0 && src_x-changecur_x = dest_x+entropy - paddle->w/2; else if ( dir > 0 && src_x+change>dest_x-entropy ) paddle->cur_x = dest_x-entropy - paddle->w/2; else paddle->cur_x += change * dir; if ( paddle->cur_x < BRICK_WIDTH ) paddle->cur_x = BRICK_WIDTH; if ( paddle->cur_x + paddle->w >= 640 - BRICK_WIDTH ) paddle->cur_x = 640 - BRICK_WIDTH - paddle->w; paddle->x = (int)paddle->cur_x; } } static LevelSet *find_levelset( char *name ) { LevelSet *set; list_reset( levelsets ); while ( (set = list_next( levelsets ) ) ) if ( !strcmp( set->name, name ) ) return set; return 0; } static void send_level( Level *level, ServerUser *user, int l_pos ) { if ( user->bot ) return; msgbuf[0] = MSG_LEVEL_DATA; msgbuf[1] = l_pos; msglen = 2; comm_pack_level( level, msgbuf, &msglen ); socket_transmit( &user->socket, CODE_BLUE, msglen, msgbuf ); } static void init_next_round( ServerGame *game ) { game->cur_round++; game->cur_level = game->cur_round / game->rounds_per_level; game_init( game->game, game->set->levels[game->cur_level] ); /* send level and wait for ready */ game->state = SERVER_AWAIT_READY; game->ready[0] = game->ready[1] = 0; send_level( game->set->levels[game->cur_level], game->users[0], PADDLE_BOTTOM ); if ( !game->users[1]->bot ) send_level( game->set->levels[game->cur_level], game->users[1], PADDLE_TOP ); else game->ready[1] = 1; /* bot is always the challenged one */ /* set up bot top paddle if any */ if ( game->users[1]->bot ) game->game->paddles[PADDLE_TOP]->bot_vx = 0.001 * game->users[1]->bot_level; } static void finalize_round( ServerGame *game ) { /* update stats */ game_update_stats( 0, &game->stats[0] ); game_update_stats( 1, &game->stats[1] ); /* finalize */ game_finalize( game->game ); /* tell clients that round is over */ if ( game->cur_round == game->rounds-1 ) msgbuf[0] = MSG_LAST_ROUND_OVER; else msgbuf[0] = MSG_ROUND_OVER; msgbuf[1] = game->game->winner; msglen = 2; socket_transmit( &game->users[0]->socket, CODE_BLUE, msglen, msgbuf ); if ( !game->users[1]->bot ) socket_transmit( &game->users[1]->socket, CODE_BLUE, msglen, msgbuf ); /* if this was the last round set game_over */ if ( game->cur_round == game->rounds-1 ) game->game_over = 1; } /* send game statistics were the first stats is the user it is send * to and the second is the opponents stats */ static void send_stats( ServerUser *user, GameStats *stats1, GameStats *stats2 ) { int count; int kept[2] = {0,0}, bricks[2] = {0,0}, extras[2] = {0,0}; if ( user->bot ) return; count = stats1->balls_reflected + stats1->balls_lost; if ( count > 0 ) kept[0] = 100 * stats1->balls_reflected / count; count = stats2->balls_reflected + stats2->balls_lost; if ( count > 0 ) kept[1] = 100 * stats2->balls_reflected / count; if ( stats1->total_brick_count > 0 ) bricks[0] = 100 * stats1->bricks_cleared / stats1->total_brick_count; if ( stats2->total_brick_count > 0 ) bricks[1] = 100 * stats2->bricks_cleared / stats2->total_brick_count; if ( stats1->total_extra_count > 0 ) extras[0] = 100 * stats1->extras_collected / stats1->total_extra_count; if ( stats2->total_extra_count > 0 ) extras[1] = 100 * stats2->extras_collected / stats2->total_extra_count; msg_begin_writing( msgbuf, &msglen, MAX_MSG_SIZE ); msg_write_int8( MSG_GAME_STATS ); msg_write_int8( stats1->wins ); msg_write_int8( stats2->wins ); msg_write_int8( stats1->losses ); msg_write_int8( stats2->losses ); msg_write_int8( stats1->draws ); msg_write_int8( stats2->draws ); msg_write_int32( stats1->total_score ); msg_write_int32( stats2->total_score ); msg_write_int8( kept[0] ); msg_write_int8( kept[1] ); msg_write_int8( bricks[0] ); msg_write_int8( bricks[1] ); msg_write_int8( extras[0] ); msg_write_int8( extras[1] ); socket_transmit( &user->socket, CODE_BLUE, msglen, msgbuf ); } /***** PUBLIC FUNCTIONS ****************************************************/ /* Add a new game by the context information, hide both users * and send a challenge message to the challenged user. */ void server_game_add( ServerChannel *channel, ServerGameCtx *ctx ) { ServerGame *game = salloc( 1, sizeof( ServerGame ) ); /* copy game data */ game->state = SERVER_AWAIT_ACCEPT; game->id = global_id++; game->channel = channel; game->set = find_levelset( ctx->name ); if ( game->set == 0 ) { /* should never happen... */ sprintf( errbuf, "game_create_failed: no levelset '%s' found\n", ctx->name ); send_info( ctx->challenger, MSG_ERROR, errbuf ); free( game ); return; } game->rounds_per_level = ctx->rounds; game->rounds = game->set->count * game->rounds_per_level; game->cur_round = -1; /* init_next_round will increase this to 0 */ /* create game module */ if ( (game->game = game_create( GT_NETWORK, ctx->diff, 100/*no rel warp*/ )) == 0 ) { /* send error to user */ strncpy(errbuf,"game_create failed: out of memory",128); send_info( ctx->challenger, MSG_ERROR, errbuf ); free( game ); return; } game_set_current( game->game ); game_set_ball_ammo( ctx->balls ); game_set_frag_limit( ctx->frags ); game_set_convex_paddle( 1 ); game_set_ball_random_angle( 1 ); /* set game for both users and set both users for game */ ctx->challenger->game = game; ctx->challenged->game = game; ctx->challenger->player_id = 0; ctx->challenged->player_id = 1; game->users[0] = ctx->challenger; game->users[1] = ctx->challenged; /* hide both users */ channel_hide_user( channel, ctx->challenger, 1 ); channel_hide_user( channel, ctx->challenged, 1 ); /* inform challenged user */ if ( !ctx->challenged->bot ) { msg_begin_writing( msgbuf, &msglen, MAX_MSG_SIZE ); msg_write_int8( MSG_CHALLENGE ); msg_write_string( ctx->challenger->name ); msg_write_string( ctx->name ); msg_write_int8( ctx->diff ); msg_write_int8( ctx->rounds ); msg_write_int8( ctx->frags ); msg_write_int8( ctx->balls ); socket_transmit( &ctx->challenged->socket, CODE_BLUE, msglen, msgbuf ); } else { /* instantly accept as bot */ msgbuf[0] = MSG_ACCEPT_CHALLENGE; socket_transmit( &game->users[0]->socket, CODE_BLUE, 1, msgbuf ); init_next_round( game ); } list_add( games, game ); printf( _("game added: %s (%i): %i rounds: %s vs. %s\n"), game->set->name, game->id, game->rounds, game->users[0]->name, game->users[1]->name ); } /* Free game memory. */ void server_game_delete( void *ptr ) { ServerGame *game = (ServerGame*)ptr; if ( game ) { printf( _("game deleted: %s (%i)\n"), game->set->name, game->id ); if ( game->game ) game_delete( &game->game ); free( game ); } } /* unhide the users to their chat channel and delete the game. * if game was beyond state AWAIT_ACCEPT the game stats are send */ void server_game_remove( ServerGame *game ) { int i; /* users are not unhidden if the actual game has already started * as they need time to read the error messages (if any) then */ if ( game->state == SERVER_AWAIT_ACCEPT ) for ( i = 0; i < 2; i++ ) { if ( game->users[i]->hidden ) channel_hide_user( game->channel, game->users[i], 0 ); } else { /* send stats */ send_stats( game->users[0], &game->stats[0], &game->stats[1] ); send_stats( game->users[1], &game->stats[1], &game->stats[0] ); /* and unhide bot if any */ if ( game->users[1]->bot ) channel_hide_user( game->channel, game->users[1], 0 ); } /* clear user game pointer */ game->users[0]->game = 0; game->users[1]->game = 0; /* free memory */ list_delete_item( games, game ); } /* void parse_packet_game * IN ServerGame *game * IN ServerUser *user * * Check all messages in packet from user who is currently within * a game. The header has already been successfully processed and * the read pointer is at the beginning of the first message. */ void parse_packet_game( ServerGame *game, ServerUser *user ) { ServerUser *peer; unsigned char type; int handled, i; game_set_current( game->game ); while ( 1 ) { type = (unsigned)msg_read_int8(); handled = 0; msglen = 0; /* the extract functions require a position pointer */ if ( msg_read_failed() ) break; /* no more messages */ /* general messages */ switch ( type ) { case MSG_HEARTBEAT: /* updates the socket information automatically * so connection is not closed */ handled = 1; break; case MSG_DISCONNECT: /* update stats and finalize context if playing */ if ( game->state != SERVER_AWAIT_ACCEPT ) { game->game->winner = -1; /* count unfinished level as draw */ game_update_stats( 0, &game->stats[0] ); game_update_stats( 1, &game->stats[1] ); game_finalize( game->game ); } if ( user == game->users[0] ) peer = game->users[1]; else peer = game->users[0]; send_info( peer, MSG_ERROR, _("Remote player has disconnected...") ); server_game_remove( game ); printf( _("%s (%i) disconnected\n"), user->name, user->id ); channel_remove_user( game->channel, user ); handled = 1; break; case MSG_QUIT_GAME: if ( user == game->users[0] ) peer = game->users[1]; else peer = game->users[0]; send_info( peer, MSG_ERROR, _("Remote player has left the game...") ); /* update stats and finalize context */ game->game->winner = -1; /* count unfinished level as draw */ game_update_stats( 0, &game->stats[0] ); game_update_stats( 1, &game->stats[1] ); game_finalize( game->game ); server_game_remove( game ); handled = 1; break; case MSG_UNHIDE: /* it's very unlikely that the user sends this * message while being in the game context but to be sure * he may unhide here */ if ( user->hidden ) channel_hide_user( game->channel, user, 0 ); handled = 1; break; } /* challenge */ if ( game->state == SERVER_AWAIT_ACCEPT ) switch ( type ) { case MSG_ACCEPT_CHALLENGE: if ( user == game->users[1] ) { /* inform opponent */ msgbuf[0] = MSG_ACCEPT_CHALLENGE; socket_transmit( &game->users[0]->socket, CODE_BLUE, 1, msgbuf ); init_next_round( game ); handled = 1; } break; case MSG_REJECT_CHALLENGE: if ( user == game->users[1] ) { /* tell challenger that you refused the offer */ msgbuf[0] = MSG_REJECT_CHALLENGE; socket_transmit( &game->users[0]->socket, CODE_BLUE, 1, msgbuf ); server_game_remove( game ); handled = 1; } break; case MSG_CANCEL_GAME: if ( user == game->users[0] ) { /* tell challenged that you cancelled the offer */ msgbuf[0] = MSG_CANCEL_GAME; socket_transmit( &game->users[1]->socket, CODE_BLUE, 1, msgbuf ); server_game_remove( game ); handled = 1; } break; } /* preparation */ if ( game->state == SERVER_AWAIT_READY ) if ( type == MSG_READY ) { game->ready[user->player_id] = 1; if ( game->ready[0] && game->ready[1] ) game->state = SERVER_PLAY; handled = 1; } /* in-game messages */ if ( game->state == SERVER_PLAY ) switch ( type ) { case MSG_PADDLE_STATE: comm_unpack_paddle( game->game->paddles[user->player_id], net_buffer, &msg_read_pos ); handled = 1; break; case MSG_PAUSE: game->state = SERVER_PAUSE; msgbuf[0] = MSG_PAUSE; msglen = 1; if ( user == game->users[0] ) peer = game->users[1]; else peer = game->users[0]; if ( !peer->bot ) socket_transmit( &peer->socket, CODE_BLUE, msglen, msgbuf ); handled = 1; break; } /* pause messages */ if ( game->state == SERVER_PAUSE ) switch ( type ) { case MSG_UNPAUSE: game->state = SERVER_PLAY; for ( i = 0; i < game->game->paddle_count; i++ ) game->game->paddles[i]->last_ball_contact = SDL_GetTicks(); msgbuf[0] = MSG_UNPAUSE; msglen = 1; if ( user == game->users[0] ) peer = game->users[1]; else peer = game->users[0]; if ( !peer->bot ) socket_transmit( &peer->socket, CODE_BLUE, msglen, msgbuf ); handled = 1; break; case MSG_CHATTER: /* client has added prefix so simply pass it * to the remote user */ msg_begin_writing( msgbuf, &msglen, MAX_MSG_SIZE ); msg_write_int8( MSG_CHATTER ); msg_write_string( msg_read_string() ); if ( !msg_write_failed() ) { if ( user == game->users[0] ) peer = game->users[1]; else peer = game->users[0]; if ( !peer->bot ) socket_transmit( &peer->socket, CODE_BLUE, msglen, msgbuf ); } handled = 1; break; } if ( !handled ) { printf( _("game %i: %s: state %i: invalid message %x: skipping %i bytes\n"), game->id, net_addr_to_string( &user->socket.remote_addr ), game->state, type, net_buffer_cur_size - msg_read_pos ); msg_read_pos = net_buffer_cur_size; } } } /* void update_games * IN int ms milliseconds passed since last call * * Update the objects of all games that are actually playing. */ void update_games( int ms ) { int i; ServerGame *game; list_reset( games ); while ( (game = list_next( games ) ) ) { if ( game->state != SERVER_PLAY ) continue; game_set_current( game->game ); game_update( ms ); /* send updates to remote players */ if ( game->game->level_over ) { finalize_round( game ); if ( game->game_over ) server_game_remove( game ); else init_next_round( game ); continue; } /* if playing against a bot update the top paddle */ if ( game->users[1]->bot ) update_bot_paddle( game->game, ms ); /* pack update */ msglen = 0; msgbuf[msglen++] = MSG_PADDLE_STATE; comm_pack_paddle( game->game->paddles[1], msgbuf, &msglen ); msgbuf[msglen++] = MSG_BALL_POSITIONS; comm_pack_balls( msgbuf, &msglen ); if ( game->game->shots->count > 0 ) { msgbuf[msglen++] = MSG_SHOT_POSITIONS; comm_pack_shots( msgbuf, &msglen ); } msgbuf[msglen++] = MSG_SCORES; comm_pack_scores( msgbuf, &msglen ); if ( game->game->mod.brick_hit_count > 0 ) { msgbuf[msglen++] = MSG_BRICK_HITS; comm_pack_brick_hits( msgbuf, &msglen ); } if ( game->game->mod.collected_extra_count[0] > 0 || game->game->mod.collected_extra_count[1] > 0 ) { msgbuf[msglen++] = MSG_NEW_EXTRAS; comm_pack_collected_extras( msgbuf, &msglen ); } /* send packet */ socket_transmit( &game->users[0]->socket, CODE_BLUE, msglen, msgbuf ); /* replace paddle which has a constant size */ i = 1; comm_pack_paddle( game->game->paddles[0], msgbuf, &i ); if ( !game->users[1]->bot ) socket_transmit( &game->users[1]->socket, CODE_BLUE, msglen, msgbuf ); game_reset_mods(); } } #endif