/***************************************************************************
                          breakout.cpp  -  description
                             -------------------
    begin                : Thu Apr 20 2000
    copyright            : (C) 2000 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 "breakout.h"
#include "level.h"
#include <stdlib.h>
#include <stdio.h>
#include <sys/timeb.h>
#include <string.h>
#include <math.h>

#define TOARC(d) (((float)d/180)*M_PI)
#define TODEG(a) (((float)a/M_PI)*180)
#define VLEN(x, y) ( sqrt( (x)*(x) + (y)*(y) ) )

#define N   0
#define E   1
#define S   2
#define W   3

extern Sdl sdl;
extern char *home_dir;
extern int fast_quit;
extern DrawRgn dr_src, dr_dst;
extern Level orig_levels[LEVEL_NUM];
#ifdef SOUND
extern SndSrv sndsrv;
#endif

/*
    egcs 2.91.66 got some problems with pow() in GetTarget
    so I'll use this function instead
*/
float Square(float x)
{
    return (x * x);
}

BreakOut::BreakOut()
{
    // misc //
    file_levels = 0;

    // load font //
    font = SFnt_LoadFixed("f_yellow.bmp", 32, 96, 10);
    font->algn = TA_X_CENTER | TA_Y_CENTER;

    // load hiscore //
    hiscore = HiScore::CreateComposite("lbreakout.hscr");

    // config path //
    cfg_path = new char[strlen(home_dir) + strlen(".lbreakout.cfg") + 2];
    sprintf(cfg_path, "%s/.lbreakout.cfg", home_dir);

    // load init //
    FILE	*f;
    if ((f = fopen(cfg_path, "r")) == 0) {
        strcpy(init_data.name, "Michael");
        strcpy(init_data.lvl_file, "test.lbr");
        strcpy(init_data.lvl_path, "lbreakout:");
        init_data.k_left = SDLK_LEFT;
        init_data.k_right = SDLK_RIGHT;
        init_data.k_fire = SDLK_SPACE;
        init_data.snd_on = 1;
        init_data.snd_vol = 8;
        init_data.key_speed = 0.45;
        init_data.trp = 1;
        init_data.anim = 2;
        init_data.warp = 0;
        init_data.control = 2;
        init_data.diff = 1;
        init_data.startlevel = 0;
        init_data.lvls_frm_file = 0;
        init_data.motion_mod = 100;
        init_data.bkgnd = 1;
        init_data.fullscreen = 0;
        init_data.convex = 0;
        init_data.invert = 0;
        init_data.rnd_start = 0;
        init_data.no_exdisp = 0;
	}
	else {
		fread(&init_data, sizeof(InitData), 1, f);
		fclose(f);
	}
	
	// background //
	ss_bkgnd = 0;
	ss_picture = 0;
	
	// frame //
	ss_fr_luc = SSur_Load("fr_luc.bmp", SDL_HWSURFACE);
	ss_fr_left = SSur_Load("fr_l.bmp", SDL_HWSURFACE);
	ss_fr_top = SSur_Load("fr_t.bmp", SDL_HWSURFACE);
	ss_fr_ruc = SSur_Load("fr_ruc.bmp", SDL_HWSURFACE);
	ss_fr_right = SSur_Load("fr_r.bmp", SDL_HWSURFACE);
	ss_fr_rlc = SSur_Load("fr_rlc.bmp", SDL_HWSURFACE);
	
	// bricks //
	brick_w = 32;
	brick_h = 16;
	brick_score = 100;
	if ((ss_bricks = SSur_Load("bricks.bmp", SDL_HWSURFACE)) == 0)
	    exit(1);
	
	// club //
	club_cw = 10;
	club_ch = 10;
	if ((ss_club = SSur_Load("club.bmp", SDL_HWSURFACE)) == 0)
	    exit(1);
	
	// ball //
	ball_vhmask = 0.363970234; // twenty degrees //
	ball_vvmask = 5.67128182; // ten degrees //
	ball_rad = 5;
	ball_dia = 11;
	ball_w = 12;
	ball_h = 12;
	if ((ss_ball = SSur_Load("ball.bmp", SDL_HWSURFACE)) == 0)
        exit(1);
    DL_Init(&ball_list);
    ball_list.flags = DL_AUTODELETE | DL_NOCALLBACK;

    // ball points //
    int i, j, a;
    a = -45;
    for (i = 0; i < 4; i++) {
        for (j = 0; j < 7; j++) {
            ball_pnts[i][j][0] = (int)ceil(sin(TOARC(a)) * ball_rad);
            ball_pnts[i][j][1] = (int)ceil(cos(TOARC(a)) * ball_rad);
            a += 15;
        }
        a -= 15;
    }

	// update rects //
	rect_num = 0;
	
	// life //
	ss_life = SSur_Load("life.bmp", SDL_HWSURFACE);
	
	// score //
	ss_numbers = SSur_Load("numbers.bmp", SDL_HWSURFACE);
	score_xoff = 44; // substracted from width
	score_y = 2; // added to 0
	score_lw = 8;
	score_h = 10;
	score_x = score_w = 0;
	
	// extras //
	ss_extras = SSur_Load("extras.bmp", SDL_HWSURFACE);
	DL_Init(&extra_list);
	extra_list.flags = DL_AUTODELETE | DL_NOCALLBACK;
	
	// shrapnells //
	DL_Init(&shrapnell_list);
	shrapnell_list.flags = DL_AUTODELETE;
	shrapnell_list.cb_destroy = Shr_Free;
	
	// plasma weapon //
	ss_weapon = SSur_Load("weapon.bmp", SDL_HWSURFACE);
	wpn_y_off = 4;
	wpn_w = 14; wpn_h = 15;
	wpn_sx_off = 2;
	wpn_sy_off = -3;
	wpn_fr_num = 6;
	wpn_fpms = 0.006;
	wpn_cur = 0;
	
	// plasma shots //
	ss_shot = SSur_Load("shot.bmp", SDL_HWSURFACE);
	DL_Init(&shot_list);
	shot_list.flags = DL_AUTODELETE | DL_NOCALLBACK;
	shot_w = shot_h = 10;
	shot_fr_num = 4;
	shot_fpms = 0.01;
	shot_v_y = -0.2;
	shot_alpha = 64;
	max_shots = 6;
	shot_time = 0;
	shot_delay = 100;
	
	// snapshot //
	snapshot = 0;
	
	// cursor //
	unsigned char mask = 0;
	original_cur = SDL_GetCursor();
	empty_cur = SDL_CreateCursor(&mask, &mask, 8, 1, 0, 0);

	// difficulty //
	// easy
	diff[0].lives = 5;
	diff[0].max_lives = 9;
	diff[0].con_cost = 20000;
	diff[0].club_size = 3;
	diff[0].club_max_size = 8;
	diff[0].score_mod = 1.0;
	// medium
	diff[1].lives = 4;
	diff[1].max_lives = 6;
	diff[1].con_cost = 20000;
	diff[1].club_size = 2;
	diff[1].club_max_size = 6;
	diff[1].score_mod = 1.1;
	// hard
	diff[2].lives = 3;
	diff[2].max_lives = 4;
	diff[2].con_cost = 30000;
	diff[2].club_size = 2;
	diff[2].club_max_size = 4;
	diff[2].score_mod = 1.2;
	
	// shine //
	ss_sh = SSur_Load("shine2.bmp", SDL_HWSURFACE);
	sh_cur = 0;
	sh_fr = 6;
	sh_pms = 0.024;
	sh_x = sh_y = 0;
	
	// credit //
	cr_time = 4000;
	cr_pms = 0.2;
	
	// extra display //
	ed_y = 40;
	memset(ed_offsets, 0, sizeof(ed_offsets));
	ed_offsets[EX_SLIME] = 20;
	ed_offsets[EX_METAL] = 40;
	ed_offsets[EX_WALL] = 60;
	ed_offsets[EX_WEAPON] = 80;
	ed_offsets[EX_FAST] = 100;
	ed_offsets[EX_SLOW] = 100;
	
#ifdef SOUND
    snd_ref = Wave_Load("reflect.wav");
    snd_boom = Wave_Load("exp.wav");
    snd_lup = Wave_Load("gainlife.wav");
    snd_ldown = Wave_Load("looselife.wav");
    snd_exp = Wave_Load("expand.wav");
    snd_shr = Wave_Load("shrink.wav");
    snd_sco = Wave_Load("score.wav");
    snd_fre = Wave_Load("freeze.wav");
    snd_shot = Wave_Load("shot.wav");
    snd_sli = Wave_Load("slime.wav");
    snd_wea = Wave_Load("weapon.wav");
    snd_met = Wave_Load("metal.wav");
    snd_wal = Wave_Load("wall.wav");
    snd_damn1 = Wave_Load("damn.wav");
    snd_damn2 = Wave_Load("damnit.wav");
    snd_good = Wave_Load("verygood.wav");
    snd_exc = Wave_Load("excellent.wav");
    snd_click = Wave_Load("click.wav");
    snd_wontgiveup = Wave_Load("wontgiveup.wav");
    snd_speedup = Wave_Load("speedup.wav");
    snd_speeddown = Wave_Load("speeddown.wav");
#endif	
}

BreakOut::~BreakOut()
{
    // save hiscore //
    if (hiscore) {
        hiscore->Save();
        delete hiscore;
    }

    //save init //
    FILE	*f = fopen(cfg_path, "w");
    fwrite(&init_data, sizeof(InitData), 1, f);
    fclose(f);
    if (cfg_path) delete cfg_path;

    if (ss_bricks) SDL_FreeSurface(ss_bricks);
    if (ss_ball) SDL_FreeSurface(ss_ball);
    if (ss_club) SDL_FreeSurface(ss_club);
    if (ss_bkgnd) SDL_FreeSurface(ss_bkgnd);
    if (ss_picture) SDL_FreeSurface(ss_picture);
    if (ss_life) SDL_FreeSurface(ss_life);
    if (ss_numbers) SDL_FreeSurface(ss_numbers);
    if (ss_extras) SDL_FreeSurface(ss_extras);
    if (ss_shot) SDL_FreeSurface(ss_shot);
    if (ss_weapon) SDL_FreeSurface(ss_weapon);
    if (font) SFnt_Free(font);
    if (ss_fr_luc) SDL_FreeSurface(ss_fr_luc);
    if (ss_fr_left) SDL_FreeSurface(ss_fr_left);
    if (ss_fr_top) SDL_FreeSurface(ss_fr_top);
    if (ss_fr_ruc) SDL_FreeSurface(ss_fr_ruc);
    if (ss_fr_right) SDL_FreeSurface(ss_fr_right);
    if (ss_fr_rlc) SDL_FreeSurface(ss_fr_rlc);
    if (ss_sh) SDL_FreeSurface(ss_sh);

#ifdef SOUND
    if (snd_ref) Wave_Free(snd_ref);
    if (snd_boom) Wave_Free(snd_boom);
    if (snd_lup) Wave_Free(snd_lup);
    if (snd_ldown) Wave_Free(snd_ldown);
    if (snd_exp) Wave_Free(snd_exp);
    if (snd_shr) Wave_Free(snd_shr);
    if (snd_sco) Wave_Free(snd_sco);
    if (snd_fre) Wave_Free(snd_fre);
    if (snd_shot) Wave_Free(snd_shot);
    if (snd_sli) Wave_Free(snd_sli);
    if (snd_wea) Wave_Free(snd_wea);
    if (snd_met) Wave_Free(snd_met);
    if (snd_wal) Wave_Free(snd_wal);
    if (snd_damn1) Wave_Free(snd_damn1);
    if (snd_damn2) Wave_Free(snd_damn2);
    if (snd_good) Wave_Free(snd_good);
    if (snd_exc) Wave_Free(snd_exc);
    if (snd_click) Wave_Free(snd_click);
    if (snd_wontgiveup) Wave_Free(snd_wontgiveup);
    if (snd_speedup) Wave_Free(snd_speedup);
    if (snd_speeddown) Wave_Free(snd_speeddown);
#endif

    DL_Clear(&ball_list);
    DL_Clear(&extra_list);
    DL_Clear(&shrapnell_list);
    DL_Clear(&shot_list);

    // cursor //
    SDL_FreeCursor(empty_cur);
}

InitData* BreakOut::Setup()
{
    return &init_data;
}

