/***************************************************************************
                          local_game.c  -  description
                             -------------------
    begin                : Thu Sep 6 2001
    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 "../game/game.h"
#include "../gui/gui.h"
#include "client_data.h"
#include "event.h"
#include "config.h"
#include "shrapnells.h"
#include "player.h"
#include "display.h"
#include "paddle.h"
#include "chart.h"
#include "shine.h"
#include "credit.h"
#include "bricks.h"
#include "shots.h"
#include "frame.h"
#include "balls.h"
#include "extras.h"
#include "help.h"
#include "game.h"
#include "comm.h"
#include "slot.h"
#include "manager.h"

SDL_Surface *bkgnd = 0; /* current background picture */
extern SDL_Surface *nuke_bkgnd; /* nuke background */
SDL_Surface *offscreen = 0; /* buffer with frame, background and bricks */
extern StkFont *font; /* standard font */
extern Config config; /* lbreakout config struct */
extern int stk_quit_request; /* terminate game */
extern SDL_Surface *stk_display; 
extern SDL_Surface *brick_pic;
extern int player_count;
extern Player players[MAX_PLAYERS]; /* player infos */
#ifdef AUDIO_ENABLED
extern StkSound *wav_click;
extern StkSound *wav_damn, *wav_dammit, *wav_wontgiveup, *wav_excellent, *wav_verygood;
#endif
extern int ball_pic_x_offset;
extern int paddle_cw;
extern List *client_users;
extern ClientUser *client_user;
extern GuiWidget *list_users;
extern char pause_chatter[CHAT_LINE_COUNT][CHAT_LINE_WIDTH];
extern GuiWidget *gui_key_widget;
extern GuiWidget *gui_clicked_widget;
extern GuiWidget *gui_focused_widget;
extern GuiWidget *dlg_pauseroom, *dlg_chatroom;

extern List *levelset_names;
Player *cur_player = 0; /* current player */
Paddle *l_paddle = 0; /* locally controlled paddle */
Paddle *r_paddle = 0; /* remotely controlled paddle */
LevelSet *game_set; /* set that is played */
Game *local_game = 0; /* in a local game the remote side is faked in this
			 game context to have just one main loop for both game
			 types */
Game *game = 0; /* local/network game context */
int game_round; /* id of current round in network game */
int game_stats[2][7]; /* network game stats */
int stats_received = 0;
int game_over = 0; /* network game is over */
int allow_disintegrate = 1; /* wether Plane of Inner Stability may be entered */
char best_name[32];
int  best_score; /* result of first place in chart of local set */
int  showing_best = 0; /* if true best score is shown, thus display should not be updated */
Display *display_score[2]; /* either score or frags */
Display *display_player[2]; /* player name and lifes or wins */
Display *bl_display; /* display some special data of bonus levels */
extern int client_state;
extern int warp_blinks, warp_blink;
extern SDL_Surface *paddle_pic, *weapon_pic, *ball_pic, *extra_pic, *shot_pic;
extern StkFont *display_font;
extern int bkgnd_count;
int bkgnd_ids[MAX_LEVELS]; /* random background ids changed everytime a game is started */
int client_comm_delay; /* delay between communications */
int no_comm_since; /* time passed this last comm */
extern char client_name[16]; /* our local username */
int gameSeed = 0; /* last used seed for freakout game */

extern int current_player;
extern Item *item_resume_0;

extern void select_chart( char *name, int update );

/*
====================================================================
Locals
====================================================================
*/


/* initiate the level of the game context by using the player's
 * snapshot. the snapshot must've been previously set either by 
 * net update or locally. 'l_pos' is either PADDLE_BOTTOM or TOP
 * indicating which paddle client controls in network game.
 */
static int init_level( Player *player, int l_pos )
{
	int length;
	char str[32];

    	/* init level by replacing with the players snapshot. this will
	 * screw the total brick count thus stats are useless for local
	 * game but they are not used anyway as the remote/local game
	 * context contains the real stats */
	if ( game->game_type == GT_LOCAL )
		game_init( local_game, &player->snapshot );
    game_init( game, &player->snapshot );
    /* if special level copy the local game data as snapshot */
    if ( game->game_type == GT_LOCAL )
    if ( player->snapshot.type!=LT_NORMAL )
        memcpy( game->bricks, local_game->bricks, sizeof(game->bricks) );

	/* initiate frame for game type */
	frame_init();

	/* create offscreen */
	offscreen = stk_surface_create( SDL_SWSURFACE, stk_display->w, stk_display->h );
	SDL_SetColorKey( offscreen, 0, 0 );

	/* add&create background */
	if ( game->game_type == GT_LOCAL )
		bkgnd_draw( bkgnd, bkgnd_ids[player->level_id] );
	else
		bkgnd_draw( bkgnd, -1 );
	/* add frame */
	frame_draw();
	/* add bricks */
	bricks_draw();
	/* draw lives */
	if ( game->game_type == GT_LOCAL )
		frame_draw_lives( player->lives, game->diff->max_lives );
   
	/* determine what is the local and what is the remote paddle and
	 * connect the proper players with each paddle */
	if ( game->game_type == GT_LOCAL ) {
		l_paddle = game->paddles[PADDLE_BOTTOM];
		l_paddle->player = player;
		r_paddle = 0;
	} else {
		if ( l_pos == PADDLE_BOTTOM ) {
			l_paddle = game->paddles[PADDLE_BOTTOM];
			r_paddle = game->paddles[PADDLE_TOP];
		} else {
			l_paddle = game->paddles[PADDLE_TOP];
			r_paddle = game->paddles[PADDLE_BOTTOM];
		}
		/* player 0 is always the local player */
		l_paddle->player = &players[0];
		r_paddle->player = &players[1];
		
		/* let top paddle use alternative graphics if any */
		if ( paddle_pic->w > 3*paddle_cw )
			game->paddles[PADDLE_TOP]->pic_x_offset = 3*paddle_cw;
	}
	player->paddle_id = l_pos;

	/* displays */
	if ( game->game_type == GT_LOCAL ) {
		/* we put these displays to the old positions 
		   at the top of the frame */
		sprintf( str, "%s", player->name );
		length = strlen(best_name);
		if (strlen(player->name)>length )
		  length = strlen(player->name);
		length *= 8;
		display_player[0] = displays_add( 402, 0,
				length + 4, 16, str, player->lives, 0 );
		display_score[0] = displays_add( stk_display->w - 52 - 76, 0,
				76, 16, "", player->stats.total_score, 9 );
		display_player[0]->use_alpha = 0;
		display_score[0]->use_alpha = 0;
        /* for special levels we need a small display in the right lower corner */
        if (game->isBonusLevel)
            bl_display = displays_add( 
                    stk_display->w - BRICK_WIDTH - 20 - 76,
                    ( MAP_HEIGHT - 1 ) * BRICK_HEIGHT + 2,
                    76, 16, "", 0, 9 );
        else
            bl_display = 0;
	} else {
		/* wins */
		sprintf( str, "%s ~x%i", 
			game->paddles[0]->player->name, 
			game->paddles[0]->player->stats.wins );
		length = strlen( str ) * 8;
		display_player[0] = displays_add( 
			BRICK_WIDTH + 20, ( MAP_HEIGHT - 1 ) * BRICK_HEIGHT + 2,
			length + 4, 16, str, 0, 0 );
		sprintf( str, "%s ~x%i", 
			game->paddles[1]->player->name, 
			game->paddles[1]->player->stats.wins );
		length = strlen( str ) * 8;
		display_player[1] = displays_add( 
			BRICK_WIDTH + 20, 2, length + 4, 16, str, 0, 0 );
		/* scores */
		display_score[0] = displays_add( 
				stk_display->w - BRICK_WIDTH - 20 - 76,
				( MAP_HEIGHT - 1 ) * BRICK_HEIGHT + 2,
				76, 16, "", game->paddles[0]->score, 9 );
		display_score[1] = displays_add( 
				stk_display->w - BRICK_WIDTH - 20 - 76, 2,
				76, 16, "", game->paddles[1]->score, 9 );
	}

	/* initiate credit */
    if ( game->game_type == GT_LOCAL && game_set )
	    credit_init( game->title, game->author, 
            player->level_id, game_set->count );
    else
	    credit_init( game->title, game->author, 
            player->level_id, 0/*don't display info*/ );

	/* show offscreen */
	stk_surface_blit( offscreen, 0,0,-1,-1, stk_display, 0,0 );

	/* no refresh rect as we want to use dim effect */
	return 1;
}

