1755 lines
61 KiB
C
1755 lines
61 KiB
C
/***************************************************************************
|
|
balls.c - description
|
|
-------------------
|
|
begin : Sun Sep 9 2001
|
|
copyright : (C) 2001 by Michael Speck
|
|
email : kulkanie@gmx.net
|
|
***************************************************************************/
|
|
|
|
/***************************************************************************
|
|
* *
|
|
* This program is free software; you can redistribute it and/or modify *
|
|
* it under the terms of the GNU General Public License as published by *
|
|
* the Free Software Foundation; either version 2 of the License, or *
|
|
* (at your option) any later version. *
|
|
* *
|
|
***************************************************************************/
|
|
|
|
#include "../client/lbreakout.h"
|
|
#include "levels.h"
|
|
#include "paddle.h"
|
|
#include "shots.h"
|
|
#include "balls.h"
|
|
#include "bricks.h"
|
|
#include "mathfuncs.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 REC_SQRT_2 (0.707106781)
|
|
|
|
float ball_vhmask = 0.363970234; /* twenty degrees */
|
|
float ball_vvmask = 5.67128182; /* ten degrees */
|
|
int ball_rad = 6;
|
|
int ball_dia = 12;
|
|
int ball_w = 12;
|
|
int ball_h = 12;
|
|
extern Game *cur_game;
|
|
|
|
int last_ball_brick_reflect_x = -1; /* HACK: used to play local sound */
|
|
int last_ball_paddle_reflect_x = -1; /* HACK: used to play local sound */
|
|
int last_ball_attach_x = -1; /* HACK: used to play local sound */
|
|
|
|
/*
|
|
====================================================================
|
|
Locals
|
|
====================================================================
|
|
*/
|
|
|
|
#ifdef WITH_BUG_REPORT
|
|
/*
|
|
====================================================================
|
|
Display info about a ball's target.
|
|
====================================================================
|
|
*/
|
|
static void ball_print_target_info( Ball *ball )
|
|
{
|
|
Coord center = { ball->cur.x + ball_rad, ball->cur.y + ball_rad }; /* ball center */
|
|
printf( "Target exists: %i\n", ball->target.exists );
|
|
printf("Ball: %4.2f,%4.2f (%i,%i) -> %4.2f,%4.2f (%4.2f)\n",
|
|
center.x, center.y, (int)center.x/BRICK_WIDTH, (int)center.y/BRICK_HEIGHT,
|
|
ball->vel.x, ball->vel.y, ball->vel.y/ball->vel.x );
|
|
printf("Brick %i,%i: Side %i (%4.2f,%4.2f)\n",
|
|
ball->target.mx, ball->target.my, ball->target.side,
|
|
ball->target.x, ball->target.y );
|
|
printf("Perp Vector: %4.2f,%4.2f\n", ball->target.perp_vector.x, ball->target.perp_vector.y);
|
|
printf("Takes %i ms\n", ball->target.time);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
====================================================================
|
|
Clear contents of target.
|
|
====================================================================
|
|
*/
|
|
void ball_clear_target( Target *t )
|
|
{
|
|
memset(t, 0, sizeof(Target));
|
|
t->side = SIDE_UNDEFINED;
|
|
}
|
|
/*
|
|
====================================================================
|
|
Attach ball to paddle.
|
|
====================================================================
|
|
*/
|
|
void ball_attach( Ball *ball, Paddle *paddle )
|
|
{
|
|
/* relative position */
|
|
ball->attached = 1;
|
|
ball->paddle = paddle;
|
|
ball->paddle->attached_ball_count++;
|
|
ball->idle_time = ball->moving_back = ball->return_allowed = 0;
|
|
ball->get_target = 0;
|
|
ball->cur.x -= paddle->x;
|
|
ball->cur.y -= paddle->y;
|
|
ball->x = (int)ball->cur.x;
|
|
ball->y = (int)ball->cur.y;
|
|
cur_game->mod.attached_ball_count++;
|
|
last_ball_attach_x = ball->x + paddle->x;
|
|
}
|
|
/*
|
|
====================================================================
|
|
Reflect ball at brick assume normed perp_vector.
|
|
====================================================================
|
|
*/
|
|
void ball_check_brick_reflection( Ball *b )
|
|
{
|
|
float old_vx;
|
|
Vector n;
|
|
int reflect;
|
|
int chaos_reflect;
|
|
int hit_type;
|
|
Vector oldBallVel = b->vel;
|
|
|
|
/* time left? */
|
|
if (b->target.cur_tm < b->target.time) return;
|
|
|
|
/* if the brick is destructible (thus it'll take damage)
|
|
* we must reset the idle time
|
|
*/
|
|
if ( cur_game->bricks[b->target.mx][b->target.my].dur != -1 ||
|
|
(cur_game->extra_active[EX_METAL] &&
|
|
cur_game->bricks[b->target.mx][b->target.my].type != MAP_WALL ) )
|
|
b->idle_time = 0;
|
|
/* or if it is within the last four rows and no chaotic penalty is active it will
|
|
hit the paddle soon so reset here too */
|
|
if ( b->target.my >= MAP_HEIGHT - 4 && b->vel.y > 0 && !cur_game->extra_active[EX_CHAOS] )
|
|
b->idle_time = 0;
|
|
|
|
/* will reflect? */
|
|
reflect = 1;
|
|
if ( cur_game->extra_active[EX_METAL] )
|
|
if ( cur_game->bricks[b->target.mx][b->target.my].type != MAP_WALL )
|
|
reflect = 0;
|
|
|
|
/* will reflect chaotic? */
|
|
chaos_reflect = 0;
|
|
if ( cur_game->extra_active[EX_CHAOS] ||
|
|
cur_game->bricks[b->target.mx][b->target.my].type == MAP_BRICK_CHAOS )
|
|
chaos_reflect = 1;
|
|
|
|
/* we have a target and so we have a reset position and even if the ball's
|
|
not reflected the position must be reset */
|
|
b->cur.x = b->target.x; b->x = (int)b->cur.x;
|
|
b->cur.y = b->target.y; b->y = (int)b->cur.y;
|
|
|
|
if ( reflect ) {
|
|
cur_game->mod.brick_reflected_ball_count++;
|
|
last_ball_brick_reflect_x = b->x; /* HACK: used to play local sound */
|
|
old_vx = b->vel.x;
|
|
if ( !chaos_reflect ) {
|
|
/* normal reflection */
|
|
n.x = (1-2*b->target.perp_vector.x*b->target.perp_vector.x)*b->vel.x +
|
|
( -2*b->target.perp_vector.x*b->target.perp_vector.y)*b->vel.y;
|
|
n.y = ( -2*b->target.perp_vector.x*b->target.perp_vector.y)*b->vel.x +
|
|
(1-2*b->target.perp_vector.y*b->target.perp_vector.y)*b->vel.y;
|
|
b->vel.x = n.x;
|
|
b->vel.y = n.y;
|
|
}
|
|
else {
|
|
b->vel.x = ((float)RANDOM( -10000, 10000 )) / 10000;
|
|
b->vel.y = (float)(RANDOM( -10000, 10000 )) / 10000;
|
|
}
|
|
if ( b->target.side >= CORNER_UPPER_LEFT && !chaos_reflect )
|
|
ball_mask_vel( b, old_vx, BALL_ADD_ENTROPY );
|
|
else
|
|
ball_mask_vel( b, old_vx, BALL_NO_ENTROPY );
|
|
/* only use 2 degree steps */
|
|
b->angle = vec2angle( &b->vel );
|
|
angle2vec( b->angle, &b->vel );
|
|
vector_set_length( &b->vel, cur_game->ball_v );
|
|
|
|
/* reset contact time: this ball is working for its paddle so it
|
|
* was cheating if it would be allowed to bring a new ball to
|
|
* game */
|
|
b->paddle->last_ball_contact = SDL_GetTicks();
|
|
}
|
|
|
|
/* remove brick -- if weak ball there is a 40% chance that no damage is done to the brick */
|
|
if ( !cur_game->extra_active[EX_WEAK_BALL] || rand() % 10 < 6 ) {
|
|
/* if explosive ball und brick is destructible by normal means set as explosive */
|
|
if ( cur_game->extra_active[EX_EXPL_BALL] )
|
|
if ( cur_game->bricks[b->target.mx][b->target.my].dur > 0 ) {
|
|
cur_game->bricks[b->target.mx][b->target.my].type = MAP_BRICK_EXP;
|
|
cur_game->bricks[b->target.mx][b->target.my].dur = 1;
|
|
}
|
|
/* hit brick */
|
|
hit_type = SHR_BY_NORMAL_BALL;
|
|
if ( cur_game->extra_active[EX_METAL] ) hit_type = SHR_BY_ENERGY_BALL;
|
|
brick_hit( b->target.mx, b->target.my,
|
|
cur_game->extra_active[EX_METAL],
|
|
hit_type, oldBallVel, b->paddle );
|
|
}
|
|
|
|
/* mark target as disabled so it won't get stuck at the
|
|
bottom of the screen but keep the target position so
|
|
that we know what needs an update. */
|
|
b->target.exists = 0;
|
|
/* check targets */
|
|
balls_check_targets( b->target.mx, b->target.my );
|
|
shots_check_targets( b->target.mx, b->target.my );
|
|
}
|
|
/*
|
|
====================================================================
|
|
Handle ball's contact with paddle: reflect at perpendicular (normed)
|
|
or attach.
|
|
====================================================================
|
|
*/
|
|
void ball_handle_paddle_contact( Ball *ball, Paddle *paddle, Vector perp_vector )
|
|
{
|
|
float old_vx = ball->vel.x;
|
|
Vector c; /* A(perp_vector) = c; */
|
|
|
|
ball->paddle = paddle;
|
|
|
|
/* valid perpendicular? */
|
|
if ( perp_vector.x == 0 && perp_vector.y == 0 )
|
|
return;
|
|
|
|
/* reflect */
|
|
/* a simple 2x2 matrix does this for us */
|
|
c.x = (1-2*perp_vector.x*perp_vector.x)*ball->vel.x +
|
|
( -2*perp_vector.x*perp_vector.y)*ball->vel.y;
|
|
c.y = ( -2*perp_vector.x*perp_vector.y)*ball->vel.x +
|
|
(1-2*perp_vector.y*perp_vector.y)*ball->vel.y;
|
|
/* if this new velocity vector does not bring back the ball to the playing field
|
|
thus the lower hemispherical parts of the paddle were hit we consider this
|
|
to be no reflection at all to prevent balls from getting stuck when 'bonus floor'
|
|
is active */
|
|
if ( (paddle->type == PADDLE_TOP && c.y < 0) ||
|
|
(paddle->type == PADDLE_BOTTOM && c.y > 0) )
|
|
return;
|
|
|
|
/* set new speed vector */
|
|
ball->vel.x = c.x; ball->vel.y = c.y;
|
|
#ifdef PADDLE_FRICTION
|
|
/* transfer friction to ball's velocity if not convex */
|
|
if ( cur_game->paddle_is_convex )
|
|
ball->vel.x += paddle->v_x * paddle->friction;
|
|
#endif
|
|
ball_mask_vel( ball, old_vx, BALL_NO_ENTROPY );
|
|
/* only use 2 degree steps */
|
|
ball->angle = vec2angle( &ball->vel );
|
|
angle2vec( ball->angle, &ball->vel );
|
|
vector_set_length( &ball->vel, cur_game->ball_v );
|
|
|
|
/* reset position if in wall */
|
|
if ( ball->x < BRICK_WIDTH ) {
|
|
ball->cur.x = BRICK_WIDTH;
|
|
ball->x = (int)ball->cur.x;
|
|
}
|
|
else
|
|
if ( ball->x + ball_dia >= 640 - BRICK_WIDTH ) {
|
|
ball->cur.x = 640 - BRICK_WIDTH - ball_dia;
|
|
ball->x = (int)ball->cur.x;
|
|
}
|
|
if ( paddle->extra_active[EX_WALL] ) {
|
|
if ( paddle->type == PADDLE_BOTTOM ) {
|
|
if ( ball->cur.y + ball_dia > 480 - BRICK_HEIGHT - 1 ) {
|
|
ball->cur.y = 480 - BRICK_HEIGHT - 1 - ball_dia;
|
|
ball->y = (int)ball->cur.y;
|
|
}
|
|
}
|
|
else {
|
|
if ( ball->cur.y < BRICK_HEIGHT ) {
|
|
ball->cur.y = BRICK_HEIGHT;
|
|
ball->y = (int)ball->cur.y;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* attach ball if sticky */
|
|
if ( paddle_slimy( paddle ) ) {
|
|
ball_attach( ball, paddle );
|
|
return;
|
|
}
|
|
|
|
/* count successful paddle contacts */
|
|
paddle->balls_reflected++;
|
|
cur_game->mod.paddle_reflected_ball_count++;
|
|
last_ball_paddle_reflect_x = ball->x; /* HACK: used to play local sound */
|
|
|
|
/* get new target */
|
|
ball->get_target = 1;
|
|
|
|
/* reset contact time */
|
|
paddle->last_ball_contact = SDL_GetTicks();
|
|
}
|
|
/*
|
|
====================================================================
|
|
Berechnung der Schnittpunkte der Geraden, die orthogonal zur
|
|
Geraden der Ballgeschwindigkeit durch den Ballmittelpunkt verläuft,
|
|
also der tangentialen Geschwindigkeitspunkte.
|
|
Der Geschwindigkeitsvektor wird auf 1 genormt. Ausgehend vom
|
|
Mittelpunkt wird der dazu orthogonale Vektor jeweils mit geändertem
|
|
Vorzeichen addiert und ergibt so die Tangentialpunkte.
|
|
|
|
If you're able and willing to translate this please send me your
|
|
result. ;-)
|
|
====================================================================
|
|
*/
|
|
void ball_get_tangents( Ball *ball, Coord *left, Coord *right )
|
|
{
|
|
Vector norm_vel = ball->vel;
|
|
float center_x = ball->cur.x + ball_rad, center_y = ball->cur.y + ball_rad;
|
|
|
|
vector_norm( &norm_vel );
|
|
left->x = center_x + norm_vel.y * ball_rad;
|
|
left->y = center_y - norm_vel.x * ball_rad;
|
|
right->x = center_x - norm_vel.y * ball_rad;
|
|
right->y = center_y + norm_vel.x * ball_rad;
|
|
}
|
|
/*
|
|
====================================================================
|
|
Check if the ball is on paddle's level and an reflect is
|
|
possible.
|
|
====================================================================
|
|
*/
|
|
int ball_paddle_contact_possible( Ball *ball, Paddle *paddle, Vector old )
|
|
{
|
|
if ( ball->attached ) return 0; /* was attached to a previous paddle */
|
|
if ( !paddle_solid( paddle ) ) return 0;
|
|
if ( paddle->type == PADDLE_TOP ) {
|
|
if ( ball->vel.y > 0 ) return 0;
|
|
if ( ball->y > paddle->y + paddle->h - 1 ) return 0; /* below paddle */
|
|
if ( ball->y + ball_dia <= paddle->y + ( paddle->h >> 1 ) )
|
|
if ( old.y + ball_dia <= paddle->y + ( paddle->h >> 1 ) )
|
|
return 0; /* already behind paddle */
|
|
}
|
|
else {
|
|
if ( ball->vel.y < 0 ) return 0; /* ball moves up so no contact possible because
|
|
if it was below the paddle it has been
|
|
reflected by the bonus floor and MUST ignore
|
|
the paddle */
|
|
if ( ball->y + ball_dia < paddle->y ) return 0; /* above paddle */
|
|
if ( ball->y >= paddle->y + ( paddle->h >> 1 ) )
|
|
if ( old.y >= paddle->y + ( paddle->h >> 1 ) )
|
|
return 0; /* already behind paddle */
|
|
}
|
|
return 1;
|
|
}
|
|
/*
|
|
====================================================================
|
|
Check reflection of ball at paddle. 'old' is the position of
|
|
the ball before update. Used to compute direction.
|
|
====================================================================
|
|
*/
|
|
enum { CONTACT_LEFT = 1, CONTACT_MIDDLE, CONTACT_RIGHT };
|
|
void ball_check_paddle_reflection( Ball *ball, Paddle *paddle )
|
|
{
|
|
Line ball_line; /* balls velocity line */
|
|
Line paddle_line; /* paddle line */
|
|
Coord pt, pt2; /* auxiliary point (result of intersection) */
|
|
int contact = 0; /* paddle contact */
|
|
Vector perp_vector; /* perpendicular of ball's direction change */
|
|
Coord center = { ball->cur.x + ball_rad, ball->cur.y + ball_rad }; /* center of ball */
|
|
Vector norm_vel; /* normed ball velocity vector */
|
|
/* paddle is constructed as two hemispheres at the side and a cylinder in the middle */
|
|
Coord right_hemi_center = { paddle->x + paddle->w - ( paddle->h >> 1 ), paddle->y + ( paddle->h >> 1 ) };
|
|
Coord left_hemi_center = { paddle->x + ( paddle->h >> 1 ), paddle->y + ( paddle->h >> 1 ) };
|
|
/* radius of hemispheres */
|
|
int hemi_r = ( ball_rad ) + ( paddle->h >> 1 );
|
|
/* if paddle's treated as convex these are the perpendiculars through the hemisphere centers */
|
|
Vector left_convex_perp = { 1, (paddle->type == PADDLE_TOP)?-1:1 };
|
|
/* paddle center */
|
|
Coord paddle_center = { paddle->x + ( paddle->w >> 1 ), paddle->y + ( paddle->h >> 1 ) };
|
|
/* center of the convex behaviour -- computed when reflecting by using
|
|
left/right_convex_perp and paddle_center */
|
|
Coord convex_center;
|
|
/* perpendicular line used for convex behaviour */
|
|
Line convex_line;
|
|
|
|
/* the simple check for the y-position of ball and paddle is done
|
|
* in ball_paddle_contact_possible() so if we got here it's possible
|
|
* by velocity and position of ball that it hits the paddle
|
|
*/
|
|
|
|
/* basic idea:
|
|
The paddle is constructed of a middle rectangle and two hemispheres.
|
|
We check the center line of the ball with the imaginary paddle that's size
|
|
is paddle_size + ball_rad. The intersection with this paddle is the reset point
|
|
for the ball at the same time (if sticky).
|
|
The perpendicular is computed as convex thing. (overwrites the perpendicular
|
|
set by the reflection)
|
|
*/
|
|
/* ball line */
|
|
line_set( &ball_line, center.x, center.y, ball->vel.y / ball->vel.x );
|
|
/* imaginary paddle upper/lower line
|
|
* -- we'll decide at intersection which hemipshere to check
|
|
*/
|
|
if ( paddle->type == PADDLE_TOP )
|
|
line_set_hori( &paddle_line, paddle->y + paddle->h - 1 + ball_rad );
|
|
else
|
|
line_set_hori( &paddle_line, paddle->y - ball_rad );
|
|
line_intersect( &paddle_line, &ball_line, &pt );
|
|
if ( pt.x < left_hemi_center.x ) {
|
|
/* intersect left */
|
|
norm_vel = ball->vel; vector_norm( &norm_vel );
|
|
if ( circle_intersect( left_hemi_center, hemi_r,
|
|
center, norm_vel,
|
|
&pt, &pt2 ) ) {
|
|
if ( VEC_DIST( center, left_hemi_center ) <= hemi_r ) {
|
|
if ( paddle->type == PADDLE_TOP ) {
|
|
/* use lower point as intersection */
|
|
if ( pt.y < pt2.y ) pt = pt2;
|
|
}
|
|
else
|
|
/* use the higher point as this is the upper intersection */
|
|
if ( pt.y > pt2.y ) pt = pt2;
|
|
/* use vector between hemi_sphere center and ball center
|
|
* as reflection perp */
|
|
perp_vector = vector_get( center.x - left_hemi_center.x,
|
|
center.y - left_hemi_center.y );
|
|
vector_norm( &perp_vector );
|
|
/* had contact */
|
|
contact = CONTACT_LEFT;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
if ( pt.x > right_hemi_center.x ) {
|
|
/* intersect right */
|
|
norm_vel = ball->vel; vector_norm( &norm_vel );
|
|
if ( circle_intersect( right_hemi_center, hemi_r,
|
|
center, norm_vel,
|
|
&pt, &pt2 ) ) {
|
|
if ( VEC_DIST( center, right_hemi_center ) <= hemi_r ) {
|
|
if ( paddle->type == PADDLE_TOP ) {
|
|
/* use lower point as intersection */
|
|
if ( pt.y < pt2.y ) pt = pt2;
|
|
}
|
|
else
|
|
/* use the higher point as this is the upper intersection */
|
|
if ( pt.y > pt2.y ) pt = pt2;
|
|
/* use vector between hemi_sphere center and ball center
|
|
* as reflection perp */
|
|
perp_vector = vector_get( center.x - right_hemi_center.x,
|
|
center.y - right_hemi_center.y );
|
|
vector_norm( &perp_vector );
|
|
/* had contact */
|
|
contact = CONTACT_RIGHT;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
contact = CONTACT_MIDDLE; /* contact with middle part */
|
|
perp_vector = vector_get( 0, 1 ); /* reflect at horizontal line */
|
|
}
|
|
|
|
/* if we got here 'pt' contains the intersection with the imaginary paddle so reset ball
|
|
to this position */
|
|
if ( contact ) {
|
|
/* reset idle time */
|
|
ball->idle_time = 0;
|
|
/* reset position if ball will be attached */
|
|
if ( paddle_slimy( paddle ) ) {
|
|
ball->cur.x = pt.x - ( ball_rad );
|
|
ball->cur.y = pt.y - ( ball_rad );
|
|
ball->x = (int)ball->cur.x; ball->y = (int)ball->cur.y;
|
|
}
|
|
/* convex perpendicular */
|
|
if ( cur_game->paddle_is_convex ) {
|
|
line_set_vert( &paddle_line, paddle_center.x );
|
|
line_set( &convex_line, left_hemi_center.x, left_hemi_center.y,
|
|
vector_monotony( left_convex_perp ) );
|
|
line_intersect( &paddle_line, &convex_line, &convex_center );
|
|
/* get actual perp_vector */
|
|
perp_vector.x = convex_center.x - pt.x;
|
|
perp_vector.y = convex_center.y - pt.y;
|
|
//vector_norm( &perp_vector );
|
|
/* this vector is not normed but for whatever reason...
|
|
the reflection behaviour is much nicer this way */
|
|
}
|
|
/* handle contact: attach, reflect, sound... */
|
|
ball_handle_paddle_contact( ball, paddle, perp_vector );
|
|
}
|
|
}
|
|
/*
|
|
====================================================================
|
|
Intersect ball line with imaginary brick line.
|
|
Use target's map position and
|
|
set reset position (centered) and perp_vector of target.
|
|
ball_rad is substracted later in ball_get_target()
|
|
====================================================================
|
|
*/
|
|
enum { LINE_HORI = 0, LINE_VERT };
|
|
int check_line( Line *ball_line, int type, int anchor, int range_start, int range_end, Coord *pt ) {
|
|
Line line;
|
|
if ( type == LINE_HORI )
|
|
line_set_hori( &line, anchor );
|
|
else
|
|
line_set_vert( &line, anchor );
|
|
line_intersect( &line, ball_line, pt );
|
|
if ( type == LINE_HORI ) {
|
|
if ( pt->x >= range_start && pt->x <= range_end ) return 1;
|
|
return 0;
|
|
}
|
|
else {
|
|
if ( pt->y >= range_start && pt->y <= range_end ) return 1;
|
|
return 0;
|
|
}
|
|
}
|
|
void ball_intersect_brick( Ball *ball, Target *target )
|
|
{
|
|
Line ball_line;
|
|
Coord pt; /* auxiliary point */
|
|
int x = target->mx * BRICK_WIDTH;
|
|
int y = target->my * BRICK_HEIGHT; /* left upper corner of brick */
|
|
int intersect = 0; /* intersected? */
|
|
|
|
/* ball_line */
|
|
line_set( &ball_line,
|
|
ball->cur.x + ball_rad,
|
|
ball->cur.y + ball_rad,
|
|
ball->vel.y / ball->vel.x );
|
|
|
|
if ( ball->vel.x > 0 ) {
|
|
/* left */
|
|
if ( check_line( &ball_line,
|
|
LINE_VERT,
|
|
x - ball_rad,
|
|
y - ball_rad, y + BRICK_HEIGHT + ball_rad,
|
|
&pt ) ) {
|
|
intersect = 1;
|
|
target->perp_vector = vector_get( 1, 0 );
|
|
}
|
|
}
|
|
else {
|
|
/* right */
|
|
if ( check_line( &ball_line,
|
|
LINE_VERT,
|
|
x + BRICK_WIDTH + ball_rad,
|
|
y - ball_rad, y + BRICK_HEIGHT + ball_rad,
|
|
&pt ) ) {
|
|
intersect = 1;
|
|
target->perp_vector = vector_get( 1, 0 );
|
|
}
|
|
}
|
|
if ( !intersect ) {
|
|
if ( ball->vel.y > 0 ) {
|
|
/* top */
|
|
if ( check_line( &ball_line,
|
|
LINE_HORI,
|
|
y - ball_rad,
|
|
x - ball_rad, x + BRICK_WIDTH + ball_rad,
|
|
&pt ) ) {
|
|
intersect = 1;
|
|
target->perp_vector = vector_get( 0, 1 );
|
|
}
|
|
}
|
|
else {
|
|
/* bottom */
|
|
if ( check_line( &ball_line,
|
|
LINE_HORI,
|
|
y + BRICK_HEIGHT + ball_rad,
|
|
x - ball_rad, x + BRICK_WIDTH + ball_rad,
|
|
&pt ) ) {
|
|
intersect = 1;
|
|
target->perp_vector = vector_get( 0, 1 );
|
|
}
|
|
}
|
|
}
|
|
/* intersected */
|
|
if ( intersect ) {
|
|
target->x = pt.x;
|
|
target->y = pt.y;
|
|
/* perp_vector is set */
|
|
}
|
|
}
|
|
/*
|
|
====================================================================
|
|
Reflect ball at target at target->side and set perp_vector
|
|
and reset position x,y of the target. Does not update the ball.
|
|
====================================================================
|
|
*/
|
|
void ball_reflect_at_side( Ball *ball, Target *target )
|
|
{
|
|
float old_vx;
|
|
int compute_vel, start;
|
|
Line ball_line;
|
|
Line brick_line;
|
|
Coord pt;
|
|
/* ball line */
|
|
line_set( &ball_line,
|
|
ball->cur.x + ball_rad,
|
|
ball->cur.y + ball_rad,
|
|
ball->vel.y / ball->vel.x );
|
|
/* brick line and perp vector */
|
|
switch ( target->side ) {
|
|
case SIDE_LEFT:
|
|
line_set_vert( &brick_line, target->mx * BRICK_WIDTH - ball_rad );
|
|
target->perp_vector = vector_get( 1, 0 );
|
|
break;
|
|
case SIDE_RIGHT:
|
|
line_set_vert( &brick_line,
|
|
target->mx * BRICK_WIDTH + BRICK_WIDTH + ball_rad );
|
|
target->perp_vector = vector_get( 1, 0 );
|
|
break;
|
|
case SIDE_TOP:
|
|
line_set_hori( &brick_line, target->my * BRICK_HEIGHT - ball_rad );
|
|
target->perp_vector = vector_get( 0, 1 );
|
|
break;
|
|
case SIDE_BOTTOM:
|
|
line_set_hori( &brick_line,
|
|
target->my * BRICK_HEIGHT + BRICK_HEIGHT + ball_rad );
|
|
target->perp_vector = vector_get( 0, 1 );
|
|
break;
|
|
default:
|
|
fprintf( stderr, "Unknown side: %i\n", target->side );
|
|
break;
|
|
}
|
|
/* intersect, it's already assured that we hit this brick so just get the reset position */
|
|
line_intersect( &brick_line, &ball_line, &pt );
|
|
target->x = pt.x;
|
|
target->y = pt.y;
|
|
/* check if ball slid into next brick because of high angle when
|
|
reflect at side (not corner) */
|
|
compute_vel = 0;
|
|
switch ( target->side ) {
|
|
case SIDE_BOTTOM:
|
|
case SIDE_TOP:
|
|
if ( ball->vel.x > 0 )
|
|
pt.x = target->x + ball_rad;
|
|
else
|
|
pt.x = target->x - ball_rad;
|
|
start = (int)pt.x / BRICK_WIDTH;
|
|
if ( cur_game->bricks[start][(int)target->y/BRICK_HEIGHT].type != MAP_EMPTY ) {
|
|
if ( ball->vel.x > 0 )
|
|
target->x = start * BRICK_WIDTH - ball_rad - 1;
|
|
else
|
|
target->x = (start+1) * BRICK_WIDTH + ball_rad;
|
|
compute_vel = 1;
|
|
}
|
|
break;
|
|
case SIDE_LEFT:
|
|
case SIDE_RIGHT:
|
|
if ( ball->vel.y > 0 )
|
|
pt.y = target->y + ball_rad;
|
|
else
|
|
pt.y = target->y - ball_rad;
|
|
start = (int)pt.y / BRICK_HEIGHT;
|
|
if ( cur_game->bricks[(int)target->x/BRICK_WIDTH][start].type != MAP_EMPTY ) {
|
|
if ( ball->vel.y > 0 )
|
|
target->y = start * BRICK_HEIGHT - ball_rad - 1;
|
|
else
|
|
target->y = (start+1) * BRICK_HEIGHT + ball_rad;
|
|
compute_vel = 1;
|
|
}
|
|
break;
|
|
}
|
|
if ( compute_vel ) {
|
|
old_vx = ball->vel.x;
|
|
ball->vel.x = target->x - (ball->cur.x + ball_rad);
|
|
ball->vel.y = target->y - (ball->cur.y + ball_rad);
|
|
ball_mask_vel( ball, old_vx, BALL_NO_ENTROPY );
|
|
/* should we mask to the 2deg steps here? yes! */
|
|
ball->angle = vec2angle( &ball->vel );
|
|
angle2vec( ball->angle, &ball->vel );
|
|
vector_set_length( &ball->vel, cur_game->ball_v );
|
|
}
|
|
}
|
|
/*
|
|
====================================================================
|
|
Reflect ball at target but ignore target::side and reflect at
|
|
corner instead. Does not update the ball.
|
|
====================================================================
|
|
*/
|
|
void ball_reflect_at_corner( Ball *ball, Target *target, int corner )
|
|
{
|
|
Coord corner_center; /* center of corner circle */
|
|
Coord ball_center = { ball->cur.x + ball_rad, ball->cur.y + ball_rad };
|
|
Vector norm_vel = ball->vel;
|
|
Coord pt, pt2; /* intersection points */
|
|
|
|
/* norm velocity */
|
|
vector_norm( &norm_vel );
|
|
|
|
/* set up center of corner */
|
|
switch ( corner ) {
|
|
case CORNER_UPPER_LEFT:
|
|
corner_center = vector_get(
|
|
target->mx * BRICK_WIDTH,
|
|
target->my * BRICK_HEIGHT );
|
|
break;
|
|
case CORNER_UPPER_RIGHT:
|
|
corner_center = vector_get(
|
|
target->mx * BRICK_WIDTH + BRICK_WIDTH - 1,
|
|
target->my * BRICK_HEIGHT );
|
|
break;
|
|
case CORNER_LOWER_LEFT:
|
|
corner_center = vector_get(
|
|
target->mx * BRICK_WIDTH,
|
|
target->my * BRICK_HEIGHT + BRICK_HEIGHT - 1);
|
|
break;
|
|
case CORNER_LOWER_RIGHT:
|
|
corner_center = vector_get(
|
|
target->mx * BRICK_WIDTH + BRICK_WIDTH - 1,
|
|
target->my * BRICK_HEIGHT + BRICK_HEIGHT - 1);
|
|
break;
|
|
}
|
|
/* intersect */
|
|
circle_intersect( corner_center, ball_rad + 2, ball_center, norm_vel, &pt, &pt2 );
|
|
/* use nearest point for reset and perp vector */
|
|
if ( VEC_DIST( ball_center, pt ) < VEC_DIST( ball_center, pt2 ) ) {
|
|
target->x = pt.x;
|
|
target->y = pt.y;
|
|
}
|
|
else {
|
|
target->x = pt2.x;
|
|
target->y = pt2.y;
|
|
}
|
|
/* compute the spherical perp vector
|
|
(corner center - intersection point) */
|
|
target->perp_vector =
|
|
vector_get( corner_center.x - target->x,
|
|
corner_center.y - target->y );
|
|
vector_norm( &target->perp_vector );
|
|
/* this vector must operate within a 90° region depending on the corner.
|
|
if it doesn't we have a side reflection unnoticed by the previous
|
|
checks as we enclosed a corner. this is the only position to check
|
|
this as the reset position is different when a corner is enclosed.
|
|
doing this anywhere else would lead to errors. */
|
|
switch ( corner ) {
|
|
case CORNER_UPPER_LEFT:
|
|
if ( target->perp_vector.x * target->perp_vector.y >= 0 ) {
|
|
/* we needed the spherical perp to determine if it is
|
|
really a corner however we might have set the
|
|
config option linear_corner */
|
|
/*if ( config.linear_corner )
|
|
target->perp_vector = vector_get( REC_SQRT_2, REC_SQRT_2 );*/
|
|
break;
|
|
}
|
|
if ( target->y < corner_center.y || target->x >= corner_center.x )
|
|
target->perp_vector = vector_get( 0, 1 ); /* top */
|
|
else
|
|
target->perp_vector = vector_get( 1, 0 ); /* left */
|
|
break;
|
|
case CORNER_LOWER_RIGHT:
|
|
if ( target->perp_vector.x * target->perp_vector.y >= 0 ) {
|
|
/*if ( config.linear_corner )
|
|
target->perp_vector = vector_get( REC_SQRT_2, REC_SQRT_2 );*/
|
|
break;
|
|
}
|
|
if ( target->y > corner_center.y || target->x <= corner_center.x )
|
|
target->perp_vector = vector_get( 0, 1 ); /* bottom */
|
|
else
|
|
target->perp_vector = vector_get( 1, 0 ); /* right */
|
|
break;
|
|
case CORNER_UPPER_RIGHT:
|
|
if ( target->perp_vector.x * target->perp_vector.y <= 0 ) {
|
|
/*if ( config.linear_corner )
|
|
target->perp_vector = vector_get( REC_SQRT_2, -REC_SQRT_2 );*/
|
|
break;
|
|
}
|
|
if ( target->y < corner_center.y || target->x <= corner_center.x )
|
|
target->perp_vector = vector_get( 0, 1 ); /* top */
|
|
else
|
|
target->perp_vector = vector_get( 1, 0 ); /* right */
|
|
break;
|
|
case CORNER_LOWER_LEFT:
|
|
if ( target->perp_vector.x * target->perp_vector.y <= 0 ) {
|
|
/*if ( config.linear_corner )
|
|
target->perp_vector = vector_get( REC_SQRT_2, -REC_SQRT_2 );*/
|
|
break;
|
|
}
|
|
if ( target->y > corner_center.y || target->x >= corner_center.x )
|
|
target->perp_vector = vector_get( 0, 1 ); /* bottom */
|
|
else
|
|
target->perp_vector = vector_get( 1, 0 ); /* left */
|
|
break;
|
|
}
|
|
}
|
|
/*
|
|
====================================================================
|
|
Reflect ball at target ball:t and decide by ball::t::side wether
|
|
to use reflect_at_side or reflect_at_corner.
|
|
====================================================================
|
|
*/
|
|
void ball_reflect( Ball *ball )
|
|
{
|
|
if ( !ball->target.exists ) return;
|
|
if ( ball->target.side <= SIDE_LEFT )
|
|
ball_reflect_at_side( ball, &ball->target );
|
|
else
|
|
ball_reflect_at_corner( ball, &ball->target, ball->target.side );
|
|
}
|
|
|
|
/*
|
|
====================================================================
|
|
Check if ball's tangents enclose a corner and update target's side.
|
|
====================================================================
|
|
*/
|
|
void ball_corner_check( Ball *ball,
|
|
Target *target_left_tang, Target *target_right_tang, Target *target )
|
|
{
|
|
/* balls moving ... */
|
|
if ( ball->vel.y > 0 ) {
|
|
if ( ball->vel.x < 0 ) {
|
|
/* ... down left */
|
|
if ( target == target_right_tang )
|
|
if ( target->side == SIDE_TOP )
|
|
target->side = CORNER_UPPER_RIGHT;
|
|
if ( target == target_left_tang )
|
|
if ( target->side == SIDE_RIGHT )
|
|
target->side = CORNER_UPPER_RIGHT;
|
|
}
|
|
else {
|
|
/* ... down right */
|
|
if ( target == target_left_tang )
|
|
if ( target->side == SIDE_TOP )
|
|
target->side = CORNER_UPPER_LEFT;
|
|
if ( target == target_right_tang )
|
|
if ( target->side == SIDE_LEFT ) target->side = CORNER_UPPER_LEFT;
|
|
}
|
|
}
|
|
else {
|
|
if ( ball->vel.x < 0 ) {
|
|
/* ... up left */
|
|
if ( target == target_right_tang )
|
|
if ( target->side == SIDE_RIGHT )
|
|
target->side = CORNER_LOWER_RIGHT;
|
|
if ( target == target_left_tang )
|
|
if ( target->side == SIDE_BOTTOM )
|
|
target->side = CORNER_LOWER_RIGHT;
|
|
}
|
|
else {
|
|
/* ... up right */
|
|
if ( target == target_left_tang )
|
|
if ( target->side == SIDE_LEFT )
|
|
target->side = CORNER_LOWER_LEFT;
|
|
if ( target == target_right_tang )
|
|
if ( target->side == SIDE_BOTTOM )
|
|
target->side = CORNER_LOWER_LEFT;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
====================================================================
|
|
Public
|
|
====================================================================
|
|
*/
|
|
|
|
/*
|
|
====================================================================
|
|
Create ball at position
|
|
====================================================================
|
|
*/
|
|
Ball* ball_create( int x, int y )
|
|
{
|
|
Ball *ball = salloc( 1, sizeof( Ball ) );
|
|
ball->cur.x = x;
|
|
ball->x = x;
|
|
ball->cur.y = y;
|
|
ball->y = y;
|
|
ball->attached = 0;
|
|
ball->idle_time = 0;
|
|
ball->moving_back = 0;
|
|
ball->return_allowed = 0;
|
|
ball_clear_target(&ball->target);
|
|
return ball;
|
|
}
|
|
/*
|
|
====================================================================
|
|
Set a special ball property like metal ball. Unused now
|
|
as it is directly checked wether extra_active is 1.
|
|
====================================================================
|
|
*/
|
|
void balls_set_type( int type )
|
|
{
|
|
}
|
|
/*
|
|
====================================================================
|
|
Set chaotic behaviour (random relfection). Unused as extra_active
|
|
is checked now.
|
|
====================================================================
|
|
*/
|
|
void balls_set_chaos( int chaos )
|
|
{
|
|
}
|
|
/*
|
|
====================================================================
|
|
Update balls and detach attached balls if fire was pressed.
|
|
====================================================================
|
|
*/
|
|
void balls_update( int ms )
|
|
{
|
|
int top = 0, bottom = 0; /* num of lost balls */
|
|
int i, x, y;
|
|
ListEntry *entry = cur_game->balls->head->next;
|
|
Ball *ball;
|
|
Vector old; /* old position of ball before update */
|
|
int fired_attached, fire_dir;
|
|
|
|
/* detach or fire balls from paddles */
|
|
for ( i = 0; i < cur_game->paddle_count; i++ ) {
|
|
/* check wether paddles created new balls or released attached ones */
|
|
if ( (cur_game->paddles[i]->ball_fire_delay-=ms) <= 0 )
|
|
if ( cur_game->paddles[i]->fire_left || cur_game->paddles[i]->fire_right ) {
|
|
/* reset delay till next ball may be released */
|
|
cur_game->paddles[i]->ball_fire_delay = BALL_FIRE_RATE;
|
|
/* get direction */
|
|
fire_dir = cur_game->paddles[i]->fire_left?-1:1;
|
|
/* try to fire attached balls */
|
|
fired_attached = balls_detach_from_paddle( cur_game->paddles[i], fire_dir );
|
|
/* if no attached balls were fired but paddle has ammo left
|
|
* it creates a new ball in PINGPONG levels */
|
|
if ( !fired_attached )
|
|
if ( cur_game->level_type == LT_PINGPONG )
|
|
if ( cur_game->paddles[i]->ball_ammo > 0 ) {
|
|
x = cur_game->paddles[i]->x + (cur_game->paddles[i]->w - ball_w) / 2;
|
|
if ( cur_game->paddles[i]->type == PADDLE_BOTTOM )
|
|
y = cur_game->paddles[i]->y - ball_dia;
|
|
else
|
|
y = cur_game->paddles[i]->y + cur_game->paddles[i]->h;
|
|
ball = ball_create( x, y );
|
|
ball->paddle = cur_game->paddles[i];
|
|
if ( cur_game->balls_use_random_angle )
|
|
ball_set_random_angle( ball, cur_game->ball_v );
|
|
else {
|
|
ball->vel.x = 1.0 * fire_dir;
|
|
if ( ball->paddle->type == PADDLE_TOP )
|
|
ball->vel.y = 1.2;
|
|
else
|
|
ball->vel.y = -1.2;
|
|
/* only use 2 degree steps */
|
|
ball->angle = vec2angle( &ball->vel );
|
|
angle2vec( ball->angle, &ball->vel );
|
|
vector_set_length( &ball->vel, cur_game->ball_v );
|
|
}
|
|
ball->get_target = 1;
|
|
list_add( cur_game->balls, ball );
|
|
cur_game->paddles[i]->ball_ammo--;
|
|
}
|
|
}
|
|
|
|
/* check wether no balls are attached and the respawn time is exceeded.
|
|
* then in NMP a ball is created and attached */
|
|
if ( cur_game->game_type == GT_NETWORK && cur_game->level_type != LT_PINGPONG )
|
|
if ( cur_game->paddles[i]->attached_ball_count == 0 )
|
|
if ( SDL_GetTicks() >= cur_game->paddles[i]->last_ball_contact + BALL_RESPAWN_TIME ) {
|
|
x = cur_game->paddles[i]->x + (cur_game->paddles[i]->w - ball_w) / 2;
|
|
if ( cur_game->paddles[i]->type == PADDLE_BOTTOM )
|
|
y = cur_game->paddles[i]->y - ball_dia;
|
|
else
|
|
y = cur_game->paddles[i]->y + cur_game->paddles[i]->h;
|
|
ball = ball_create( x, y );
|
|
list_add( cur_game->balls, ball );
|
|
ball_attach( ball, cur_game->paddles[i] );
|
|
ball_set_random_angle( ball, cur_game->ball_v );
|
|
}
|
|
}
|
|
|
|
/* speed up/down balls on request */
|
|
if ( cur_game->game_type == GT_LOCAL )
|
|
{
|
|
if ( cur_game->paddles[0]->maxballspeed_request && !cur_game->paddles[0]->maxballspeed_request_old )
|
|
{
|
|
cur_game->ball_v = cur_game->accelerated_ball_speed;
|
|
balls_set_velocity( cur_game->balls, cur_game->ball_v );
|
|
}
|
|
if ( !cur_game->paddles[0]->maxballspeed_request && cur_game->paddles[0]->maxballspeed_request_old )
|
|
{
|
|
if ( cur_game->extra_active[EX_SLOW] )
|
|
cur_game->ball_v = cur_game->ball_v_min;
|
|
else
|
|
if ( cur_game->extra_active[EX_FAST] )
|
|
cur_game->ball_v = cur_game->ball_v_max;
|
|
else
|
|
cur_game->ball_v = cur_game->diff->v_start +
|
|
cur_game->diff->v_add * cur_game->speedup_level;
|
|
balls_set_velocity( cur_game->balls, cur_game->ball_v );
|
|
}
|
|
}
|
|
|
|
/* increase speed */
|
|
if ( !cur_game->extra_active[EX_SLOW] )
|
|
if ( !cur_game->extra_active[EX_FAST] )
|
|
if ( cur_game->game_type != GT_LOCAL || !cur_game->paddles[0]->maxballspeed_request )
|
|
balls_inc_vel( ms );
|
|
|
|
/* return idle balls if not autoreturn */
|
|
for ( i = 0; i < cur_game->paddle_count; i++ )
|
|
if ( cur_game->paddles[i]->ball_return_key_pressed )
|
|
balls_return( cur_game->paddles[i] );
|
|
|
|
/* move balls */
|
|
while ( entry != cur_game->balls->tail ) {
|
|
ball = entry->item;
|
|
old.x = ball->cur.x;
|
|
old.y = ball->cur.y;
|
|
|
|
/* update ball when moving back */
|
|
if ( ball->moving_back ) {
|
|
/* update velocity */
|
|
ball->vel.x = ( ball->paddle->x + ( ball->paddle->w >> 1 ) ) -
|
|
( ball->cur.x + ball_rad );
|
|
ball->vel.y = ( ball->paddle->y - ball_rad + 2 ) - ( ball->cur.y + ball_rad );
|
|
vector_set_length( &ball->vel, cur_game->ball_v_max );
|
|
/* new position */
|
|
ball->cur.x += ball->vel.x * ms;
|
|
ball->cur.y += ball->vel.y * ms;
|
|
ball->x = (int)ball->cur.x;
|
|
ball->y = (int)ball->cur.y;
|
|
/* check if paddle is reached and attach the ball */
|
|
if ( ball->x + ball_rad >= ball->paddle->x )
|
|
if ( ball->x + ball_rad < ball->paddle->x + ball->paddle->w )
|
|
if ( ball->y + ball_dia >= ball->paddle->y )
|
|
if ( ball->y + ball_dia < ball->paddle->y + ball->paddle->h ) {
|
|
ball->cur.x = ball->paddle->x + ( ball->paddle->w >> 1 ) - ball_rad;
|
|
if ( ball->paddle->type == PADDLE_TOP )
|
|
ball->cur.y = ball->paddle->y + ball->paddle->h;
|
|
else
|
|
ball->cur.y = ball->paddle->y - ball_dia;
|
|
ball->x = (int)ball->cur.x;
|
|
ball->y = (int)ball->cur.y;
|
|
ball_attach( ball, ball->paddle );
|
|
ball_set_random_angle( ball, cur_game->ball_v );
|
|
}
|
|
}
|
|
|
|
/* update ball if not attached and not moving back */
|
|
if ( !ball->attached && !ball->moving_back ) {
|
|
/* increase idle time -- paddle and brick_check will reset this value */
|
|
if ( !ball->return_allowed )
|
|
ball->idle_time += ms;
|
|
|
|
/* check if reflected by any paddle */
|
|
for ( i = 0; i < cur_game->paddle_count; i++ )
|
|
if ( ball_paddle_contact_possible( ball, cur_game->paddles[i], old ) )
|
|
ball_check_paddle_reflection( ball, cur_game->paddles[i] );
|
|
|
|
/* update target? */
|
|
if ( ball->get_target ) {
|
|
ball_get_target( ball );
|
|
ball->get_target = 0;
|
|
}
|
|
|
|
/* new position if NOT attached*/
|
|
if ( !ball->attached ) {
|
|
ball->cur.x += ball->vel.x * ms;
|
|
ball->cur.y += ball->vel.y * ms;
|
|
ball->x = (int)ball->cur.x;
|
|
ball->y = (int)ball->cur.y;
|
|
}
|
|
|
|
/* reflection by brick */
|
|
/* quick hack to handle the case when the ball was just attached but
|
|
* touches the wall and the slimy paddle in the same instant. -
|
|
* Patrick Hohmeyer 19.12.01 */
|
|
if ( ball->target.exists && !ball->attached ) {
|
|
ball->target.cur_tm += ms;
|
|
ball_check_brick_reflection( ball );
|
|
}
|
|
|
|
/* check if idle time is above limit and the ball has a target because if
|
|
* there is no target the ball moves out of the window and should not go
|
|
* back to the paddle as it's moving into this direction by itself
|
|
*/
|
|
if ( ball->idle_time >= BALLS_IDLE_LIMIT )
|
|
if ( !ball->return_allowed )
|
|
if ( ball->target.exists ) {
|
|
/* okay send this ball back home or allow to do so by click */
|
|
if ( !cur_game->balls_return_by_click ) {
|
|
ball->idle_time = 0;
|
|
ball->moving_back = 1;
|
|
ball->target.exists = 0; /* no target */
|
|
}
|
|
else {
|
|
ball->idle_time = 0;
|
|
ball->return_allowed = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* get next entry in list and remove those out of the window */
|
|
entry = entry->next;
|
|
if (!ball->attached )
|
|
if ( ball->x >= 640 ||
|
|
ball->x + ball_dia < 0 ||
|
|
ball->y >= 480 ||
|
|
ball->y + ball_dia < 0 ) {
|
|
if ( ball->y + ball_rad <= 480 >> 1 )
|
|
top++;
|
|
else
|
|
bottom++;
|
|
list_delete_entry( cur_game->balls, entry->prev );
|
|
}
|
|
}
|
|
|
|
/* update stats */
|
|
cur_game->paddles[PADDLE_BOTTOM]->balls_lost += bottom;
|
|
if ( cur_game->paddles[PADDLE_TOP] )
|
|
cur_game->paddles[PADDLE_TOP]->balls_lost += top;
|
|
|
|
/* modify scores in network game. for local games lost balls do not
|
|
* change score. */
|
|
if ( cur_game->game_type == GT_NETWORK ) {
|
|
/* modify scores when a ball got lost */
|
|
if ( cur_game->level_type == LT_PINGPONG ) {
|
|
/* in pingpong a lost ball gives opponent one point and
|
|
* the paddle that lost the ball can bring it back to
|
|
* game */
|
|
cur_game->paddles[PADDLE_BOTTOM]->score += top;
|
|
cur_game->paddles[PADDLE_TOP]->score += bottom;
|
|
cur_game->paddles[PADDLE_BOTTOM]->ball_ammo += bottom;
|
|
cur_game->paddles[PADDLE_TOP]->ball_ammo += top;
|
|
}
|
|
else {
|
|
/* in a normal level 10% of score gets lost and
|
|
* last contact time is reset so that it will take
|
|
* ten seconds penalty before a new ball is
|
|
* generated. */
|
|
if ( top ) {
|
|
while ( top-- > 0 )
|
|
cur_game->paddles[PADDLE_TOP]->score =
|
|
90 * cur_game->paddles[PADDLE_TOP]->score / 100;
|
|
cur_game->paddles[PADDLE_TOP]->last_ball_contact = SDL_GetTicks();
|
|
}
|
|
if ( bottom ) {
|
|
while ( bottom-- > 0 )
|
|
cur_game->paddles[PADDLE_BOTTOM]->score =
|
|
90 * cur_game->paddles[PADDLE_BOTTOM]->score / 100;
|
|
cur_game->paddles[PADDLE_BOTTOM]->last_ball_contact = SDL_GetTicks();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/*
|
|
====================================================================
|
|
All balls with target mx,my will have there 'get_target' flag
|
|
set True so they compute a new target next time balls_update()
|
|
is called. If 'mx' is -1 all balls will set their flag.
|
|
====================================================================
|
|
*/
|
|
void balls_check_targets(int mx, int my) {
|
|
Ball *ball;
|
|
int reset = 0;
|
|
list_reset( cur_game->balls );
|
|
while ( ( ball = list_next( cur_game->balls ) ) )
|
|
if ( !ball->attached && !ball->moving_back )
|
|
if ( mx == -1 || ( ball->target.mx == mx && ball->target.my == my ) ) {
|
|
/* As we don't have a constant velocity but assume one it is possible that
|
|
* the ball is within a wall when this function is called because it actually
|
|
* passed it's reset position without time expiration because of the velocity
|
|
* change. So we have to check here if it is already behind this position
|
|
* and if so simply reset here. This doesn't hurt as this would happen
|
|
* before reflection, too. */
|
|
if ( ball->target.exists ) {
|
|
if ( ball->vel.y > 0 ) {
|
|
if ( ball->cur.y > ball->target.y )
|
|
reset = 1;
|
|
}
|
|
else {
|
|
if ( ball->cur.y < ball->target.y )
|
|
reset = 1;
|
|
}
|
|
if ( ball->vel.x > 0 ) {
|
|
if ( ball->cur.x > ball->target.x )
|
|
reset = 1;
|
|
}
|
|
else {
|
|
if ( ball->cur.x < ball->target.x )
|
|
reset = 1;
|
|
}
|
|
if ( reset ) {
|
|
ball->cur.x = ball->target.x;
|
|
ball->cur.y = ball->target.y;
|
|
ball->x = (int)ball->cur.x;
|
|
ball->y = (int)ball->cur.y;
|
|
}
|
|
}
|
|
ball->get_target = 1;
|
|
}
|
|
}
|
|
/*
|
|
====================================================================
|
|
Adjust velocity of ball to spare out any illegal values.
|
|
Add a little entropy to the vector if 'entropy' is True.
|
|
====================================================================
|
|
*/
|
|
void ball_mask_vel(Ball *b, float old_vx, int entropy )
|
|
{
|
|
float m, entropy_level = 0;
|
|
|
|
if ( b->vel.x == 0 && b->vel.y == 0 ) return;
|
|
|
|
/* b->vel.x == 0 would cause seg faults */
|
|
if (b->vel.x == 0) {
|
|
if (old_vx < 0)
|
|
b->vel.x = 0.01;
|
|
else
|
|
b->vel.x = -0.01;
|
|
}
|
|
|
|
if ( entropy == BALL_ADD_ENTROPY )
|
|
entropy_level = (float)((rand() % 81)+40)/1000.0;
|
|
|
|
m = b->vel.y / b->vel.x;
|
|
if (fabs(m) < ball_vhmask) {
|
|
/* mask angles from 70 to 110 and -110 to -70 */
|
|
if (b->vel.y < 0)
|
|
b->vel.y = -fabs(ball_vhmask * b->vel.x);
|
|
else
|
|
b->vel.y = fabs(ball_vhmask * b->vel.x);
|
|
if ( entropy == BALL_ADD_ENTROPY )
|
|
b->vel.x -= b->vel.x * entropy_level;
|
|
}
|
|
else
|
|
if (fabs(m) > ball_vvmask) {
|
|
/* mask angles from -10 to 10 and 170 to 190 */
|
|
if (b->vel.x < 0)
|
|
b->vel.x = -fabs(b->vel.y / ball_vvmask);
|
|
else
|
|
b->vel.x = fabs(b->vel.y / ball_vvmask);
|
|
if ( entropy == BALL_ADD_ENTROPY )
|
|
b->vel.x += b->vel.x * entropy_level;
|
|
}
|
|
else
|
|
if ( entropy == BALL_ADD_ENTROPY ) {
|
|
if ( rand() % 2 )
|
|
entropy_level = -entropy_level;
|
|
b->vel.x += b->vel.x * entropy_level;
|
|
}
|
|
|
|
/* avoid 45° angles */
|
|
if (b->vel.x == b->vel.y)
|
|
b->vel.x *= 0.98;
|
|
|
|
/* adjust speed */
|
|
vector_set_length( &b->vel, cur_game->ball_v );
|
|
}
|
|
/*
|
|
====================================================================
|
|
Get target for a ball.
|
|
====================================================================
|
|
*/
|
|
enum { TANG_LEFT = 0, TANG_RIGHT };
|
|
enum { DIR_UP = 0, DIR_DOWN, DIR_LEFT, DIR_RIGHT };
|
|
void ball_get_target( Ball *ball )
|
|
{
|
|
int cur_tang;
|
|
float mono; /* monotony */
|
|
Coord tang_pts[2]; /* tangential points */
|
|
Line tang; /* current tangent */
|
|
Coord center = {
|
|
ball->cur.x + ball_rad,
|
|
ball->cur.y + ball_rad }; /* ball center */
|
|
int start, end, dir, line_pos, change; /* used to intersect the brick grid */
|
|
Line cur_line; /* dito */
|
|
Coord pt; /* auxiliary point. used for this 'n' that */
|
|
Target targets[2]; /* targets hit by the tangents: nearest is the actual target */
|
|
Target hori_target[2], vert_target[2]; /* used to get target of tangent */
|
|
float dist; /* distance between two points */
|
|
Vector norm_vel; /* normed ball velocity */
|
|
#ifdef WITH_BUG_REPORT
|
|
char tang_target_chosen_str[2][128]; /* either hori or vert target chosen */
|
|
char side_str[128];
|
|
Coord test_pts[2];
|
|
#endif
|
|
Target *prim, *sec; /* primary, secondary target */
|
|
int maybe_corner;
|
|
|
|
#ifdef WITH_BUG_REPORT
|
|
side_str[0] = 0;
|
|
#endif
|
|
|
|
/* balls moving back to paddle must not be reflected */
|
|
if ( ball->moving_back ) return;
|
|
/* attached balls MUST NOT be reflected!!!! */
|
|
if ( ball->attached ) return;
|
|
/* balls already out of the screen though still visible don't need new reflection, too */
|
|
if ( ball->cur.y + ball_dia >= 480 - 1 ) return;
|
|
|
|
/* clear ball targets */
|
|
ball_clear_target( &ball->target );
|
|
ball_clear_target( &targets[TANG_LEFT] );
|
|
ball_clear_target( &targets[TANG_RIGHT] );
|
|
/* monotony */
|
|
mono = ball->vel.y / ball->vel.x;
|
|
/* normed velocity */
|
|
norm_vel = ball->vel; vector_norm( &norm_vel );
|
|
/* tangential points */
|
|
ball_get_tangents( ball, &tang_pts[TANG_LEFT], &tang_pts[TANG_RIGHT] );
|
|
/* get all map bricks the tangents intersect and check target */
|
|
for ( cur_tang = 0; cur_tang < 2; cur_tang++ ) {
|
|
/* clear targets */
|
|
ball_clear_target( &hori_target[cur_tang] );
|
|
ball_clear_target( &vert_target[cur_tang] );
|
|
/* current tangent */
|
|
line_set( &tang, tang_pts[cur_tang].x, tang_pts[cur_tang].y, mono );
|
|
/* intersect horizontal lines */
|
|
/* get direction */
|
|
dir = DIR_DOWN;
|
|
if ( ball->vel.y < 0 ) dir = DIR_UP;
|
|
/* get starting line */
|
|
start = ((int)( tang_pts[cur_tang].y / BRICK_HEIGHT )) * BRICK_HEIGHT;
|
|
/* get end line */
|
|
if ( dir == DIR_UP )
|
|
end = 0;
|
|
else
|
|
end = ( MAP_HEIGHT - 1 ) * BRICK_HEIGHT;
|
|
/* adjust lines if ball moves up */
|
|
if ( dir == DIR_UP ) {
|
|
start += BRICK_HEIGHT - 1;
|
|
end += BRICK_HEIGHT - 1;
|
|
}
|
|
/* get position change */
|
|
change = BRICK_HEIGHT;
|
|
if ( dir == DIR_UP ) change = -change;
|
|
/* we're at this brick so we can't reflect here */
|
|
start += change;
|
|
/* intersect */
|
|
line_pos = start;
|
|
/* end specifies the last line to be checked to we have to add
|
|
another line to state the break condition.
|
|
this last line is not checked */
|
|
end += change;
|
|
while ( line_pos != end ) {
|
|
line_set_hori( &cur_line, line_pos );
|
|
if ( line_intersect( &cur_line, &tang, &pt ) && ( pt.x >= 0 && pt.x < 640 ) )
|
|
if ( cur_game->bricks[(int)pt.x / BRICK_WIDTH][(int)pt.y / BRICK_HEIGHT].type != MAP_EMPTY ) {
|
|
/* we got our horizontal target */
|
|
hori_target[cur_tang].exists = 1;
|
|
hori_target[cur_tang].x = pt.x;
|
|
hori_target[cur_tang].y = pt.y;
|
|
hori_target[cur_tang].mx = (int)pt.x / BRICK_WIDTH;
|
|
hori_target[cur_tang].my = (int)pt.y / BRICK_HEIGHT;
|
|
if ( ball->vel.y < 0 )
|
|
hori_target[cur_tang].side = SIDE_BOTTOM;
|
|
else
|
|
hori_target[cur_tang].side = SIDE_TOP;
|
|
break; /* we got our target for this tangent */
|
|
}
|
|
line_pos += change;
|
|
}
|
|
/* intersect vertical lines */
|
|
/* get direction */
|
|
dir = DIR_RIGHT;
|
|
if ( ball->vel.x < 0 ) dir = DIR_LEFT;
|
|
/* get starting line */
|
|
start = ((int)( tang_pts[cur_tang].x / BRICK_WIDTH )) * BRICK_WIDTH;
|
|
/* get end line */
|
|
if ( dir == DIR_LEFT )
|
|
end = 0;
|
|
else
|
|
end = ( MAP_WIDTH - 1 ) * BRICK_WIDTH;
|
|
/* adjust lines if ball moves up */
|
|
if ( dir == DIR_LEFT ) {
|
|
start += BRICK_WIDTH - 1;
|
|
end += BRICK_WIDTH - 1;
|
|
}
|
|
/* get position change */
|
|
change = BRICK_WIDTH;
|
|
if ( dir == DIR_LEFT ) change = -change;
|
|
/* we're at this brick so we can't reflect here */
|
|
start += change;
|
|
/* intersect */
|
|
line_pos = start;
|
|
/* end specifies the last line to be checked too we have to add
|
|
another line to state the break condition.
|
|
this last line is not checked */
|
|
end += change;
|
|
while ( line_pos != end ) {
|
|
line_set_vert( &cur_line, line_pos );
|
|
if ( line_intersect( &cur_line, &tang, &pt ) && ( pt.y >= 0 && pt.y < 480 ) )
|
|
if ( cur_game->bricks[(int)pt.x / BRICK_WIDTH][(int)pt.y / BRICK_HEIGHT].type != MAP_EMPTY ) {
|
|
/* we got our vertical target */
|
|
vert_target[cur_tang].exists = 1;
|
|
vert_target[cur_tang].x = pt.x;
|
|
vert_target[cur_tang].y = pt.y;
|
|
vert_target[cur_tang].mx = (int)pt.x / BRICK_WIDTH;
|
|
vert_target[cur_tang].my = (int)pt.y / BRICK_HEIGHT;
|
|
if ( ball->vel.x < 0 )
|
|
vert_target[cur_tang].side = SIDE_RIGHT;
|
|
else
|
|
vert_target[cur_tang].side = SIDE_LEFT;
|
|
break; /* we got our target for this tangent */
|
|
}
|
|
line_pos += change;
|
|
}
|
|
/* get closest target */
|
|
if ( !hori_target[cur_tang].exists ) {
|
|
targets[cur_tang] = vert_target[cur_tang];
|
|
#ifdef WITH_BUG_REPORT
|
|
if ( !vert_target[cur_tang].exists )
|
|
sprintf( tang_target_chosen_str[cur_tang], "No target chosen." );
|
|
else
|
|
sprintf( tang_target_chosen_str[cur_tang], "Vertical target chosen." );
|
|
#endif
|
|
}
|
|
else
|
|
if ( !vert_target[cur_tang].exists ) {
|
|
targets[cur_tang] = hori_target[cur_tang];
|
|
#ifdef WITH_BUG_REPORT
|
|
sprintf( tang_target_chosen_str[cur_tang], "Horizontal target chosen." );
|
|
#endif
|
|
}
|
|
else {
|
|
/* check the relation and choose the correct target */
|
|
/* if vertical and hori hit the same brick we have hit the corner */
|
|
if ( hori_target[cur_tang].mx == vert_target[cur_tang].mx && hori_target[cur_tang].my == vert_target[cur_tang].my ) {
|
|
/* congrats! we hit the exact corner pixel! now we have to decide by corner and
|
|
tangent which target to use */
|
|
if ( cur_tang == TANG_LEFT ) {
|
|
/* left tangent */
|
|
if ( ball->vel.y > 0 ) {
|
|
if ( ball->vel.x > 0 ) /* upper, right */
|
|
targets[cur_tang] = vert_target[cur_tang];
|
|
else
|
|
targets[cur_tang] = hori_target[cur_tang];
|
|
}
|
|
else {
|
|
if ( ball->vel.x > 0 ) /* lower, right */
|
|
targets[cur_tang] = hori_target[cur_tang];
|
|
else
|
|
targets[cur_tang] = vert_target[cur_tang];
|
|
}
|
|
}
|
|
else {
|
|
/* right tangent */
|
|
if ( ball->vel.y > 0 ) {
|
|
if ( ball->vel.x > 0 ) /* upper, right */
|
|
targets[cur_tang] = hori_target[cur_tang];
|
|
else
|
|
targets[cur_tang] = vert_target[cur_tang];
|
|
}
|
|
else {
|
|
if ( ball->vel.x > 0 ) /* lower, right */
|
|
targets[cur_tang] = vert_target[cur_tang];
|
|
else
|
|
targets[cur_tang] = hori_target[cur_tang];
|
|
}
|
|
}
|
|
#ifdef WITH_BUG_REPORT
|
|
if ( targets[cur_tang].x == hori_target[cur_tang].x && targets[cur_tang].y == hori_target[cur_tang].y )
|
|
sprintf( tang_target_chosen_str[cur_tang], "(TRICKY) Horizontal target chosen." );
|
|
else
|
|
sprintf( tang_target_chosen_str[cur_tang], "(TRICKY) Vertical target chosen." );
|
|
#endif
|
|
}
|
|
else {
|
|
if ( VEC_DIST( tang_pts[cur_tang], vector_get( hori_target[cur_tang].x, hori_target[cur_tang].y ) ) < VEC_DIST( tang_pts[cur_tang], vector_get( vert_target[cur_tang].x, vert_target[cur_tang].y ) ) ) {
|
|
targets[cur_tang] = hori_target[cur_tang];
|
|
#ifdef WITH_BUG_REPORT
|
|
sprintf( tang_target_chosen_str[cur_tang], "Horizontal target chosen." );
|
|
#endif
|
|
}
|
|
else {
|
|
targets[cur_tang] = vert_target[cur_tang];
|
|
#ifdef WITH_BUG_REPORT
|
|
sprintf( tang_target_chosen_str[cur_tang], "Vertical target chosen." );
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
} /* now we have the two targets hit by the tangents */
|
|
/* whatever's up the nearest brick is hit */
|
|
if ( targets[TANG_LEFT].exists || targets[TANG_RIGHT].exists ) {
|
|
prim = sec = 0;
|
|
if ( !targets[TANG_LEFT].exists || !targets[TANG_RIGHT].exists ) {
|
|
if ( targets[TANG_LEFT].exists )
|
|
prim = &targets[TANG_LEFT];
|
|
else
|
|
prim = &targets[TANG_RIGHT];
|
|
}
|
|
else {
|
|
if ( VEC_DIST( center, vector_get( targets[TANG_RIGHT].x, targets[TANG_RIGHT].y ) ) < VEC_DIST( center, vector_get( targets[TANG_LEFT].x, targets[TANG_LEFT].y ) ) ) {
|
|
prim = &targets[TANG_RIGHT];
|
|
sec = &targets[TANG_LEFT];
|
|
}
|
|
else {
|
|
prim = &targets[TANG_LEFT];
|
|
sec = &targets[TANG_RIGHT];
|
|
}
|
|
}
|
|
/* however, the primary target maybe be blocked by another brick or may be a corner */
|
|
/* check if side of prim target isn't blocked by a brick */
|
|
switch ( prim->side ) {
|
|
case SIDE_TOP:
|
|
if ( cur_game->bricks[prim->mx][prim->my - 1].type != MAP_EMPTY ) {
|
|
if ( ball->vel.x > 0 )
|
|
prim->side = SIDE_LEFT;
|
|
else
|
|
prim->side = SIDE_RIGHT;
|
|
#ifdef WITH_BUG_REPORT
|
|
sprintf( side_str, "Had to change side as TOP wasn't appropriate!" );
|
|
#endif
|
|
}
|
|
break;
|
|
case SIDE_BOTTOM:
|
|
if ( cur_game->bricks[prim->mx][prim->my + 1].type != MAP_EMPTY ) {
|
|
if ( ball->vel.x > 0 )
|
|
prim->side = SIDE_LEFT;
|
|
else
|
|
prim->side = SIDE_RIGHT;
|
|
#ifdef WITH_BUG_REPORT
|
|
sprintf( side_str, "Had to change side as BOTTOM wasn't appropriate!" );
|
|
#endif
|
|
}
|
|
break;
|
|
case SIDE_LEFT:
|
|
if ( cur_game->bricks[prim->mx - 1][prim->my].type != MAP_EMPTY ) {
|
|
if ( ball->vel.y > 0 )
|
|
prim->side = SIDE_TOP;
|
|
else
|
|
prim->side = SIDE_BOTTOM;
|
|
#ifdef WITH_BUG_REPORT
|
|
sprintf( side_str, "Had to change side as LEFT wasn't appropriate!" );
|
|
#endif
|
|
}
|
|
break;
|
|
case SIDE_RIGHT:
|
|
if ( cur_game->bricks[prim->mx + 1][prim->my].type != MAP_EMPTY ) {
|
|
if ( ball->vel.y > 0 )
|
|
prim->side = SIDE_TOP;
|
|
else
|
|
prim->side = SIDE_BOTTOM;
|
|
#ifdef WITH_BUG_REPORT
|
|
sprintf( side_str, "Had to change side as RIGHT wasn't appropriate!" );
|
|
#endif
|
|
}
|
|
break;
|
|
}
|
|
/* now it still may be a corner */
|
|
if ( sec == 0 || prim->mx != sec->mx || prim->my != sec->my || prim->side != sec->side ) {
|
|
maybe_corner = 1;
|
|
if ( ball->vel.y > 0 ) {
|
|
if ( ball->vel.x > 0 ) {
|
|
/* upper left corner */
|
|
if ( cur_game->bricks[prim->mx][prim->my - 1].type != MAP_EMPTY ) maybe_corner = 0;
|
|
if ( cur_game->bricks[prim->mx - 1][prim->my].type != MAP_EMPTY ) maybe_corner = 0;
|
|
}
|
|
else {
|
|
/* upper right corner */
|
|
if ( cur_game->bricks[prim->mx][prim->my - 1].type != MAP_EMPTY ) maybe_corner = 0;
|
|
if ( cur_game->bricks[prim->mx + 1][prim->my].type != MAP_EMPTY ) maybe_corner = 0;
|
|
}
|
|
}
|
|
else {
|
|
if ( ball->vel.x > 0 ) {
|
|
/* lower left corner */
|
|
if ( cur_game->bricks[prim->mx][prim->my + 1].type != MAP_EMPTY ) maybe_corner = 0;
|
|
if ( cur_game->bricks[prim->mx - 1][prim->my].type != MAP_EMPTY ) maybe_corner = 0;
|
|
}
|
|
else {
|
|
/* lower right corner */
|
|
if ( cur_game->bricks[prim->mx][prim->my + 1].type != MAP_EMPTY ) maybe_corner = 0;
|
|
if ( cur_game->bricks[prim->mx + 1][prim->my].type != MAP_EMPTY ) maybe_corner = 0;
|
|
}
|
|
}
|
|
if ( maybe_corner )
|
|
ball_corner_check( ball, &targets[TANG_LEFT], &targets[TANG_RIGHT], prim );
|
|
}
|
|
/* we updated primary's side info correctly and may reflect now */
|
|
ball->target = *prim;
|
|
ball_reflect( ball );
|
|
/* we got the reset position and the perpvector so finalize target */
|
|
/* compute time: assume constant velocity: velocity change must not be too high! */
|
|
dist = sqrt( SQUARE(center.x - ball->target.x) + SQUARE(center.y - ball->target.y) );
|
|
ball->target.time = (int)floor(dist / cur_game->ball_v);
|
|
/* target's reset position is center position right now but
|
|
we need the upper left corner of the ball */
|
|
ball->target.x -= ball_rad; ball->target.y -= ball_rad;
|
|
/* some error information */
|
|
#ifdef WITH_BUG_REPORT
|
|
pt.x = ball->cur.x; pt.y = ball->cur.y;
|
|
ball->cur.x = ball->target.x; ball->cur.y = ball->target.y;
|
|
ball_get_tangents( ball, &test_pts[TANG_LEFT], &test_pts[TANG_RIGHT] );
|
|
ball->cur.x = pt.x; ball->cur.y = pt.y;
|
|
if ( cur_game->bricks[(int)test_pts[0].x/BRICK_WIDTH][(int)test_pts[0].y/BRICK_HEIGHT].type != MAP_EMPTY ||
|
|
cur_game->bricks[(int)test_pts[1].x/BRICK_WIDTH][(int)test_pts[1].y/BRICK_HEIGHT].type != MAP_EMPTY ) {
|
|
printf( "*****\n" );
|
|
printf( "Test Failed: %4.2f,%4.2f (%i,%i):\n",
|
|
ball->target.x+ball_rad, ball->target.y+ball_rad,
|
|
(int)(ball->target.x+ball_rad)/BRICK_WIDTH,
|
|
(int)(ball->target.y+ball_rad)/BRICK_HEIGHT );
|
|
printf( "Left Tangent %4.2f,%4.2f (%i,%i) or Right Tangent %4.2f,%4.2f (%i,%i)\n",
|
|
test_pts[0].x,test_pts[0].y,
|
|
(int)test_pts[0].x/BRICK_WIDTH,(int)test_pts[0].y/BRICK_HEIGHT,
|
|
test_pts[1].x,test_pts[1].y,
|
|
(int)test_pts[1].x/BRICK_WIDTH,(int)test_pts[1].y/BRICK_HEIGHT);
|
|
printf( "*****\n" );
|
|
printf( "2.4: Balls: %i\n", cur_game->balls->count );
|
|
if ( targets[TANG_LEFT].exists ) {
|
|
printf( "Left Tangential Point: %4.2f,%4.2f\n",
|
|
tang_pts[TANG_LEFT].x, tang_pts[TANG_LEFT].y );
|
|
printf( "Left Tangent: Horizontal: %i,%i, %i (%4.2f,%4.2f)\n",
|
|
hori_target[TANG_LEFT].mx, hori_target[TANG_LEFT].my, hori_target[TANG_LEFT].side,
|
|
hori_target[TANG_LEFT].x, hori_target[TANG_LEFT].y );
|
|
printf( "Left Tangent: Vertical: %i,%i, %i (%4.2f,%4.2f)\n",
|
|
vert_target[TANG_LEFT].mx, vert_target[TANG_LEFT].my, vert_target[TANG_LEFT].side,
|
|
vert_target[TANG_LEFT].x, vert_target[TANG_LEFT].y );
|
|
printf( "%s\n", tang_target_chosen_str[TANG_LEFT] );
|
|
}
|
|
if ( targets[TANG_RIGHT].exists ) {
|
|
printf( "Right Tangential Point: %4.2f,%4.2f\n",
|
|
tang_pts[TANG_RIGHT].x, tang_pts[TANG_RIGHT].y );
|
|
printf( "Right Tangent: Horizontal: %i,%i, %i (%4.2f,%4.2f)\n",
|
|
hori_target[TANG_RIGHT].mx, hori_target[TANG_RIGHT].my, hori_target[TANG_RIGHT].side,
|
|
hori_target[TANG_RIGHT].x, hori_target[TANG_RIGHT].y );
|
|
printf( "Right Tangent: Vertical: %i,%i, %i (%4.2f,%4.2f)\n",
|
|
vert_target[TANG_RIGHT].mx, vert_target[TANG_RIGHT].my, vert_target[TANG_RIGHT].side,
|
|
vert_target[TANG_RIGHT].x, vert_target[TANG_RIGHT].y );
|
|
printf( "%s\n", tang_target_chosen_str[TANG_RIGHT] );
|
|
}
|
|
if ( side_str[0] != 0 ) printf( "BTW: %s\n", side_str );
|
|
printf( "-----\n" );
|
|
ball_print_target_info( ball );
|
|
printf("*****\n");
|
|
printf( "\nYou encountered a bug! Please send this output to kulkanie@gmx.net. Thanks!\n" );
|
|
//exit(1);
|
|
/* move ball back to paddle as the current target is nonsense */
|
|
ball->target.exists = 0;
|
|
ball->idle_time = 0;
|
|
ball->moving_back = 1;
|
|
ball->return_allowed = 0;
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
/*
|
|
====================================================================
|
|
Increase velocity acording to vel_change
|
|
====================================================================
|
|
*/
|
|
void balls_inc_vel( int ms )
|
|
{
|
|
Ball *ball;
|
|
|
|
if ( cur_game->ball_v >= cur_game->ball_v_max ) return;
|
|
|
|
if ( !delay_timed_out( &cur_game->speedup_delay, ms ) ) return;
|
|
|
|
cur_game->ball_v += cur_game->diff->v_add;
|
|
cur_game->speedup_level++;
|
|
|
|
list_reset( cur_game->balls );
|
|
while ( ( ball = list_next( cur_game->balls ) ) != 0 ) {
|
|
if ( ball->attached ) continue;
|
|
vector_set_length( &ball->vel, cur_game->ball_v );
|
|
}
|
|
}
|
|
/*
|
|
====================================================================
|
|
Return all balls that have ball->return_allowed True.
|
|
====================================================================
|
|
*/
|
|
void balls_return( Paddle *paddle )
|
|
{
|
|
Ball *ball;
|
|
|
|
list_reset( cur_game->balls );
|
|
while ( ( ball = list_next( cur_game->balls ) ) != 0 )
|
|
if ( ball->return_allowed && ball->paddle == paddle ) {
|
|
ball->moving_back = 1;
|
|
ball->target.exists = 0;
|
|
ball->return_allowed = 0;
|
|
}
|
|
}
|
|
|
|
/* set random starting angle for ball according to its paddle */
|
|
void ball_set_random_angle( Ball *ball, double ball_v )
|
|
{
|
|
if ( ball->paddle->type == PADDLE_TOP )
|
|
ball->vel.y = 1.0;
|
|
else
|
|
ball->vel.y = -1.0;
|
|
ball->vel.x = (float)((rand() % 145) + 6);
|
|
if ( rand() % 2 )
|
|
ball->vel.x /= -100.0;
|
|
else
|
|
ball->vel.x /= 100.0;
|
|
|
|
/* only use 2 degree steps */
|
|
ball->angle = vec2angle( &ball->vel );
|
|
angle2vec( ball->angle, &ball->vel );
|
|
ball->vel.x *= ball_v; ball->vel.y *= ball_v;
|
|
}
|
|
|
|
/*
|
|
====================================================================
|
|
Set velocity of all balls and get new targets if any.
|
|
====================================================================
|
|
*/
|
|
void balls_set_velocity( List *balls, double vel )
|
|
{
|
|
Ball *b;
|
|
double dist;
|
|
|
|
list_reset( balls );
|
|
while ( ( b = list_next( balls ) ) ) {
|
|
vector_set_length( &b->vel, vel );
|
|
if ( b->target.exists ) {
|
|
dist = sqrt( SQUARE(b->cur.x - b->target.x) +
|
|
SQUARE(b->cur.y - b->target.y) );
|
|
b->target.time = (int)floor(dist / vel);
|
|
b->target.cur_tm = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
====================================================================
|
|
Detach all balls to the passed direction (-1 or 1) and return True
|
|
if there were any balls detached. As balls within walls are not
|
|
fired the result may differ from paddle::attached_ball_count!
|
|
====================================================================
|
|
*/
|
|
int balls_detach_from_paddle( Paddle *paddle, int dir )
|
|
{
|
|
Ball *ball;
|
|
int fired = 0;
|
|
|
|
list_reset( cur_game->balls );
|
|
while ( ( ball = list_next( cur_game->balls ) ) ) {
|
|
if ( !ball->attached || ball->paddle != paddle )
|
|
continue;
|
|
/* balls in walls (hehe) are not fired */
|
|
if ( ball->x + paddle->x < BRICK_WIDTH )
|
|
continue;
|
|
if ( ball->x + ball_dia + paddle->x >= 640 - BRICK_WIDTH )
|
|
continue;
|
|
/* release ball */
|
|
ball->attached = 0;
|
|
ball->paddle->attached_ball_count--;
|
|
ball->moving_back = ball->idle_time = ball->return_allowed = 0;
|
|
ball->x += paddle->x;
|
|
ball->y += paddle->y;
|
|
ball->cur.x = ball->x;
|
|
ball->cur.y = ball->y;
|
|
if ( !cur_game->balls_use_random_angle ) {
|
|
/* when random angle is used the vector is not
|
|
* changed but the one before the attachment is
|
|
* used */
|
|
ball->vel.x = (float)dir;
|
|
if ( ball->paddle->type == PADDLE_TOP )
|
|
ball->vel.y = 1.2;
|
|
else
|
|
ball->vel.y = -1.2;
|
|
/* only use 2 degree steps */
|
|
ball->angle = vec2angle( &ball->vel );
|
|
angle2vec( ball->angle, &ball->vel );
|
|
vector_set_length( &ball->vel, cur_game->ball_v );
|
|
}
|
|
ball->get_target = 1;
|
|
fired = 1;
|
|
}
|
|
|
|
/* if no balls are attached anymore set last contact time */
|
|
if ( fired && paddle->attached_ball_count == 0 )
|
|
paddle->last_ball_contact = SDL_GetTicks();
|
|
|
|
return fired;
|
|
}
|