int BreakOut::Run()
{
    int         leave = 0, ask_for_con = 0, restart = 0, disappear = 0, appear = 0, con_bought = 0;
    SDL_Event   event;
    int         call, last_call;
    int         ms = 0;
    float       ca = 0;
    DL_Entry    *e;

    // motion modifier if using relative mouse motion //
    motion_mod = (float)init_data.motion_mod / 100;

    // check if path of own levels is ok //
    if (init_data.lvls_frm_file)
        Levels_LoadFromFile();
    else
        UseOrigLevels();

    // init game //
    InitGame();

    // cursor ? //
    if (init_data.warp || init_data.control == 0 || (!init_data.warp && fullscreen))
    	SDL_SetCursor(empty_cur);

    SDL_UNDIM();

    // set time //
    call = SDL_GetTicks();
    last_call = call;

    // shine //
    Sh_New();

    // main loop //
    while (1) {
        // clear input states //
        while (SDL_PollEvent(&event));

        // game loop //
        while (!fast_quit && !leave && !new_level && !restart) {
            // reset //
            mouse_moved = 0;
            // check key/mousestate //
            if (SDL_PollEvent(&event)) {
                switch (event.type) {
                    case SDL_QUIT:
                        fast_quit = 1;
                        break;
                    case SDL_MOUSEBUTTONDOWN:
                        buttonstate[event.button.button] = 1;
                        break;
                    case SDL_MOUSEBUTTONUP:
                        buttonstate[event.button.button] = 0;
                        fire_shot = 0;
                        shot_time = 0;
                        break;
                    case SDL_KEYDOWN:
                        keystate[event.key.keysym.sym] = 1;
                        break;
                    case SDL_KEYUP:
                        keystate[event.key.keysym.sym] = 0;
                        if (!keystate[init_data.k_fire]) {
                            fire_shot = 0;
                            shot_time = 0;
                        }
                        // screenshot
                        if (event.key.keysym.sym == SDLK_TAB)
                            SnapShot();
                        // quit
                        if (event.key.keysym.sym == SDLK_ESCAPE) {
                            if (ConfirmQuit())
                                leave = 1;
                            else
                                last_call = SDL_GetTicks();
                        }
                        // restart
                        if (event.key.keysym.sym == SDLK_r)
                            disappear = 1;
                        // turn on/off sound
#ifdef SOUND
                        if (event.key.keysym.sym == SDLK_s) {
                            init_data.snd_on = !init_data.snd_on;
                    		SndSrv_SetActive(init_data.snd_on);
                    	}	
#endif              	
                    	// turn on/off transparancy
                        if (event.key.keysym.sym == SDLK_t)
                            init_data.trp = !init_data.trp;
                        // animation level
                        if (event.key.keysym.sym == SDLK_a) {
                            init_data.anim++;
                            if (init_data.anim >= 3)
                                init_data.anim = 0;
                        }
                        // set fullscreen
                        if (event.key.keysym.sym == SDLK_f) {
                            fullscreen = !fullscreen;
                            if (fullscreen) {
                                Sdl_SetVideoMode(lev_w * brick_w, lev_h * brick_h, 16, SDL_HWSURFACE | SDL_FULLSCREEN);
                                SDL_SetCursor(empty_cur);
                            }
                            else {
                                Sdl_SetVideoMode(lev_w * brick_w, lev_h * brick_h, 16, SDL_HWSURFACE);
                                if (!init_data.warp && !init_data.control == 0)
                                	SDL_SetCursor(original_cur);
                            }
                            DR_SETFULLDST(sdl.scr);
                            DR_SETFULLSRC(ss_bkgnd);
                            SSur_Blit();
                            Sdl_FullUpdate();
                            last_call = SDL_GetTicks();
                        }
                        // pause
                        if (event.key.keysym.sym == SDLK_p) {
                            Pause();
                            last_call = SDL_GetTicks();
                        }
                        break;
                    }
                if (!disappear)
                    switch (event.type) {
                        case SDL_KEYDOWN:
                            if (cur_extras[EX_WEAPON] && event.key.keysym.sym == init_data.k_fire) {
                                fire_shot = 1;
                                shot_time = shot_delay;
                            }
                            break;
                        case SDL_MOUSEBUTTONDOWN:
                            if (cur_extras[EX_WEAPON]) {
                                fire_shot = 1;
                                shot_time = shot_delay;
                            }
                            break;
                        case SDL_MOUSEMOTION:
                            mouse_moved = 1;
                            if (init_data.warp) {
                                mx = (int)(motion_mod * event.motion.xrel);
                                if (init_data.invert)
                                    mx = -mx;
                            }
                            else
                                mx = event.motion.x;
                            break;
                    }
            }
            if (leave)
                break;

            // hide //
            Life_Hide();
            Club_Hide();
            Balls_Hide();
            Shots_Hide();
            Wall_Hide();
            Extras_Hide();
            Shr_Hide();
            Score_Hide();
            ExDisp_Hide();
            if ( init_data.anim )
                Sh_Hide();
            Credit_Hide();

            // compute time //
            call = SDL_GetTicks();
            ms = call - last_call;
            last_call = call;
            if (ms <= 0) {
                ms = 1;
                SDL_Delay(1);
            }

            // update //
            if (!disappear && !appear) {
                Club_Update(ms);
                if (Balls_Update(ms))
                    disappear = 1;
            }
            Shots_Update(ms);
            Wall_Update(ms);
            Wpn_Update(ms);
            Extras_Update(ms);
            Shr_Update(ms);
            Score_Update(ms);
            if ( init_data.anim )
                Sh_Update(ms);
            Credit_Update(ms);

            // update club alpha //
            if (appear) {
                ca -= 0.5 * ms;
                if (ca < 0) {
                    restart = 1;
                    appear = 0;
                    ca = 0;
                }
            }
            else
                if (disappear) {
                    ca += 0.5 * ms;
                    if (ca > 255) {
                        appear = 1;
                        disappear = 0;
                        ca = 255;
                        Club_Reset();
                        Balls_Reset();
                        cur_extras[EX_SLIME] =
                        cur_extras[EX_METAL] =
                        cur_extras[EX_WEAPON] =
                        cur_extras[EX_FROZEN] =
                        cur_extras[EX_SLOW] =
                        cur_extras[EX_FAST] = 0;
                    }
                }

            // increase and adjust ball's speed //
            if ( !cur_extras[EX_SLOW] && !cur_extras[EX_FAST] ) {

                if (ball_v < ball_vm && !(ball_list.counter == 1 && ((Ball*)(ball_list.head.next->data))->attached)) {
                    ball_v += ball_v_add * ms;
                    e = ball_list.head.next;
                    while (e != &ball_list.tail) {
                        Ball_AdjustSpeed((Ball*)e->data, ball_v);
                        e = e->next;
                    }
                }

            }

            // update lifes //
            if (!con_bought && ( (restart && player.lives-- == 0) || (disappear && player.lives == 0) ) )
               ask_for_con = 1;

            // show //
            Life_Show();
            Shots_Show();
            if (disappear || appear) {
                Balls_AlphaShow(ca);
                Club_AlphaShow(ca);
            }
            else {
                Balls_Show();
                Club_Show();
            }
            Wall_Show();
            Extras_Show();
            if ( init_data.anim )
                Sh_Show();
            Score_Show();
            ExDisp_Show();
            Shr_Show();
            Credit_Show();

            // refresh //
            Refresh();

            if (ask_for_con) {
                if (BuyContinue()) {
                    last_call = SDL_GetTicks();
                    con_bought = 1;
                }
                else
                    leave = 1;
                ask_for_con = 0;
            }
        }
        if (!fast_quit && !leave) {
            if (new_level) {
                if (++level >= lev_num)
                    break;
                SDL_DIM();
                InitLevel(level);
                SDL_UNDIM();
            }
            else {
#ifdef SOUND
    SndSrv_Play(snd_ldown, 0);
#endif
               	// reset event stuff //
            	mx = 0;
            	memset(buttonstate, 0, sizeof(buttonstate));
            	memset(keystate, 0, sizeof(keystate));
            	ball_v = levels[level].v;
            	con_bought = 0;
            }
            new_level = 0;
            restart = 0;
        }
        else
            break;
    }

    fullscreen = 0;

    // cursor ? //
  	SDL_SetCursor(original_cur);

    // delete file levels //
    if (levels == file_levels)
        Levels_Delete();
    	
    // modify score //
    player.score = (int)(player.score * diff[init_data.diff].score_mod);

    SDL_DIM();

    if (!fast_quit)
        GameOver();

    Sdl_SetVideoMode(450, 325, 16, SDL_HWSURFACE);

    return hiscore->CheckEntry(&player);
}

void BreakOut::InitGame()
{
    // reset player //
    player.score = 0;
    cur_score = 0;
    player.lives = diff[init_data.diff].lives;
    strcpy(player.name, init_data.name);
    player.level = init_data.startlevel;

    // fullscreen ? //
    fullscreen = init_data.fullscreen;

    // int first level //
    lev_w = lev_h = 0;
    level = init_data.startlevel;
    InitLevel(level);

}

void BreakOut::InitLevel(int l)
{
    char    str[256];
    SDL_Surface *pic;
    int     flgs = SDL_HWSURFACE;
    int     old_w = lev_w, old_h = lev_h;

    // load map //
     LoadLevel(l);

    // set new resolution //
    if (lev_w != old_w || lev_h != old_h) {
        if (fullscreen)
            flgs = flgs | SDL_FULLSCREEN;
        Sdl_SetVideoMode(lev_w * brick_w, lev_h * brick_h, 16, flgs);
    }

    // reset background //
    if (ss_bkgnd) SDL_FreeSurface(ss_bkgnd);
    ss_bkgnd = SSur_Create(sdl.scr->w, sdl.scr->h, SDL_HWSURFACE);
    SDL_SetColorKey(ss_bkgnd, 0, 0);
    if (ss_picture) SDL_FreeSurface(ss_picture);
    ss_picture = SSur_Create(sdl.scr->w, sdl.scr->h, SDL_HWSURFACE);
    SDL_SetColorKey(ss_picture, 0, 0);
    if (init_data.bkgnd) {
        sprintf(str, "bkgnd%i.bmp", level / 10);
      	if ((pic = SSur_Load(str, SDL_HWSURFACE)) == 0)
       	    if ((pic = SSur_Load("bkgnd0.bmp", SDL_HWSURFACE)) == 0) {
                pic = SSur_Create(sdl.scr->w, sdl.scr->h, SDL_HWSURFACE);
                DR_SETFULLDST(pic);
                SSur_Fill(0x0);
            }
        SDL_SetColorKey(pic, 0, 0);
        DR_SETDST(ss_picture, (ss_picture->w - pic->w) / 2, (ss_picture->h - pic->h) / 2, pic->w, pic->h);
        DR_SETFULLSRC(pic);
        SSur_Blit();
        DR_SETFULLDST(ss_bkgnd);
        DR_SETFULLSRC(ss_picture);
        SSur_Blit();
        SDL_FreeSurface(pic);
    }
    else {
        DR_SETFULLDST(ss_bkgnd);
        SSur_Fill(0x0);
    }

    // draw frame to background //
    int i, j;
    for (i = 1; i < lev_w - 1; i++)
        for (j = 1; j < lev_h - 1; j++)
            if (lev_map[i][j].type == MAP_WALL) {
                DR_SETDST(ss_bkgnd, i * brick_w, j * brick_h, brick_w, brick_h);
                DR_SETSRC(ss_bricks, lev_map[i][j].id * brick_w, 0);
                SSur_Blit();
            }
    // left upper corner
    DR_SETDST(ss_bkgnd, 0, 0, ss_fr_luc->w, ss_fr_luc->h);
    DR_SETFULLSRC(ss_fr_luc);
    SSur_Blit();
    // life lights //
    for (j = 11; j < lev_h; j++) {
         DR_SETDST(ss_bkgnd, 0, j * brick_h, brick_w, brick_h);
         DR_SETFULLSRC(ss_fr_left);
         SSur_Blit();
    }
    // top tiles
    for (i = 4; i < lev_w - 4; i++) {
         DR_SETDST(ss_bkgnd, i * brick_w, 0, brick_w, brick_h);
         DR_SETFULLSRC(ss_fr_top);
         SSur_Blit();
    }
    // right upper corner
    DR_SETDST(ss_bkgnd, (lev_w - 4) * brick_w, 0, ss_fr_ruc->w, ss_fr_ruc->h);
    DR_SETFULLSRC(ss_fr_ruc);
    SSur_Blit();
    // right tiles
    for (j = 1; j < lev_h - 4; j++) {
         DR_SETDST(ss_bkgnd, (lev_w - 1) * brick_w, j * brick_h, brick_w, brick_h);
         DR_SETFULLSRC(ss_fr_right);
         SSur_Blit();
    }
    // right lower corner
    DR_SETDST(ss_bkgnd, (lev_w - 1) * brick_w, (lev_h - 4) * brick_h, ss_fr_rlc->w, ss_fr_rlc->h);
    DR_SETFULLSRC(ss_fr_rlc);
    SSur_Blit();
    // draw lifes //
    max_lives = lev_h - 11;
    if (diff[init_data.diff].max_lives <= max_lives)
        life_y = (lev_h - diff[init_data.diff].max_lives) * brick_h;
    else
        life_y = (lev_h - max_lives) * brick_h;
    for (j = life_y; j < sdl.scr->h; j += brick_h) {
         DR_SETDST(ss_bkgnd, 0, j, brick_w, brick_h);
         DR_SETSRC(ss_life, 0, 0);
         SSur_Blit();
    }

    // draw bricks to background //
    for (int i = 0; i < lev_w; i++)
        for (int j = 0; j < lev_h; j++)
            if (lev_map[i][j].type > MAP_WALL) {
                DR_SETDST(ss_bkgnd, i * brick_w, j * brick_h, brick_w, brick_h);
                DR_SETSRC(ss_bricks, lev_map[i][j].id * brick_w, 0);
                SSur_Blit();
            }

    // copy background to screen //
    DR_SETFULLDST(sdl.scr);
    DR_SETFULLSRC(ss_bkgnd);
    SSur_Blit();

    // reset //
    Club_Reset();
    Balls_Reset();
    DL_Clear(&extra_list);
    DL_Clear(&shrapnell_list);
    DL_Clear(&shot_list);
    new_level = 0;
    memset(cur_extras, 0, sizeof(cur_extras));
	player.level = level;

	// reset event stuff //
	mx = 0;
	memset(buttonstate, 0, sizeof(buttonstate));
	memset(keystate, 0, sizeof(keystate));
	
	// credit init //
	Credit_Init();
}

void BreakOut::LoadLevel(int l)
{
    int i, j;
    strcpy(lev_author, levels[l].author);
    strcpy(lev_name, levels[l].name);
    lev_w = levels[l].w + 2;
    lev_h = levels[l].h + 2;
    ball_v = levels[l].v;
    ball_v_add = levels[l].v_pms;
    ball_vm = levels[l].vm;

    // build walls //
    for (i = 0; i < lev_w; i++) {
        lev_map[i][0].type = MAP_WALL;
        lev_map[i][0].id = 0;
        lev_map[i][lev_h - 1].type = MAP_EMPTY;
        lev_map[i][lev_h - 1].id = -1;
    }
    for (j = 0; j < lev_h; j++) {
        lev_map[0][j].type = MAP_WALL;
        lev_map[lev_w - 1][j].type = MAP_WALL;
        lev_map[0][j].id = 0;
        lev_map[lev_w - 1][j].id = 0;
    }

    // load map //
    for (i = 0; i < levels[l].w; i++)
        for (j = 0; j < levels[l].h; j++) {
            // create map //
            switch (levels[l].map[j * levels[l].w + i]) {
                case '#':
                    // wall //
                    lev_map[i + 1][j + 1].type = MAP_WALL;
                    lev_map[i + 1][j + 1].id = 0;
                    lev_map[i + 1][j + 1].score = brick_score;
                    break;
                case 'a':
                case 'b':
                case 'c':
                case 'd':
                case 'e':
                case 'f':
                case 'g':
                case 'h':
                case 'i':
                    // brick //
                    lev_map[i + 1][j + 1].type = MAP_BRICK;
                    lev_map[i + 1][j + 1].id = levels[l].map[j * levels[l].w + i] - 96;
                    lev_map[i + 1][j + 1].dur = 1;
                    // some bricks need more shots //
                    if (lev_map[i + 1][j + 1].id < 4)
                        lev_map[i + 1][j + 1].dur = lev_map[i + 1][j + 1].id + 1;
                    lev_map[i + 1][j + 1].score = brick_score * lev_map[i + 1][j + 1].dur;
                    break;
                default:
                    // empty //
                    lev_map[i + 1][j + 1].type = MAP_EMPTY;
                    lev_map[i + 1][j + 1].id = -1;
                    break;
            }
            // create extras //
            switch (levels[l].extras[j * levels[l].w + i]) {
                case '0':
                    lev_map[i + 1][j + 1].extra = EX_SCORE200;
                    break;
                case '1':
                    lev_map[i + 1][j + 1].extra = EX_SCORE500;
                    break;
                case '2':
                    lev_map[i + 1][j + 1].extra = EX_SCORE1000;
                    break;
                case '3':
                    lev_map[i + 1][j + 1].extra = EX_SCORE2000;
                    break;
                case '4':
                    lev_map[i + 1][j + 1].extra = EX_SCORE5000;
                    break;
                case '5':
                    lev_map[i + 1][j + 1].extra = EX_SCORE10000;
                    break;
                case '+':
                    lev_map[i + 1][j + 1].extra = EX_LENGTHEN;
                    break;
                case '-':
                    lev_map[i + 1][j + 1].extra = EX_SHORTEN;
                    break;
                case 'l':
                    lev_map[i + 1][j + 1].extra = EX_LIFE;
                    break;
                case 's':
                    lev_map[i + 1][j + 1].extra = EX_SLIME;
                    break;
                case 'm':
                    lev_map[i + 1][j + 1].extra = EX_METAL;
                    break;
                case 'b':
                    lev_map[i + 1][j + 1].extra = EX_BALL;
                    break;
                case 'w':
                    lev_map[i + 1][j + 1].extra = EX_WALL;
                    break;
                case 'f':
                    lev_map[i + 1][j + 1].extra = EX_FROZEN;
                    break;
                case 'p':
                    lev_map[i + 1][j + 1].extra = EX_WEAPON;
                    break;
                case '?':
                    lev_map[i + 1][j + 1].extra = EX_RANDOM;
                    break;
                case '>':
                    lev_map[i + 1][j + 1].extra = EX_FAST;
                    break;
                case '<':
                    lev_map[i + 1][j + 1].extra = EX_SLOW;
                    break;
                default:
                    lev_map[i + 1][j + 1].extra = EX_NONE;
                    break;
            }
        }

    // count bricks //
    lev_bricks = 0;
    for (i = 1; i < lev_w - 1; i++)
        for (j = 1; j < lev_h - 1; j++)
            if (lev_map[i][j].type == MAP_BRICK)
                lev_bricks++;
}