static void finalize_level( void )
{
	/* set alpha keys to OPAQUE */
	SDL_SetAlpha( paddle_pic, 0,0 );
	SDL_SetAlpha( weapon_pic, 0,0 );
	SDL_SetAlpha( extra_pic, 0,0 );
	SDL_SetAlpha( ball_pic, 0,0 );
	SDL_SetAlpha( shot_pic, 0,0 );
	SDL_SetAlpha( display_font->surface, 0,0 );

	/* reset ball graphic */
	ball_pic_x_offset = 0;
	
	/* reset shrapnells */
	shrapnells_reset();
	/* reset shine */
	shine_reset();
	/* reset explosions */
	exps_clear();
	exps_set_dark( 0 );

	/* delete offscreen */
	stk_surface_free( &offscreen );

	/* clear credits */
	credit_clear();
	
	/* clear displays */
	displays_clear();
	
	/* clear game contexts */
	if ( game->game_type == GT_LOCAL ) 
		game_finalize( local_game );
	game_finalize( game );
}

/* display formatted info + score table if multiple players */
static void display_score_table( char *format, ... )
{
	va_list args;
	int i;
	char info[256], buf[32];

	va_start( args, format );
	vsnprintf( info, 64, format, args );
	va_end( args );
	
	if ( player_count > 1 ) {
		strcat( info, "##" );
		for ( i = 0; i < player_count; i++ ) {
			/* add player and score */
			sprintf( buf, "#%12s %10i", " ", 
					players[i].stats.total_score );
			strcpy( buf + 1, players[i].name );
			buf[strlen(players[i].name)+1] = 32;
			strcat( info, buf );
		}
	}

	display_text( font, info );
	stk_display_update( STK_UPDATE_ALL );
}

static void display_bonus_level_score()
{
    double avgRatio = 0;
    char info[256]; 
    info[0] = 0;
    if (local_game->blNumCompletedRuns==0) 
        avgRatio = 0;
    else
        avgRatio = local_game->blRatioSum/local_game->blNumCompletedRuns;
    switch (local_game->level_type)
    {
        case LT_JUMPING_JACK:
            display_text( font, _("%s, you hit %d Jumping Jacks!##Your average ratio: %5d%%#        Your score: %6d"),
                      cur_player->name,local_game->blNumCompletedRuns,
                      (int)(100.0*avgRatio),local_game->totalBonusLevelScore);
            break;
        case LT_OUTBREAK:
            display_text( font, _("%s, you stopped %d Outbreaks!##Your average ratio: %5d%%#        Your score: %6d"),
                      cur_player->name,local_game->blNumCompletedRuns,
                      (int)(100.0*avgRatio),local_game->totalBonusLevelScore);
            break;
        case LT_BARRIER:
            display_text( font, _("%s, you broke through %d Barriers!##Your average ratio: %5d%%#        Your score: %6d"),
                      cur_player->name,local_game->blNumCompletedRuns,
                      (int)(100.0*avgRatio),local_game->totalBonusLevelScore);
            break;
        case LT_SITTING_DUCKS:
            display_text( font, _("%s, you shot %d Sitting Ducks!##Your score: %6d"),
                      cur_player->name,local_game->blNumCompletedRuns,
                      local_game->totalBonusLevelScore);
            break;
        case LT_HUNTER:
            display_text( font, _("%s, you hunted down %d bricks!##Your average ratio: %5d%%#        Your score: %6d"),
                      cur_player->name,local_game->blNumCompletedRuns,
                      (int)(100.0*avgRatio),local_game->totalBonusLevelScore);
            break;
		case LT_DEFENDER:
            display_text( font, _("%s, you stopped %d waves#killing a total of %d invaders!##Your average ratio: %5d%%#        Your score: %6d"),
                      cur_player->name,local_game->blNumCompletedRuns,local_game->blTotalNumKilledInvaders,
                      (int)(100.0*avgRatio),local_game->totalBonusLevelScore);
			break;
    }
    stk_display_update( STK_UPDATE_ALL );
}

