617 lines
18 KiB
C
617 lines
18 KiB
C
/***************************************************************************
|
|
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_x<src_x)?-1:(dest_x>src_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-change<dest_x+entropy )
|
|
paddle->cur_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 <user> 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
|
|
|