HiScore* BreakOut::GetHiScore()
{
    return hiscore;
}

int BreakOut::BuyContinue()
{
    if (player.score < diff[init_data.diff].con_cost) return 0;

    if (init_data.warp && init_data.control != 0)
    	SDL_SetCursor(original_cur);
    	
    SDL_Surface *buffer = SSur_Create(sdl.scr->w, sdl.scr->h, SDL_HWSURFACE);
    SDL_SetColorKey(buffer, 0, 0);

    DR_SETFULLDST(buffer);
    DR_SETFULLSRC(sdl.scr);
    SSur_Blit();
    DR_SETFULLDST(sdl.scr);
    SSur_Fill(0x0);
    DR_SETFULLSRC(buffer);
    SSur_AlphaBlit(128);
    char    str[64];
    sprintf(str, "Continue? y/n");
    SFnt_Write(font, sdl.scr, sdl.scr->w / 2, sdl.scr->h / 2, str, 0);
    sprintf(str, "(costs %i points)", diff[init_data.diff].con_cost);
    SFnt_Write(font, sdl.scr, sdl.scr->w / 2, sdl.scr->h / 2 + font->lh, str, 0);
    Sdl_FullUpdate();

    SDL_Event e;
    int go_on = 1;
    int ret = 0;
    while (go_on && !fast_quit) {
        SDL_WaitEvent(&e);
        if (e.type == SDL_QUIT) {
            fast_quit = 1;
            break;
        }
        if (e.type == SDL_KEYUP)
            switch (e.key.keysym.sym) {
                case SDLK_y:
                    player.lives = 0;
                    player.score -= diff[init_data.diff].con_cost;
                    cur_score = player.score;
                    DR_SETFULLDST(sdl.scr);
                    DR_SETFULLSRC(buffer);
                    SSur_Blit();
                    Sdl_FullUpdate();
                    go_on = 0;
                    ret = 1;
                    break;
                case SDLK_n:
                    go_on = 0;
                    ret = 0;
                    break;
                default:
                    break;
            }
    }
    SDL_FreeSurface(buffer);
    if (init_data.warp && init_data.control != 0)
       	SDL_SetCursor(empty_cur);
#ifdef SOUND
    SndSrv_Play(snd_click, 0);
    if (ret)
        SndSrv_Play(snd_wontgiveup, 1);
#endif
    return ret;
}

int BreakOut::ConfirmQuit()
{
#ifdef SOUND
    SndSrv_Play(snd_click, 0);
#endif

    SDL_Surface *buffer = SSur_Create(sdl.scr->w, sdl.scr->h, SDL_HWSURFACE);
    SDL_SetColorKey(buffer, 0, 0);

    if (init_data.warp && init_data.control != 0)
    	SDL_SetCursor(original_cur);
    	
    DR_SETFULLDST(buffer);
    DR_SETFULLSRC(sdl.scr);
    SSur_Blit();
    DR_SETFULLDST(sdl.scr);
    SSur_Fill(0x0);
    DR_SETFULLSRC(buffer);
    SSur_AlphaBlit(128);
    char    str[64];
    sprintf(str, "Exit game? y/n");
    SFnt_Write(font, sdl.scr, sdl.scr->w / 2, sdl.scr->h / 2, str, 0);
    Sdl_FullUpdate();

    SDL_Event e;
    int go_on = 1;
    int ret = 0;
    while (go_on && !fast_quit) {
        SDL_WaitEvent(&e);
        if (e.type == SDL_QUIT) {
            fast_quit = 1;
            break;
        }
        if (e.type == SDL_KEYUP)
            switch (e.key.keysym.sym) {
                case SDLK_ESCAPE:
                case SDLK_y:
                    go_on = 0;
                    ret = 1;
                    break;
                case SDLK_n:
                    DR_SETFULLDST(sdl.scr);
                    DR_SETFULLSRC(buffer);
                    SSur_Blit();
                    Sdl_FullUpdate();
                    go_on = 0;
                    ret = 0;
                default:
                    break;
            }
    }
    SDL_FreeSurface(buffer);

    if (init_data.warp && init_data.control != 0)
    	SDL_SetCursor(empty_cur);
    	
#ifdef SOUND
    SndSrv_Play(snd_click, 0);
#endif
    return ret;
}

void BreakOut::GameOver()
{
    char    str[64];
    sprintf(str, "Game Over");
    SFnt_Write(font, sdl.scr, sdl.scr->w / 2, sdl.scr->h / 2, str, 0);
    sprintf(str, "Your score: %i\n", player.score);
    SFnt_Write(font, sdl.scr, sdl.scr->w / 2, sdl.scr->h / 2 + font->lh, str, 0);
    Sdl_FullUpdate();

#ifdef SOUND
    if (rand() % 2)
        SndSrv_Play(snd_damn1, 1);
    else
        SndSrv_Play(snd_damn2, 1);
#endif

    Sdl_WaitForClick();
}

// club //
void BreakOut::Club_Reset()
{
    club.len = diff[init_data.diff].club_size;
    club.max_len = diff[init_data.diff].club_max_size;
    club.w = (club.len + 2) * club_cw;
    club.h = club_ch;
    club.y = (lev_h - 2) * brick_h;
    club.x = ((lev_w * brick_w) - club.w) / 2; // centered //
    club.cur_x = club.x;
    club.friction = 0.3;
    club.time = 0;
}

void BreakOut::Club_Hide()
{
    int h = club.h;
    int y = club.y;
    if (cur_extras[EX_WEAPON]) {
        y -= wpn_y_off;
        h = wpn_h;
    }
    DR_SETDST(sdl.scr, club.x, y, club.w, h);
    DR_SETSRC(ss_bkgnd, club.x, y);
    SSur_Blit();
    AddRefreshRect(club.x, y, club.w, h);
}

void BreakOut::Club_Update(int ms)
{
    // old position //
    int old_x = club.x;
    int off_x;
    int moved = 0;
    SDL_Event event;
    int n;

    if (!init_data.warp)
        SDL_GetMouseState(&mx, 0);

    if (cur_extras[EX_FROZEN]) {
        club.v_x = 0;
        if (init_data.warp) {
            // reset the mouse pointer //
            SDL_EventState(SDL_MOUSEMOTION, SDL_IGNORE);
            SDL_WarpMouse(sdl.scr->w >> 1, sdl.scr->h >> 1);
            SDL_EventState(SDL_MOUSEMOTION, SDL_ENABLE);
        }
        return;
    }

    if (mouse_moved && init_data.control != 0) {
        if (init_data.warp)
            club.cur_x += mx;
        else
            club.cur_x = mx;
        club.time = 200;
        moved = 1;
    }
    if (keystate[init_data.k_left] && init_data.control != 1) {
        club.cur_x -= init_data.key_speed * ms;
        club.time = 0;
        moved = 1;
    }
    if (keystate[init_data.k_right] && init_data.control != 1) {
        club.cur_x += init_data.key_speed * ms;
        club.time = 0;
        moved = 1;
    }

    if (club.time > 0) {
        club.time -= ms;
        if (club.time < 0) {
            club.time = 0;
            club.v_x = 0.0;
        }
    }
    else
        club.v_x = 0.0;

    if (moved) {
        //check range//
        if (club.cur_x < brick_w)
            club.cur_x = brick_w;
        if (club.cur_x + club.w >= sdl.scr->w - brick_w)
            club.cur_x = sdl.scr->w - brick_w - club.w;

        club.x = (int)club.cur_x;

        off_x = club.x - old_x;

        // speed //
        club.v_x = (float)(off_x) / ms;

        if (mouse_moved) {
            // limit mouse speed //
            if (club.v_x > 5.0) club.v_x = 5.0;
            if (club.v_x < -5.0) club.v_x = -5.0;
            club.v_x /= 5;
            if (init_data.warp) {
                // reset the mouse pointer //
                // although only mouse motion is filtered it seems to handicap button events //
                SDL_EventState(SDL_MOUSEMOTION, SDL_IGNORE);
                SDL_WarpMouse(sdl.scr->w >> 1, sdl.scr->h >> 1);
                SDL_EventState(SDL_MOUSEMOTION, SDL_ENABLE);
            }
        }

        // we need another ball reflection check here //
//        Club_CheckBallReflection(old_x);
    }
}

void BreakOut::Club_Show()
{
    int i, off, cy = 0;
    if (cur_extras[EX_FROZEN])
        cy = club_ch * 2;
    else
        if (cur_extras[EX_SLIME])
            cy = club_ch;
    DR_SETDST(sdl.scr, club.x, club.y, club_cw, club_ch);
    DR_SETSRC(ss_club, 0, cy);
    SSur_Blit();
    for (i = 0, off = club_cw; i < club.len; i++, off += club_cw) {
        DR_SETDST(sdl.scr, club.x + off, club.y, club_cw, club_ch);
        DR_SETSRC(ss_club, club_cw, cy);
        SSur_Blit();
    }
    DR_SETDST(sdl.scr, club.x + off, club.y, club_cw, club_ch);
    DR_SETSRC(ss_club, club_cw * 2, cy);
    SSur_Blit();
    if (cur_extras[EX_WEAPON]) {
        wpn_x = club.x + (club.w - wpn_w) / 2;
        wpn_y = club.y - wpn_y_off;
        DR_SETDST(sdl.scr, wpn_x, wpn_y, wpn_w, wpn_h);
        DR_SETSRC(ss_weapon, (int)wpn_cur * wpn_w, 0);
        SSur_Blit();
        AddRefreshRect(club.x, wpn_y, club.w, wpn_h);
    }
    else
        AddRefreshRect(club.x, club.y, club.w, club.h);
}

void BreakOut::Club_AlphaShow(int a)
{
    int i, off, cy = 0;
    if (cur_extras[EX_FROZEN])
        cy = club_ch * 2;
    else
        if (cur_extras[EX_SLIME])
            cy = club_ch;
    DR_SETDST(sdl.scr, club.x, club.y, club_cw, club_ch);
    DR_SETSRC(ss_club, 0, cy);
    SSur_AlphaBlit(a);
    for (i = 0, off = club_cw; i < club.len; i++, off += club_cw) {
        DR_SETDST(sdl.scr, club.x + off, club.y, club_cw, club_ch);
        DR_SETSRC(ss_club, club_cw, cy);
        SSur_AlphaBlit(a);
    }
    DR_SETDST(sdl.scr, club.x + off, club.y, club_cw, club_ch);
    DR_SETSRC(ss_club, club_cw * 2, cy);
    SSur_AlphaBlit(a);
    if (cur_extras[EX_WEAPON]) {
        wpn_x = club.x + (club.w - wpn_w) / 2;
        wpn_y = club.y - wpn_y_off;
        DR_SETDST(sdl.scr, wpn_x, wpn_y, wpn_w, wpn_h);
        DR_SETSRC(ss_weapon, (int)wpn_cur * wpn_w, 0);
        SSur_AlphaBlit(a);
        AddRefreshRect(club.x, wpn_y, club.w, wpn_h);
    }
    else
        AddRefreshRect(club.x, club.y, club.w, club.h);
}

int BreakOut::Club_Resize(int c)
{
    DL_Entry *e;
    Ball *b;
    if (club.len + c > club.max_len || club.len + c < 1/*diff[init_data.diff].club_size*/)
        return 1;
#ifdef SOUND
    if (c < 0)
        SndSrv_Play(snd_shr, 0);
    else
        SndSrv_Play(snd_exp, 0);
#endif
    // move attached balls
    e = ball_list.head.next;
    while (e != &ball_list.tail) {
        b = (Ball*)e->data;
        if (b->attached) {
            if (c > 0 && b->x + ball_rad > club_cw)
                b->x += club_cw * c;
            else
                if (c < 0 && b->x + ball_rad > club_cw * 3)
                    b->x += club_cw * c;
        }
        e = e->next;
    }

    // resize
    club.cur_x -= (float)(c * club_cw) / 2;
    club.x = (int)club.cur_x;
    club.len += c;
    club.w = (club.len + 2) * club_cw;

    // check range
    if (club.x < brick_w)
        club.x = brick_w;
    if (club.x + club.w > sdl.scr->w - brick_w)
        club.x = sdl.scr->w - brick_w - club.w;
    return 0;
}