/* begin frame by hiding all objects */
static void begin_frame( void )
{
	/*
	int i;
        displays_hide();
        frame_info_hide();
        extras_hide();
        for ( i = 0; i < game->paddle_count; i++ ) {
            paddle_hide( game->paddles[i] );
	    paddle_ammo_hide( game->paddles[i] );
	}
        balls_hide();
        shots_hide();
        shrapnells_hide();
        walls_hide();
        frame_warp_icon_hide();
        shine_hide();
        exps_hide();
        credit_hide();
    */
}

/* end frame by drawing all objects and updating the screen */
static void end_frame( void )
{
	int i;

	if ( game->game_type == GT_LOCAL && cur_player )
		bkgnd_draw( bkgnd, bkgnd_ids[cur_player->level_id] );
	else
		bkgnd_draw( bkgnd, -1 );
	/* add frame */
	frame_draw();
	/* add bricks */
	bricks_draw();
	/* draw lives */
	if ( game->game_type == GT_LOCAL && cur_player )
		frame_draw_lives( cur_player->lives, game->diff->max_lives );
	
	/* show -- some things will be missing if darkness is enabled */
	balls_show_shadow();
	extras_show_shadow();
	for ( i = 0; i < game->paddle_count; i++ )
		paddle_show_shadow( game->paddles[i] );
	shots_show();
	if ( config.debris_level == DEBRIS_BELOW_BALL ) {
		exps_show();
		if ( !game->extra_active[EX_DARKNESS] ) {
			shrapnells_show();
			frame_info_show();
		}
	}	
	if ( config.ball_level == BALL_ABOVE_BONUS )
		extras_show();
	balls_show();
	if ( config.ball_level == BALL_BELOW_BONUS )
		extras_show();
	for ( i = 0; i < game->paddle_count; i++ )
		paddle_show( game->paddles[i] );
	if ( !game->extra_active[EX_DARKNESS] ) walls_show();
	shine_show();
	if ( config.debris_level == DEBRIS_ABOVE_BALL ) {
		exps_show();
		if ( !game->extra_active[EX_DARKNESS] ) {
			shrapnells_show();
			frame_info_show();
		}
	}	
	frame_warp_icon_show();
	displays_show();
	for ( i = 0; i < game->paddle_count; i++ )
		paddle_ammo_show( game->paddles[i] );
	credit_show();
}

/* grab/ungrab input of actual game */
static void grab_input( int grab )
{
	if ( grab ) {
		SDL_ShowCursor(0);
		SDL_WM_GrabInput( SDL_GRAB_ON );
		SDL_GetRelativeMouseState(0,0);
	} else {
		SDL_ShowCursor(1);
		SDL_WM_GrabInput( SDL_GRAB_OFF );
	}
}

/* switch client to new state */
void set_state( int newstate )
{
	if ( client_state == newstate ) return;
	
	if ( newstate == CS_PLAY )
		grab_input( 1 );
	if ( client_state == CS_PLAY )
		grab_input( 0 );
	if ( client_state == CS_CONFIRM_WARP ||
	     client_state == CS_CONFIRM_RESTART ||
	     client_state == CS_CONFIRM_QUIT ||
             client_state == CS_CONFIRM_CONTINUE ||
	     client_state == CS_GET_READY ||
	     client_state == CS_PAUSE ||
	     client_state == CS_FINAL_PLAYER_INFO ||
         client_state == CS_GET_READY_FOR_NEXT_LEVEL ||
	     client_state == CS_RECV_LEVEL ||
	     client_state == CS_ROUND_RESULT ||
	     client_state == CS_RECV_STATS ||
	     client_state == CS_FATAL_ERROR ) {
		/* show offscreen */
		if ( offscreen ) {
			stk_surface_blit( offscreen, 0,0,-1,-1, stk_display, 0,0 );
			end_frame();
		}
		/* do not refresh when coming from RECV_LEVEL as a GET_READY
		 * will follow */
		if ( client_state != CS_RECV_LEVEL )
		if ( client_state != CS_ROUND_RESULT )
		if ( client_state != CS_RECV_STATS );
			stk_display_update( STK_UPDATE_ALL );
	}

	client_state = newstate;
	stk_timer_reset();
}

/* Fade all animations until they disappear */
static void fade_anims()
{
	float alpha = 255.0;
	int ms, i;
	stk_timer_reset();
	if ( game->game_type == GT_LOCAL && game->winner != PADDLE_BOTTOM )
		frame_remove_life();
	while ( alpha > 0 ) {
		displays_hide();
		for ( i = 0; i < game->paddle_count; i++ )
			paddle_hide( game->paddles[i] );
		balls_hide();
		extras_hide();
		shrapnells_hide();
		shots_hide();
		walls_hide();
		credit_hide();
		ms = stk_timer_get_time();
		alpha -= 0.3 * ms;
		if ( alpha < 0 ) alpha = 0;
		shrapnells_update( ms );
		for ( i = 0; i < game->paddle_count; i++ )
			paddle_alphashow( game->paddles[i], alpha );
		balls_alphashow( alpha );
		extras_alphashow( alpha );
		shots_alphashow( alpha );
		shrapnells_show();
		walls_alphashow( alpha );
		displays_show();
		credit_alphashow( alpha );
		stk_display_update( STK_UPDATE_RECTS );
	}
}

void open_pause_chat( char *text )
{
	set_state( CS_PAUSE );
	
	/* clear pause_chatter */
	memset( pause_chatter, 0, sizeof( pause_chatter ) );
	/* clear global gui widgets */
	gui_focused_widget = 0;
	gui_clicked_widget = 0;
	gui_key_widget = 0;
	/* use 'text' as initial chatter */
	client_add_pausechatter( text, 1 );
	/* gray screen */
	stk_surface_gray( stk_display, 0,0,-1,-1, 1 );
	/* show pauseroom */
	gui_widget_show( dlg_pauseroom );
	stk_display_update( STK_UPDATE_ALL );

	/* disable event filter */
	SDL_SetEventFilter( 0 );
	/* disable client_recv which is called as time event */
	gui_widget_disable_event( dlg_chatroom, GUI_TIME_PASSED );
}

void close_pause_chat( void )
{
	gui_widget_hide( dlg_pauseroom );
	set_state( CS_PLAY );

	/* enable event filter */
	SDL_SetEventFilter( event_filter );
	gui_widget_enable_event( dlg_chatroom, GUI_TIME_PASSED );
}

