Logo Search packages:      
Sourcecode: lbreakout2 version File versions  Download package

bricks.c

/***************************************************************************
                          bricks.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 "../client/lbreakout.h"
#include "extras.h"
#include "balls.h"
#include "bricks.h"
#include "mathfuncs.h"

extern Game *cur_game;
extern int ball_dia;

/* extras conversion table */
Extra_Conv extra_conv_table[EX_NUMBER] = {
      { EX_SCORE200,   '0' },
      { EX_SCORE500,   '1' },
      { EX_SCORE1000,  '2' },
      { EX_SCORE2000,  '3' },
      { EX_SCORE5000,  '4' },
      { EX_SCORE10000, '5' },
      { EX_GOLDSHOWER, 'g' },
      { EX_LENGTHEN,   '+' },
      { EX_SHORTEN,    '-' },
      { EX_LIFE,       'l' },
      { EX_SLIME,      's' },
      { EX_METAL,      'm' },
      { EX_BALL,       'b' },
      { EX_WALL,       'w' },
      { EX_FROZEN,     'f' },
      { EX_WEAPON,     'p' },
      { EX_RANDOM,     '?' },
      { EX_FAST,       '>' },
      { EX_SLOW,       '<' },
      { EX_JOKER,      'j' },
      { EX_DARKNESS,   'd' },
      { EX_CHAOS,      'c' },
      { EX_GHOST_PADDLE, '~' },
      { EX_DISABLE,      '!' },
      { EX_TIME_ADD,     '&' },
      { EX_EXPL_BALL,    '*' },
      { EX_BONUS_MAGNET, '}' },
      { EX_MALUS_MAGNET, '{' },
      { EX_WEAK_BALL,    'W' }
};
/* brick conversion table: brick id, char */
Brick_Conv brick_conv_table[BRICK_COUNT] = {
      { 'E', MAP_WALL,        0, -1, 0 },
      { '#', MAP_BRICK,       1, -1, 1000 },
      { '@', MAP_BRICK_CHAOS, 2, -1, 1000 },
      { 'a', MAP_BRICK,       3,  1, BRICK_SCORE * 1 },
      { 'b', MAP_BRICK,       4,  2, BRICK_SCORE * 2 },
      { 'c', MAP_BRICK,       5,  3, BRICK_SCORE * 3 },
      { 'v', MAP_BRICK,       6,  4, BRICK_SCORE * 4 },
      { 'x', MAP_BRICK_HEAL,  7,  1, BRICK_SCORE * 2},
      { 'y', MAP_BRICK_HEAL,  8,  2, BRICK_SCORE * 4},
      { 'z', MAP_BRICK_HEAL,  9,  3, BRICK_SCORE * 6},
      { 'd', MAP_BRICK,      10,  1, BRICK_SCORE },
      { 'e', MAP_BRICK,      11,  1, BRICK_SCORE },
      { 'f', MAP_BRICK,      12,  1, BRICK_SCORE },
      { 'g', MAP_BRICK,      13,  1, BRICK_SCORE },
      { 'h', MAP_BRICK,      14,  1, BRICK_SCORE },
      { 'i', MAP_BRICK,      15,  1, BRICK_SCORE },
      { 'j', MAP_BRICK,      16,  1, BRICK_SCORE },
      { 'k', MAP_BRICK,      17,  1, BRICK_SCORE },
      { '*', MAP_BRICK_EXP,  18,  1, BRICK_SCORE * 2 },
      { '!', MAP_BRICK_GROW, GROW_BRICK_ID,  1, BRICK_SCORE * 2 },
};

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

/*
====================================================================
Initiate a brick explosion.
====================================================================
*/
void brick_start_expl( int x, int y, Paddle *paddle )
{
      cur_game->bricks[x][y].exp_time = BRICK_EXP_TIME;
      cur_game->bricks[x][y].exp_paddle = paddle;
      cur_game->bricks[x][y].mx = x; cur_game->bricks[x][y].my = y;
      list_add( cur_game->exp_bricks, &cur_game->bricks[x][y] );
}