void BreakOut::Club_CheckBallReflection(int old_x)
{
    DL_Entry    *e = ball_list.head.next;
    Ball        *b;
    float       bx, by;
    float       cx, cy, cx_off;
    float       vlen, s;
    Vector      a;

    while (e != &ball_list.tail) {
        b = (Ball*)e->data;
        e = e->next;
        /* contact possible */
        if (!(club.v_x == 0 ||
            b->ign_club || b->club_con ||
            b->y + ball_dia - 1 < club.y ||
            b->y > club.y + club.h ||
            (club.v_x < 0 && b->cur_x > club.x + club.w && b->cur_x > old_x + club.w) ||
            (club.v_x > 0 && b->cur_x + ball_dia - 1 < club.x && b->cur_x + ball_dia - 1 < old_x)) ) {

            /* centre of ball */
            bx = b->cur_x + ball_rad;
            by = b->cur_y + ball_rad;

            /* maybe it just hit the club's top */
            if (b->cur_y + ball_dia < club.y + 1 && bx >= club.x + ball_rad - 1 && bx <= club.x + club.w - ball_rad) {
                a.y = 1;
                a.x = 0;
                Club_HandleContact(b, a);
                printf("club hits with top\n");
                break;
            }

            /* horizontal line in the middle of the club */
            cy = club.y + ball_rad;
            /*
                There are two points on C(cx+t|cy) which have a distance of ball_dia from B(bx|by)
                P1(bx+cx_off|cy) and P2(bx-cx_off|cy). t computes as follows:
                                ___
                    ball_dia = |BC|;
                    ball_dia = (by - cy) + (bx-cx-t)
                    t = bx - cx (+-) cx_off
            */
            cx_off = ceil( sqrt( Square(ball_dia + 1) - Square(by - cy + 1) ) ) + 1;
            if (club.v_x < 0) {
                /* moves to the left */
                cx = bx + cx_off;
                if (old_x + ball_rad >= cx && club.x + ball_rad <= cx) {
                    /* and it hits the ball */
                    /* normed reflection vector */
                    a.x = bx - cx;
                    a.y = by - cy;
                    vlen = VLEN(a.x, a.y);
                    a.x /= vlen; a.y /= vlen;
                    /*
                        ok, now will the ball be reflected and club.v_x added or do
                        we have to add only a vector v? computed as follows:
                            ->  ->
                            v = a * club.v_x
                        to check this we have to do this:
                               ->
                            b: x = s(b->v_x) + (bx)
                                    (b->v_y)   (by)
                               ->
                            c: x = r(-a.y) + (cx)
                                    (-a.x)   (cy)

                        these lines will intersect and we have to compute s
                        if s < 0 we'll need the vector v else we'll reflect
                    */
    //                s = ( (by - cy) * a.y / (-a.x) + cx - bx ) / (b->v_x - a.y * b->v_y / (-a.x));
                    /* momentarily it just uses the vector v because the reflection does not work */
                    if ( /*s <= 0*/ 1 ) {
                        /* use vector v */
                        b->v_x += a.x * fabs(club.v_x);
                        b->v_y += -fabs(a.y) * fabs(club.v_x);
                        a.x = a.y = 0;
                        Club_HandleContact(b, a);
#ifdef DEBUG
    printf("club influences ball\n");
#endif
                    }
                    else {
                        /* reflect */
                        Club_HandleContact(b, a);
#ifdef DEBUG
    printf("club hits ball\n");
#endif
                    }
                }
            }
            else {
                /* moves to the right */
                cx = bx - cx_off;
                if (old_x + club.w - ball_rad - 1 <= cx && club.x + club.w - ball_rad - 1 >= cx) {
                    /* and it hits the ball */
                    /* normed reflection vector */
                    a.x = bx - cx;
                    a.y = by - cy;
                    vlen = VLEN(a.x, a.y);
                    a.x /= vlen; a.y /= vlen;
                    // compute
    //                s = ( (by - cy) * a.y / (-a.x) + cx - bx ) / (b->v_x - a.y * b->v_y / (-a.x));
                    /* momentarily it just uses the vector v */
                    if ( /*s <= 0*/ 1 ) {
                        /* use vector v */
                        b->v_x += a.x * fabs(club.v_x);
                        b->v_y += -fabs(a.y) * fabs(club.v_x);
                        a.x = a.y = 0;
                        Club_HandleContact(b, a);
#ifdef DEBUG
    printf("club influences ball\n");
#endif
                    }
                    else {
                        /* reflect */
                        Club_HandleContact(b, a);
#ifdef DEBUG
    printf("club hits ball\n");
#endif
                    }
                }
        }
        }
    }
}

void BreakOut::Club_HandleContact(Ball *b, Vector a)
{
    Vector n;
    float   old_vx = b->v_x, d;

    // do not use club till a brick's been touched //
    b->club_con = 1;

    if (a.x != 0 && a.y != 0) {
        d = sqrt(2);
        a.x /= d; a.y /= d;
    }

    if (a.y != 0 || a.x != 0) {
        // reflect //
        n.x = (1 - 2 * a.x * a.x) * b->v_x + (-2 * a.x * a.y) * b->v_y;
        n.y = (-2 * a.x * a.y) * b->v_x + (1 - 2 * a.y * a.y) * b->v_y;
        b->v_x = n.x;
        b->v_y = n.y;

        // club speed //
        b->v_x += club.v_x * club.friction;
    }

    Ball_MaskV(b, old_vx);

    // attach if slimy //
    if (cur_extras[EX_SLIME]) {
        b->attached = 1;
        b->x -= club.x;
        b->y -= club.y;
        b->cur_x = b->x;
        b->cur_y = b->y;
#ifdef SOUND
    SndSrv_Play(snd_sli, 0);
#endif
    }
    else {
#ifdef SOUND
    SndSrv_Play(snd_ref, 0);
#endif
        // or reset position if in wall //
        if (b->x < brick_w) {
            b->cur_x = brick_w;
            b->x = (int)b->cur_x;
        }
        else
            if (b->x + ball_dia >= sdl.scr->w - brick_w) {
                b->cur_x = sdl.scr->w - brick_w - ball_dia;
                b->x = (int)b->cur_x;
            }
        // get new target //
        Ball_GetTarget(b);
    }
}

// balls //
Ball* BreakOut::Ball_Create(int x, int y, float a, float v)
{
    Ball    *b = new Ball;
    b->cur_x = x;
    b->x = x;
    b->cur_y = y;
    b->y = y;
    b->attached = 0;
    b->club_con = 0;
    b->ign_club = 0;
    Ball_ComputeVec(b, a, v);
    Ball_ClearTarget(&b->t);
    Ball_ClearTarget(&b->t2);
    return b;
}

void BreakOut::Ball_ComputeVec(Ball *b, float a, float v)
{
#ifdef DEBUG
    printf("wanted ball angle: %4.2f\n", a);
#endif
    b->v_x = sin(TOARC(a)) * v;
    b->v_y = cos(TOARC(a)) * v;
    // y-coordinates are upside down //
    b->v_y = -b->v_y;
    // mask angles //
    Ball_MaskV(b, b->v_x);
}

void BreakOut::Ball_AdjustSpeed(Ball *b, float v)
{
    float d = sqrt(b->v_x * b->v_x + b->v_y * b->v_y);
    b->v_x /= d; b->v_y /= d;
    b->v_x *= v; b->v_y *= v;
}

/*
    input:
        Ball* b
    function:
        -check if the time's up for a brick
        -remove this brick
        -if balls are reflected:
        -reset position, reflect, get new target
*/
void BreakOut::Ball_CheckBrickReflection(Ball *b)
{
    if (b->t.cur_tm < b->t.time) return;

    // remove brick //
    Brick_Remove(b->t.mx, b->t.my, 0);
    if (b->t2.exists)
        Brick_Remove(b->t2.mx, b->t2.my, 0);

    if (!cur_extras[EX_METAL] || (cur_extras[EX_METAL] && (b->t.mx == 0 || b->t.mx == lev_w - 1 || b->t.my == 0 || b->t.my == lev_h - 1) ) ) {
#ifdef SOUND
    SndSrv_Play(snd_ref, 0);
#endif
        float old_vx = b->v_x;

        // reset position
        b->cur_x = b->t.x;
        b->x = (int)b->cur_x;
        b->cur_y = b->t.y;
        b->y = (int)b->cur_y;

        // reflection
        Vector n,a = b->t.a;
        float d;

        // norm //
        if (a.x != 0 && a.y != 0) {
            d = sqrt(2);
            a.x /= d; a.y /= d;
        }

        n.x = (1 - 2 * a.x * a.x) * b->v_x + (-2 * a.x * a.y) * b->v_y;
        n.y = (-2 * a.x * a.y) * b->v_x + (1 - 2 * a.y * a.y) * b->v_y;
        b->v_x = n.x;
        b->v_y = n.y;

        // disable club contact
        b->club_con = 0;

        Ball_MaskV(b, old_vx);

        // only use club if y + ball_dia < club.y coz anything else would get the ball in the wall
        if (b->y + ball_rad> club.y)
            b->ign_club = 1;
        else
            b->ign_club = 0;
    }

    // check targets //
    Ball_NewTargets(b->t.mx, b->t.my);
    if (b->t2.exists)
        Ball_NewTargets(b->t2.mx, b->t2.my);

    // check shot targets //
    Shots_NewTargets(b->t.mx, b->t.my);
    if (b->t2.exists)
        Shots_NewTargets(b->t2.mx, b->t2.my);
}

void BreakOut::Ball_NewTargets(int mx, int my)
{
    DL_Entry    *e = ball_list.head.next;
    Ball        *ball;
    while (e != &ball_list.tail) {
        ball = (Ball*)e->data;
        if (mx == -1 || (ball->t.mx == mx && ball->t.my == my))
            Ball_GetTarget(ball);
        e = e->next;
    }
}

/*
    reflect ball at paddle when paddle is understood convex

void BreakOut::Ball_CheckConvexReflection(Ball *bl, float old_x, float old_y)
{
    float bx, by; // centre of ball //
    float   m; // monotony //
    Vector  p; // point //
    float   mod = 0.3; // convex modifier //
    Vector  c = {0, 0}; // convex change //
    int contact = 0;
    Vector a = {0, 0}; // reflection vector //

    // centre of ball //
    bx = old_x + ball_rad;
    by = old_y + ball_rad;

    // monotony //
    m = bl->v_y / bl->v_x;

    // checking top horizontal line //
    p.y = club.y - ball_rad - 1;
    p.x = (p.y - by) / m + bx;
    if (p.x >= club.x - ball_rad && p.x <= club.x + club.w + ball_rad - 1 && bl->v_y > 0) {
        c.x = ( (p.x - (club.x + club.w / 2)) / (club.w / 2) ) * mod;
        bl->v_x += c.x;
        a.y = 1;
        contact = 1;
#ifdef DEBUG
    printf("ball hits convex top\n");
#endif
    }
    else {
        // checking left vertical line //
        p.x = club.x - ball_rad - 1;
        p.y = (p.x - bx) * m + by;
        if (p.y >= club.y - ball_rad && p.y <= club.y + club.h && bl->v_x > 0) {
            // hit//
            a.x = 1;
            contact = 1;
#ifdef DEBUG
            printf("ball hits convex left vertical line\n");
#endif
        }
        else {
            // checking right vertical line //
            p.x = club.x + club.w + ball_rad;
            p.y = (p.x - bx) * m + by;
            if (p.y >= club.y - ball_rad && p.y <= club.y + club.h && bl->v_x < 0) {
                // hit //
                a.x = 1;
                contact = 1;
#ifdef DEBUG
                printf("ball hits convex right vertical line\n");
#endif
            }
        }
    }

    if (contact) {
        if (club.v_x == 0) {
            bl->cur_x = p.x - ball_rad;
            bl->cur_y = p.y - ball_rad;
            bl->x = (int)bl->cur_x;
            bl->y = (int)bl->cur_y;
        }
        Club_HandleContact(bl, a);
    }
}*/

/*
    input
        Ball* b
    function:
        -check if a ball hits the club and reflect it
*/
void BreakOut::Ball_CheckClubReflection(Ball *bl, float old_x, float old_y)
{
    float bx, by; // centre of ball //
    int cx, cy; // centre of club circle //
    Vector  rv = {0, 0};
    int     contact = 0;
    Vector  p; // point //
    float   m; // monotony //
    float   mod = 0.3; // convex modifier //
    Vector  c = {0, 0}; // convex change //

    // no contact possible ? //
    if (bl->club_con || bl->ign_club || bl->y + ball_dia < club.y || (bl->y >= club.y + club.h - 4 && old_y > club.y) || bl->x + ball_dia < club.x || bl->x > club.x + club.w)
        return;

    // centre of ball //
    bx = old_x + ball_rad;
    by = old_y + ball_rad;

    // monotony //
    m = bl->v_y / bl->v_x;

    // checking top horizontal line //
    p.y = club.y - ball_rad - 1;
    p.x = (p.y - by) / m + bx;
    if (p.x >= club.x + ball_rad - 3 && p.x <= club.x + club.w - ball_rad + 2 && bl->v_y > 0) {
        // hit //
        if (init_data.convex) {
            // convex behaviour //
            c.x = ( (p.x - (club.x + club.w / 2)) / (club.w / 2) ) * mod;
            bl->v_x += c.x;
        }
        rv.y = 1;
        contact = 1;
#ifdef DEBUG
        printf("ball hits top\n");
#endif
    }
    else {
        // checking left vertical line //
        p.x = club.x - ball_rad - 1;
        p.y = (p.x - bx) * m + by;
        if (p.y >= club.y + 2 && p.y <= club.y + club.h - 3 && bl->v_x > 0) {
            // hit//
//            rv.y = 1;
            rv.x = 1;
            contact = 1;
#ifdef DEBUG
            printf("ball hits left vertical line\n");
#endif
        }
        else {
            // checking right vertical line //
            p.x = club.x + club.w + ball_rad;
            p.y = (p.x - bx) * m + by;
            if (p.y >= club.y + 2 && p.y <= club.y + club.h - 3 && bl->v_x < 0) {
                // hit //
//                rv.y = 1;
                rv.x = 1;
                contact = 1;
#ifdef DEBUG
                printf("ball hits right vertical line\n");
#endif
            }
            else {
                // checking left upper diagonal line //
                // it is: x=(xb * mb - xc * mc + yc - yb) / (mb - mc)
                // mb = m; mc = -1;
                cx = club.x - ball_rad - 1;
                cy = club.y + 2;
                if (m != -1) {
                    p.x = (bx * m - cx * (-1) + cy - by) / (m - (-1));
                    p.y = (p.x - bx) * m + by;
                }
                if (m != -1 && p.x >= cx && p.x <= club.x + ball_rad - 3) {
                    // hit
//                    rv.y = 1;
                    rv.x = rv.y = 1;
                    contact = 1;
#ifdef DEBUG
                    printf("ball hits left upper corner\n");
#endif
                }
                else {
                    // checking right upper diagonal line //
                    cx = club.x + club.w + ball_rad;
                    cy = club.y + 2;
                    if (m != 1) {
                        p.x = (bx * m - cx * (1) + cy - by) / (m - (1));
                        p.y = (p.x - bx) * m + by;
                    }
                    if (m != 1 && p.x <= cx && p.x >= club.x + club.w - ball_rad + 2) {
                        // hit
//                        rv.y = 1;
                        rv.x = 1; rv.y = -1;
                        contact = 1;
#ifdef DEBUG
                        printf("ball hits right upper corner\n");
#endif
                    }
/*                    else {
                        // checking left lower dia line //
                        cx = club.x - ball_rad - 1;
                        cy = club.y + club.h - 3;
                        if (m != 1) {
                            p.x = (bx * m - cx * (1) + cy - by) / (m - (1));
                            p.y = (p.x - bx) * m + by;
                        }
                        if (m != 1 && p.x >= cx && p.x <= club.x + ball_rad - 3) {
                            // hit
                            rv.x = 1; rv.y = -1;
                            contact = 1;
                        }
                        else {
                            // checking right lower dia line //
                            cx = club.x + club.w + ball_rad;
                            cy = club.y + 2;
                            if (m != -1) {
                                p.x = (bx * m - cx * (-1) + cy - by) / (m - (-1));
                                p.y = (p.x - bx) * m + by;
                            }
                            if (m != -1 && p.x <= cx && p.x >= club.x + club.w - ball_rad + 2) {
                                // hit
                                rv.x = rv.y = 1;
                                contact = 1;
                            }
                        }
                    }*/
                }
            }
        }
    }

    if (contact) {
        if (club.v_x == 0 || cur_extras[EX_SLIME]) {
            bl->cur_x = p.x - ball_rad;
            bl->cur_y = p.y - ball_rad;
            bl->x = (int)bl->cur_x;
            bl->y = (int)bl->cur_y;
        }
        Club_HandleContact(bl, rv);
    }
}