/* Pause/unpause a local/network game. */
static void client_set_pause( int pause )
{
  if (game_set==0) return; /* test level */
  if (pause&&client_state==CS_PLAY)
    {
      /* in local game simply darken the screen, in
       * network game enter the pausechatroom */
      if ( game->game_type == GT_LOCAL ) {
	set_state(CS_PAUSE);
	display_text( font, _("Pause") );
      }
      else {
	open_pause_chat( _("You have paused the game.") );
	comm_send_short( MSG_PAUSE );
      }
    }
  else if (!pause&&client_state==CS_PAUSE)
    {
      /* unpause (local game only)*/
      if ( game->game_type == GT_LOCAL )
	set_state(CS_PLAY);
    }
}

/* modify the client and its state according to the key pressed */
static int handle_default_key( int key, int *abort )
{
	SDL_Surface *buffer;
	
	switch ( key ) {
		case SDLK_F1:
		case SDLK_h:
			if ( client_state != CS_PLAY ) break;
			if ( game->game_type == GT_NETWORK ) break; /* only for single player */
			grab_input(0);
			help_run();
			grab_input(1);
			return 1;
		case SDLK_q:
		case SDLK_ESCAPE:
			/* recv_stats or final_stats means we already broke up
			 * the game so ESC will directly quit */
			if ( client_state == CS_RECV_STATS || client_state == CS_FINAL_STATS ) {
				*abort = 1;
				break;
			}

			if ( client_state == CS_CONFIRM_QUIT ) break;
			if ( client_state == CS_PAUSE ) break;
                        if ( players_count() == 0 ) break; 
			set_state(CS_CONFIRM_QUIT); 
                        if ( game->game_type == GT_LOCAL && game_set != 0 /*not testing a level*/ )
  			    display_text( font, _("Quit Game? y/n#(If yes, this game may be resumed later.#No highscore entry is created yet.)") );
                        else
  			    display_text( font,  _("Quit Game? y/n") );
			return 1;
		case SDLK_r:
			if ( client_state != CS_PLAY ) break;
			if ( game->game_type == GT_NETWORK ) break; /* only for single player */
			if ( game_set == 0 ) break; /* test level */
			if ( cur_player->lives < 2 ) break;
			set_state(CS_CONFIRM_RESTART); 
			display_text( font,  _("Restart Level? y/n") );
			return 1;
		case SDLK_d:
			if ( client_state != CS_PLAY ) break;
			if ( game->game_type == GT_NETWORK ) break; /* only for single player */
			if ( !allow_disintegrate ) break;
			if ( game->level_type != LT_NORMAL ) break; /* not in bonus levels */
			grab_input(0);
			game_nuke();
			grab_input(1);
			return 1;
		case SDLK_f:
			buffer = stk_surface_create( SDL_SWSURFACE, 640, 480 );
			SDL_BlitSurface( stk_display, 0, buffer, 0 );
			config.fullscreen = !config.fullscreen;
			stk_display_apply_fullscreen( config.fullscreen );
			SDL_BlitSurface( buffer, 0, stk_display, 0 );
			stk_display_update( STK_UPDATE_ALL);
			SDL_FreeSurface( buffer );
			return 1;
		case SDLK_s:
#ifdef AUDIO_ENABLED
			config.sound = !config.sound;
			stk_audio_enable_sound( config.sound );
#endif
			return 1;
		case SDLK_a:
			config.anim++;
			if ( config.anim >= 4 ) config.anim = 0;
			return 1;
		case SDLK_TAB:
			stk_display_take_screenshot();
			return 1;
		case SDLK_t:
			return 0;
		case SDLK_p:
			if ( client_state == CS_PLAY )
			  client_set_pause(1);
			else if (client_state==CS_PAUSE)
			  client_set_pause(0);
			return 1;
		default: 
			if ( client_state != CS_PLAY ) break;
			if ( game->game_type != GT_LOCAL ) break;
			if ( game->bricks_left > game->warp_limit ) break;
			if ( game_set == 0 ) break; /* test level */
			if ( game->level_type != LT_NORMAL ) break; /* not in bonus levels */
			if ( key == config.k_warp ) {
				set_state(CS_CONFIRM_WARP);
				display_text( font,  _("Warp to next level? y/n") );
				return 1;
			}
			break;
	}
	
	return 0;
}

void update_bonus_level_display()
{
    /* Michael! What are you doing? You're messing this all up!
       Who the fuck are you? 
       Your conscience! You can do better! I can do this way
       cleaner. I know! This is just another terrible hack. Stop it!
       Shhiiiiit, who cares? It's too goddamn late you bastard. You should
       have come up with that earlier. This game is down on its knees 
       already. Dying from hacks spreading like cancer... */
    switch (local_game->level_type)
    {
        case LT_JUMPING_JACK:
            display_set_value(bl_display,(local_game->bricks[local_game->bl_jj_mx][local_game->bl_jj_my].exp_time+500)/1000);
            break;
        case LT_OUTBREAK:
            display_set_value(bl_display,local_game->blCancerCount);
            break;
        case LT_BARRIER:
            display_set_value(bl_display,local_game->blBarrierMaxMoves-local_game->blBarrierMoves);
            break;
        case LT_SITTING_DUCKS:
            display_set_value(bl_display,local_game->blMaxScore);
            break;
        case LT_HUNTER:
            display_set_value(bl_display,local_game->blHunterTimeLeft/1000);
            break;
        case LT_DEFENDER:
            display_set_value(bl_display,local_game->blInvaderLimit-local_game->blNumKilledInvaders);
            break;
    }
}
        
/* update local objects (shrapnells,extras,explosions...) and communicate
 * every client_comm_delay seconds either with real or fake server */
static void update_game( int ms )
{
	int i;
	
	/* run the fake server game */
	if ( game->game_type == GT_LOCAL ) {
		game_set_current( local_game );
		game_update( ms );
		game_set_current( game );
	}
		
	/* local animations and movements */
	for ( i = 0; i < game->paddle_count; i++ )
		client_paddle_update( game->paddles[i], ms );
	client_shots_update( ms );
	client_balls_update( ms );
	client_extras_update( ms );
	client_walls_update( ms );
	shrapnells_update( ms );
	frame_warp_icon_update( ms );
	shine_update( ms );
	exps_update( ms );
	displays_update( ms );
	credit_update( ms );

	/* communicate */
	if ( (no_comm_since+=ms) >= client_comm_delay ) {
		no_comm_since -= client_comm_delay;

		/* send paddle state */
		comm_send_paddle( l_paddle );
	
		/* receive game data from local or remote server and 
		 * apply it to the game context. */
		comm_recv();
		
		/* update score displays */
		if (!showing_best)
		  display_set_value( 
			  display_score[0], 
			  game->paddles[0]->player->stats.total_score + 
			  game->paddles[0]->score );
		if ( game->game_type == GT_NETWORK )
			display_set_value( 
				display_score[1], 
				game->paddles[1]->player->stats.total_score + 
				game->paddles[1]->score );

        /* update bonus level information */
        if (bl_display) update_bonus_level_display();
	}
}