/*
====================================================================
Grow a brick at mx,my if the ball does not block the tile.
====================================================================
*/
static void brick_grow( int mx, int my, int id )
{
      Ball *ball;

      /* check all balls */
      list_reset( cur_game->balls );
      while ( (ball = list_next( cur_game->balls )) != 0 )  {
            if ( mx == (ball->x) / BRICK_WIDTH )
            if ( my == (ball->y) / BRICK_HEIGHT )
                  return;
            if ( mx == (ball->x + ball_dia) / BRICK_WIDTH )
            if ( my == (ball->y) / BRICK_HEIGHT )
                  return;
            if ( mx == (ball->x) / BRICK_WIDTH )
            if ( my == (ball->y + ball_dia) / BRICK_HEIGHT )
                  return;
            if ( mx == (ball->x + ball_dia) / BRICK_WIDTH )
            if ( my == (ball->y + ball_dia) / BRICK_HEIGHT )
                  return;
      }
      
      /* add brick */
      cur_game->bricks[mx][my].brick_c = brick_conv_table[id].c;
      cur_game->bricks[mx][my].id = brick_conv_table[id].id;
      cur_game->bricks[mx][my].type = brick_conv_table[id].type;
      cur_game->bricks[mx][my].score = brick_conv_table[id].score;
      cur_game->bricks[mx][my].dur = brick_conv_table[id].dur;
      /* keep the extra that is already assigned to this position */
      cur_game->bricks[mx][my].exp_time = -1;
      cur_game->bricks[mx][my].heal_time = -1;
      /* adjust brick count */
      cur_game->bricks_left++;
      cur_game->brick_count++;
      /* add modification */
      bricks_add_mod( mx, my, HT_GROW, 0, vector_get(0,0), 0 );

      /* get new targets */
      balls_check_targets( -1, 0 );
}
/*
====================================================================
Remove brick from offscreen and screen.
Create shrapnells by type and impulse.
'paddle' is the paddle that initiated hit either by shot or ball.
====================================================================
*/
void brick_remove( int mx, int my, int type, Vector imp, Paddle *paddle )
{
      int i,j,px,py;
      int dir;

      /* if explosive set exp_time of surrounding bricks */
      if ( cur_game->bricks[mx][my].type == MAP_BRICK_EXP ) {
            for ( i = mx - 1; i <= mx + 1; i++ )
            for ( j = my - 1; j <= my + 1; j++ )
                  if ( i != mx || j != my ) 
                  if ( cur_game->bricks[i][j].type != MAP_EMPTY )
                  if ( cur_game->bricks[i][j].dur > 0 )
                  if ( cur_game->bricks[i][j].exp_time == -1 )
                        brick_start_expl( i,j,paddle );
      }
      if ( cur_game->bricks[mx][my].type == MAP_BRICK_GROW ) {
            for ( i = mx - 1; i <= mx + 1; i++ )
            for ( j = my - 1; j <= my + 1; j++ )
                  if ( cur_game->bricks[i][j].type == MAP_EMPTY )
                        brick_grow( i, j, RANDOM( BRICK_GROW_FIRST, BRICK_GROW_LAST ) );
      }

      /* decrease brick count if no indestructible brick was destroyed */
      if ( cur_game->bricks[mx][my].dur != -1 ) {
            --cur_game->bricks_left;
            
            /* update stats */
            paddle->bricks_cleared++;
      }
      
      /* remove brick from map */
      cur_game->bricks[mx][my].id = -1;
      cur_game->bricks[mx][my].dur = -1;
      cur_game->bricks[mx][my].exp_time = -1;
      cur_game->bricks[mx][my].heal_time = -1;
      cur_game->bricks[mx][my].type = MAP_EMPTY;
      cur_game->bricks[mx][my].brick_c = ' ';
      
      px = mx*BRICK_WIDTH;
      py = my*BRICK_HEIGHT;
      
      /* release extra if one exists */
      dir = ( paddle->type == PADDLE_TOP ) ? -1 : 1;
      if ( cur_game->bricks[mx][my].extra != EX_NONE ) {
            if ( cur_game->diff->allow_maluses || 
                 !extra_is_malus( cur_game->bricks[mx][my].extra ) )
                  list_add( cur_game->extras, 
                          extra_create( cur_game->bricks[mx][my].extra, px, py, dir ) );
      }
      else if ( paddle->extra_active[EX_GOLDSHOWER] )
            list_add( cur_game->extras,
                          extra_create( EX_SCORE1000, px, py, dir ) );
      cur_game->bricks[mx][my].extra = EX_NONE;
      cur_game->bricks[mx][my].extra_c = ' ';
      
      /* add score */
      paddle->score += cur_game->bricks[mx][my].score;
        /* give 5000 extra if this was the last brick 
        if ( cur_game->bricks_left == 0 )
            paddle->score += 5000;*/
}

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