/*
    input :
        Ball* b
    function :
        -check if ball b hits a brick and if so:
        -compute the hitten brick in lev_map (int mx, my)
        -the reset position of the ball after destroying the brick (float x, y)
        -the time in milliseconds it takes the ball to hit the brick from its current position
        by using ball_v as velocity (int time)
        -the side at which the ball hits; might be LEFT, RIGHT, TOP, BOTTOM (int side)
        -the reflection vector (float a); if reflecting at an horizontal wall it's a = {0, 1} else a = {1, 0}
*/
void BreakOut::Ball_GetTarget(Ball *b)
{
    float bx = b->cur_x + ball_rad, by = b->cur_y + ball_rad; // centre of ball
    float x, y; // positions
    float m = (b->v_y / b->v_x); // monotony
    Vector v = {b->v_x, b->v_y}; // normed radial vector
    float n = sqrt(v.x * v.x + v.y * v.y);
    v.x /= n; v.y /= n;
    // tangential points
    Vector  p[2] = { {bx + v.y * ball_rad, by - v.x * ball_rad}, {bx - v.y * ball_rad, by + v.x * ball_rad} };
    Target  t[2];
    Corner  cor[2]; // 0 == x, 1 == y //
    int     corner = -1; // which is it? -1 means no corner //
    int d; // direction
    int i; // index
    int c_mod; // needed to get centre of ball
    float cx, cy;
    Vector pos;
    int contact = 0;
    int br_x, br_y;
//    int same_brick;

    // clear targets //
    Ball_ClearTarget(&b->t);
    Ball_ClearTarget(&b->t2);
    Ball_ClearTarget(&t[0]);
    Ball_ClearTarget(&t[1]);

    for (int j = 0; j < 2; j++) {
        /* check hori grid */
        d = b->v_x >= 0 ? 1 : -1;
        i = (int)(p[j].x / brick_w) + d;
        x = i * brick_w;
        if (d == -1)
            x += brick_w - 1;
        t[j].exists = 0;
        while (i < lev_w && i >= 0) {
            /* compute y */
            y = ((m * (x - p[j].x)) + p[j].y);
            /* check for brick */
            if (lev_map[i][(int)(y / brick_h)].type != MAP_EMPTY && y >= 0 && y < lev_h * brick_h) {
                t[j].x = x;
                t[j].y = y;
                if (d == 1)
                    t[j].side = LEFT;
                else
                    t[j].side = RIGHT;
                t[j].exists = 1;
                // check if it's a corner //
                cor[0].x = (int)x;
                cor[0].y = (int)y;
                Corner_Check(&cor[0], b->v_x, b->v_y);
                break;
            }

            // next line //
            i += d;
            x += brick_w * d;
        }
        // check vert grid
        d = b->v_y >= 0 ? 1 : -1;
        i = (int)(p[j].y / brick_h) + d;
        y = i * brick_h;
        if (d == -1)
            y += brick_h - 1;
        while (i < lev_h && i >= 0) {
                // compute y
                x = (( (y - p[j].y) / m ) + p[j].x);
                // check for brick
                if (lev_map[(int)(x / brick_w)][i].type != MAP_EMPTY  && x >= 0 && x < lev_w * brick_w) {
                    if (!t[j].exists) {
                        t[j].x = x;
                        t[j].y = y;
                        if (d == 1)
                            t[j].side = TOP;
                        else
                            t[j].side = BOTTOM;
                        t[j].exists = 1;
                    }
                    else {
                        // check if it's a corner //
                        cor[1].x = (int)x;
                        cor[1].y = (int)y;
                        Corner_Check(&cor[1], b->v_x, b->v_y);

                        // get point //
                        if (VLEN(t[j].x - bx, t[j].y - by) > VLEN(x - bx, y - by)) {
                            t[j].x = x;
                            t[j].y = y;
                            corner = 1;
                            if (cor[corner].type == NONE) {
                                if (d == 1)
                                    t[j].side = TOP;
                                else
                                    t[j].side = BOTTOM;
                            }
                        }
                        else
                            corner = 0;
                        if (cor[corner].type != NONE) {
                            // it's a corner
                            t[j].x = cor[corner].x;
                            t[j].y = cor[corner].y;
                            switch (cor[corner].type) {
                                case UPPERLEFT:
                                    if (j == 1)
                                        t[j].side = TOP;
                                    break;
                                case UPPERRIGHT:
                                    if (j == 0)
                                        t[j].side = TOP;
                                    break;
                                case LOWERLEFT:
                                    if (j == 0)
                                        t[j].side = BOTTOM;
                                    break;
                                case LOWERRIGHT:
                                    if (j == 1)
                                        t[j].side = BOTTOM;
                                    break;
                            };
                        }
                    }
                    break;
                }

            // next line
            i += d;
            y += brick_h * d;
        }
    }

    // if both targets hit the same brick but not the same side it's a corner //
    if ((int)(t[0].x / brick_w) == (int)(t[1].x / brick_w) && (int)(t[0].y / brick_h) == (int)(t[1].y / brick_h)) {
        if (t[0].side == TOP && t[1].side == LEFT)
            corner = UPPERLEFT;
        else
            if (t[0].side == LEFT && t[1].side == BOTTOM)
                corner = LOWERLEFT;
            else
                if (t[0].side == BOTTOM && t[1].side == RIGHT)
                    corner = LOWERRIGHT;
                else
                    if (t[0].side == RIGHT && t[1].side == TOP)
                        corner = UPPERRIGHT;
                    else
                        corner = -1;
    }
    else
        corner = -1;

    // which target is nearer ? //
    if (t[0].exists && !t[1].exists) {
        b->t = t[0];
        c_mod = -1;
    }
    else
        if (!t[0].exists && t[1].exists) {
            b->t = t[1];
            c_mod = 1;
        }
        else
            if (t[0].exists && t[1].exists) {
                if (sqrt(Square(bx - t[0].x) + Square(by - t[0].y)) > sqrt(Square(bx - t[1].x) + Square(by - t[1].y))) {
                    b->t = t[1];
                    c_mod = 1;
                }
                else {
                    b->t = t[0];
                    c_mod = -1;
                }
            }
            else
                return;

    // brick in map
    b->t.mx = (int)b->t.x / brick_w;
    b->t.my = (int)b->t.y / brick_h;

    // reset and reflection vector
    if (corner == -1) {
        // centre of ball when hitting target
        bx = b->t.x + c_mod * v.y * ball_rad;
        by = b->t.y - c_mod * v.x * ball_rad;

        if (b->t.side == BOTTOM || b->t.side == TOP) {
            if (b->t.side == BOTTOM)
                b->t.y = b->t.y + ball_rad + 1;
            else
                b->t.y = b->t.y - ball_rad - 1;
            b->t.x = (b->t.y - by) / m + bx;
            b->t.a.y = 1;
        }
        if (b->t.side == LEFT || b->t.side == RIGHT) {
            if (b->t.side == RIGHT)
                b->t.x = b->t.x + ball_rad + 1;
            else
                b->t.x = b->t.x - ball_rad - 1;
            b->t.y = m * (b->t.x - bx) + by;
            b->t.a.x = 1;
        }
    }
    else {
        br_x = (int)(b->t.x / brick_w); br_y = (int)(b->t.y / brick_h);
        br_x *= brick_w; br_y *= brick_h;
        if (corner == UPPERLEFT) {
            cx = br_x - ball_rad - 1;
            cy = br_y;
            if (m != -1) {
                pos.x = (bx * m - cx * (-1) + cy - by) / (m - (-1));
                pos.y = (pos.x - bx) * m + by;
            }
            if (m != -1) {
                // hit
                b->t.a.x = 1; b->t.a.y = 1;
                contact = 1;
            }
        }
        else
            if (corner == UPPERRIGHT) {
                cx = br_x + brick_w + ball_rad;
                cy = br_y;
                if (m != 1) {
                    pos.x = (bx * m - cx * (1) + cy - by) / (m - (1));
                    pos.y = (pos.x - bx) * m + by;
                }
                if (m != 1) {
                    // hit
                    b->t.a.x = 1; b->t.a.y = -1;
                    contact = 1;
                }
            }
            else
                if (corner == LOWERLEFT) {
                    cx = br_x - ball_rad - 1;
                    cy = br_y + brick_h - 1;
                    if (m != 1) {
                        pos.x = (bx * m - cx * (1) + cy - by) / (m - (1));
                        pos.y = (pos.x - bx) * m + by;
                    }
                    if (m != 1) {
                        // hit
                        b->t.a.x = 1; b->t.a.y = -1;
                        contact = 1;
                    }
                }
                else
                    if (corner == LOWERRIGHT) {
                        cx = br_x + brick_w + ball_rad;
                        cy = br_y + brick_h - 1;
                        if (m != -1) {
                            pos.x = (bx * m - cx * (-1) + cy - by) / (m - (-1));
                            pos.y = (pos.x - bx) * m + by;
                        }
                        if (m != -1) {
                            // hit
                            b->t.a.x = 1; b->t.a.y = 1;
                            contact = 1;
                        }
                    }
        if (contact) {
            b->t.x = pos.x;
            b->t.y = pos.y;
        }
        // final corner
        b->t.side = corner;
    }

    // possible second target //
    switch (b->t.side) {
        case 0:
            // top //
            b->t2.my = b->t.my;
            if (c_mod == -1 && lev_map[(int)(b->t.x - 3) / brick_w][b->t.my].type == MAP_BRICK) {
                b->t2.mx = (int)(b->t.x - 3) / brick_w;
                b->t2.exists = 1;
            }
            else
                if (c_mod == 1 && lev_map[(int)(b->t.x + 3) / brick_w][b->t.my].type == MAP_BRICK) {
                    b->t2.mx = (int)(b->t.x + 3) / brick_w;
                    b->t2.exists = 1;
                }
            break;
        case 2:
            // bottom //
            b->t2.my = b->t.my;
            if (c_mod == -1 && lev_map[(int)(b->t.x + 3) / brick_w][b->t.my].type == MAP_BRICK) {
                b->t2.mx = (int)(b->t.x + 3) / brick_w;
                b->t2.exists = 1;
            }
            else
                if (c_mod == 1 && lev_map[(int)(b->t.x - 3) / brick_w][b->t.my].type == MAP_BRICK) {
                    b->t2.mx = (int)(b->t.x - 3) / brick_w;
                    b->t2.exists = 1;
                }
            break;
        case 1:
            // right side //
            b->t2.mx = b->t.mx;
            if (c_mod == -1 && lev_map[b->t.mx][(int)(b->t.y - 3) / brick_h].type == MAP_BRICK) {
                b->t2.my = (int)(b->t.y - 3) / brick_h;
                b->t2.exists = 1;
            }
            else
                if (c_mod == 1 && lev_map[b->t.mx][(int)(b->t.y + 3) / brick_h].type == MAP_BRICK) {
                    b->t2.my = (int)(b->t.y + 3) / brick_h;
                    b->t2.exists = 1;
                }
            break;
        case 3:
            // left side //
            b->t2.mx = b->t.mx;
            if (c_mod == -1 && lev_map[b->t.mx][(int)(b->t.y + 3) / brick_h].type == MAP_BRICK) {
                b->t2.my = (int)(b->t.y + 3) / brick_h;
                b->t2.exists = 1;
            }
            else
                if (c_mod == 1 && lev_map[b->t.mx][(int)(b->t.y - 3) / brick_h].type == MAP_BRICK) {
                    b->t2.my = (int)(b->t.y - 3) / brick_h;
                    b->t2.exists = 1;
                }
            break;
    }
    if (b->t2.mx == b->t.mx && b->t2.my == b->t.my)
        b->t2.exists = 0;

    // reset position is left upper corner; not ball centre
    b->t.x -= ball_rad;
    b->t.y -= ball_rad;

    // estimate time //
    n = sqrt( Square(b->cur_x - b->t.x) + Square(b->cur_y - b->t.y) );
    b->t.time = (int)floor(n / ball_v);

#ifdef DEBUG
    printf("*****\n");
    printf("current centre: %4.2f, %4.2f\n", b->cur_x + ball_rad, b->cur_y + ball_rad);
    printf("monotony %4.2f\n", m);
    printf("hits side %i of brick %i,%i\n", b->t.side, b->t.mx, b->t.my);
    printf("reflection vector: %4.2f,%4.2f\n", b->t.a.x, b->t.a.y);
    printf("reset position %4.2f,%4.2f\n", b->t.x, b->t.y);
    printf("takes %i ms\n", b->t.time);
    printf("*****\n");
#endif
}

void BreakOut::Ball_ClearTarget(Target *t)
{
    memset(t, 0, sizeof(Target));
}

void BreakOut::Ball_MaskV(Ball *b, float old_vx)
{
    float m;

    // b->v_x == 0 would cause seg faults //
    if (b->v_x == 0) {
        if (old_vx < 0)
            b->v_x = 0.01;
        else
            b->v_x = -0.01;
    }

    // avoid 45 angles //
    if (b->v_x == b->v_y)
        b->v_x *= 0.99;

    m = b->v_y / b->v_x;

    // mask angles from 70 to 110 and -110 to -70 //
    if (fabs(m) < ball_vhmask) {
        if (b->v_y < 0 || b->club_con)
            b->v_y = fabs(ball_vhmask * b->v_x) * -1;
        else
            b->v_y = fabs(ball_vhmask * b->v_x);
#ifdef DEBUG
        printf("horizontal mask: %4.2f\n", b->v_y / b->v_x);
#endif
    }

    // mask angles from -10 to 10 and 170 to 190 //
    if (fabs(m) > ball_vvmask) {
        if (b->v_x < 0)
            b->v_x = fabs(b->v_y / ball_vvmask) * -1;
        else
            b->v_x = fabs(b->v_y / ball_vvmask);
#ifdef DEBUG
        printf("vertical mask; new m: %4.2f\n", b->v_y / b->v_x);
#endif
    }

    // adjust speed
    Ball_AdjustSpeed(b, ball_v);
}

void BreakOut::Balls_Reset()
{
    DL_Clear(&ball_list);
    // add one ball //
    Ball *b;
    b = Ball_Create((club.w - ball_w) / 2, -ball_dia, (rand() % 120) - 60, ball_v);
    b->attached = 1;
    DL_Add(&ball_list, b);
}

void BreakOut::Balls_Hide()
{
    DL_Entry    *e = ball_list.head.next;
    Ball        *b;
    int         bx, by;
    while (e != &ball_list.tail) {
        b = (Ball*)e->data;
        // balls position; add club pos if attached //
        bx = b->x;
        by = b->y;
        if (b->attached) {
            bx += club.x;
            by += club.y;
        }
        // put background //
        DR_SETDST(sdl.scr, bx, by, ball_dia, ball_dia);
        DR_SETSRC(ss_bkgnd, bx, by);
        SSur_Blit();
        AddRefreshRect(bx, by, ball_dia, ball_dia);
        e = e->next;
    }
}