/* give us a damn or excellent depending on the outcome of the level.
 * the result for network game must've been received already so that
 * game::level_over and game::winner are valid entries. */
static void play_speech( void )
{
#ifdef AUDIO_ENABLED
	if ( !config.speech )
		return;
	if ( game->winner == -1 ) 
		return; /* draw */
	
	if ( game->paddles[game->winner] == l_paddle ) {
		if ( rand() % 2 )
			stk_sound_play( wav_excellent );
		else
			stk_sound_play( wav_verygood );
	} else {
		if (!config.badspeech) /* no swearing! */
			return;
		if ( rand() % 2 )
			stk_sound_play( wav_damn );
		else
			stk_sound_play( wav_dammit );
	}
#endif
}

/* check players of local game wether they entered a highscore */
static void check_highscores( void )
{
	int i;
	
	chart_clear_new_entries();
	for ( i = 0; i < config.player_count; i++ )
		chart_add( 
			chart_set_query(game_set->name), 
			players[i].name, 
			players[i].level_id + 1, 
			players[i].stats.total_score );
	chart_save();
}

/* init next network game round by displaying a message and switching
 * to GET_READY. */
void init_next_round( void )
{
	game_round++;
	set_state( CS_GET_READY );
	init_level( cur_player, cur_player->paddle_id );
	display_text( font,
		_("***** Round %i *****###You control the %s paddle in this level!#"
		"To fire a ball keep the mouse button PRESSED.#Don't just click.###"
		"Press any key when you are ready...###(You can pause the game with 'p' any time.)"
		"###NOTE: Due to latency, bonuses on the server are closer than they "
		"appear! I'll try to work on that."), 
		game_round, cur_player->paddle_id==0?_("BOTTOM"):_("TOP") );
}

/* display a message about the winner */
void finalize_round( void )
{
	if ( (char)game->winner == -1 )
		display_text( font, _("DRAW") );
	else {
		game->paddles[game->winner]->player->stats.wins++;
		if ( game->winner == cur_player->paddle_id )
			display_text( font, _("You have won this round!") );
		else
			display_text( font, _("You have lost this round.") );
	}
	finalize_level();
	set_state( CS_ROUND_RESULT );
}

/* display the final statistics. the first player is always this client
 * and the second is the remote. */
void display_final_stats( void )
{
	int win;

	/* won this match? */
	if ( game_stats[0][0] > game_stats[1][0] )
		win = 1;
	else
	if ( game_stats[0][0] == game_stats[1][0] )
		win = -1;
	else
		win = 0;
	
	/* build stats string */
	display_text( font, 
			_("             Result: %s              ##" \
			"                  %12s %12s##" \
			"Wins:             %12i %12i#" \
			"Losses:           %12i %12i#" \
			"Draws:            %12i %12i#" \
			"#" \
			"Total Score:      %12i %12i#" \
			"#" \
			"Balls Kept:       %11i%% %11i%%#" \
			"Bricks Cleared:   %11i%% %11i%%#" \
			"Extras Collected: %11i%% %11i%%##" \
                        "(Press SPACE to continue)"),
			win==1?_("VICTORY"):win==0?_(" DEFEAT"):_("   DRAW"),
			players[0].name, players[1].name,
			game_stats[0][0], game_stats[1][0],
			game_stats[0][1], game_stats[1][1],
			game_stats[0][2], game_stats[1][2],
			game_stats[0][3], game_stats[1][3],
			game_stats[0][4], game_stats[1][4],
			game_stats[0][5], game_stats[1][5],
			game_stats[0][6], game_stats[1][6] );
}

/* save data from local and local_game variables and update
 * the menu hint. */
void save_local_game( int slot_id )
{
    GameSlot gs;
    int i;

    memset( &gs, 0, sizeof(GameSlot) );
    strcpy( gs.setname, game_set->name );
    gs.diff = config.diff;
    gs.player_count = config.player_count;
    gs.cur_player = current_player;
    for ( i = 0; i < MAX_PLAYERS; i++ )
    {
        strcpy( gs.player_names[i], config.player_names[i] );
        gs.player_cur_level_id[i] = players[i].level_id;
        gs.player_lives[i] = players[i].lives;
        gs.player_scores[i] =  players[i].stats.total_score;
    }
    gs.gameSeed = gameSeed;
    if ( !slot_save( slot_id, &gs ) )
        fprintf( stderr, _("ERROR: couldn't save game!\n") );
    slot_update_hint( slot_id, item_resume_0->hint );
}

/* check whether Shift is pressed to switch between own and highest score */
void handle_display_switch()
{
  int modstate = 0;
  modstate = SDL_GetModState();
  if (!showing_best)
    {
      if (modstate&KMOD_RSHIFT||modstate&KMOD_LSHIFT)
	{
	  display_set_text( display_player[0], best_name );
	  display_set_value_directly( display_score[0], best_score );
	  display_set_highlight( display_player[0], 1 );
	  display_set_highlight( display_score[0], 1 );
	  showing_best = 1;
	}
    }
  else
    {
      if (!(modstate&KMOD_RSHIFT||modstate&KMOD_LSHIFT))
	{
	  display_set_text( display_player[0], cur_player->name );
	  display_set_value_directly( display_score[0],
			     game->paddles[0]->player->stats.total_score +
			     game->paddles[0]->score );
	  display_set_highlight( display_player[0], 0 );
	  display_set_highlight( display_score[0], 0 );
	  showing_best = 0;
	}
    }
}

/*
====================================================================
Publics
====================================================================
*/

/* create various resources like shrapnells */
void client_game_create()
{
	frame_create();
	shrapnells_init();
	shine_load();
	init_angles();

	/* background */
	bkgnd = stk_surface_create( SDL_SWSURFACE, 
			stk_display->w, stk_display->h );
	SDL_SetColorKey( bkgnd, 0, 0 );
	stk_surface_fill( bkgnd, 0,0,-1,-1, 0x0 );

}
void client_game_delete()
{
	stk_surface_free( &bkgnd );
	
	displays_clear();
	frame_delete();
	shrapnells_delete();
	shine_delete();
}

