/***************************************************************************
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 */
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 freakout_seed = 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 );
/* 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;
} 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( player->snapshot.name, player->snapshot.author,
player->level_id, game_set->count );
else
credit_init( player->snapshot.name, player->snapshot.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 );
}
/* 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;
/* 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_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;
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 ( key == config.k_warp ) {
set_state(CS_CONFIRM_WARP);
display_text( font, "Warp to next level? y/n" );
return 1;
}
break;
}
return 0;
}
/* 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 );
}
}
/* 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 ( !game->diff->allow_maluses ) return; /* bad speech is bad somehow */
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.freakout_seed = freakout_seed;
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 );
/* 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, freakout_seed );
else
game_set = levelset_load( setname );
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] );
freakout_seed = gs.freakout_seed;
/* 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 );
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 ) {
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_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_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 )
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 == 0 ) break;
if ( key_pressed==SDLK_n||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 ( key_pressed != SDLK_y && key_pressed != SDLK_z ) 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 );
}