/*
====================================================================
Init bricks from level data, set the warp limit (percent) and 
add regenerating bricks. As this function is called when 
initializing a level it does not use the 'cur_game' context.
'score_mod' is percentual and 100 means normal score.
====================================================================
*/
void bricks_init( Game *game, int game_type, Level *level, int score_mod, int rel_warp_limit  )
{
      int i, j, k;
      int y_off;

      /* clear everything */
      for (i = 0; i < MAP_WIDTH; i++)
            for (j = 0; j < MAP_HEIGHT; j++) {
                  game->bricks[i][j].id = -1;
                  game->bricks[i][j].dur = -1;
                  game->bricks[i][j].type = MAP_EMPTY;
                  game->bricks[i][j].brick_c = ' ';
                  game->bricks[i][j].extra_c = ' ';
            }

      /* build walls */
      for (i = 0; i < MAP_WIDTH; i++)
            if ( game_type == GT_LOCAL ) {
                  /* in multiplayer this is open */
                  game->bricks[i][0].id = 0;
                  game->bricks[i][0].dur = -1;
                  game->bricks[i][0].type = MAP_WALL; /* this means - indestructible */
            }
      for (j = 0; j < MAP_HEIGHT; j++) {
            game->bricks[0][j].id = 0;
            game->bricks[0][j].dur = -1;
            game->bricks[0][j].type = MAP_WALL; /* this means - indestructible */
            game->bricks[MAP_WIDTH - 1][j].id = 0;
            game->bricks[MAP_WIDTH - 1][j].dur = -1;
            game->bricks[MAP_WIDTH - 1][j].type = MAP_WALL;
      }

      /* load map -- center if multiplayer */
      if ( 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++) {
            /* create bricks */
            game->bricks[i + 1][j + y_off].exp_time = -1;
            game->bricks[i + 1][j + y_off].heal_time = -1;
            for ( k = 0; k < BRICK_COUNT; k++ )
                  if ( level->bricks[i][j] == brick_conv_table[k].c ) {
                        game->bricks[i + 1][j + y_off].brick_c = brick_conv_table[k].c;
                        game->bricks[i + 1][j + y_off].type = brick_conv_table[k].type;
                        game->bricks[i + 1][j + y_off].id = brick_conv_table[k].id;
                        game->bricks[i + 1][j + y_off].dur = brick_conv_table[k].dur;
                        game->bricks[i + 1][j + y_off].score = 
                              (score_mod * brick_conv_table[k].score) / 10;
                        break;
                  }
            if ( k == BRICK_COUNT && level->bricks[i][j] != '.' && level->bricks[i][j] != ' ' )
                  printf( "unknown: %i,%i: %c\n", i, j, level->bricks[i][j] );
            /* create extras */
            game->bricks[i + 1][j + y_off].extra = EX_NONE;
            for ( k = 0; k < EX_NUMBER; k++ )
                  if ( level->extras[i][j] == extra_conv_table[k].c ) {
                        game->bricks[i + 1][j + y_off].extra_c = extra_conv_table[k].c;
                        game->bricks[i + 1][j + y_off].extra = extra_conv_table[k].type;
                        break;
                  }
      }

      /* count bricks & extras */
      game->bricks_left = 0; game->extra_count = 0;
      for (i = 1; i < MAP_WIDTH - 1; i++)
            for (j = 1; j < MAP_HEIGHT - 1; j++) {
                  if ( game->bricks[i][j].dur > 0 ) {
                        game->bricks_left++;
                        if ( game->bricks[i][j].extra != EX_NONE )
                              game->extra_count++;
                  }
            }
      game->brick_count = game->bricks_left;

        /* the above code is fine for levels that are loaded from a 
         * snapshot identical to the original level which is the
         * case for first load in local game and always in network
         * game (as in this case, the complete level is sent and
         * never restarted). for a local game where a ball gets
         * lost the total number of bricks gets distorted. to allow
         * fair usage of the warp function the level's original 
         * brick count is used in any case. 'game->brick_count as
         * computed above is screwed anyway for re-initiated levels
         * as it does not remember the cleared bricks. the original 
         * number does not take the grown bricks in account. i don't
         * see an easy way to sync both, so let's keep it, nobody
         * is hurt as long as no one asks for stats on local games.
         * DONT ASK FOR STATS ON LOCAL GAMES!!!
         * okay, grown bricks are not taken in account for the
         * warp limit, it is computed based on the initial brick
         * count, thus the additional bricks from a growth must be
         * cleared without helping to hit the warp limit. that is
         * not too unfair i think, as it will allow to warp when
         * you come below the limit and then loose the ball. 
         * why do i write so much anyway? */
      game->warp_limit = ( 100 - rel_warp_limit ) * 
                           level->normal_brick_count / 100;
      
      /* clear explosion/healing list */
      list_clear( game->exp_bricks );
      list_clear( game->heal_bricks );

      /* add regenerating bricks */
        for ( i = 1; i < MAP_WIDTH - 1; i++ )
            for ( j = 1; j < MAP_HEIGHT - 1; j++ )
                if ( game->bricks[i][j].type == MAP_BRICK_HEAL )
            if ( game->bricks[i][j].dur < 3 ) {
                    game->bricks[i][j].mx = i;
                    game->bricks[i][j].my = j;
                    game->bricks[i][j].heal_time = BRICK_HEAL_TIME;
                    list_add( game->heal_bricks, &game->bricks[i][j] );
                }
}
/*
====================================================================
Hit brick and remove if destroyed. 'metal' means the ball
destroys any brick with the first try.
type and imp are used for shrapnell creation.
'extra' contains the pushed extra if one was released.
'paddle' is the paddle that initiated hit either by shot or ball.
Return true on destruction
====================================================================
*/
int brick_hit( int mx, int my, int metal, int type, Vector imp, Paddle *paddle )
{
      int remove = 0;
      int loose_dur = 0;

      /* a map wall can't be touched */
      if ( cur_game->bricks[mx][my].type == MAP_WALL ) return 0;

      /* if metal ball resistance is futile */
      if ( metal ) 
            remove = 1;
      else {
            if ( cur_game->bricks[mx][my].dur == -1 ) 
                  return 0; /* duration of -1 means only breakable 
                             by engery ball (metal ball) */
            if ( cur_game->bricks[mx][my].dur <= 1 )
                  remove = 1;
            else
                  loose_dur = 1;
      }

      if ( remove ) { 
            bricks_add_mod( mx, my, HT_REMOVE, type, imp, paddle );
            brick_remove( mx, my, type, imp, paddle );
      }
      else
            if ( loose_dur )  {
                  bricks_add_mod( mx, my, HT_HIT, type, imp, paddle );
                  brick_loose_dur( mx, my, 1 );
            }

      return remove;
}
/*
====================================================================
Make brick at mx,my loose 'points' duration. It must have been
previously checked that this operation is completely valid.
It does not update net_bricks or the player's duration reference.
====================================================================
*/
void brick_loose_dur( int mx, int my, int points )
{
      while ( points-- > 0 ) {
            cur_game->bricks[mx][my].dur--;
            cur_game->bricks[mx][my].id--;
            /* adjust brick character:
             * a,b,c - multiple hits
             * v - invisible */
            if ( cur_game->bricks[mx][my].brick_c == 'v' )
                  cur_game->bricks[mx][my].brick_c = 'c';
            else
                  cur_game->bricks[mx][my].brick_c--; /* successive order */
            /* set regeneration time if it's a healing brick */
            if ( cur_game->bricks[mx][my].type == MAP_BRICK_HEAL ) {
                  /* if this brick is already healing just reset the time
                     but don't add to the list again */
                  if ( cur_game->bricks[mx][my].heal_time != -1 )
                        cur_game->bricks[mx][my].heal_time = BRICK_HEAL_TIME;
                  else {
                        cur_game->bricks[mx][my].mx = mx;
                        cur_game->bricks[mx][my].my = my;
                        cur_game->bricks[mx][my].heal_time = BRICK_HEAL_TIME;
                        list_add( cur_game->heal_bricks, &cur_game->bricks[mx][my] );
                  }
            }
      }
}