/* create network/local game context and initiate game state:
 * network needs to receive the level data and a local game
 * has to load the next level */
int client_game_init_local( char *setname )
{
        Set_Chart *chart;
	int i, warp_limit;

	warp_limit = config.rel_warp_limit;
	allow_disintegrate = 1;

	/* the original levelsets do not need these workarounds */
	if ( STRCMP( setname, "LBreakout2" ) || STRCMP( setname, "LBreakout1" ) ) {
		warp_limit = 100;
		allow_disintegrate = 0;
	}
	
	/* the approach for a local game is to use the same
	 * settings as a network game. the receiving of packets
	 * is simply faked by a local_game context that
	 * runs the game locally. but to use only one game loop
	 * we do not use it directly but apply its modificiations
	 * to game which is visualized */
	local_game = game_create( GT_LOCAL, config.diff, warp_limit );
	game_set_current( local_game );
	game_set_convex_paddle( config.convex );
	game_set_ball_auto_return( !config.return_on_click );
	game_set_ball_random_angle( config.random_angle );
    game_set_ball_accelerated_speed( config.maxballspeed_float );
    local_game->localServerGame = 1;
	
	/* load levels:
	 * only required for local games. in network both players
	 * just require a single level that can store the incoming
	 * data that is send by the server via the net.
	 */
	if ( !strcmp( setname, TOURNAMENT ) )
	    game_set = levelset_load_all( levelset_names, gameSeed, config.addBonusLevels );
	else
		game_set = levelset_load( setname, ((config.addBonusLevels)?gameSeed:0) );
	if ( game_set == 0 ) return 0;

	/* load highest score so far if any */
	chart = chart_set_query(setname);
	strcpy(best_name,_("nobody")); best_score = 0;
	if (chart)
	  {
	    strcpy(best_name,chart->entries[0].name);
            best_score = chart->entries[0].score;
	  }
	
	/* create client game context */
	game = game_create( GT_LOCAL, config.diff, warp_limit );
	game_set_current( game );
	
	/* a local game is not limited in its communication */
	client_comm_delay = 0;
	no_comm_since = 0;
	
	/* prepare warp icon at frame */
	warp_blinks = 4; warp_blink = 1;
	
	/* set list of level background ids */
	for ( i = 0; i < MAX_LEVELS; i++ )
		bkgnd_ids[i] = rand() % bkgnd_count;
	
	/* initiate players */
	players_clear();
	for ( i = 0; i < config.player_count; i++ )
		player_add( config.player_names[i], 
			    game->diff->lives, 
			    levelset_get_first( game_set ) );
	cur_player = players_get_first();

	/* init first level */
	init_level( cur_player, PADDLE_BOTTOM );
	
	/* if only one player don't show score table */
	client_state = CS_NONE;
	if ( player_count > 1 )
		set_state( CS_SCORE_TABLE );
	else
		set_state( CS_PLAY ); /* one player starts immediately */
	return 1;
}
int client_game_init_network( char *opponent_name, int diff )
{
	/* create an empty one level levelset. the server will send
	 * the data into the level everytime we play. */
	game_set = levelset_create_empty( 1, "empty", "empty" );
	
	/* create client game context */
	game = game_create( GT_NETWORK, diff, 100 );
	game_set_current( game );
	game_round = 0; /* will be increased by init_next_round() */
	game_over = 0;
	
	/* a network game communicates every 25 ms by default */
	client_comm_delay = 25;
	no_comm_since = 0;
	
	/* initiate players */
	players_clear();
	player_add( client_name, game->diff->lives, levelset_get_first( game_set ) );
	player_add( opponent_name, game->diff->lives, levelset_get_first( game_set ) );
	cur_player = players_get_first();

	display_text( font, _("Receiving level data...") );
	set_state( CS_RECV_LEVEL );
	return 1;
}

/* create local game context and initiate game state
 * as given from slot 'slot_id'. */
int client_game_resume_local( int slot_id )
{
    int i;
    GameSlot gs;
   
    /* load saved game */
    if ( !slot_load( slot_id, &gs ) ) return 0;
    
    /* FIXME: config settings are overwritten for this */
    config.diff = gs.diff;
    config.player_count = gs.player_count;
    for ( i = 0; i < config.player_count; i++ )
        strcpy( config.player_names[i], gs.player_names[i] );
    gameSeed = gs.gameSeed;

    /* create local game where all players have full lives */
    if ( !client_game_init_local( gs.setname ) ) return 0;

    /* re-initiate players */
    players_clear();
    for ( i = 0; i < config.player_count; i++ )
    {
        /* name + lives */
        player_add( config.player_names[i], 
                gs.player_lives[i], 
                levelset_get_first( game_set ) );
        /* level */
        player_init_level( &players[i], 
                           game_set->levels[gs.player_cur_level_id[i]],
                           gs.player_cur_level_id[i] );
        /* score */
        players[i].stats.total_score = gs.player_scores[i];
    }
    cur_player = players_set_current( gs.cur_player );
    
    /* init first level */
    init_level( cur_player, PADDLE_BOTTOM );
	
    return 1;
}

/* create a one level game context for testing a level */
int client_game_init_testing( Level *level )
{
	local_game = game_create( GT_LOCAL, config.diff, 100 );
	game_set_current( local_game );
	game_set_convex_paddle( config.convex );
	game_set_ball_auto_return( !config.return_on_click );
	game_set_ball_random_angle( config.random_angle );
        game_set_ball_accelerated_speed( config.maxballspeed_float );
    local_game->localServerGame = 1;
	
	game = game_create( GT_LOCAL, config.diff, 100 );
	game_set_current( game );

	players_clear();
	player_add( config.player_names[0], game->diff->lives, level );
	cur_player = players_get_first();

	bkgnd_ids[0] = 0;

	init_level( cur_player, PADDLE_BOTTOM );
	
	client_state = CS_NONE;
	set_state( CS_PLAY ); 

	return 1;
}

/* finalize a game and free anything allocated by init process */
void client_game_finalize()
{
	players_clear();

	if ( game && game->game_type == GT_LOCAL ) {
		game_delete( &local_game );
		levelset_delete( &game_set );
	}
	game_delete( &game );
}