int BreakOut::Balls_Update(int ms)
{
    DL_Entry    *e, *next;
    Ball        *b;
    float       old_x, old_y;
    e = ball_list.head.next;
    while (e != &ball_list.tail) {
        next = e->next;
        b = (Ball*)e->data;
        old_x = b->cur_x;
        old_y = b->cur_y;
        if (b->attached && (buttonstate[MB_LEFT] || buttonstate[MB_RIGHT]|| keystate[init_data.k_fire])) {
            // if not in wall //
            if (b->x + club.x >= brick_w && b->x + ball_dia + club.x < sdl.scr->w - brick_w) {
                // release ball //
#ifdef SOUND
    SndSrv_Play(snd_ref, 0);
#endif
                b->attached = 0;
                b->x += club.x;
                b->y += club.y;
                b->cur_x = b->x;
                b->cur_y = b->y;
                if (!init_data.rnd_start) {
                    if (buttonstate[MB_LEFT])
                        Ball_ComputeVec(b, -50, ball_v);
                    else
                        Ball_ComputeVec(b, 50, ball_v);
                }
                Ball_GetTarget(b);
            }
        }
        // new position //
        if (!b->attached) {
            b->cur_x += b->v_x * ms;
            b->cur_y += b->v_y * ms;
            b->x = (int)b->cur_x;
            b->y = (int)b->cur_y;
            // check if reflected by club //
            if (!b->club_con && !b->ign_club)
                Ball_CheckClubReflection(b, old_x, old_y);
            // or by brick //
            if (b->t.exists) {
                b->t.cur_tm += ms;
                Ball_CheckBrickReflection(b);
            }
        }
        // delete ball if outside of window //
        if (!b->attached && (b->x >= sdl.scr->w || b->x + ball_dia < 0 || b->y >= sdl.scr->h))
            DL_DeleteEntry(&ball_list, e);
        e = next;
    }
    // game over ? //
    return ball_list.counter == 0;
}

void BreakOut::Balls_Show()
{
    DL_Entry    *e = ball_list.head.next;
    Ball        *b;
    int         bx, by;
    int         off = 0;
    if (cur_extras[EX_METAL])
        off = ball_w;
    while (e != &ball_list.tail) {
        b = (Ball*)e->data;
        // balls position; add club pos if attached //
        bx = b->x;
        by = b->y;
        if (b->attached) {
            bx += club.x;
            by += club.y;
        }
        // show ball //
        DR_SETDST(sdl.scr, bx, by, ball_w, ball_h);
        DR_SETSRC(ss_ball, off, 0);
        SSur_Blit();
        AddRefreshRect(bx, by, ball_w, ball_h);
        e = e->next;
    }
}

void BreakOut::Balls_AlphaShow(int a)
{
    DL_Entry    *e = ball_list.head.next;
    Ball        *b;
    int         bx, by;
    int         off = 0;
    if (cur_extras[EX_METAL])
        off = ball_w;
    while (e != &ball_list.tail) {
        b = (Ball*)e->data;
        // balls position; add club pos if attached //
        bx = b->x;
        by = b->y;
        if (b->attached) {
            bx += club.x;
            by += club.y;
        }
        // show ball //
        DR_SETDST(sdl.scr, bx, by, ball_w, ball_h);
        DR_SETSRC(ss_ball, off, 0);
        SSur_AlphaBlit(a);
        AddRefreshRect(bx, by, ball_w, ball_h);
        e = e->next;
    }
}

// brick //
void BreakOut::Brick_Remove(int mx, int my, int shot)
{
    int px, py;
    int was_wall = 0;

    if ( (lev_map[mx][my].type == MAP_BRICK && (--lev_map[mx][my].dur == 0 || cur_extras[EX_METAL])) ||
          (lev_map[mx][my].type == MAP_WALL && cur_extras[EX_METAL] && mx > 0 && mx < lev_w - 1 && my > 0 && my < lev_h - 1) ) {
        // remove //
        lev_map[mx][my].id = -1;
        if ( lev_map[mx][my].type == MAP_WALL)
            was_wall = 1;
        lev_map[mx][my].type = MAP_EMPTY;
        px = mx * brick_w;
        py = my * brick_h;
        // add shrapnells //
        Brick_CreateShrapnells(px, py, shot);
#ifdef SOUND
    SndSrv_Play(snd_boom, 0);
#endif
        // clear ss_bkgnd //
        DR_SETDST(ss_bkgnd, px, py, brick_w, brick_h);
        DR_SETSRC(ss_picture, px, py);
        SSur_Blit();
        DR_SETDST(sdl.scr, px, py, brick_w, brick_h);
        DR_SETSRC(ss_bkgnd, px, py);
        SSur_Blit();
        AddRefreshRect(px, py, brick_w, brick_h);
        // release extra if one exists //
        if (lev_map[mx][my].extra != EX_NONE)
            Extra_Create(lev_map[mx][my].extra, mx * brick_w, my * brick_h);
        // get score //
        player.score += lev_map[mx][my].score;
        // check for next level //
        if (!was_wall && --lev_bricks == 0) {
            new_level = 1;
#ifdef SOUND
    if (rand() % 2)
        SndSrv_Play(snd_good, 1);
    else
        SndSrv_Play(snd_exc, 1);
#endif
        }
        if (px == sh_x && py == sh_y)
            Sh_New();
    }
    else
        if (lev_map[mx][my].type == MAP_BRICK) {
            px = mx * brick_w;
            py = my * brick_h;
            DR_SETDST(ss_bkgnd, px, py, brick_w, brick_h);
            if (lev_map[mx][my].dur == 1) {
                DR_SETSRC(ss_bricks, brick_w * 4, 0);
            }
            else
                DR_SETSRC(ss_bricks, brick_w * (lev_map[mx][my].dur - 1), 0);
            SSur_Blit();
            DR_SETDST(sdl.scr, px, py, brick_w, brick_h);
            DR_SETSRC(ss_bkgnd, px, py);
            SSur_Blit();
            AddRefreshRect(px, py, brick_w, brick_h);
        }
}

void BreakOut::Brick_CreateShrapnells(int x, int y, int shot)
{
    if (!init_data.anim) return;
    int mod = init_data.anim;
    int i, j;
    int r;
    int w, h, dx, dy;
    if (cur_extras[EX_METAL] || shot)
        r = rand() % 3;
    else
        r = rand() % 5;
    if (r == 4) r = 3;
    switch (r) {
        case 0:
            w = brick_w / (8 * mod);
            h = brick_h;
            for (i = 0; i < (4 * mod); i++)
                Shr_Create(x + i * w, y, w, h, -0.05 - i * 0.01, 0);
            for (i = (4 * mod) - 1; i >= 0; i--)
                Shr_Create(x + brick_w - (i + 1) * w, y, w, h, 0.05 + i * 0.01, 0);
            break;
        case 1:
            w = brick_w;
            h = brick_h / (4 * mod);
            for (i = 0; i < (2 * mod); i++)
                Shr_Create(x, y + i * h, w, h, 0, -0.05 - i * 0.01);
            for (i = (2 * mod) - 1; i >= 0; i--)
                Shr_Create(x, y + brick_h - (i + 1) * h, w, h, 0, 0.05 + i * 0.01);
            break;
        case 2:
            w = brick_w / (8 * mod);
            h = brick_h;
            for (i = 0; i < (8 * mod); i++)
                Shr_Create(x + i * w, y, w, h, 0, (1 - 2 * (i & 1)) * 0.1);
            break;
        case 3:
            w = init_data.anim == 1 ? 4 : 2;
            h = init_data.anim == 1 ? 4 : 2;
            for (i = 0; i < brick_w / w; i++)
                for (j = 0; j < brick_h / h; j++) {
                    dx = rand() % 2 == 0 ? 1 : -1;
                    dy = rand() % 2 == 0 ? 1 : -1;
                    Shr_Create(x + i * w, y + j * h, w, h, float(rand()%6+5) / 100 * dx, float(rand()%6+5) / 100 * dy);
                 }
            break;
    }
}

// update //
void BreakOut::AddRefreshRect(int x, int y, int w, int h)
{
    if (rect_num == UPDATERECTS) return;
    if (x < 0) {
        w += x;
        x = 0;
    }
    if (y < 0) {
        h += y;
        y = 0;
    }
    if (x + w > sdl.scr->w)
        w = sdl.scr->w - x;
    if (y + h > sdl.scr->h)
        h = sdl.scr->h - y;
    if (w <= 0 || h <= 0)
        return;
    rects[rect_num].x = x;
    rects[rect_num].y = y;
    rects[rect_num].w = w;
    rects[rect_num].h = h;
    rect_num++;
}

void BreakOut::Refresh()
{
    if (rect_num == UPDATERECTS)
        SDL_UpdateRect(sdl.scr, 0, 0, sdl.scr->w, sdl.scr->h);
    else
        SDL_UpdateRects(sdl.scr, rect_num, rects);
    rect_num = 0;
}

void BreakOut::Life_Hide()
{
    DR_SETDST(sdl.scr, 0, life_y, brick_w, sdl.scr->h - life_y);
    DR_SETSRC(ss_bkgnd, 0, life_y);
    SSur_Blit();
    AddRefreshRect(0, life_y, sdl.scr->h - life_y, brick_w);
}

void BreakOut::Life_Show()
{
    int limit = player.lives > max_lives ? max_lives : player.lives;
    for (int i = 0; i < limit; i++) {
        DR_SETDST(sdl.scr, 0, sdl.scr->h - (i + 1) * brick_h, brick_w, brick_h);
        DR_SETSRC(ss_life, 0, brick_h);
        SSur_Blit();
    }
    AddRefreshRect(0, life_y, brick_w, sdl.scr->h - life_y);
}

// score //
void BreakOut::Score_Hide()
{
    DR_SETDST(sdl.scr, score_x, score_y, score_w, score_h);
    DR_SETSRC(ss_bkgnd, score_x, score_y);
    SSur_Blit();
    AddRefreshRect(score_x, score_y, score_w, score_h);
}

void BreakOut::Score_Update(int ms)
{
    float diff;
    float change;

    if ( cur_score < player.score) {

        diff = (float)player.score - cur_score;
        change = 0.005 * diff;
        if ( change < 0.5 )
            change = 0.5;
        cur_score += change * ms;
        if (cur_score > player.score)
            cur_score = player.score;

    }
}

void BreakOut::Score_Show()
{
    char    str[8];
    sprintf(str, "%i", (int)cur_score);
    score_w = strlen(str) * score_lw;
    score_x = sdl.scr->w - score_w - score_xoff;
    for (unsigned int i = 0; i < strlen(str); i++) {
        DR_SETDST(sdl.scr, score_x + i * score_lw, score_y, score_lw, score_h);
        DR_SETSRC(ss_numbers, (str[i] - 48) * score_lw, 0);
        SSur_Blit();
    }
    AddRefreshRect(score_x, score_y, score_w, score_h);
}

// extras //
void BreakOut::Extra_Create(ExtraType extra, int x, int y)
{
    Extra   *e;
    e = new Extra;
    e->extra = extra;
    e->x = x;
    e->y = y;
    if (!init_data.trp)
        e->alpha = 0;
    else
        e->alpha = 255;
    DL_Add(&extra_list, e);
}

void BreakOut::Extra_Use(int e)
{
    int     i;
    Ball    *b;
    DL_Entry *ent;

    while( e == EX_RANDOM )
        e = rand() % (EX_NUMBER);

    switch (e) {
        case EX_SCORE200:
            player.score += 200;
#ifdef SOUND
    SndSrv_Play(snd_sco, 0);
#endif
            break;
        case EX_SCORE500:
            player.score += 500;
#ifdef SOUND
    SndSrv_Play(snd_sco, 0);
#endif
            break;
        case EX_SCORE1000:
            player.score += 1000;
#ifdef SOUND
    SndSrv_Play(snd_sco, 0);
#endif
            break;
        case EX_SCORE2000:
            player.score += 2000;
#ifdef SOUND
    SndSrv_Play(snd_sco, 0);
#endif
            break;
        case EX_SCORE5000:
            player.score += 5000;
#ifdef SOUND
    SndSrv_Play(snd_sco, 0);
#endif
            break;
        case EX_SCORE10000:
            player.score += 10000;
#ifdef SOUND
    SndSrv_Play(snd_sco, 0);
#endif
            break;
        case EX_LIFE:
            if (player.lives < diff[init_data.diff].max_lives) {
                player.lives++;
#ifdef SOUND
    SndSrv_Play(snd_lup, 0);
#endif
            }
            else {
                player.score += 10000;
#ifdef SOUND
    SndSrv_Play(snd_sco, 0);
#endif
            }
            break;
        case EX_SHORTEN:
            Club_Resize(-1);
            break;
        case EX_LENGTHEN:
            Club_Resize(1);
            break;
        case EX_BALL:
            b = Ball_Create(club.x + (club.w - ball_w) / 2, club.y - ball_dia, (rand() % 90) - 45, 0.15);
            Ball_MaskV(b, b->v_x);
            Ball_GetTarget(b);
            DL_Add(&ball_list, b);
#ifdef SOUND
    SndSrv_Play(snd_ref, 0);
#endif
            break;
        case EX_WALL:
            if (cur_extras[EX_WALL]) {
                wall_time = 10000;
                break;
            }
            for (i = 1; i < lev_w - 1; i++) {
                lev_map[i][lev_h - 1].type = MAP_WALL;
                lev_map[i][lev_h - 1].id = 0;
            }
            wall_time = 10000;
            wall_alpha = 255;
            Ball_NewTargets(-1, 0);
#ifdef SOUND
    SndSrv_Play(snd_wal, 0);
#endif
            break;
        case EX_METAL:
            metal_time = 5000;
#ifdef SOUND
    SndSrv_Play(snd_met, 0);
#endif
            break;
        case EX_FROZEN:
            frozen_time = 1000;
#ifdef SOUND
    SndSrv_Play(snd_fre, 0);
#endif
            break;
        case EX_WEAPON:
            wpn_time = 5000;
#ifdef SOUND
    SndSrv_Play(snd_wea, 0);
#endif
            break;
        case EX_SLIME:
            slime_time = 20000;
#ifdef SOUND
    SndSrv_Play(snd_sli, 0);
#endif
            break;

        case EX_FAST:

#ifdef SOUND
    SndSrv_Play(snd_speedup, 0);
#endif
            if ( cur_extras[EX_SLOW] || cur_extras[EX_FAST] )
                ball_v = ball_old_v;

            if ( cur_extras[EX_SLOW] ) {

                slow_time = 0;
                cur_extras[EX_SLOW] = 0;

            }
            fast_time = 20000;
            ball_old_v = ball_v;
            ball_v = ball_vm;

            ent = ball_list.head.next;
            while ( ent != &ball_list.tail ) {

                Ball_AdjustSpeed((Ball*)ent->data, ball_v);
                Ball_GetTarget((Ball*)ent->data);
                ent = ent->next;

            }

            break;

        case EX_SLOW:

#ifdef SOUND
    SndSrv_Play(snd_speeddown, 0);
#endif
            if ( cur_extras[EX_SLOW] || cur_extras[EX_FAST] )
                ball_v = ball_old_v;

            if ( cur_extras[EX_FAST] ) {

                fast_time = 0;
                cur_extras[EX_FAST] = 0;

            }
            slow_time = 20000;
            ball_old_v = ball_v;
            ball_v = levels[level].v;

            ent = ball_list.head.next;
            while ( ent != &ball_list.tail ) {

                Ball_AdjustSpeed((Ball*)ent->data, ball_v);
                Ball_GetTarget((Ball*)ent->data);
                ent = ent->next;

            }

            break;

    }
    cur_extras[e] = 1;
}

void BreakOut::Extras_Hide()
{
    DL_Entry    *e = extra_list.head.next;
    Extra       *ex;
    int         x, y;
    while (e != &extra_list.tail) {
        ex = (Extra*)e->data;
        x = (int)ex->x;
        y = (int)ex->y;
        DR_SETDST(sdl.scr, x, y, brick_w, brick_h);
        DR_SETSRC(ss_bkgnd, x, y);
        SSur_Blit();
        AddRefreshRect(x, y, brick_w, brick_h);
        e = e->next;
    }
}