/* add a modification to the list. if 'mod' is HT_HIT and the
 * tile is empty it is an HT_REMOVE. 'type' is the type of
 * the responsible source and 'src' its impact vector. */
void bricks_add_mod( int x, int y, int mod, int dest_type, Vector imp, Paddle *paddle )
{
      BrickHit *hit;
      
      if ( cur_game->mod.brick_hit_count > MAX_MODS ) return; /* drop hit */

      hit = &cur_game->mod.brick_hits[cur_game->mod.brick_hit_count++];
      memset(hit,0,sizeof(BrickHit)); /* clear hit */
      
      if ( mod == HT_REMOVE ) {
            if ( paddle->extra_active[EX_GOLDSHOWER] )
                  if ( cur_game->bricks[x][y].extra == EX_NONE )
                        hit->gold_shower = 1;
            if (cur_game->bricks[x][y].type==MAP_BRICK_EXP)
                  hit->draw_explosion = 1;
            if (dest_type==SHR_BY_DELAYED_EXPL)
              {
                dest_type = SHR_BY_EXPL; /* delayed explosion thus initiated by a close-by
                                    explosion have no explosion animation */
              }
            else if (dest_type==SHR_BY_NORMAL_BALL && cur_game->extra_active[EX_EXPL_BALL])
              {
                dest_type = SHR_BY_EXPL;
                hit->draw_explosion = 1;
              }
      }

      hit->x = x; hit->y = y; 
      hit->type = mod; 
      hit->dest_type = dest_type;
      hit->paddle = (cur_game->paddles[PADDLE_BOTTOM]==paddle)?PADDLE_BOTTOM:PADDLE_TOP;
      
      hit->degrees = 0;
      if ( mod == HT_REMOVE && dest_type == SHR_BY_NORMAL_BALL ) {
            hit->degrees = vec2angle( &imp );
      }
      else
      if ( dest_type == SHR_BY_SHOT ) {
            if ( hit->paddle == PADDLE_BOTTOM )
                  hit->degrees = 270 / 2;
            else
                  hit->degrees = 90 / 2;
      }
}