/* run the state driven loop until game is broken up or finished */
void client_game_run( void )
{
	int ms, frame_delay = config.fps?10:1;
	int button_clicked, key_pressed;
	SDL_Event event;
	int abort = 0, i, j, penalty;
	/* frame rate */
	int frames = 0;
	int frame_time = SDL_GetTicks();

	event_clear_sdl_queue();
	
	stk_display_fade( STK_FADE_IN, STK_FADE_DEFAULT_TIME );
	
	stats_received = 0;
	stk_timer_reset(); ms = 1;
	while ( !abort && !stk_quit_request ) {
		/* check wether an event occured */
		button_clicked = key_pressed = 0;
		if ( SDL_PollEvent( &event ) ) {
			if ( client_state == CS_PAUSE && game->game_type == GT_NETWORK )
				gui_dispatch_event( &event, ms );
			else
			if ( event.type == SDL_MOUSEBUTTONDOWN )
				button_clicked = event.button.button;
			else
			if ( event.type == SDL_KEYDOWN ) {
				key_pressed = event.key.keysym.sym;
				if ( handle_default_key( key_pressed, &abort ) )
					key_pressed = 0;
			}
			else
			if (event.type == SDL_ACTIVEEVENT)
		          {
			    if (event.active.state == SDL_APPINPUTFOCUS ||
				event.active.state == SDL_APPACTIVE )
                            if (event.active.gain == 0 )
			      client_set_pause(1);
			  }
		}
		else if ( client_state == CS_PAUSE && game->game_type == GT_NETWORK )
			gui_dispatch_event( 0, ms );

		/* check whether Shift is pressed to switch between own and highest score */
		if (game->game_type == GT_LOCAL)
		  handle_display_switch();

		/* let server know we're still alive except
		 * in CS_PLAY as we send paddle updates there */
		if ( game->game_type == GT_NETWORK )
			comm_send_heartbeat();

		/* handle client */
		switch ( client_state ) {

		case CS_FINAL_STATS:
			if ( key_pressed==SDLK_SPACE ) abort = 1;
			break;
			
		case CS_FATAL_ERROR:
			/* after game was violently broken up the server
			 * may still send the stats of the game so far */
			if ( button_clicked || key_pressed ) {
				SDL_Delay(250); /* give time to release button */
				set_state( CS_RECV_STATS );
				display_text( font, _("Receiving final stats...") );
			}
			break;
			
		case CS_FINAL_TABLE:
			if ( button_clicked || key_pressed ) {
				chart_load();
				check_highscores();
				select_chart( game_set->name, 0 );
                                /* remove saved game */
                                slot_delete( 0 );
                                slot_update_hint( 0, item_resume_0->hint );
				/* quit local game */
				abort = 1;
			}
			break;

		case CS_SCORE_TABLE:
			/* show who's next player and scores in local game */
			display_score_table( _("Next Player: %s"), cur_player->name );
			set_state( CS_GET_READY );
			break;
        
        case CS_BONUS_LEVEL_SCORE:
            /* display total score from this level for player */
            display_bonus_level_score();
			set_state( CS_GET_READY_FOR_NEXT_LEVEL );
            break;
			
		case CS_FINAL_PLAYER_INFO:
			if ( button_clicked || key_pressed ) {
				SDL_Delay(250); /* give time to release button */
				set_state( CS_NEXT_PLAYER );
			}
			break;

		case CS_RECV_LEVEL:
			comm_recv();
			if ( cur_player->next_level_received ) {
				cur_player->next_level_received = 0;
				cur_player->paddle_id = cur_player->next_paddle_id;
				init_next_round();
			}
			break;

		case CS_RECV_STATS:
			comm_recv();
			if ( stats_received ) {
				set_state( CS_FINAL_STATS );
				display_final_stats();
			}
			break;
			
		case CS_ROUND_RESULT:
			if ( button_clicked || key_pressed ) {
				SDL_Delay(250); /* give time to release button */
				if ( game_over ) {
					set_state( CS_RECV_STATS );
					display_text( font, _("Receiving final stats...") );
				} else {
					set_state( CS_RECV_LEVEL );
					display_text( font, _("Receiving level data...") );
 				}
			}
			break;
			
		case CS_GET_READY:
			if ( button_clicked || key_pressed ) {
				SDL_Delay(250); /* give time to release button */
				comm_send_short( MSG_READY );
				set_state( CS_PLAY );
			}
			break;

		case CS_GET_READY_FOR_NEXT_LEVEL:
			if ( button_clicked || key_pressed ) {
				SDL_Delay(250); /* give time to release button */
				set_state( CS_NEXT_LEVEL );
			}
			break;

		case CS_PAUSE:
			if ( game->game_type == GT_LOCAL ) break;

			/* check wether pause chatroom has been closed
			 * either by client or remote */
			comm_recv();
			break;
			
		case CS_PLAY:
			/* hide objects */
			begin_frame();
			
			/* apply events to local paddle */
			paddle_handle_events( l_paddle, ms );

			/* update local objects and communicate if
			 * comm_delay ms have passed */
			update_game( ms );
			
			/* show objects */
			end_frame();

			/* handle local level over */
			if ( game->level_over ) {
				if ( game->game_type == GT_LOCAL ) {
					if ( game_set == 0 ) {
						abort = 1; /* was a test level */
						grab_input(0);
						break;
					}
					if ( game->winner == PADDLE_BOTTOM )
                    {
                        if (local_game->isBonusLevel)
                            set_state( CS_BONUS_LEVEL_SCORE );
                        else
                            set_state( CS_NEXT_LEVEL );
                    }
					else
						set_state( CS_LOOSE_LIFE );
				} else {
					finalize_round();
				}
			}
			break;

		case CS_NEXT_LEVEL:
			/* apply paddle stats to player */
			game_set_current( local_game );
			game_update_stats( PADDLE_BOTTOM, &cur_player->stats );
			game_set_current( game );
			/* init next level for player in local game */
			cur_player->level_id++;
			if ( cur_player->level_id >= game_set->count ) {
				/* deactivate player */
				cur_player->lives = 0;
				display_text( font, 
					_("You've cleared all levels...#Congratulations!!!") );
				set_state( CS_FINAL_PLAYER_INFO );
				break;
			}
			/* get snapshot for next init */
			cur_player->snapshot = *game_set->levels[cur_player->level_id];
			/* cycle players */
			set_state( CS_NEXT_PLAYER );
			break;

		case CS_RESTART_LEVEL:
			/* apply paddle stats to player */
			game_set_current( local_game );
			game_update_stats( PADDLE_BOTTOM, &cur_player->stats );
			game_set_current( game );
			/* reset level for next turn */
			cur_player->snapshot = *game_set->levels[cur_player->level_id];
			/* decrease lives (is checked that this wasn't the last one) */
			cur_player->lives--;
			/* cycle players */
			set_state( CS_NEXT_PLAYER );
			break;
			
		case CS_LOOSE_LIFE:
			/* apply paddle stats to player */
			game_set_current( local_game );
			game_update_stats( PADDLE_BOTTOM, &cur_player->stats );
			game_set_current( game );

			/* remember level for next turn */
			game_get_level_snapshot( &cur_player->snapshot );

			/* decrease lives */
			cur_player->lives--;
			if ( cur_player->lives == 0 ) {
				display_text( font, 
					_("You've lost all lives...#Do you want to buy a continue#for 100%% of your score? y/n") );
                                set_state( CS_CONFIRM_CONTINUE );
				//set_state( CS_FINAL_PLAYER_INFO );
				break;
			}
			set_state( CS_NEXT_PLAYER );
			break;

		case CS_NEXT_PLAYER:
			/* game over? */
			if ( players_count() == 0 ) {
				display_score_table( _("Game Over!") );
				set_state( CS_FINAL_TABLE );
				break;
			}
			/* speak and fade */
			play_speech();
			fade_anims();
			/* finalize current game context */
			finalize_level();
			/* set next player */
			cur_player = players_get_next();
			init_level( cur_player, PADDLE_BOTTOM );
			if ( player_count > 1 )
				set_state( CS_SCORE_TABLE );
			else {
				set_state( CS_PLAY ); /* one player starts immediately */
				stk_display_update( STK_UPDATE_ALL );
			}
			break;
		
        case CS_CONFIRM_CONTINUE:
		case CS_CONFIRM_QUIT:
		case CS_CONFIRM_WARP:
		case CS_CONFIRM_RESTART:
			if ( key_pressed )
            {
                char *keyName = SDL_GetKeyName(key_pressed);
                char *yesLetter = _("y"), *noLetter = _("n");
                if ( strcmp(keyName,noLetter)==0||key_pressed==SDLK_ESCAPE ) {
                    /* if denying continue... DIE!!! */
                    if ( client_state == CS_CONFIRM_CONTINUE )
                    {
                        SDL_Delay(250); /* give time to release button */
                        set_state( CS_NEXT_PLAYER );
                        //set_state( CS_FINAL_PLAYER_INFO );
                    }
                    else
                        set_state( CS_PLAY );
                    break;
                }
                if ( strcmp(keyName, yesLetter) ) break;
            } else break;
			/* handle confirmed action */
			SDL_Delay(250); /* give time to release button */
			switch( client_state ) {
                case CS_CONFIRM_CONTINUE:
                    /* clear score and give full lives again */
                    cur_player->lives = game->diff->lives;
                    cur_player->stats.total_score = 0;
                    set_state( CS_NEXT_PLAYER );
                    break;
				case CS_CONFIRM_QUIT:
					comm_send_short( MSG_QUIT_GAME );
					if ( game->game_type == GT_LOCAL ) {
						/* apply paddle stats to player */
						game_set_current( local_game );
						game_update_stats( PADDLE_BOTTOM, &cur_player->stats );
						game_set_current( game );
                        /* no higscore check anymore as game is supposed to
                         * be resumed until normal game over */
						/* testing levels don't got for
						 * high scores ***
						if ( game_set ) {
							check_highscores();
							select_chart( game_set->name, 0 );
						}*/
                        /* save local game */
                        if ( game_set != 0 /*not testing a level*/ )
                            save_local_game( 0 );
                        abort = 1;
					}
					else {
						/* await game stats */
						set_state( CS_RECV_STATS );
						display_text( font, _("Receiving final stats...") );
					}
					break;
				case CS_CONFIRM_WARP:
					game->winner = -1; /* no speech */
					local_game->winner = -1; /* not counted as win */
                                        /* substract doubled score of remaining bricks */
                                        penalty = 0;
                                        for ( i = 0; i < MAP_WIDTH; i++ )
                                            for ( j = 0; j < MAP_HEIGHT; j++ )
                                                if ( local_game->bricks[i][j].dur != -1 )
                                                    penalty += local_game->bricks[i][j].score;
                                        printf( _("warp penalty: -%d\n"), penalty );
                                        local_game->paddles[0]->score -= penalty;
					set_state( CS_NEXT_LEVEL );
					break;
				case CS_CONFIRM_RESTART:
					game->winner = -1; /* no speech */
					local_game->winner = -1; /* not counted as win */
					local_game->level_over = 1;
					set_state( CS_RESTART_LEVEL );
					break;
			}
			break;

		}

		/* update anything that was changed */
		stk_display_update( STK_UPDATE_RECTS );

		/* get time since last call and delay if below frame_delay */
		ms = stk_timer_get_time();
		if ( ms < frame_delay ) {
			SDL_Delay( frame_delay - ms );
			ms += stk_timer_get_time();
		}
		frames++;
	}
	finalize_level();
	client_state = CLIENT_NONE;

	stk_display_fade( STK_FADE_OUT, STK_FADE_DEFAULT_TIME );
	if ( stk_quit_request )
		comm_send_short( MSG_DISCONNECT );
	else
		comm_send_short( MSG_UNHIDE );

	/* frame rate */
	frame_time = SDL_GetTicks() - frame_time;
	printf( _("Time: %.2f, Frames: %i -> FPS: %.2f\n"), 
		(double)frame_time / 1000, frames, 1000.0*frames/frame_time );

	event_clear_sdl_queue();

	/* update the selected user and the user list in network as 
	 * we received ADD/REMOVE_USER messages */
	gui_list_update( list_users, client_users->count );
	/* re-select current entry */
	if ( client_user ) {
		i = list_check( client_users, client_user );
		if ( i != -1 )
			gui_list_select( list_users, 0, i, 1 );
	}
}

/* test a level until all balls got lost */
void client_game_test_level( Level *level )
{
	stk_display_fade( STK_FADE_IN, STK_FADE_DEFAULT_TIME );
	client_game_init_testing( level );
	client_game_run();
	client_game_finalize();
	stk_display_fade( STK_FADE_OUT, STK_FADE_DEFAULT_TIME );
}