void BreakOut::Extras_Update(int ms)
{
    DL_Entry    *e, *next;
    Extra       *ex;
    SDL_Event   event;

   // slime
    if (cur_extras[EX_SLIME]) {
        slime_time -= ms;
        if (slime_time < 0) {
            cur_extras[EX_SLIME] = 0;
            slime_time = 0;
        }
    }
    // weapon
    if (cur_extras[EX_WEAPON]) {
        wpn_time -= ms;
        if (wpn_time < 0) {
            cur_extras[EX_WEAPON] = 0;
            wpn_time = 0;
        }
    }
    // metal balls //
    if (cur_extras[EX_METAL]) {
        metal_time -= ms;
        if (metal_time < 0) {
            cur_extras[EX_METAL] = 0;
            metal_time = 0;
        }
    }
    // fast balls //
    if ( cur_extras[EX_FAST] ) {

        fast_time -= ms;
        if ( fast_time < 0 ) {

            cur_extras[EX_FAST] = 0;
            fast_time = 0;
            ball_v = ball_old_v;
            e = ball_list.head.next;
            while ( e != &ball_list.tail ) {

                Ball_AdjustSpeed((Ball*)e->data, ball_v);
                Ball_GetTarget((Ball*)e->data);
                e = e->next;

            }

        }

    }

    // slow balls //
    if ( cur_extras[EX_SLOW] ) {

        slow_time -= ms;
        if ( slow_time < 0 ) {

            cur_extras[EX_SLOW] = 0;
            slow_time = 0;
            ball_v = ball_old_v;
            e = ball_list.head.next;
            while ( e != &ball_list.tail ) {

                Ball_AdjustSpeed((Ball*)e->data, ball_v);
                Ball_GetTarget((Ball*)e->data);
                e = e->next;

            }

        }

    }
    // frozen paddle //
    if (cur_extras[EX_FROZEN]) {
        frozen_time -= ms;
        if (frozen_time < 0) {
            cur_extras[EX_FROZEN] = 0;
            frozen_time = 0;
          	memset(buttonstate, 0, sizeof(buttonstate));
           	memset(keystate, 0, sizeof(keystate));
            while (SDL_PollEvent(&event));
        }
    }

    e = extra_list.head.next;
    while (e != &extra_list.tail) {
        next = e->next;
        ex = (Extra*)e->data;
        ex->y += 0.05 * ms;
        if (ex->alpha > 0)
            ex->alpha -= 0.25 * ms;
        if (ex->alpha < 0)
            ex->alpha = 0;
        if (ex->y >= sdl.scr->h) {
            DL_DeleteEntry(&extra_list, e);
            e = next;
        }
        else {
            // contact with club core ? //
            if (ex->x + brick_w > club.x && ex->x < club.x + club.w - 1 && ex->y + brick_h > club.y && ex->y < club.y + club.h) {
                Extra_Use(ex->extra);
                DL_DeleteEntry(&extra_list, e);
            }
            e = next;
        }
    }
}

void BreakOut::Extras_Show()
{
    DL_Entry    *e = extra_list.head.next;
    Extra       *ex;
    int         x, y;
    while (e != &extra_list.tail) {
        ex = (Extra*)e->data;
        x = (int)ex->x;
        y = (int)ex->y;
        DR_SETDST(sdl.scr, x, y, brick_w, brick_h);
        DR_SETSRC(ss_extras, (ex->extra - 1) * brick_w, 0);
        SSur_AlphaBlit((int)ex->alpha);
        AddRefreshRect(x, y, brick_w, brick_h);
        e = e->next;
    }
}

// shrapnells //
void BreakOut::Shr_Create(int x, int y, int w, int h, float vx, float vy)
{
    Shrapnell *s = new Shrapnell;
    s->ss_pic = SSur_Create(w, h, SDL_HWSURFACE);
    SDL_SetColorKey(s->ss_pic, 0, 0);
    DR_SETDST(s->ss_pic, 0, 0, w, h);
    DR_SETSRC(ss_bkgnd, x, y);
    SSur_Blit();
    s->x = x;
    s->y = y;
    s->v.x = vx;
    s->v.y = vy;
    s->alpha = 0;
    DL_Add(&shrapnell_list, s);
}

void Shr_Free(void *p)
{
    Shrapnell *s = (Shrapnell*)p;
    SDL_FreeSurface(s->ss_pic);
    delete s;
}

void BreakOut::Shr_Hide()
{
    DL_Entry    *e = shrapnell_list.head.next;
    Shrapnell   *s;
    int         x, y;
    while (e != &shrapnell_list.tail) {
        s = (Shrapnell*)e->data;
        x = (int)s->x;
        y = (int)s->y;
        DR_SETDST(sdl.scr, x, y, s->ss_pic->w, s->ss_pic->h);
        DR_SETSRC(ss_bkgnd, x, y);
        SSur_Blit();
        AddRefreshRect(x, y, s->ss_pic->w, s->ss_pic->h);
        e = e->next;
    }
}

void BreakOut::Shr_Update(int ms)
{
    DL_Entry    *e, *next;
    Shrapnell   *s;
    e = shrapnell_list.head.next;
    while (e != &shrapnell_list.tail) {
        next = e->next;
        s = (Shrapnell*)e->data;
        s->x += s->v.x * ms;
        s->y += s->v.y * ms;
        if (s->alpha < 255)
            s->alpha += 0.20 * ms;
        if (s->alpha > 255 || s->x + s->ss_pic->w < 0 || s->y + s->ss_pic->h < 0 || s->x > sdl.scr->w || s->y > sdl.scr->h)
            DL_DeleteEntry(&shrapnell_list, e);
        e = next;
    }
}

void BreakOut::Shr_Show()
{
    DL_Entry    *e = shrapnell_list.head.next;
    Shrapnell   *s;
    int         x, y;
    while (e != &shrapnell_list.tail) {
        s = (Shrapnell*)e->data;
        x = (int)s->x;
        y = (int)s->y;
        DR_SETDST(sdl.scr, x, y, s->ss_pic->w, s->ss_pic->h);
        DR_SETSRC(s->ss_pic, 0, 0);
        if (!init_data.trp || s->alpha == 0)
            SSur_Blit();
        else
            SSur_AlphaBlit((int)s->alpha);
        AddRefreshRect(x, y, s->ss_pic->w, s->ss_pic->h);
        e = e->next;
    }
}

// wall //
void BreakOut::Wall_Hide()
{
    if (!cur_extras[EX_WALL]) return;
    DR_SETDST(sdl.scr, brick_w, (lev_h - 1) * brick_h, brick_w * (lev_w - 2), brick_h);
    DR_SETSRC(ss_bkgnd, brick_w, (lev_h - 1) * brick_h);
    SSur_Blit();
}

void BreakOut::Wall_Update(int ms)
{
    if (!cur_extras[EX_WALL]) return;
    int i;
    wall_time -= ms;
    if (wall_time > 0) {
        if (wall_alpha > 0)
            wall_alpha -= 0.25 * ms;
        if (wall_alpha < 0)
            wall_alpha = 0;
    }
    else {
        if (wall_alpha < 255) {
            wall_alpha += 0.25 * ms;
            if (wall_alpha >= 255) {
                // vanish //
                cur_extras[EX_WALL] = 0;
                for (i = 0; i < lev_w; i++)
                    lev_map[i][lev_h - 1].type = MAP_EMPTY;
                if (!init_data.trp) {
                    DR_SETDST(sdl.scr, 0, (lev_h - 1) * brick_h, sdl.scr->w, brick_h);
                    DR_SETSRC(ss_bkgnd, 0, (lev_h - 1) * brick_h);
                    SSur_Blit();
                    AddRefreshRect(0, (lev_h - 1) * brick_h, sdl.scr->w, brick_h);
                }
                Ball_NewTargets(-1, 0);
            }
        }
    }
}

void BreakOut::Wall_Show()
{
    if (!cur_extras[EX_WALL]) return;
    int x, y = (lev_h - 1) * brick_h;
    for (int i = 1; i < lev_w - 1; i++) {
        x = i * brick_w;
        DR_SETDST(sdl.scr, x, y, brick_w, brick_h);
        DR_SETSRC(ss_bricks, 0, 0);
        if (!init_data.trp || wall_alpha == 0)
            SSur_Blit();
        else
            SSur_AlphaBlit((int)wall_alpha);
    }
    AddRefreshRect(0, y, sdl.scr->w, brick_h);
}

// plasma weapon //
void BreakOut::Wpn_Update(int ms)
{
    wpn_cur += ms * wpn_fpms;
    if (wpn_cur >= wpn_fr_num)
        wpn_cur -= wpn_fr_num;
}

// plasma shot //
void BreakOut::Shots_Hide()
{
    DL_Entry    *e = shot_list.head.next;
    Shot        *s;
    while (e != &shot_list.tail) {
        s = (Shot*)e->data;
        DR_SETDST(sdl.scr, (int)s->x, (int)s->y, shot_w, shot_h);
        DR_SETSRC(ss_bkgnd, (int)s->x, (int)s->y);
        SSur_Blit();
        AddRefreshRect((int)s->x, (int)s->y, shot_w, shot_h);
        e = e->next;
    }
}

void BreakOut::Shots_Update(int ms)
{
    DL_Entry    *e = shot_list.head.next, *old_e;
    Shot        *s;
    while (e != &shot_list.tail) {
        s = (Shot*)e->data;
        s->cur_fr += ms * shot_fpms;
        if (s->cur_fr >= shot_fr_num)
            s->cur_fr -= shot_fr_num;
        s->y += ms * shot_v_y;
        old_e = e; e = e->next;
        s->t.cur_tm += ms;
        if (s->t.cur_tm > s->t.time) {
            Brick_Remove(s->t.mx, s->t.my, 1);
            if (s->next_too) {
                Brick_Remove(s->t.mx + 1, s->t.my, 1);
                Shots_NewTargets(s->t.mx + 1, s->t.my);
                Ball_NewTargets(s->t.mx + 1, s->t.my);
            }
            Ball_NewTargets(s->t.mx, s->t.my);
            Shots_NewTargets(s->t.mx, s->t.my);
            DL_DeleteEntry(&shot_list, old_e);
        }
    }
    if (cur_extras[EX_WEAPON] && fire_shot && (int)shot_list.counter < max_shots) {
        shot_time += ms;
        if (shot_time >= shot_delay) {
            DL_Add(&shot_list, Shot_Create());
            shot_time = 0;
        }
    }
}

void BreakOut::Shots_Show()
{
    DL_Entry    *e = shot_list.head.next;
    Shot        *s;
    while (e != &shot_list.tail) {
        s = (Shot*)e->data;
        DR_SETDST(sdl.scr, (int)s->x, (int)s->y, shot_w, shot_h);
        DR_SETSRC(ss_shot, (int)s->cur_fr * shot_w, 0);
        SSur_AlphaBlit(shot_alpha);
        AddRefreshRect((int)s->x, (int)s->y, shot_w, shot_h);
        e = e->next;
    }
}

void BreakOut::Shots_NewTargets(int mx, int my)
{
    DL_Entry    *e = shot_list.head.next;
    Shot        *s;
    while (e != &shot_list.tail) {
        s = (Shot*)e->data;
        if (s->t.mx == mx && s->t.my == my)
            Shot_GetTarget(s);
        e = e->next;
    }
}

Shot* BreakOut::Shot_Create()
{
#ifdef SOUND
    SndSrv_Play(snd_shot, 0);
#endif
    Shot    *s = new Shot;
    s->cur_fr = 0;
    s->x = wpn_x + wpn_sx_off;
    s->y = wpn_y + wpn_sy_off;
    Shot_GetTarget(s);
    return s;
}

void BreakOut::Shot_GetTarget(Shot *s)
{
    int mx = (int)(s->x + 3) / brick_w;
    int my = (int)(s->y + 3) / brick_h;
    memset(&s->t, 0, sizeof(Target));
    while (lev_map[mx][my].type == MAP_EMPTY) my--;
    s->t.mx = mx; s->t.my = my;
    mx = (int)(s->x + 6) / brick_w;
    if (mx != s->t.mx) {
        my = (int)(s->y + 3) / brick_h;
        while(lev_map[mx][my].type == MAP_EMPTY) my--;
        if (my > s->t.my) {
            s->t.mx = mx;
            s->t.my = my;
            s->next_too = 0;
        }
        else
            if (my == s->t.my)
                s->next_too = 1;
    }
    s->t.cur_tm = 0;
    s->t.time = (int)((s->y + 3 - (my * brick_h + brick_h - 1)) / fabs(shot_v_y));
}

// pause //
void BreakOut::Pause()
{
    SDL_Event event;
    int leave = 0;

#ifdef SOUND
    SndSrv_Play(snd_click, 0);
#endif

    if (init_data.warp && init_data.control != 0)
    	SDL_SetCursor(original_cur);
    	
    SDL_Surface *buffer = SSur_Create(sdl.scr->w, sdl.scr->h, SDL_HWSURFACE);
    SDL_SetColorKey(buffer, 0, 0);

    DR_SETFULLDST(buffer);
    DR_SETFULLSRC(sdl.scr);
    SSur_Blit();
    DR_SETFULLDST(sdl.scr);
    SSur_Fill(0x0);
    DR_SETFULLSRC(buffer);
    SSur_AlphaBlit(128);

    char    str[64];
    sprintf(str, "Paused");
    SFnt_Write(font, sdl.scr, sdl.scr->w / 2, sdl.scr->h / 2, str, 0);
    Sdl_FullUpdate();

    while (!leave) {
        if (SDL_PollEvent(&event)) {
            switch (event.type) {
                case SDL_KEYUP:
                    if (event.key.keysym.sym == SDLK_f) {
                        fullscreen = !fullscreen;
                        if (fullscreen) {
                            Sdl_SetVideoMode(lev_w * brick_w, lev_h * brick_h, 16, SDL_HWSURFACE | SDL_FULLSCREEN);
                            SDL_SetCursor(empty_cur);
                        }
                        else {
                            Sdl_SetVideoMode(lev_w * brick_w, lev_h * brick_h, 16, SDL_HWSURFACE);
                            if (!init_data.warp && !init_data.control == 0)
                            	SDL_SetCursor(original_cur);
                        }
                        DR_SETFULLDST(sdl.scr);
                        DR_SETFULLSRC(buffer);
                        SSur_AlphaBlit(128);
                        SFnt_Write(font, sdl.scr, sdl.scr->w / 2, sdl.scr->h / 2, str, 0);
                        Sdl_FullUpdate();
                        break;
                    }
                    if (event.key.keysym.sym == SDLK_p)
                        leave = 1;
                    break;
                case SDL_QUIT:
                    leave = 1;
                    fast_quit = 1;
                    break;
                default:
                    break;
            }
        }
    }
#ifdef SOUND
    SndSrv_Play(snd_click, 0);
#endif

    DR_SETFULLDST(sdl.scr);
    DR_SETFULLSRC(buffer);
    SSur_Blit();
    Sdl_FullUpdate();
    SDL_FreeSurface(buffer);

    if (init_data.warp && init_data.control != 0)
    	SDL_SetCursor(empty_cur);
}

