/*************************************************************************** comm.c - description ------------------- begin : Fri Aug 2 2002 copyright : (C) 2001 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. * * * ***************************************************************************/ #include "lbreakout.h" #include "../gui/gui.h" #include "../game/game.h" #include "game.h" #include "client_data.h" #include "bricks.h" #include "extras.h" #include "comm.h" extern char net_buffer[MAX_MSG_SIZE + PACKET_HEADER_SIZE]; extern int net_buffer_cur_size; extern int msg_read_pos; extern char msgbuf[MAX_MSG_SIZE]; extern int msglen; extern NetSocket client; extern StkFont *chat_font_error; extern int client_is_connected; extern int client_state; extern Game *game, *local_game; extern Player *cur_player; extern Paddle *r_paddle; #ifdef AUDIO_ENABLED extern StkSound *wav_shot, *wav_reflect_paddle, *wav_reflect_brick, *wav_attach; #endif extern int game_stats[2][7]; extern int game_over; extern int client_comm_delay; extern int stats_received; extern GuiWidget *dlg_pauseroom; extern List *client_levelsets; extern int client_recv_limit; extern void set_state( int newstate ); extern void init_next_round( void ); extern void display_final_stats( void ); extern void open_pause_chat( char *text ); extern void close_pause_chat( void); extern int last_ball_brick_reflect_x; /* HACK: used to play local sound */ extern int last_ball_paddle_reflect_x; /* HACK: used to play local sound */ extern int last_ball_attach_x; /* HACK: used to play local sound */ extern int last_shot_fire_x; /* HACK: used to play local sound */ /* handle modifications game::mod_* which were either copied * from local_game or received via network, apply the changes to * game and clear these entries. the current game entry must've * been set to 'game'. Ball and shot updates have already been * applied to the game context. */ #ifdef NETWORK_ENABLED static void handle_mods() { BrickHit *brickhits; int *extras, count; int i, j; #ifdef AUDIO_ENABLED /* play sounds for attached, reflected balls or fired shots */ /* HACK: in a local game use externals from game/balls.c * last_ball_*_x to play sound stereo. this cannot be used for * network games */ if ( local_game == 0 ) { if ( game_get_fired_shot_count() ) stk_sound_play( wav_shot ); if ( game_get_brick_reflected_ball_count() ) stk_sound_play( wav_reflect_brick ); if ( game_get_paddle_reflected_ball_count() ) stk_sound_play( wav_reflect_paddle ); if ( game_get_attached_ball_count() ) stk_sound_play( wav_attach ); } else { if ( last_ball_brick_reflect_x != -1 ) stk_sound_play_x( last_ball_brick_reflect_x, wav_reflect_brick ); if ( last_ball_paddle_reflect_x != -1 ) stk_sound_play_x( last_ball_paddle_reflect_x, wav_reflect_paddle ); if ( last_ball_attach_x != -1 ) stk_sound_play_x( last_ball_attach_x, wav_attach ); if ( last_shot_fire_x != -1 ) stk_sound_play_x( last_shot_fire_x, wav_shot ); last_ball_brick_reflect_x = -1; last_ball_paddle_reflect_x = -1; last_ball_attach_x = -1; last_shot_fire_x = -1; } #endif /* handle brick hits and create new extras. the extras * are moved independently by the client and destroyed on * collection but DO NOT TAKE EFFECT */ brickhits = game_get_brick_hits( &count ); for ( i = 0; i < count; i++ ) client_handle_brick_hit( &brickhits[i] ); /* these collected extras take effect */ for ( i = 0; i < game->paddle_count; i++ ) { extras = game_get_collected_extras( i, &count ); for ( j = 0; j < count; j++ ) client_handle_collected_extra( game->paddles[i], extras[j] ); } game_reset_mods(); } #endif #ifdef NETWORK_ENABLED /* copy stuff from local_game to game */ static void comm_recv_local( void ) { Ball *ball, *ballcopy; Shot *shot, *shotcopy; /* copy balls */ list_clear( game->balls ); list_reset( local_game->balls ); while ( (ball = list_next(local_game->balls)) ) { ballcopy = salloc( 1, sizeof(Ball) ); ballcopy->x = ball->x; ballcopy->y = ball->y; ballcopy->attached = ball->attached; if ( ball->attached ) { if ( ball->paddle == local_game->paddles[0] ) ballcopy->paddle = game->paddles[0]; else ballcopy->paddle = game->paddles[1]; } list_add( game->balls, ballcopy ); } /* copy shots */ list_clear( game->shots ); list_reset( local_game->shots ); while ( (shot = list_next(local_game->shots)) ) { shotcopy = salloc( 1, sizeof(Shot) ); shotcopy->x = shot->x; shotcopy->y = shot->y; list_add( game->shots, shotcopy ); } /* copy score */ game->paddles[0]->score = local_game->paddles[0]->score; /* copy level_over */ game->level_over = local_game->level_over; game->winner = local_game->winner; /* modifications are transferred to game::mod * and handled by the same function that does this * for net updates below. while handle_mods() * resets the mods in the game struct, the * local_game mods must be cleared as well */ game->mod = local_game->mod; handle_mods(); local_game->mod = game->mod; /* copy new level when altered in bonus level */ if (local_game->blRefreshBricks) { local_game->blRefreshBricks = 0; /* HACK HACK HACK HACK.... did I mention this is a hack? And a really bad one indeed. */ memcpy(game->bricks,local_game->bricks,sizeof(game->bricks)); client_redraw_all_bricks(); } return; } /* parse incoming packets and handle them according to the client's * state */ static void comm_parse_packet() { BrickHit *brickhits; unsigned char type; int *extras, count; int i, j; char name[16]; int handled; /* the list of shots is reset before as shot_positions * will not be send when no shots are available */ list_clear( game->shots ); while ( 1 ) { type = (unsigned)msg_read_int8(); handled = 0; if ( msg_read_failed() ) break; /* no more messages */ msglen = 0; /* game unpack functions require a position pointer which is always 0 at the moment */ switch ( type ) { case MSG_SET_COMM_DELAY: client_comm_delay = msg_read_int16(); printf( _("comm_delay set to %i\n"), client_comm_delay ); handled = 1; break; case MSG_DISCONNECT: /* close the pause chatroom window if open */ if ( client_state == CS_PAUSE ) close_pause_chat(); set_state( CS_FATAL_ERROR ); display_text( chat_font_error, _("You've been disconnected.") ); client_data_clear(); client_is_connected = 0; handled = 1; break; case MSG_ERROR: /* close the pause chatroom window if open */ if ( client_state == CS_PAUSE ) close_pause_chat(); /* break up game but wait for the stats */ set_state( CS_FATAL_ERROR ); display_text( chat_font_error, msg_read_string() ); handled = 1; break; case MSG_LEVEL_DATA: cur_player->next_paddle_id = msg_read_int8(); /* top or bottom? */ msg_read( 536, msgbuf ); if ( !msg_read_failed() ) { comm_unpack_level( &cur_player->snapshot, msgbuf, &msglen ); cur_player->next_level_received = 1; handled = 1; } else { set_state( CS_FATAL_ERROR ); display_text( chat_font_error, _("corrupted level data") ); } break; case MSG_ADD_USER: i = msg_read_int32(); snprintf( name, 16, "%s", msg_read_string() ); name[15] = 0; if ( msg_read_failed() ) break; client_add_user( i, name ); handled = 1; break; case MSG_REMOVE_USER: i = msg_read_int32(); if ( msg_read_failed() ) break; client_remove_user( i ); handled = 1; break; case MSG_ADD_LEVELSET: list_add( client_levelsets, strdup(msg_read_string()) ); handled = 1; break; case MSG_PAUSE: open_pause_chat( _("Remote player has paused the game.") ); handled = 1; break; case MSG_UNPAUSE: close_pause_chat(); handled = 1; break; case MSG_CHATTER: client_add_pausechatter( msg_read_string(), 0 ); handled = 1; break; } /* game_only packets but received any time */ //if ( client_state == CS_PLAY ) switch ( type ) { case MSG_PADDLE_STATE: comm_unpack_paddle( r_paddle, net_buffer, &msg_read_pos ); handled = 1; break; case MSG_SHOT_POSITIONS: comm_unpack_shots( net_buffer, &msg_read_pos ); handled = 1; break; case MSG_BALL_POSITIONS: comm_unpack_balls( net_buffer, &msg_read_pos ); #ifdef AUDIO_ENABLED /* play sounds for attached, reflected balls or fired shots */ if ( game_get_fired_shot_count() ) stk_sound_play( wav_shot ); if ( game_get_brick_reflected_ball_count() ) stk_sound_play( wav_reflect_brick ); if ( game_get_paddle_reflected_ball_count() ) stk_sound_play( wav_reflect_paddle ); if ( game_get_attached_ball_count() ) stk_sound_play( wav_attach ); game->mod.fired_shot_count = 0; game->mod.attached_ball_count = 0; game->mod.brick_reflected_ball_count = 0; game->mod.paddle_reflected_ball_count = 0; #endif handled = 1; break; case MSG_SCORES: comm_unpack_scores( net_buffer, &msg_read_pos ); handled = 1; break; case MSG_BRICK_HITS: comm_unpack_brick_hits( net_buffer, &msg_read_pos ); /* handle brick hits and create new extras. the extras * are moved independently by the client and destroyed on * collection but DO NOT TAKE EFFECT */ brickhits = game_get_brick_hits( &count ); for ( i = 0; i < count; i++ ) client_handle_brick_hit( &brickhits[i] ); game->mod.brick_hit_count = 0; handled = 1; break; case MSG_NEW_EXTRAS: comm_unpack_collected_extras( net_buffer, &msg_read_pos ); /* these collected extras take effect */ for ( i = 0; i < game->paddle_count; i++ ) { extras = game_get_collected_extras( i, &count ); for ( j = 0; j < count; j++ ) client_handle_collected_extra( game->paddles[i], extras[j] ); } game->mod.collected_extra_count[0] = 0; game->mod.collected_extra_count[1] = 0; handled = 1; break; case MSG_ROUND_OVER: game->level_over = 1; game->winner = msg_read_int8(); handled = 1; break; case MSG_LAST_ROUND_OVER: game->level_over = 1; game->winner = msg_read_int8(); game_over = 1; handled = 1; break; } /* stats may arrive anywhere */ switch ( type ) { case MSG_GAME_STATS: memset( game_stats, 0, sizeof( game_stats ) ); game_stats[0][0] = msg_read_int8(); game_stats[1][0] = msg_read_int8(); game_stats[0][1] = msg_read_int8(); game_stats[1][1] = msg_read_int8(); game_stats[0][2] = msg_read_int8(); game_stats[1][2] = msg_read_int8(); game_stats[0][3] = msg_read_int32(); game_stats[1][3] = msg_read_int32(); game_stats[0][4] = msg_read_int8(); game_stats[1][4] = msg_read_int8(); game_stats[0][5] = msg_read_int8(); game_stats[1][5] = msg_read_int8(); game_stats[0][6] = msg_read_int8(); game_stats[1][6] = msg_read_int8(); stats_received = 1; handled = 1; break; } if ( !handled ) { printf( _("game: state %i: invalid message %x: skipping %i bytes\n"), client_state, type, net_buffer_cur_size - msg_read_pos ); msg_read_pos = net_buffer_cur_size; } } } #endif /* send state of local paddle */ void comm_send_paddle( Paddle *paddle ) { if ( game->game_type == GT_LOCAL ) { /* simply copy the data */ if ( local_game->paddles[PADDLE_BOTTOM]->x != paddle->x ) local_game->paddles[PADDLE_BOTTOM]->invis_delay = PADDLE_INVIS_DELAY; local_game->paddles[PADDLE_BOTTOM]->x = paddle->x; local_game->paddles[PADDLE_BOTTOM]->fire_left = paddle->fire_left; local_game->paddles[PADDLE_BOTTOM]->fire_right = paddle->fire_right; local_game->paddles[PADDLE_BOTTOM]->ball_return_key_pressed = paddle->ball_return_key_pressed; local_game->paddles[PADDLE_BOTTOM]->maxballspeed_request = paddle->maxballspeed_request; local_game->paddles[PADDLE_BOTTOM]->maxballspeed_request_old = paddle->maxballspeed_request_old; return; } msgbuf[0] = MSG_PADDLE_STATE; msglen = 1; comm_pack_paddle( paddle, msgbuf, &msglen ); client_transmit( CODE_BLUE, msglen, msgbuf ); } void comm_send_short( int msg ) { if ( game->game_type == GT_LOCAL ) return; /* send these messages as code blue. if they are dropped * the player and server will notice as nothing happens */ msgbuf[0] = msg; client_transmit( CODE_BLUE, 1, msgbuf ); } /* receive any data but only handle such messages valid * in the current state. */ void comm_recv( void ) { #ifdef NETWORK_ENABLED int recv_limit; /* fake communication in local game */ if ( game->game_type == GT_LOCAL ) { comm_recv_local(); return; } if ( !client_is_connected ) return; recv_limit = client_recv_limit; /* limited number of packets if not -1 */ while ( net_recv_packet() && ( recv_limit==-1 || recv_limit > 0) ) { /* check if this is a valid packet and update the socket */ if ( msg_is_connectionless() ) msg_begin_connectionless_reading(); else if ( !socket_process_header( &client ) ) continue; comm_parse_packet(); if ( recv_limit != -1 ) recv_limit--; } #endif } /* send a heartbeat every 2 seconds except in the actual game as we * send paddle updates there */ void comm_send_heartbeat( void ) { static int last_heartbeat = 0; if ( client_state != CS_PLAY ) if ( time(0) >= last_heartbeat + 3 ) { last_heartbeat = time(0); msgbuf[0] = MSG_HEARTBEAT; client_transmit( CODE_BLUE, 1, msgbuf ); } }