pass LDFLAGS to compiler at link time
[tennix.git] / game.c
blob11aa799a7d62ecae5e2978804006302317778f3c
2 /**
4 * Tennix! SDL Port
5 * Copyright (C) 2003, 2007, 2008, 2009 Thomas Perl <thp@thpinfo.com>
6 *
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,
20 * MA 02110-1301, USA.
22 **/
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <assert.h>
28 #include <unistd.h>
30 #include "tennix.h"
31 #include "game.h"
32 #include "graphics.h"
33 #include "input.h"
34 #include "sound.h"
35 #include "util.h"
36 #include "network.h"
39 GameState *gamestate_new() {
40 GameState *s;
42 GameState gs_template = {
43 NULL,
44 -1,
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 },
51 REFEREE_NORMAL,
53 WINNER_NONE,
54 SOUND_EVENT_NONE, /* sound events */
55 SCORE_UNDECIDED,
57 EVENTCOUNTER_GAMESTATE_START,
58 EVENTCOUNTER_GAMESTATE_START,
59 STATUSMSG_WELCOME,
62 s = (GameState*)malloc(sizeof(GameState));
63 if (s == NULL) abort();
65 memcpy(s, &gs_template, sizeof(GameState));
67 game_setup_serve(s);
69 return s;
73 int gamestate_save(GameState* s, const char* filename)
75 const Location *location;
76 InputDevice* input_devices[MAXPLAYERS];
77 int i, result = 0;
78 FILE* fp = NULL;
79 #ifndef WIN32
80 char tmp[MAXPATHLEN];
82 assert(getcwd(tmp, MAXPATHLEN) == tmp);
83 assert(chdir(getenv("HOME")) == 0);
84 #endif
86 /**
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).
92 **/
94 fp = fopen(filename, "w");
95 if (fp == NULL) {
96 return -1;
99 /* Save data for later recovery + clear */
100 location = s->location;
101 s->location = NULL;
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) {
108 result = -2;
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];
117 fclose(fp);
119 #ifndef WIN32
120 assert(chdir(tmp) == 0);
121 #endif
123 return result;
127 GameState* gamestate_load(const char* filename)
129 FILE* fp;
130 GameState* s = NULL;
131 #ifndef WIN32
132 char tmp[MAXPATHLEN];
134 assert(getcwd(tmp, MAXPATHLEN) == tmp);
135 assert(chdir(getenv("HOME")) == 0);
136 #endif
138 fp = fopen(filename, "r");
140 if (fp != NULL) {
141 s = (GameState*)malloc(sizeof(GameState));
142 if (s != NULL) {
143 if (fread(s, sizeof(GameState), 1, fp) == 1) {
144 /* Restore/create process-specific data */
145 /* FIXME: s->location, players' "input" */
146 } else {
147 free(s);
148 s = NULL;
151 fclose(fp);
154 #ifndef WIN32
155 assert(chdir(tmp) == 0);
156 #endif
158 return s;
162 void gameloop(GameState *s, TennixNet* c) {
163 Uint32 ot = SDL_GetTicks();
164 Uint32 nt;
165 Uint32 dt = GAME_TICKS;
166 Uint32 diff;
167 Uint32 accumulator = 0;
168 bool quit = false;
169 int p;
170 int i=0;
171 RenderState r = {
172 SOUND_EVENT_NONE,
173 REFEREE_COUNT,
174 EVENTCOUNTER_RENDERSTATE_START,
175 EVENTCOUNTER_RENDERSTATE_START,
176 STATUSMSG_NONE,
177 NULL,
178 NULL,
179 NULL,
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 */
187 #endif
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
201 frames = 0;
202 ft = SDL_GetTicks();
203 #endif
204 while( !quit) {
205 nt = SDL_GetTicks();
206 diff = nt-ot;
207 if( diff > 2000) {
208 diff = 0;
211 accumulator += diff;
212 ot = nt;
214 while( accumulator >= dt) {
215 step(s);
217 network_receive(c);
218 network_get_gamestate(c, s);
220 if ((i++) % 10 == 0) {
221 network_send_state(c, s);
224 quit = handle_input(s, c);
225 accumulator -= dt;
228 #ifdef ENABLE_FPS_LIMIT
229 while (frames*1000.0/((float)(SDL_GetTicks()-ft+1))>(float)(DEFAULT_FPS)) {
230 SDL_Delay(10);
232 frames++;
233 #endif
235 render(s, &r);
238 for (p=1; p<=MAXPLAYERS; p++) {
239 input_device_part_game(PLAYER(s, p).input);
242 clear_screen();
243 //rectangle(0, 0, WIDTH, HEIGHT, 80, 80, 80);
244 store_screen();
246 stop_sample(SOUND_AUDIENCE);
247 stop_sample(SOUND_RAIN);
250 void step(GameState* s) {
251 bool ground_event = false;
252 int p;
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,
266 PLAYER_POWER_MAX));
270 if (s->ball.z < 0) {
271 /* ground event */
272 ground_event = true;
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;
280 } else {
281 s->ball.move_z = 0;
283 s->ball.z = 0;
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) {
291 s->ball.x += 1;
292 } else {
293 s->ball.x -= 1;
296 s->ball.move_x = 0;
297 s->ball.move_y = 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;
324 } else {
325 /* first ground hit in valid area */
326 s->ball.ground_hit = true;
328 } else {
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 */
341 s->score_time++;
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)) {
346 case WINNER_PLAYER1:
347 s->status_message = STATUSMSG_P1SCORES;
348 s->referee = REFEREE_PLAYER1;
349 break;
350 case WINNER_PLAYER2:
351 s->status_message = STATUSMSG_P2SCORES;
352 s->referee = REFEREE_PLAYER2;
353 break;
354 default:
355 assert(0);
356 break;
358 s->score_time = 0;
360 game_setup_serve(s);
361 if (s->location->max_visitors > 100) {
362 s->sound_events ^= SOUND_EVENT_APPLAUSE;
365 } else {
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) {
369 /* RACKET HIT */
370 if (!s->ball.ground_hit && s->ball.move_x != 0.0) {
371 s->status_message = STATUSMSG_VOLLEY;
372 } else {
373 s->status_message = STATUSMSG_DEFAULT;
375 switch (PLAYER(s, p).desire) {
376 case DESIRE_NORMAL:
377 /* normal swing */
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;
380 break;
381 case DESIRE_TOPSPIN:
382 /* top spin */
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;
385 break;
386 case DESIRE_SMASH:
387 /* smash */
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;
390 break;
391 default:
392 assert(false);
393 break;
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;
400 if (p==2) {
401 s->ball.move_x *= -1;
408 bool handle_input(GameState* s, TennixNet* c) {
409 static NetworkGameState tmp;
410 static NetworkInputData net_input;
411 Uint8* keys = NULL;
412 int p;
414 SDL_PumpEvents();
415 keys = SDL_GetKeyState(NULL);
417 if (keys['1']) {
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));
431 input_human(s, p);
432 if (PLAYER(s, p).input->type != INPUT_TYPE_NETWORK) {
433 network_send_input(c, &(PLAYER(s, p).input->net));
435 } else {
436 input_ai(s, p);
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) {
453 int x, y, b;
454 unsigned int i;
455 float zoom;
456 float rotate;
457 int t=1000;
458 soundevent_t sounds;
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);
479 } else {
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) {
488 clear_screen();
489 //rectangle(0, 0, WIDTH, HEIGHT, 80, 80, 80);
490 store_screen();
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);*/
494 updatescr();
495 return;
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;
519 clear_screen();
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) {
528 case REFEREE_NORMAL:
529 t = 1000;
530 break;
531 case REFEREE_OUT:
532 t = 200;
533 break;
534 case REFEREE_PLAYER1:
535 case REFEREE_PLAYER2:
536 t = 400;
537 break;
539 t = (time/t)%4;
540 switch (t) {
541 case 0:
542 t=0;
543 break;
544 case 1:
545 t=1;
546 break;
547 case 2:
548 t=0;
549 break;
550 case 3:
551 t=2;
552 break;
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;
560 store_screen();
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;
578 } else {
579 b = 0;
582 rotate = 0.0;
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++) {
606 x = rand()%WIDTH;
607 y = rand()%HEIGHT;
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);
618 #ifdef DEBUG
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);
629 #endif
630 switch (s->location->fog) {
631 default:
632 case 4:
633 fill_image_offset(GR_FOG2, 0, 0, WIDTH, HEIGHT, -time/150, 0);
634 case 3:
635 fill_image_offset(GR_FOG, 0, 0, WIDTH, HEIGHT, -time/100, 20);
636 case 2:
637 fill_image_offset(GR_FOG2, 0, 0, WIDTH, HEIGHT, -time/180, 80);
638 case 1:
639 fill_image_offset(GR_FOG, 0, 0, WIDTH, HEIGHT, time/200, 0);
640 case 0:
641 break;
643 if (s->location->night) {
644 show_image(GR_NIGHT, 0, 0, 255);
647 updatescr();
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;
656 pa = RACKET_Y_MID*2;
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 */
666 if( player == 1) {
667 x_len = GAME_X_MAX - s->ball.x;
668 } else {
669 x_len = s->ball.x - GAME_X_MIN;
671 y_len = dest - by;
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;
679 float move_y;
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);
693 if (move_y != 0) {
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;
702 } else {
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;
712 } else {
713 PLAYER(s, player).use_power = true;
717 void input_ai(GameState* s, int player) {
718 float fact = 1.7;
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) {
729 fact = 3.5;
732 if(1) {
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) {
751 s->ball.z = 10.0;
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;
761 } else {
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));
777 } else {
778 last_hit_by = &(PLAYER(s, 2));
779 other = &(PLAYER(s, 1));
782 switch (s->score_event) {
783 case SCORE_EVENT_NET:
784 winner = other;
785 break;
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;
792 } else {
793 winner = other;
795 break;
796 default:
797 break;
800 /* determine loser based on winner */
801 if (winner == last_hit_by) {
802 loser = other;
803 } else {
804 loser = last_hit_by;
807 /* we cannot be in an "impossibly high" set */
808 assert(s->current_set < SETS_TO_WIN*2-1);
810 winner->game++;
811 s->ec_game++;
812 if( loser->game < winner->game-1) {
813 if( winner->game >= 4) {
814 winner->game = loser->game = 0;
815 winner->sets[s->current_set]++;
816 s->ec_sets++;
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);
824 #endif
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) {
829 s->current_set++;
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;
840 } else {
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;
850 sets[0] = '\0';
852 if( s->winner != WINNER_NONE) {
853 max--;
855 for( i=0; i<=max; i++) {
856 sprintf( tmp, "%d:%d, ", PLAYER(s, 1).sets[i], PLAYER(s, 2).sets[i]);
857 strcat( sets, tmp);
860 sets[strlen(sets)-2] = '\0';
862 return sets;
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);
874 } else {
875 voice_say_list(2, VOICE_LOVE_IN + 2*(PLAYER(s, 1).game), VOICE_LOVE_OUT + 2*(PLAYER(s, 2).game));
878 #endif
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);
883 #endif
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);
888 #endif
889 strcpy( game, "advantage player 2");
890 } else {
891 #ifdef HAVE_VOICE_FILES
892 voice_say_list(1, VOICE_DEUCE);
893 #endif
894 strcpy( game, "deuce");
897 return game;
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) {
905 case STATUSMSG_NONE:
906 return "";
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]);
914 return status;
915 case STATUSMSG_NET:
916 return "net!";
917 case STATUSMSG_OUT:
918 return "out!";
919 case STATUSMSG_FAULT:
920 return "fault!";
921 case STATUSMSG_P1SCORES:
922 return "player 1 scores.";
923 case STATUSMSG_P2SCORES:
924 return "player 2 scores.";
925 case STATUSMSG_VOLLEY:
926 return "volley!";
927 case STATUSMSG_DIDNTCATCH:
928 return "did not catch the ball.";
929 default:
930 return "";
934 winner_t game_get_winner(const GameState* s) {
935 unsigned int i;
936 int sets[2] = {0};
938 for( i=0; i<s->current_set; i++) {
939 if( PLAYER(s, 1).sets[i] > PLAYER(s, 2).sets[i]) {
940 sets[0]++;
941 } else {
942 sets[1]++;
946 if( sets[0] == SETS_TO_WIN) return WINNER_PLAYER1;
947 if( sets[1] == SETS_TO_WIN) return WINNER_PLAYER2;
949 return WINNER_NONE;