5 * Copyright (C) 2003, 2007, 2008, 2009 Thomas Perl <thp@thpinfo.com>
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
39 GameState
*gamestate_new() {
42 GameState gs_template
= {
45 { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false, -1, false },
47 { NULL
, -1, GAME_X_MIN
-RACKET_X_MID
*2, GAME_Y_MID
, 0.0, true, 0, DESIRE_NORMAL
, PLAYER_TYPE_AI
, 0, {0}, PLAYER_ACCEL_DEFAULT
},
48 { NULL
, -1, GAME_X_MAX
+RACKET_X_MID
*2, GAME_Y_MID
, 0.0, true, 0, DESIRE_NORMAL
, PLAYER_TYPE_AI
, 0, {0}, PLAYER_ACCEL_DEFAULT
},
54 SOUND_EVENT_NONE
, /* sound events */
57 EVENTCOUNTER_GAMESTATE_START
,
58 EVENTCOUNTER_GAMESTATE_START
,
62 s
= (GameState
*)malloc(sizeof(GameState
));
63 if (s
== NULL
) abort();
65 memcpy(s
, &gs_template
, sizeof(GameState
));
73 int gamestate_save(GameState
* s
, const char* filename
)
75 const Location
*location
;
76 InputDevice
* input_devices
[MAXPLAYERS
];
82 assert(getcwd(tmp
, MAXPATHLEN
) == tmp
);
83 assert(chdir(getenv("HOME")) == 0);
87 * Process-specific data (pointers to data
88 * structures only of interest to this process)
89 * has to be "removed" before saving.
91 * It can later be restored (in gamestate_load).
94 fp
= fopen(filename
, "w");
99 /* Save data for later recovery + clear */
100 location
= s
->location
;
102 for (i
=1; i
<=MAXPLAYERS
; i
++) {
103 input_devices
[i
-1] = PLAYER(s
, i
).input
;
104 PLAYER(s
, i
).input
= NULL
;
107 if (fwrite(s
, sizeof(GameState
), 1, fp
) != 1) {
111 /* Restore process-specific data */
112 s
->location
= location
;
113 for (i
=1; i
<=MAXPLAYERS
; i
++) {
114 PLAYER(s
, i
).input
= input_devices
[i
-1];
120 assert(chdir(tmp
) == 0);
127 GameState
* gamestate_load(const char* filename
)
132 char tmp
[MAXPATHLEN
];
134 assert(getcwd(tmp
, MAXPATHLEN
) == tmp
);
135 assert(chdir(getenv("HOME")) == 0);
138 fp
= fopen(filename
, "r");
141 s
= (GameState
*)malloc(sizeof(GameState
));
143 if (fread(s
, sizeof(GameState
), 1, fp
) == 1) {
144 /* Restore/create process-specific data */
145 /* FIXME: s->location, players' "input" */
155 assert(chdir(tmp
) == 0);
162 void gameloop(GameState
*s
, TennixNet
* c
) {
163 Uint32 ot
= SDL_GetTicks();
165 Uint32 dt
= GAME_TICKS
;
167 Uint32 accumulator
= 0;
174 EVENTCOUNTER_RENDERSTATE_START
,
175 EVENTCOUNTER_RENDERSTATE_START
,
182 /* Catch-up with existing sound events */
183 r
.sound_events
= s
->sound_events
;
185 #ifdef ENABLE_FPS_LIMIT
186 Uint32 ft
, frames
; /* frame timer and frames */
189 if (s
->location
->rain
> 0) {
190 play_sample_background(SOUND_RAIN
);
192 if (s
->location
->max_visitors
> 100) {
193 play_sample_loop(SOUND_AUDIENCE
);
196 for (p
=1; p
<=MAXPLAYERS
; p
++) {
197 input_device_join_game(PLAYER(s
, p
).input
, s
, p
);
200 #ifdef ENABLE_FPS_LIMIT
214 while( accumulator
>= dt
) {
218 network_get_gamestate(c
, s
);
220 if ((i
++) % 10 == 0) {
221 network_send_state(c
, s
);
224 quit
= handle_input(s
, c
);
228 #ifdef ENABLE_FPS_LIMIT
229 while (frames
*1000.0/((float)(SDL_GetTicks()-ft
+1))>(float)(DEFAULT_FPS
)) {
238 for (p
=1; p
<=MAXPLAYERS
; p
++) {
239 input_device_part_game(PLAYER(s
, p
).input
);
243 //rectangle(0, 0, WIDTH, HEIGHT, 80, 80, 80);
246 stop_sample(SOUND_AUDIENCE
);
247 stop_sample(SOUND_RAIN
);
250 void step(GameState
* s
) {
251 bool ground_event
= false;
254 s
->ball
.z
+= s
->ball
.move_z
;
255 if (!s
->ball
.inhibit_gravity
) {
256 s
->ball
.move_z
+= GRAVITY
;
259 s
->ball
.x
+= s
->ball
.move_x
;
260 s
->ball
.y
+= s
->ball
.move_y
;
262 for (p
=1; p
<=MAXPLAYERS
; p
++) {
263 if (PLAYER(s
, p
).use_power
) {
264 PLAYER(s
, p
).power
= fmaxf(0.0, fminf(
265 PLAYER(s
, p
).power
*POWER_DOWN_FACTOR
,
274 s
->referee
= REFEREE_NORMAL
;
276 /* bounce from the ground */
277 if (fabsf(s
->ball
.move_z
) > 0.3) {
278 s
->sound_events
^= SOUND_EVENT_GROUND
;
279 s
->ball
.move_z
*= -BALL_RESTITUTION
;
286 if (NET_COLLISION_BALL(s
->ball
)) {
287 /* the net blocks movement of the ball */
288 while (NET_COLLISION_BALL(s
->ball
)) {
289 /* make sure the ball appears OUTSIDE of the net */
290 if (s
->ball
.move_x
< 0) {
300 /* see if we have something to score */
301 if (s
->score_event
== SCORE_UNDECIDED
) {
302 if (NET_COLLISION_BALL(s
->ball
)) {
303 /* the ball "fell" into the net */
304 s
->score_event
= SCORE_EVENT_NET
;
305 s
->sound_events
^= SOUND_EVENT_OUT
;
306 s
->status_message
= STATUSMSG_NET
;
307 } else if (IS_OFFSCREEN(s
->ball
.x
, s
->ball
.y
)) {
308 /* ball flew offscreen */
309 s
->score_event
= SCORE_EVENT_OFFSCREEN
;
310 s
->sound_events
^= SOUND_EVENT_OUT
;
311 s
->status_message
= STATUSMSG_OUT
;
312 } else if (ground_event
) {
313 /* the ball hit the ground on the screen */
314 if (IS_OUT(s
->ball
.x
, s
->ball
.y
)) {
315 /* the ball bounced in the OUT area */
316 s
->score_event
= SCORE_EVENT_OUT
;
317 s
->sound_events
^= SOUND_EVENT_OUT
;
318 s
->status_message
= STATUSMSG_OUT
;
319 } else if (GROUND_IS_VALID(s
->ball
.last_hit_by
, s
->ball
.x
, s
->ball
.y
)) {
320 if (s
->ball
.ground_hit
) {
321 s
->score_event
= SCORE_EVENT_GROUND_VALID
;
322 s
->sound_events
^= SOUND_EVENT_OUT
;
323 s
->status_message
= STATUSMSG_DIDNTCATCH
;
325 /* first ground hit in valid area */
326 s
->ball
.ground_hit
= true;
329 /* ball hit somewhere invalid */
330 s
->score_event
= SCORE_EVENT_GROUND_INVALID
;
331 s
->sound_events
^= SOUND_EVENT_OUT
;
332 s
->status_message
= STATUSMSG_FAULT
;
337 if (s
->score_event
!= SCORE_UNDECIDED
) {
338 /* we have some scoring to do */
339 if (s
->score_time
< SCORING_DELAY
/GAME_TICKS
) {
340 /* schedule scoring in the future */
342 s
->referee
= REFEREE_OUT
;
343 } else if (s
->score_time
>= SCORING_DELAY
/GAME_TICKS
) {
344 /* time has ran out - score now */
345 switch (score_game(s
)) {
347 s
->status_message
= STATUSMSG_P1SCORES
;
348 s
->referee
= REFEREE_PLAYER1
;
351 s
->status_message
= STATUSMSG_P2SCORES
;
352 s
->referee
= REFEREE_PLAYER2
;
361 if (s
->location
->max_visitors
> 100) {
362 s
->sound_events
^= SOUND_EVENT_APPLAUSE
;
366 /* score is still undecided - do the racket swing thing */
367 for (p
=1; p
<=2; p
++) {
368 if (IS_NEAR_X(PLAYER(s
, p
).x
, s
->ball
.x
) && IS_NEAR_Y(PLAYER(s
, p
).y
, s
->ball
.y
-s
->ball
.z
) && PLAYER(s
, p
).use_power
&& PLAYER(s
, p
).power
> 30.0 && s
->ball
.last_hit_by
!= p
) {
370 if (!s
->ball
.ground_hit
&& s
->ball
.move_x
!= 0.0) {
371 s
->status_message
= STATUSMSG_VOLLEY
;
373 s
->status_message
= STATUSMSG_DEFAULT
;
375 switch (PLAYER(s
, p
).desire
) {
378 s
->ball
.move_x
= 2.7 + 2.0*PLAYER(s
, p
).power
/PLAYER_POWER_MAX
;
379 s
->ball
.move_z
= 1.2*PLAYER(s
, p
).power
/PLAYER_POWER_MAX
;
383 s
->ball
.move_x
= 1.1 + 2.2*PLAYER(s
, p
).power
/PLAYER_POWER_MAX
;
384 s
->ball
.move_z
= 2.5*PLAYER(s
, p
).power
/PLAYER_POWER_MAX
;
388 s
->ball
.move_x
= 4.0 + 3.0*PLAYER(s
, p
).power
/PLAYER_POWER_MAX
;
389 s
->ball
.move_z
= 1.1*PLAYER(s
, p
).power
/PLAYER_POWER_MAX
;
395 s
->ball
.move_y
= get_move_y( s
, p
);
396 s
->sound_events
^= SOUND_EVENT_RACKET
;
397 s
->ball
.ground_hit
= false;
398 s
->ball
.inhibit_gravity
= false;
399 s
->ball
.last_hit_by
= p
;
401 s
->ball
.move_x
*= -1;
408 bool handle_input(GameState
* s
, TennixNet
* c
) {
409 static NetworkGameState tmp
;
410 static NetworkInputData net_input
;
415 keys
= SDL_GetKeyState(NULL
);
418 net_serialize_gamestate(s
, &tmp
);
419 } else if (keys
['2']) {
420 net_unserialize_gamestate(&tmp
, s
);
423 network_get_input(c
, &net_input
);
424 if (s
->winner
== WINNER_NONE
) {
425 for (p
=1; p
<=MAXPLAYERS
; p
++) {
426 if( PLAYER(s
, p
).type
== PLAYER_TYPE_HUMAN
) {
427 if (PLAYER(s
, p
).input
->type
== INPUT_TYPE_NETWORK
) {
428 memcpy(&(PLAYER(s
, p
).input
->net
), &net_input
,
429 sizeof(NetworkInputData
));
432 if (PLAYER(s
, p
).input
->type
!= INPUT_TYPE_NETWORK
) {
433 network_send_input(c
, &(PLAYER(s
, p
).input
->net
));
439 /* Make sure player coordinates are valid */
440 if (PLAYER(s
, p
).y
< PLAYER_Y_MIN
) {
441 PLAYER(s
, p
).y
= PLAYER_Y_MIN
;
442 } else if (PLAYER(s
, p
).y
> PLAYER_Y_MAX
) {
443 PLAYER(s
, p
).y
= PLAYER_Y_MAX
;
448 /* Maemo: The "F4" button is the "Open menu" button */
449 return (keys
[SDLK_ESCAPE
] || keys
['q']);
452 void render(const GameState
* s
, RenderState
* r
) {
459 Uint32 time
= SDL_GetTicks();
461 /* The bits in sound_events flip when the sound should play */
462 if ((sounds
= (r
->sound_events
^ s
->sound_events
)) != 0) {
463 if (sounds
& SOUND_EVENT_GROUND
) {
464 sample_volume_group(SOUND_GROUND_FIRST
, SOUND_GROUND_LAST
,
465 fmaxf(0.0, fminf(1.0, fabsf(s
->ball
.move_z
)/2)));
466 pan_sample_group(SOUND_GROUND_FIRST
, SOUND_GROUND_LAST
,
467 fmaxf(0.0, fminf(1.0, (s
->ball
.x
)/WIDTH
)));
468 play_sample(SOUND_GROUND
);
470 if (sounds
& SOUND_EVENT_OUT
) {
471 play_sample(SOUND_OUT
);
473 if (sounds
& SOUND_EVENT_APPLAUSE
) {
474 play_sample(SOUND_APPLAUSE
);
476 if (sounds
& SOUND_EVENT_RACKET
) {
477 if (((s
->ball
.x
)/WIDTH
) < 0.5) {
478 pan_sample_group(SOUND_RACKET_FIRST
, SOUND_RACKET_LAST
, 0.3);
480 pan_sample_group(SOUND_RACKET_FIRST
, SOUND_RACKET_LAST
, 0.7);
482 play_sample(SOUND_RACKET
);
484 r
->sound_events
= s
->sound_events
;
487 if( s
->winner
!= WINNER_NONE
) {
489 //rectangle(0, 0, WIDTH, HEIGHT, 80, 80, 80);
491 show_sprite( GR_RACKET
, 2*(s
->winner
-1), 4, WIDTH
/2 - get_image_width( GR_RACKET
)/8, HEIGHT
/2 - get_image_height( GR_RACKET
), 255);
492 /*sprintf( s->game_score_str, "player %d wins the match with %s", s->winner, format_sets( s));
493 font_draw_string(FONT_LARGE, s->game_score_str, (WIDTH-font_get_string_width(FONT_LARGE, s->game_score_str))/2, HEIGHT/2 + 30);*/
497 if ((r
->referee
!= s
->referee
) ||
498 (r
->ec_game
!= s
->ec_game
) ||
499 (r
->ec_sets
!= s
->ec_sets
) ||
500 (r
->status_message
!= s
->status_message
)) {
501 /* Update status message text */
502 if (r
->status_message
!= s
->status_message
) {
503 r
->text_status
= format_status(s
);
504 r
->status_message
= s
->status_message
;
506 /* Update game status text */
507 if (r
->ec_game
!= s
->ec_game
) {
508 r
->text_game
= format_game(s
);
509 r
->ec_game
= s
->ec_game
;
511 /* Update set status text */
512 if (r
->ec_sets
!= s
->ec_sets
) {
513 r
->text_sets
= format_sets(s
);
514 if (r
->status_message
== STATUSMSG_DEFAULT
) {
515 r
->text_status
= format_status(s
);
517 r
->ec_sets
= s
->ec_sets
;
520 //rectangle(0, 0, WIDTH, HEIGHT, 80, 80, 80);
521 fill_image(s
->location
->court_type
, 120, 120, 400, 250);
522 show_image(GR_COURT
, 0, 0, 255);
523 font_draw_string(FONT_XLARGE
, r
->text_game
, 14, 14);
524 font_draw_string(FONT_XLARGE
, r
->text_sets
,
525 (WIDTH
-font_get_string_width(FONT_XLARGE
, r
->text_sets
))-14, 14);
526 if (s
->location
->has_referee
) {
527 switch (s
->referee
) {
534 case REFEREE_PLAYER1
:
535 case REFEREE_PLAYER2
:
554 show_sprite( GR_REFEREE
, s
->referee
*3+t
, 12, 250, 10, 255);
555 if (voice_finished_flag
== 0) {
556 show_sprite(GR_TALK
, (time
/150)%2, 2, 280, 45, 255);
559 r
->referee
= s
->referee
;
563 /* show_sprite( GR_RACKET, (!PLAYER(s, 1).state), 4, PLAYER(s, 1).x-RACKET_X_MID, PLAYER(s, 1).y-RACKET_Y_MID, 255);
564 show_sprite( GR_RACKET, (!PLAYER(s, 2).state)+2, 4, PLAYER(s, 2).x-RACKET_X_MID, PLAYER(s, 2).y-RACKET_Y_MID, 255);*/
565 show_sprite( GR_RACKET
, !PLAYER(s
, 1).use_power
, 4, PLAYER(s
, 1).x
-RACKET_X_MID
, PLAYER(s
, 1).y
-RACKET_Y_MID
, 255);
566 show_sprite( GR_RACKET
, !PLAYER(s
, 2).use_power
+ 2, 4, PLAYER(s
, 2).x
-RACKET_X_MID
, PLAYER(s
, 2).y
-RACKET_Y_MID
, 255);
568 rectangle(10, HEIGHT
-30, PLAYER_POWER_MAX
, 10, 50, 50, 50);
569 rectangle(WIDTH
-PLAYER_POWER_MAX
-10, HEIGHT
-30, PLAYER_POWER_MAX
, 10, 50, 50, 50);
571 rectangle(10, HEIGHT
-30, (int)(PLAYER(s
, 1).power
), 10, 200, 200, 200);
572 rectangle(WIDTH
-PLAYER_POWER_MAX
-10, HEIGHT
-30, (int)(PLAYER(s
, 2).power
), 10, 200, 200, 200);
574 if( s
->ball
.move_x
> 0) {
575 b
= (time
/100)%BALL_STATES
;
576 } else if( s
->ball
.move_x
< 0) {
577 b
= BALL_STATES
-1-(time
/100)%BALL_STATES
;
583 zoom
= fmaxf(0.5, fminf(1.0, (float)(30.-(s
->ball
.z
))/10.));
584 show_image_rotozoom(GR_SHADOW
, s
->ball
.x
, s
->ball
.y
+3, rotate
, zoom
);
586 rotate
= (-s
->ball
.move_x
* (float)((float)time
/10.));
587 zoom
= 1.0 + fmaxf(0.0, (s
->ball
.z
-10.)/100.);
588 show_image_rotozoom(GR_BALL
, s
->ball
.x
, s
->ball
.y
- s
->ball
.z
, rotate
, zoom
);
590 /* Player 1's mouse rectangle */
591 if (PLAYER(s
, 1).input
!= NULL
&& PLAYER(s
, 1).input
->type
== INPUT_TYPE_MOUSE
) {
592 rectangle(PLAYER(s
, 1).x
-2+5, PLAYER(s
, 1).input
->my
-2, 4, 4, 255, 255, 255);
593 rectangle(PLAYER(s
, 1).x
-1+5, PLAYER(s
, 1).input
->my
-1, 2, 2, 0, 0, 0);
596 /* Player 2's mouse rectangle */
597 if (PLAYER(s
, 2).input
!= NULL
&& PLAYER(s
, 2).input
->type
== INPUT_TYPE_MOUSE
) {
598 rectangle(PLAYER(s
, 2).x
-2+5, PLAYER(s
, 2).input
->my
-2, 4, 4, 255, 255, 255);
599 rectangle(PLAYER(s
, 2).x
-1+5, PLAYER(s
, 2).input
->my
-1, 2, 2, 0, 0, 0);
602 font_draw_string(FONT_MEDIUM
, r
->text_status
,
603 (WIDTH
-font_get_string_width(FONT_MEDIUM
, r
->text_status
))/2, HEIGHT
-50);
605 for (i
=0; i
<s
->location
->rain
; i
++) {
608 draw_line_faded(x
, y
, x
+10, y
+30, 0, 0, 255, 100, 200, 255);
610 if (s
->location
->rain
) {
612 * Cheap-ish update of the whole screen. This can
613 * probably be optimized.
615 update_rect(0, 0, WIDTH
, HEIGHT
);
619 line_horiz( PLAYER(s
, 1).y
, 255, 0, 0);
620 line_horiz( PLAYER(s
, 2).y
, 0, 0, 255);
621 line_horiz( s
->ball
.y
, 0, 255, 0);
623 line_vert( PLAYER(s
, 1).x
, 255, 0, 0);
624 line_vert( PLAYER(s
, 2).x
, 0, 0, 255);
625 line_vert( s
->ball
.x
, 0, 255, 0);
627 line_horiz( GAME_Y_MIN
, 100, 100, 100);
628 line_horiz( GAME_Y_MAX
, 100, 100, 100);
630 switch (s
->location
->fog
) {
633 fill_image_offset(GR_FOG2
, 0, 0, WIDTH
, HEIGHT
, -time
/150, 0);
635 fill_image_offset(GR_FOG
, 0, 0, WIDTH
, HEIGHT
, -time
/100, 20);
637 fill_image_offset(GR_FOG2
, 0, 0, WIDTH
, HEIGHT
, -time
/180, 80);
639 fill_image_offset(GR_FOG
, 0, 0, WIDTH
, HEIGHT
, time
/200, 0);
643 if (s
->location
->night
) {
644 show_image(GR_NIGHT
, 0, 0, 255);
650 float get_move_y( GameState
* s
, unsigned char player
) {
651 float pct
, dest
, x_len
, y_len
;
652 float py
, by
, pa
, move_x
;
654 py
= (player
==1)?(PLAYER(s
, 1).y
):(PLAYER(s
, 2).y
);
655 by
= s
->ball
.y
- s
->ball
.z
;
657 move_x
= s
->ball
.move_x
;
659 /* -1.0 .. 1.0 for racket hit position */
660 pct
= fmaxf(-1.0, fminf(1.0, (by
-py
)/(pa
/2)));
662 /* Y destination for ball */
663 dest
= GAME_Y_MID
+ pct
*(GAME_Y_MAX
-GAME_Y_MIN
);
665 /* lengths for the ball's journey */
667 x_len
= GAME_X_MAX
- s
->ball
.x
;
669 x_len
= s
->ball
.x
- GAME_X_MIN
;
673 /* return the should-be value for move_y */
674 return (y_len
*move_x
)/(x_len
);
677 void input_human(GameState
* s
, int player
) {
678 bool hit
, topspin
, smash
;
681 /* For mouse input, hand the player coordinates to the InputDevice */
682 if (PLAYER(s
, player
).input
->type
== INPUT_TYPE_MOUSE
) {
683 PLAYER(s
, player
).input
->player_x
= (int)(PLAYER(s
, player
).x
);
684 PLAYER(s
, player
).input
->player_y
= (int)(PLAYER(s
, player
).y
);
687 move_y
= PLAYER_MOVE_Y
*input_device_get_axis(PLAYER(s
, player
).input
, INPUT_AXIS_Y
);
689 hit
= input_device_get_key(PLAYER(s
, player
).input
, INPUT_KEY_HIT
);
690 topspin
= input_device_get_key(PLAYER(s
, player
).input
, INPUT_KEY_TOPSPIN
);
691 smash
= input_device_get_key(PLAYER(s
, player
).input
, INPUT_KEY_SMASH
);
694 if (fabsf(move_y
) > fabsf(move_y
*PLAYER(s
, player
).accelerate
)) {
695 move_y
*= PLAYER(s
, player
).accelerate
;
697 PLAYER(s
, player
).y
+= move_y
;
698 PLAYER(s
, player
).accelerate
*= PLAYER_ACCEL_INCREASE
;
699 if (PLAYER(s
, player
).accelerate
> 190) {
700 PLAYER(s
, player
).accelerate
= 190;
703 PLAYER(s
, player
).accelerate
= PLAYER_ACCEL_DEFAULT
;
706 if(hit
|| topspin
|| smash
) {
707 PLAYER(s
, player
).desire
= (topspin
)?(DESIRE_TOPSPIN
):(DESIRE_NORMAL
);
708 PLAYER(s
, player
).desire
= (smash
)?(DESIRE_SMASH
):(PLAYER(s
, player
).desire
);
710 PLAYER(s
, player
).power
= fmaxf(10.0, fminf(PLAYER(s
, player
).power
*POWER_UP_FACTOR
, PLAYER_POWER_MAX
));
711 PLAYER(s
, player
).use_power
= false;
713 PLAYER(s
, player
).use_power
= true;
717 void input_ai(GameState
* s
, int player
) {
719 int ball_approaching
= 0;
721 if ((PLAYER(s
, player
).x
< GAME_X_MID
&& s
->ball
.move_x
<= 0) ||
722 (PLAYER(s
, player
).x
> GAME_X_MID
&& s
->ball
.move_x
>= 0)) {
723 ball_approaching
= 1;
726 /* FIXME - this is broken since the new physics model has been introduced */
728 if (fabsf(PLAYER(s
, player
).y
- (s
->ball
.y
-s
->ball
.z
)) > RACKET_Y_MID
*5) {
733 if( PLAYER(s
, player
).desire
== DESIRE_NORMAL
&& !IS_NEAR_Y_AI(PLAYER(s
, player
).y
, (s
->ball
.y
-s
->ball
.z
)) && ball_approaching
) {
734 if( PLAYER(s
, player
).y
< (s
->ball
.y
-s
->ball
.z
)) {
735 PLAYER(s
, player
).y
+= fminf(2*fact
, (s
->ball
.y
-s
->ball
.z
) - PLAYER(s
, player
).y
);
736 } else if( PLAYER(s
, player
).y
> (s
->ball
.y
-s
->ball
.z
)) {
737 PLAYER(s
, player
).y
-= fminf(2*fact
, PLAYER(s
, player
).y
- (s
->ball
.y
-s
->ball
.z
));
741 if (IS_NEAR_Y(PLAYER(s
, player
).y
, (s
->ball
.y
-s
->ball
.z
)) && IS_NEAR_X_AI(PLAYER(s
, player
).x
, s
->ball
.x
) && PLAYER(s
, player
).power
> 90.) {
742 PLAYER(s
, player
).use_power
= true;
743 } else if (ball_approaching
) {
744 PLAYER(s
, player
).power
= fmaxf(10., fminf(PLAYER(s
, player
).power
*POWER_UP_FACTOR
, PLAYER_POWER_MAX
));
745 PLAYER(s
, player
).use_power
= false;
750 void game_setup_serve( GameState
* s
) {
752 s
->ball
.y
= GAME_Y_MID
;
753 s
->ball
.move_x
= 0.0;
754 s
->ball
.move_y
= 0.0;
755 s
->ball
.move_z
= 0.0;
756 s
->ball
.last_hit_by
= -1;
757 s
->ball
.inhibit_gravity
= true;
759 if( s
->serving_player
== 1) {
760 s
->ball
.x
= GAME_X_MIN
-RACKET_X_MID
*1.5;
762 s
->ball
.x
= GAME_X_MAX
+RACKET_X_MID
*1.5;
766 int score_game(GameState
* s
) {
767 Player
*last_hit_by
= NULL
;
768 Player
*other
= NULL
;
770 Player
* winner
= NULL
;
771 Player
* loser
= NULL
;
773 /* determine "last hit by" and "other" */
774 if (s
->ball
.last_hit_by
== 1) {
775 last_hit_by
= &(PLAYER(s
, 1));
776 other
= &(PLAYER(s
, 2));
778 last_hit_by
= &(PLAYER(s
, 2));
779 other
= &(PLAYER(s
, 1));
782 switch (s
->score_event
) {
783 case SCORE_EVENT_NET
:
786 case SCORE_EVENT_OUT
:
787 case SCORE_EVENT_GROUND_INVALID
:
788 case SCORE_EVENT_OFFSCREEN
:
789 case SCORE_EVENT_GROUND_VALID
:
790 if (s
->ball
.ground_hit
) {
791 winner
= last_hit_by
;
800 /* determine loser based on winner */
801 if (winner
== last_hit_by
) {
807 /* we cannot be in an "impossibly high" set */
808 assert(s
->current_set
< SETS_TO_WIN
*2-1);
812 if( loser
->game
< winner
->game
-1) {
813 if( winner
->game
>= 4) {
814 winner
->game
= loser
->game
= 0;
815 winner
->sets
[s
->current_set
]++;
818 /* serving is changed when the "game" is over */
819 s
->serving_player
= (s
->serving_player
==1)?(2):(1);
821 #ifdef HAVE_VOICE_FILES
822 /* speak the current score */
823 voice_say_list(4, VOICE_ZERO_IN
+ (PLAYER(s
, 1).sets
[s
->current_set
])*2, VOICE_TO
, VOICE_ZERO_OUT
+ (PLAYER(s
, 2).sets
[s
->current_set
])*2, VOICE_IN_THE_FIRST_SET
+s
->current_set
);
826 /* scoring the set.. */
827 if( (winner
->sets
[s
->current_set
] == 6 && loser
->sets
[s
->current_set
] < 5) ||
828 winner
->sets
[s
->current_set
] == 7) {
830 s
->winner
= game_get_winner( s
);
835 /* forget this event - we've handled it */
836 s
->score_event
= SCORE_UNDECIDED
;
838 if (winner
== &PLAYER(s
, 1)) {
839 return WINNER_PLAYER1
;
841 return WINNER_PLAYER2
;
845 const char* format_sets(const GameState
* s
) {
846 static char sets
[100];
847 static char tmp
[100];
848 int i
, max
= s
->current_set
;
852 if( s
->winner
!= WINNER_NONE
) {
855 for( i
=0; i
<=max
; i
++) {
856 sprintf( tmp
, "%d:%d, ", PLAYER(s
, 1).sets
[i
], PLAYER(s
, 2).sets
[i
]);
860 sets
[strlen(sets
)-2] = '\0';
865 const char* format_game(const GameState
* s
) {
866 static char game
[100];
867 static const int game_scoring
[] = { 0, 15, 30, 40 };
869 if( PLAYER(s
, 1).game
< 4 && PLAYER(s
, 2).game
< 4) {
870 #ifdef HAVE_VOICE_FILES
871 if (PLAYER(s
, 1).game
> 0 || PLAYER(s
, 2).game
> 0) {
872 if (PLAYER(s
, 1).game
== PLAYER(s
, 2).game
) {
873 voice_say_list(2, VOICE_LOVE_IN
+ 2*(PLAYER(s
, 1).game
), VOICE_ALL
);
875 voice_say_list(2, VOICE_LOVE_IN
+ 2*(PLAYER(s
, 1).game
), VOICE_LOVE_OUT
+ 2*(PLAYER(s
, 2).game
));
879 sprintf( game
, "%d - %d", game_scoring
[PLAYER(s
, 1).game
], game_scoring
[PLAYER(s
, 2).game
]);
880 } else if( PLAYER(s
, 1).game
> PLAYER(s
, 2).game
) {
881 #ifdef HAVE_VOICE_FILES
882 voice_say_list(1, VOICE_ADVANTAGE_PLAYER_ONE
);
884 strcpy( game
, "advantage player 1");
885 } else if( PLAYER(s
, 1).game
< PLAYER(s
, 2).game
) {
886 #ifdef HAVE_VOICE_FILES
887 voice_say_list(1, VOICE_ADVANTAGE_PLAYER_TWO
);
889 strcpy( game
, "advantage player 2");
891 #ifdef HAVE_VOICE_FILES
892 voice_say_list(1, VOICE_DEUCE
);
894 strcpy( game
, "deuce");
900 const char* format_status(const GameState
* s
) {
901 static char status
[100];
902 static const char* set_names
[] = { "first", "second", "third", "fourth", "fifth" };
904 switch (s
->status_message
) {
907 case STATUSMSG_WELCOME
:
908 return "welcome to tennix " VERSION
;
909 case STATUSMSG_DEFAULT
:
910 sprintf(status
, "%d:%d in %s set",
911 PLAYER(s
, 1).sets
[s
->current_set
],
912 PLAYER(s
, 2).sets
[s
->current_set
],
913 set_names
[s
->current_set
]);
919 case STATUSMSG_FAULT
:
921 case STATUSMSG_P1SCORES
:
922 return "player 1 scores.";
923 case STATUSMSG_P2SCORES
:
924 return "player 2 scores.";
925 case STATUSMSG_VOLLEY
:
927 case STATUSMSG_DIDNTCATCH
:
928 return "did not catch the ball.";
934 winner_t
game_get_winner(const GameState
* s
) {
938 for( i
=0; i
<s
->current_set
; i
++) {
939 if( PLAYER(s
, 1).sets
[i
] > PLAYER(s
, 2).sets
[i
]) {
946 if( sets
[0] == SETS_TO_WIN
) return WINNER_PLAYER1
;
947 if( sets
[1] == SETS_TO_WIN
) return WINNER_PLAYER2
;