/* update regeneration and explosion of bricks */
void bricks_update( int ms )
{
      Brick *brick;
      
      /* check if bricks were destroyed by explosion */
      if ( cur_game->exp_bricks->count > 0 ) {
            list_reset( cur_game->exp_bricks );
            while ( ( brick = list_next( cur_game->exp_bricks ) ) != 0 ) {
                  if ( (brick->exp_time -= ms) <= 0 ) {
                        brick->exp_time = -1;
                        bricks_add_mod( brick->mx, brick->my, 
                                    HT_REMOVE, SHR_BY_DELAYED_EXPL, 
                                    vector_get( 0, 0 ), brick->exp_paddle );
                        brick_remove( brick->mx, brick->my, SHR_BY_EXPL, 
                                    vector_get( 0, 0 ), brick->exp_paddle );
                        balls_check_targets( brick->mx, brick->my );
                        list_delete_current( cur_game->exp_bricks );
                  }
            }
      }
      
      /* check if bricks regenerate */
      if ( cur_game->heal_bricks->count > 0 ) {
            list_reset( cur_game->heal_bricks );
            while ( ( brick = list_next( cur_game->heal_bricks ) ) != 0 ) {
                  /* skip brick if destroyed meanwhile */
                  if ( brick->type != MAP_BRICK_HEAL ) {
                        list_delete_current( cur_game->heal_bricks );
                        continue;
                  }
                  if ( (brick->heal_time -= ms) < 0 ) {
                        brick->dur++;
                        brick->id++;
                        bricks_add_mod( brick->mx, brick->my, 
                                    HT_HEAL, 0, vector_get( 0, 0 ), 0 );
                        if ( brick->dur < 3 ) {
                              /* initate next healing step */
                              brick->heal_time = BRICK_HEAL_TIME;
                        }
                        else {
                              brick->heal_time = -1;
                              list_delete_current( cur_game->heal_bricks );
                        }
                  }
            }
      }
}

/* return the character that represents the brick with this type id */
char brick_get_char( int type )
{
      int i;
      for ( i = 0; i < BRICK_COUNT; i++ )
            if ( brick_conv_table[i].id == type )
                  return brick_conv_table[i].c;
      return ' ';
}



Generated by  Doxygen 1.6.0   Back to index