// snap shot //
void BreakOut::SnapShot()
{
#ifdef SOUND
    SndSrv_Play(snd_click, 0);
#endif
	char filename[32];
	sprintf(filename, "snapshot_%i.bmp", snapshot++);
	SDL_SaveBMP(sdl.scr, filename);
}

// corner //
void BreakOut::Corner_Check(Corner *c, float v_x, float v_y)
{
    if (v_x > 0 && v_y > 0 && c->x % brick_w == 0 && c->y % brick_h == 0 )
        c->type = UPPERLEFT;
    else
        if (v_x < 0 && v_y > 0 && (c->x + 1) % brick_w == 0 && c->y % brick_h == 0 )
            c->type = UPPERRIGHT;
        else
            if (v_x > 0 && v_y < 0 && c->x % brick_w == 0 && (c->y + 1) % brick_h == 0 )
                c->type = LOWERLEFT;
            else
                if (v_x < 0 && v_y < 0 && (c->x + 1) % brick_w == 0 && (c->y + 1) % brick_h == 0 )
                    c->type = LOWERRIGHT;
                 else
                     c->type = NONE;
}

// levels //
FILE* BreakOut::Levels_OpenFile(char *f_str)
{
    if (init_data.lvl_path[0] == 0 || init_data.lvl_file[0] == 0) {
        printf("empty path or file - using original levels\n");
        levels = orig_levels;
        return 0;
    }
    if (init_data.lvl_path[0] == '~') {
        strcpy(f_str, home_dir);
        if (strlen(init_data.lvl_path) > 1)
            strcat(f_str, init_data.lvl_path + 1);
    }
    else
        strcpy(f_str, init_data.lvl_path);
    if (f_str[strlen(f_str) - 1] != '/')
        strcat(f_str, "/");
    strcat(f_str, init_data.lvl_file);
    FILE *f = fopen(f_str, "r");
    return f;
}

void BreakOut::Levels_LoadFromFile()
{
    char f_str[64];
    FILE *f;
    if ((f = Levels_OpenFile(f_str)) == 0) {
        // not found; switch back to original levels
        printf("cannot find '%s' - using original levels\n", f_str);
        UseOrigLevels();
    }
    else {
        // exists //
        printf("attempting to parse file '%s'...\n", f_str);
        if (Levels_ParseFile(f))
            levels = file_levels;
        else {
            printf("failed somewhere in Levels_ParseFile() - using original levels\n");
            if (file_levels) Levels_Delete();
            UseOrigLevels();
        }
        fclose(f);
    }
}

char* BreakOut::NextLine(char *buf, char *cp, char *l)
{
    char *bgn = cp;
    while (*cp != 10 && cp < l) cp++;
    if (buf != 0 && bgn != cp) {
        strncpy(buf, bgn, cp - bgn);
        buf[cp - bgn] = 0;
    }
    while (*cp == 10 && cp < l) cp++;
    return cp;
}

char* BreakOut::ParseEntry(char *buf, char *cp, char *l)
{
    // look up entry from cp to next ';'
    // returns are converted to spaces
    char *bgn = cp;
    while (*cp != ';' && cp < l) cp++;
    if (buf != 0 && bgn != cp) {
        strncpy(buf, bgn, cp - bgn);
        buf[cp - bgn] = 0;
        for (unsigned int i = 0; i < strlen(buf); i++)
            if (buf[i] == 10)
                buf[i] = 32;
    }
    while (*cp == ';' && cp < l) cp++;
    while (*cp == 10 && cp < l) cp++;
    return cp;
}

int BreakOut::Levels_ParseFile(FILE *f)
{
    int i, j, k, l;
    char entry[256];
    char *cur_pos, *limit;
    char *str_lb = "[LEV";
    char *str_mb = "[MAP";
    char *str_eb = "[EXT";
    char *str_e = "[END";
    char extras[EX_NUMBER + 1] = {'0','1','2','3','4','5','b','l','+','-','s','m','w','p','?','>','<',' '};
    char bricks[11] = {'a','b','c','d','e','f','g','h','i','#',' '};

    // load file into mem //
    fseek(f, 0, SEEK_END);
    int fsize = ftell(f);
    char *fbuf = new char[fsize];
    fseek(f, 0, SEEK_SET);
    fread(fbuf, fsize, 1, f);
    cur_pos = fbuf;
    limit = fbuf + fsize;

    // how many levels ? //
    lev_num = 0;
    while (cur_pos < fbuf + fsize) {
        if (!strncmp(cur_pos, str_lb, strlen(str_lb)))
            lev_num++;
        cur_pos = NextLine(0, cur_pos, limit);
    }
    printf("levels found: %i...\n", lev_num);
    if (lev_num == 0) {
        printf("sorry but no game without any levels...\n");
        return 0;
    }
    file_levels = new Level[lev_num];
    memset(file_levels, 0, sizeof(Level) * lev_num);

    // parse levels //
    cur_pos = fbuf;
    for (i = 0; i < lev_num; i++) {
        // [LEVEL]
        cur_pos = ParseEntry(entry, cur_pos, limit);

        // Author
        cur_pos = ParseEntry(entry, cur_pos, limit);
        strcpy(file_levels[i].author, entry);
        // Name
        cur_pos = ParseEntry(entry, cur_pos, limit);
        strcpy(file_levels[i].name, entry);
        // start velocity
        cur_pos = ParseEntry(entry, cur_pos, limit);
        file_levels[i].v = strtod(entry, 0);
        // velocity change
        cur_pos = ParseEntry(entry, cur_pos, limit);
        file_levels[i].v_pms = strtod(entry, 0);
        // max velocity
        cur_pos = ParseEntry(entry, cur_pos, limit);
        file_levels[i].vm = strtod(entry, 0);
        // map width
        cur_pos = ParseEntry(entry, cur_pos, limit);
        file_levels[i].w = atoi(entry);
        // map height
        cur_pos = ParseEntry(entry, cur_pos, limit);
        file_levels[i].h = atoi(entry);

        // check size //
        if (file_levels[i].w < 14 || file_levels[i].w > MAX_MAP_W - 2 || file_levels[i].h < 18 || file_levels[i].h > MAX_MAP_H - 2) {
            printf("parse error in level %i: size out of range (w: 14->%i; h: 18->%i)\n", i, MAX_MAP_W - 2, MAX_MAP_H - 2);
            return 0;
        }

        // create map & extras
        file_levels[i].map = new char[file_levels[i].w * file_levels[i].h];
        file_levels[i].extras = new char[file_levels[i].w * file_levels[i].h];

        // [MAP]
        cur_pos = ParseEntry(entry, cur_pos, limit);
        if (strncmp(entry, str_mb, strlen(str_mb))) {
            printf("parse error in level %i: [MAP] expected\n", i);
            return 0;
        }

        // read map
        for (j = 0; j < file_levels[i].h; j++) {
            cur_pos = ParseEntry(entry, cur_pos, limit);
            // wrong size?
            if (strlen(entry) != (unsigned)file_levels[i].w) {
                printf("parse error in map of level %i, line %i: invalid length %i\n", i, j, strlen(entry));
                return 0;
            }
            // bad brick ?
            for (k = 0; k < file_levels[i].w; k++) {
                for (l = 0; l < 11; l++)
                    if (entry[k] == bricks[l])
                        break;
                if (l == 11)
                    printf("warning: in map of level %i, line %i: unknown brick '%c'\n", i, j, entry[k]);
            }
            strncpy(file_levels[i].map + j * file_levels[i].w, entry, strlen(entry));
        }

        //[EXTRAS]
        cur_pos = ParseEntry(entry, cur_pos, limit);
        if (strncmp(entry, str_eb, strlen(str_eb))) {
            printf("parse error in level %i: [EXTRAS] expected\n", i);
            return 0;
        }

        // read extras
        for (j = 0; j < file_levels[i].h; j++) {
            cur_pos = ParseEntry(entry, cur_pos, limit);
            // wrong size ?
            if (strlen(entry) != (unsigned)file_levels[i].w) {
                printf("warning in extra map of level %i, line %i: invalid length %i\n", i, j, strlen(entry));
                return 0;
            }
            // bad extra ?
            for (k = 0; k < file_levels[i].w; k++) {
                for (l = 0; l < EX_NUMBER; l++)
                    if (entry[k] == extras[l])
                        break;
                if (l == EX_NUMBER)
                    printf("warning: in extra map of level %i, line %i: unknown extra %c\n", i, j, entry[k]);
            }
            strncpy(file_levels[i].extras + j * file_levels[i].w, entry, strlen(entry));
        }

        // [END]
        cur_pos = ParseEntry(entry, cur_pos, limit);
        if (strncmp(entry, str_e, strlen(str_e))) {
            printf("parse error in level %i: [END] expected\n", i);
            return 0;
        }
    }

    // free mem
    delete fbuf;

    return 1;
}

void BreakOut::Levels_Delete()
{
    // delete levels loaded from file //
    if (file_levels == 0) return;
    for (int i = 0; i < lev_num; i++) {
        if (file_levels[i].map)
            delete file_levels[i].map;
        if (file_levels[i].extras)
            delete file_levels[i].extras;
    }
    delete file_levels;
    file_levels = 0;
}

void BreakOut::UseOrigLevels()
{
    levels = orig_levels;
    lev_num = LEVEL_NUM;
}

// shine //
void BreakOut::Sh_Hide()
{
    if (sh_x == 0 && sh_y == 0) return;
    DR_SETDST(sdl.scr, sh_x, sh_y, brick_w, brick_h);
    DR_SETSRC(ss_bkgnd, sh_x, sh_y);
    SSur_Blit();
    AddRefreshRect(sh_x, sh_y, brick_w, brick_h);
}

void BreakOut::Sh_Update(int ms)
{
    if (sh_x == 0 && sh_y == 0)
        Sh_New();
    else {
        sh_cur += sh_pms * ms;
        if (sh_cur > sh_fr)
            Sh_New();
    }
}

void BreakOut::Sh_Show()
{
    if (sh_x == 0 && sh_y == 0) return;
    DR_SETDST(sdl.scr, sh_x, sh_y, brick_w, brick_h);
    DR_SETSRC(ss_sh, (int)sh_cur * brick_w, 0);
    SSur_Blit();
    AddRefreshRect(sh_x, sh_y, brick_w, brick_h);
}

void BreakOut::Sh_New()
{
    int x_add, y_add, x, y;
    sh_cur = 0;
    sh_x = 0; sh_y = 0;

    x = (rand() % (brick_w - 2)) + 1;
    y = (rand() % (brick_h - 2)) + 1;
    x_add = rand() % 2 == 0 ? 1 : -1;
    y_add = rand() % 2 == 0 ? 1 : -1;

    while (x > 0 && x < lev_w - 1 && y > 0 && y <lev_h - 1) {
        if (lev_map[x][y].type != MAP_EMPTY && lev_map[x][y].id < 4) {
            sh_x = x * brick_w;
            sh_y = y * brick_h;
            break;
        }
        x += x_add; y += y_add;
    }
}

// credit //
void BreakOut::Credit_Hide()
{
    if (cr_status == 3) return;
    DR_SETDST(sdl.scr, cr_x, cr_y, cr_w, cr_h);
    DR_SETSRC(ss_bkgnd, cr_x, cr_y);
    SSur_Blit();
    AddRefreshRect(cr_x, cr_y, cr_w, cr_h);
}

void BreakOut::Credit_Update(int ms)
{
    if (cr_status == 0) {
        cr_alpha -= cr_pms * ms;
        if (cr_alpha <= 0) {
            cr_status = 1;
            cr_alpha = 0;
        }
    }
    else
        if (cr_status == 1) {
            cr_cur += ms;
            if (cr_cur > cr_time)
                cr_status = 2;
        }
        else
            if (cr_status == 2) {
                cr_alpha += cr_pms * ms;
                if (cr_alpha >= 255)
                    cr_status = 3;
            }
}

void BreakOut::Credit_Show()
{
    if (cr_status == 3) return;
    font->algn = TA_X_LEFT | TA_Y_TOP;
    SFnt_Write(font, sdl.scr, cr_x, cr_y, cr_str, (int)cr_alpha);
    font->algn = TA_X_CENTER | TA_Y_CENTER;
    AddRefreshRect(cr_x, cr_y, cr_w, cr_h);
}

void BreakOut::Credit_Init()
{
    cr_cur = 0; cr_alpha = 255; cr_status = 0;

    // string //
    if (init_data.lvls_frm_file)
        sprintf(cr_str, "%s (%s)", lev_name, lev_author);
    else
        sprintf(cr_str, "%s", lev_name);

    // position //
    cr_w = SFnt_TextWidth(font, cr_str);
    cr_h = font->lh;
    cr_x = (sdl.scr->w - cr_w) / 2;
    cr_y = sdl.scr->h - 2 - cr_h;
}

// extra display //
void BreakOut::ExDisp_Hide()
{
    int i;

    if ( init_data.no_exdisp ) return;

    ed_x = sdl.scr->w - brick_w;

    for ( i = 0; i < EX_NUMBER; i++ )
        if ( ed_offsets[i] != 0 && (!cur_extras[i] || (i == EX_WALL && wall_time <= 0) ) ) {

            DR_SETDST(sdl.scr, ed_x, ed_y + ed_offsets[i], brick_w, brick_h);
            DR_SETSRC(ss_bkgnd, ed_x, ed_y + ed_offsets[i]);
            SSur_Blit();
            AddRefreshRect(ed_x, ed_y + ed_offsets[i], brick_w, brick_h);

        }

}

void BreakOut::ExDisp_Show()
{
    int i, j;
    char str[12];
    int w, x;

    if ( init_data.no_exdisp ) return;

    ed_x = sdl.scr->w - brick_w;

    font->algn = TA_X_CENTER | TA_Y_CENTER;

    for ( i = 0; i < EX_NUMBER; i++ )
        if ( ed_offsets[i] != 0 && cur_extras[i] ) {

            if (i == EX_WALL && wall_time <= 0) continue;

            // picture
            DR_SETDST(sdl.scr, ed_x, ed_y + ed_offsets[i], brick_w, brick_h);
            SSur_Fill(0x0);
            DR_SETSRC(ss_extras, ( i - 1 ) * brick_w, 0);
            SSur_AlphaBlit(128);

            // remaining time
            switch ( i ) {

                case EX_SLIME: sprintf(str, "%i", (slime_time / 1000) + 1); break;
                case EX_METAL: sprintf(str, "%i", (metal_time / 1000) + 1); break;
                case EX_FAST: sprintf(str, "%i", (fast_time / 1000) + 1); break;
                case EX_SLOW: sprintf(str, "%i", (slow_time / 1000) + 1); break;
                case EX_WALL: sprintf(str, "%i", (wall_time / 1000) + 1); break;
                case EX_WEAPON: sprintf(str, "%i", (wpn_time / 1000) + 1); break;

            }
            w = strlen(str) * score_lw;
            x = ed_x + (brick_w - w) / 2;
            for ( j = 0; j < strlen(str); j++ ) {

                DR_SETDST(sdl.scr, x + j * score_lw, ed_y + ed_offsets[i] + (brick_h - score_h) / 2, score_lw, score_h);
                DR_SETSRC(ss_numbers, (str[j] - 48) * score_lw, 0);
                SSur_Blit();

            }

            AddRefreshRect(ed_x, ed_y + ed_offsets[i], brick_w, brick_h);

        }

}
