/***************************************************************************
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 "../client/lbreakout.h"
#include "game.h"
#include "bricks.h"
#include "paddle.h"
#include "balls.h"
#include "shots.h"
#include "extras.h"
/***** EXTERNAL VARIABLES **************************************************/
extern int ball_w, ball_dia;
/***** EXPORTS *************************************************************/
Game *cur_game = 0;
/***** FORWARDED DECLARATIONS **********************************************/
/***** LOCAL TYPE DEFINITIONS **********************************************/
/***** LOCAL VARIABLES *****************************************************/
static GameDiff diffs[DIFF_COUNT] = {
{ 9, 12, 8, 12, 20, 5, 0.10, 0.0016, 0.20, 32000, 0, 4 },
{ 6, 9, 0, 2, 8, 8, 0.24, 0.0016, 0.40, 1800, 1, 1 },
{ 5, 7, 0, 1, 6, 10, 0.27, 0.0016, 0.43, 1800, 1, 1 },
{ 4, 5, 0, 1, 4, 13, 0.30, 0.0015, 0.45, 1800, 1, 1 }
};
/* in network game the ball is slower and the paddle is bigger */
static GameDiff net_diffs[DIFF_COUNT] = {
{ 6, 9, 1, 2, 8, 8, 0.18, 0.0012, 0.30, 1800, 1, 1 },
{ 5, 7, 1, 2, 6, 10, 0.21, 0.0012, 0.33, 1800, 1, 1 },
{ 4, 5, 1, 2, 4, 13, 0.24, 0.0011, 0.35, 1800, 1, 1 }
};
/***** LOCAL FUNCTIONS *****************************************************/
static void game_setup_title_and_author( Game *game, Level *level )
{
switch (level->type)
{
case LT_NORMAL:
snprintf( game->title, 32, "%s", level->name );
snprintf( game->author, 32, "%s", level->author );
break;
case LT_JUMPING_JACK:
snprintf( game->title, 32, _("Jumping Jack") );
snprintf( game->author, 32, _("Bonus Level") );
break;
case LT_OUTBREAK:
snprintf( game->title, 32, _("Outbreak") );
snprintf( game->author, 32, _("Bonus Level") );
break;
case LT_BARRIER:
snprintf( game->title, 32, _("Barrier") );
snprintf( game->author, 32, _("Bonus Level") );
break;
case LT_SITTING_DUCKS:
snprintf( game->title, 32, _("Sitting Ducks") );
snprintf( game->author, 32, _("Bonus Level") );
break;
case LT_HUNTER:
snprintf( game->title, 32, _("Hunter") );
snprintf( game->author, 32, _("Bonus Level") );
break;
case LT_DEFENDER:
snprintf( game->title, 32, _("Defender") );
snprintf( game->author, 32, _("Bonus Level") );
break;
default:
snprintf( game->title, 32, _("Unknown Level Type") );
snprintf( game->author, 32, "???" );
break;
}
}
/***** PUBLIC FUNCTIONS ****************************************************/
/* create/delete game context */
Game *game_create( int game_type, int diff, int rel_warp_limit )
{
Game *game = salloc( 1, sizeof( Game ) );
/* set diff and game type */
game->game_type = game_type;
if ( game_type == GT_LOCAL )
game->diff = &diffs[diff];
else
game->diff = &net_diffs[diff];
game->rel_warp_limit = rel_warp_limit;
/* create lists */
game->shots = list_create( LIST_AUTO_DELETE, LIST_NO_CALLBACK );
game->exp_bricks = list_create( LIST_NO_AUTO_DELETE, LIST_NO_CALLBACK );
game->heal_bricks = list_create( LIST_NO_AUTO_DELETE, LIST_NO_CALLBACK );
game->extras = list_create( LIST_AUTO_DELETE, LIST_NO_CALLBACK );
game->balls = list_create( LIST_AUTO_DELETE, LIST_NO_CALLBACK );
/* set ball speed */
game->ball_v_min = game->diff->v_start;
game->ball_v_max = game->diff->v_max;
delay_set( &game->speedup_delay, game->diff->v_delay );
/* create paddles */
game->paddle_count = (game_type==GT_NETWORK)?2:1;
/* bottom */
game->paddles[PADDLE_BOTTOM] = paddle_create( 0, PADDLE_BOTTOM,
(MAP_HEIGHT-2)*BRICK_HEIGHT,
game->diff->paddle_size,
game->diff->paddle_min_size, game->diff->paddle_max_size,
0 );
/* top */
if ( game_type == GT_NETWORK )
game->paddles[PADDLE_TOP] = paddle_create( 0, PADDLE_TOP,
BRICK_HEIGHT+2,
game->diff->paddle_size,
game->diff->paddle_min_size, game->diff->paddle_max_size,
0 );
return game;
}
void game_delete( Game **_game )
{
Game *game = *_game;
int i;
if ( game == 0 ) return;
/* delete paddles */
for ( i = 0; i < game->paddle_count; i++ )
paddle_delete( game->paddles[i] );
/* delete lists */
if ( game->shots ) list_delete( game->shots );
if ( game->exp_bricks ) list_delete( game->exp_bricks );
if ( game->heal_bricks ) list_delete( game->heal_bricks );
if ( game->extras ) list_delete( game->extras );
if ( game->balls ) list_delete( game->balls );
/* delete bonus level stuff */
if (game->blDuckPositions) free(game->blDuckPositions);
if (game->blInvaders) free(game->blInvaders);
free( game );
*_game = 0;
}
/* finalize single game level. the level_type is determined by
* counting the bricks. the data of 'level' is copied and modified
* while playing. */
void game_init( Game *game, Level *level )
{
int i;
Ball *ball;
game->level_over = 0;
game->isBonusLevel = 0;
game->totalBonusLevelScore = 0;
/* set title and author */
game_setup_title_and_author( game, level );
/* set level type. is level::type except for normal level with no bricks: pingpong */
if (level->type==LT_NORMAL && game->game_type == GT_NETWORK && game->brick_count == 0 )
game->level_type = LT_PINGPONG;
else
{
game->level_type = level->type;
if (game->level_type!=LT_NORMAL) game->isBonusLevel = 1;
}
/* clear extras */
memset( game->extra_active, 0, sizeof( game->extra_active ) );
memset( game->extra_time, 0, sizeof( game->extra_time ) );
/* set ball speed */
game->ball_v = game->ball_v_min;
game->speedup_level = 0;
/* clear maxballspeed_request */
if ( game->game_type == GT_LOCAL )
{
cur_game->paddles[0]->maxballspeed_request = 0;
cur_game->paddles[0]->maxballspeed_request_old = 0;
}
/* attach one ball to each paddle */
list_clear( game->balls );
for ( i = 0; i < game->paddle_count; i++ ) {
if ( game->paddles[i]->type == PADDLE_BOTTOM )
ball = ball_create((game->paddles[i]->w - ball_w) / 2, -ball_dia );
else
ball = ball_create((game->paddles[i]->w - ball_w) / 2, game->paddles[i]->h );
ball->attached = 1;
ball->paddle = game->paddles[i];
ball->paddle->attached_ball_count = 1;
ball_set_random_angle( ball, game->ball_v );
list_add( game->balls, ball );
}
/* do bricks as last to have influence on balls to keep bonus level stuff in one place */
/* setup bricks (from level data or from special level type; this includes setting the
bonus level data if any */
bricks_init( game, game->game_type, level, game->diff->score_mod, game->rel_warp_limit );
}
/* reset level/in_game data */
void game_finalize( Game *game )
{
int i;
/* reset lists */
list_clear( game->balls );
list_clear( game->extras );
list_clear( game->shots );
list_clear( game->heal_bricks );
list_clear( game->exp_bricks );
/* reset paddles (and their statistics which are only for
* the currently played level) */
for ( i = 0; i < game->paddle_count; i++ )
paddle_reset( game->paddles[i] );
/* reset updates */
game_reset_mods();
}
/* set the game context the subfunctions will apply their changes to */
void game_set_current( Game *game )
{
cur_game = game;
}
/* set score of paddle 'id'. 0 is bottom paddle and 1 is top paddle */
void game_set_score( int id, int score )
{
if ( id < 0 || id >= cur_game->paddle_count ) return;
cur_game->paddles[id]->score = score;
}
/* set number of additional balls a paddle can fire (all paddles) */
void game_set_ball_ammo( int ammo )
{
int i;
for ( i = 0; i < cur_game->paddle_count; i++ ) {
cur_game->paddles[i]->ball_ammo = ammo;
cur_game->paddles[i]->start_ball_ammo = ammo;
}
}
/* set the number of points required to win a PINGPONG level */
void game_set_frag_limit( int limit )
{
cur_game->frag_limit = limit;
}
/* set wether to use convex paddle */
void game_set_convex_paddle( int convex )
{
cur_game->paddle_is_convex = convex;
}
/* set wether balls are returned to a paddle by pressing fire.
* the alternative is that they automatically return. */
void game_set_ball_auto_return( int auto_return )
{
cur_game->balls_return_by_click = !auto_return;
}
/* set wether balls are fired at random angle or wether the
* left/right fire keys are used */
void game_set_ball_random_angle( int random )
{
cur_game->balls_use_random_angle = random;
}
/* set the speed of balls will have in accelerated state */
void game_set_ball_accelerated_speed( float speed )
{
cur_game->accelerated_ball_speed = speed;
}
/* update state of a paddle. x or y may be 0 which is not a valid value.
* in this case the property is left unchanged */
void game_set_paddle_state( int id, int x, int y, int left_fire, int right_fire, int return_key )
{
Paddle *paddle = 0;
if ( id < 0 || id >= cur_game->paddle_count ) return;
paddle = cur_game->paddles[id];
if ( x != 0 ) { paddle->x = x; paddle->cur_x = x; }
if ( y != 0 ) paddle->y = y;
paddle->fire_left = left_fire;
paddle->fire_right = right_fire;
paddle->ball_return_key_pressed = return_key;
}
/* move objects, modify game data, store brick hits and collected extras.
* return wether level has been finished and the id of the winning paddle
* in network games. -1 is a draw. level_over and winner is saved in the
* game struct. */
void game_update( int ms )
{
int i;
extras_update( ms );
walls_update( ms );
shots_update( ms );
bricks_update( ms );
for ( i = 0; i < cur_game->paddle_count; i++ )
{
paddle_update( cur_game->paddles[i], ms );
/* release all balls from paddle if invisible */
if (!paddle_solid(cur_game->paddles[i]))
balls_detach_from_paddle( cur_game->paddles[i], ((rand()%2==1)?-1:1) );
}
balls_update( ms );
/* level finished? */
cur_game->level_over = 0;
if ( cur_game->game_type == GT_LOCAL ) {
/* local game */
if ( cur_game->bricks_left == 0 ) cur_game->level_over = 1;
if ( cur_game->balls->count == 0 ) cur_game->level_over = 1;
} else {
/* network game */
if ( cur_game->level_type != LT_PINGPONG ) {
if ( cur_game->bricks_left == 0 ) cur_game->level_over = 1;
}
else
if ( cur_game->paddles[PADDLE_TOP]->score >= cur_game->frag_limit ||
cur_game->paddles[PADDLE_BOTTOM]->score >= cur_game->frag_limit )
cur_game->level_over = 1;
}
/* if so, determine winner */
if ( cur_game->level_over ) {
if ( cur_game->game_type == GT_LOCAL ) {
if ( cur_game->bricks_left == 0 || cur_game->isBonusLevel )
cur_game->winner = PADDLE_BOTTOM; /* praise */
else
cur_game->winner = PADDLE_TOP; /* swear */
} else {
cur_game->winner = PADDLE_BOTTOM;
if ( cur_game->game_type == GT_NETWORK ) {
if ( cur_game->paddles[PADDLE_TOP]->score >
cur_game->paddles[PADDLE_BOTTOM]->score )
cur_game->winner = PADDLE_TOP;
else
if ( cur_game->paddles[PADDLE_TOP]->score ==
cur_game->paddles[PADDLE_BOTTOM]->score )
cur_game->winner = -1;
}
}
}
}
/* get the modifications that occured in game_update() */
/* get current score of player. return 0 if player does not exist */
int game_get_score( int id, int *score )
{
if ( id < 0 || id >= cur_game->paddle_count ) return 0;
*score = cur_game->paddles[id]->score;
return 1;
}
/* get number of ball reflections */
int game_get_reflected_ball_count( void )
{
return cur_game->mod.brick_reflected_ball_count+
cur_game->mod.paddle_reflected_ball_count;
}
/* get number of ball reflections on bricks */
int game_get_brick_reflected_ball_count( void )
{
return cur_game->mod.brick_reflected_ball_count;
}
/* get number of ball reflections on paddle */
int game_get_paddle_reflected_ball_count( void )
{
return cur_game->mod.paddle_reflected_ball_count;
}
/* get number of newly attached balls */
int game_get_attached_ball_count( void )
{
return cur_game->mod.attached_ball_count;
}
/* get number of fired shots no matter which paddle */
int game_get_fired_shot_count( void )
{
return cur_game->mod.fired_shot_count;
}
/* hit bricks since last call to game_update() */
BrickHit *game_get_brick_hits( int *count )
{
*count = cur_game->mod.brick_hit_count;
return cur_game->mod.brick_hits;
}
/* get a list of extras collected by paddle id */
int *game_get_collected_extras( int id, int *count )
{
*count = 0;
if ( id < 0 || id >= cur_game->paddle_count ) return 0;
*count = cur_game->mod.collected_extra_count[id];
return cur_game->mod.collected_extras[id];
}
/* get a snapshot of the level data which is the brick states
* converted to the original file format. this can be used to
* overwrite a levels data when player changes in alternative
* game */
void game_get_level_snapshot( Level *shot )
{
int i, j;
int y_off;
if ( cur_game->game_type == GT_NETWORK )
y_off = ( MAP_HEIGHT - EDIT_HEIGHT ) / 2;
else
y_off = 1;
for ( i = 0; i < EDIT_WIDTH; i++ )
for ( j = 0; j < EDIT_HEIGHT; j++ ) {
shot->bricks[i][j] = cur_game->bricks[i+1][j+y_off].brick_c;
shot->extras[i][j] = cur_game->bricks[i+1][j+y_off].extra_c;
}
}
/* reset the modification of game_update() */
void game_reset_mods( void )
{
memset( &cur_game->mod, 0, sizeof( GameMod ) );
}
/* update a statistics struct by the level stats of a paddle.
* updates the win/loss/draw as well. the played_rounds entry
* is simply increased everytime this function is called */
void game_update_stats( int id, GameStats *stats )
{
Paddle *paddle;
if ( id < 0 || id >= cur_game->paddle_count ) return;
/* this should be called before game_finalize() as the
* stats will be cleared there */
paddle = cur_game->paddles[id];
stats->total_score += paddle->score;
if ( stats->total_score < 0 ) stats->total_score = 0;
stats->balls_reflected += paddle->balls_reflected;
stats->balls_lost += paddle->balls_lost;
stats->bricks_cleared += paddle->bricks_cleared;
stats->total_brick_count += cur_game->brick_count;
stats->extras_collected += paddle->extras_collected;
stats->total_extra_count += cur_game->extra_count;
if ( cur_game->winner == -1 )
stats->draws++;
else
if ( cur_game->winner == id )
stats->wins++;
else
stats->losses++;
stats->played_rounds++;
}