Version 1.2.0 with new build/configure system
[tennix.git] / src / game.cc
blob9b0e73d74a740df5eb32c5e57a9cf209cb2402f5
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 <unistd.h>
29 #include "tennix.h"
30 #include "game.h"
31 #include "graphics.h"
32 #include "input.h"
33 #include "sound.h"
34 #include "util.h"
35 #include "network.h"
38 GameState *gamestate_new() {
39 GameState *s;
41 GameState gs_template = {
42 NULL,
43 -1,
44 { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false, -1, false },
46 { 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 },
47 { 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 },
50 REFEREE_NORMAL,
52 WINNER_NONE,
53 SOUND_EVENT_NONE, /* sound events */
54 SCORE_UNDECIDED,
56 EVENTCOUNTER_GAMESTATE_START,
57 EVENTCOUNTER_GAMESTATE_START,
58 STATUSMSG_WELCOME,
61 s = (GameState*)malloc(sizeof(GameState));
62 if (s == NULL) abort();
64 memcpy(s, &gs_template, sizeof(GameState));
66 game_setup_serve(s);
68 return s;
72 int gamestate_save(GameState* s, const char* filename)
74 const Location *location;
75 InputDevice* input_devices[MAXPLAYERS];
76 int i, result = 0;
77 FILE* fp = NULL;
79 /**
80 * Process-specific data (pointers to data
81 * structures only of interest to this process)
82 * has to be "removed" before saving.
84 * It can later be restored (in gamestate_load).
85 **/
87 fp = fopen(filename, "w");
88 if (fp == NULL) {
89 return -1;
92 /* Save data for later recovery + clear */
93 location = s->location;
94 s->location = NULL;
95 for (i=1; i<=MAXPLAYERS; i++) {
96 input_devices[i-1] = PLAYER(s, i).input;
97 PLAYER(s, i).input = NULL;
100 if (fwrite(s, sizeof(GameState), 1, fp) != 1) {
101 result = -2;
104 /* Restore process-specific data */
105 s->location = location;
106 for (i=1; i<=MAXPLAYERS; i++) {
107 PLAYER(s, i).input = input_devices[i-1];
110 fclose(fp);
112 return result;
116 GameState* gamestate_load(const char* filename)
118 FILE* fp;
119 GameState* s = NULL;
121 fp = fopen(filename, "r");
123 if (fp != NULL) {
124 s = (GameState*)malloc(sizeof(GameState));
125 if (s != NULL) {
126 if (fread(s, sizeof(GameState), 1, fp) == 1) {
127 /* Restore/create process-specific data */
128 /* FIXME: s->location, players' "input" */
129 } else {
130 free(s);
131 s = NULL;
134 fclose(fp);
137 return s;
141 void gameloop(GameState *s, TennixNet* c) {
142 Uint32 ot = SDL_GetTicks();
143 Uint32 nt;
144 Uint32 dt = GAME_TICKS;
145 Uint32 diff;
146 Uint32 accumulator = 0;
147 #ifdef HAVE_SDL_NET
148 int i = 0;
149 #endif /* HAVE_SDL_NET */
150 bool quit = false;
151 int p;
152 RenderState r = {
153 SOUND_EVENT_NONE,
154 REFEREE_COUNT,
155 EVENTCOUNTER_RENDERSTATE_START,
156 EVENTCOUNTER_RENDERSTATE_START,
157 STATUSMSG_NONE,
158 NULL,
159 NULL,
160 NULL,
163 /* Catch-up with existing sound events */
164 r.sound_events = s->sound_events;
166 #ifdef ENABLE_FPS_LIMIT
167 Uint32 ft, frames; /* frame timer and frames */
168 #endif
170 if (s->location->rain > 0) {
171 play_sample_background(SOUND_RAIN);
173 if (s->location->max_visitors > 100) {
174 play_sample_loop(SOUND_AUDIENCE);
177 for (p=1; p<=MAXPLAYERS; p++) {
178 input_device_join_game(PLAYER(s, p).input, s, p);
181 #ifdef ENABLE_FPS_LIMIT
182 frames = 0;
183 ft = SDL_GetTicks();
184 #endif
185 while( !quit) {
186 nt = SDL_GetTicks();
187 diff = nt-ot;
188 if( diff > 2000) {
189 diff = 0;
192 accumulator += diff;
193 ot = nt;
195 while( accumulator >= dt) {
196 step(s);
198 #ifdef HAVE_SDL_NET
199 network_receive(c);
200 network_get_gamestate(c, s);
202 if ((i++) % 10 == 0) {
203 network_send_state(c, s);
205 #endif /* HAVE_SDL_NET */
207 quit = handle_input(s, c);
208 accumulator -= dt;
211 #ifdef ENABLE_FPS_LIMIT
212 while (frames*1000.0/((float)(SDL_GetTicks()-ft+1))>(float)(DEFAULT_FPS)) {
213 SDL_Delay(10);
215 frames++;
216 #endif
218 render(s, &r);
221 for (p=1; p<=MAXPLAYERS; p++) {
222 input_device_part_game(PLAYER(s, p).input);
225 clear_screen();
226 //rectangle(0, 0, WIDTH, HEIGHT, 80, 80, 80);
227 store_screen();
229 stop_sample(SOUND_AUDIENCE);
230 stop_sample(SOUND_RAIN);
233 void step(GameState* s) {
234 bool ground_event = false;
235 int p;
237 s->ball.z += s->ball.move_z;
238 if (!s->ball.inhibit_gravity) {
239 s->ball.move_z += GRAVITY;
242 s->ball.x += s->ball.move_x;
243 s->ball.y += s->ball.move_y;
245 for (p=1; p<=MAXPLAYERS; p++) {
246 if (PLAYER(s, p).use_power) {
247 PLAYER(s, p).power = fmaxf(0.0, fminf(
248 PLAYER(s, p).power*POWER_DOWN_FACTOR,
249 PLAYER_POWER_MAX));
253 if (s->ball.z < 0) {
254 /* ground event */
255 ground_event = true;
257 s->referee = REFEREE_NORMAL;
259 /* bounce from the ground */
260 if (fabsf(s->ball.move_z) > 0.3) {
261 s->sound_events ^= SOUND_EVENT_GROUND;
262 s->ball.move_z *= -BALL_RESTITUTION;
263 } else {
264 s->ball.move_z = 0;
266 s->ball.z = 0;
269 if (NET_COLLISION_BALL(s->ball)) {
270 /* the net blocks movement of the ball */
271 while (NET_COLLISION_BALL(s->ball)) {
272 /* make sure the ball appears OUTSIDE of the net */
273 if (s->ball.move_x < 0) {
274 s->ball.x += 1;
275 } else {
276 s->ball.x -= 1;
279 s->ball.move_x = 0;
280 s->ball.move_y = 0;
283 /* see if we have something to score */
284 if (s->score_event == SCORE_UNDECIDED) {
285 if (NET_COLLISION_BALL(s->ball)) {
286 /* the ball "fell" into the net */
287 s->score_event = SCORE_EVENT_NET;
288 s->sound_events ^= SOUND_EVENT_OUT;
289 s->status_message = STATUSMSG_NET;
290 } else if (IS_OFFSCREEN(s->ball.x, s->ball.y)) {
291 /* ball flew offscreen */
292 s->score_event = SCORE_EVENT_OFFSCREEN;
293 s->sound_events ^= SOUND_EVENT_OUT;
294 s->status_message = STATUSMSG_OUT;
295 } else if (ground_event) {
296 /* the ball hit the ground on the screen */
297 if (IS_OUT(s->ball.x, s->ball.y)) {
298 /* the ball bounced in the OUT area */
299 s->score_event = SCORE_EVENT_OUT;
300 s->sound_events ^= SOUND_EVENT_OUT;
301 s->status_message = STATUSMSG_OUT;
302 } else if (GROUND_IS_VALID(s->ball.last_hit_by, s->ball.x, s->ball.y)) {
303 if (s->ball.ground_hit) {
304 s->score_event = SCORE_EVENT_GROUND_VALID;
305 s->sound_events ^= SOUND_EVENT_OUT;
306 s->status_message = STATUSMSG_DIDNTCATCH;
307 } else {
308 /* first ground hit in valid area */
309 s->ball.ground_hit = true;
311 } else {
312 /* ball hit somewhere invalid */
313 s->score_event = SCORE_EVENT_GROUND_INVALID;
314 s->sound_events ^= SOUND_EVENT_OUT;
315 s->status_message = STATUSMSG_FAULT;
320 if (s->score_event != SCORE_UNDECIDED) {
321 /* we have some scoring to do */
322 if (s->score_time < SCORING_DELAY/GAME_TICKS) {
323 /* schedule scoring in the future */
324 s->score_time++;
325 s->referee = REFEREE_OUT;
326 } else if (s->score_time >= SCORING_DELAY/GAME_TICKS) {
327 /* time has ran out - score now */
328 switch (score_game(s)) {
329 case WINNER_PLAYER1:
330 s->status_message = STATUSMSG_P1SCORES;
331 s->referee = REFEREE_PLAYER1;
332 break;
333 case WINNER_PLAYER2:
334 s->status_message = STATUSMSG_P2SCORES;
335 s->referee = REFEREE_PLAYER2;
336 break;
337 default:
338 tnx_assert(0);
339 break;
341 s->score_time = 0;
343 game_setup_serve(s);
344 if (s->location->max_visitors > 100) {
345 s->sound_events ^= SOUND_EVENT_APPLAUSE;
348 } else {
349 /* score is still undecided - do the racket swing thing */
350 for (p=1; p<=2; p++) {
351 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) {
352 /* RACKET HIT */
353 if (!s->ball.ground_hit && s->ball.move_x != 0.0) {
354 s->status_message = STATUSMSG_VOLLEY;
355 } else {
356 s->status_message = STATUSMSG_DEFAULT;
358 switch (PLAYER(s, p).desire) {
359 case DESIRE_NORMAL:
360 /* normal swing */
361 s->ball.move_x = 2.7 + 2.0*PLAYER(s, p).power/PLAYER_POWER_MAX;
362 s->ball.move_z = 1.2*PLAYER(s, p).power/PLAYER_POWER_MAX;
363 break;
364 case DESIRE_TOPSPIN:
365 /* top spin */
366 s->ball.move_x = 1.1 + 2.2*PLAYER(s, p).power/PLAYER_POWER_MAX;
367 s->ball.move_z = 2.5*PLAYER(s, p).power/PLAYER_POWER_MAX;
368 break;
369 case DESIRE_SMASH:
370 /* smash */
371 s->ball.move_x = 4.0 + 3.0*PLAYER(s, p).power/PLAYER_POWER_MAX;
372 s->ball.move_z = 1.1*PLAYER(s, p).power/PLAYER_POWER_MAX;
373 break;
374 default:
375 tnx_assert(false);
376 break;
378 s->ball.move_y = get_move_y( s, p);
379 s->sound_events ^= SOUND_EVENT_RACKET;
380 s->ball.ground_hit = false;
381 s->ball.inhibit_gravity = false;
382 s->ball.last_hit_by = p;
383 if (p==2) {
384 s->ball.move_x *= -1;
391 bool handle_input(GameState* s, TennixNet* c) {
392 Uint8* keys = NULL;
393 int p;
395 SDL_PumpEvents();
396 keys = SDL_GetKeyState(NULL);
398 #ifdef HAVE_SDL_NET
399 static NetworkGameState tmp;
400 static NetworkInputData net_input;
402 if (keys['1']) {
403 net_serialize_gamestate(s, &tmp);
404 } else if (keys['2']) {
405 net_unserialize_gamestate(&tmp, s);
408 network_get_input(c, &net_input);
409 #endif /* HAVE_SDL_NET */
410 if (s->winner == WINNER_NONE) {
411 for (p=1; p<=MAXPLAYERS; p++) {
412 if( PLAYER(s, p).type == PLAYER_TYPE_HUMAN) {
413 #ifdef HAVE_SDL_NET
414 if (PLAYER(s, p).input->type == INPUT_TYPE_NETWORK) {
415 memcpy(&(PLAYER(s, p).input->net), &net_input,
416 sizeof(NetworkInputData));
418 #endif /* HAVE_SDL_NET */
419 input_human(s, p);
420 #ifdef HAVE_SDL_NET
421 if (PLAYER(s, p).input->type != INPUT_TYPE_NETWORK) {
422 network_send_input(c, &(PLAYER(s, p).input->net));
424 #endif /* HAVE_SDL_NET */
425 } else {
426 input_ai(s, p);
429 /* Make sure player coordinates are valid */
430 if (PLAYER(s, p).y < PLAYER_Y_MIN) {
431 PLAYER(s, p).y = PLAYER_Y_MIN;
432 } else if (PLAYER(s, p).y > PLAYER_Y_MAX) {
433 PLAYER(s, p).y = PLAYER_Y_MAX;
438 /* Maemo: The "F4" button is the "Open menu" button */
439 return (keys[SDLK_ESCAPE] || keys['q']);
442 void render(const GameState* s, RenderState* r) {
443 int x, y;
444 unsigned int i;
445 float zoom;
446 float rotate;
447 int t=1000;
448 soundevent_t sounds;
449 Uint32 time = SDL_GetTicks();
451 /* The bits in sound_events flip when the sound should play */
452 if ((sounds = (r->sound_events ^ s->sound_events)) != 0) {
453 if (sounds & SOUND_EVENT_GROUND) {
454 sample_volume_group(SOUND_GROUND_FIRST, SOUND_GROUND_LAST,
455 fmaxf(0.0, fminf(1.0, fabsf(s->ball.move_z)/2)));
456 pan_sample_group(SOUND_GROUND_FIRST, SOUND_GROUND_LAST,
457 fmaxf(0.0, fminf(1.0, (s->ball.x)/WIDTH)));
458 play_sample(SOUND_GROUND);
460 if (sounds & SOUND_EVENT_OUT) {
461 play_sample(SOUND_OUT);
463 if (sounds & SOUND_EVENT_APPLAUSE) {
464 play_sample(SOUND_APPLAUSE);
466 if (sounds & SOUND_EVENT_RACKET) {
467 if (((s->ball.x)/WIDTH) < 0.5) {
468 pan_sample_group(SOUND_RACKET_FIRST, SOUND_RACKET_LAST, 0.3);
469 } else {
470 pan_sample_group(SOUND_RACKET_FIRST, SOUND_RACKET_LAST, 0.7);
472 play_sample(SOUND_RACKET);
474 r->sound_events = s->sound_events;
477 if( s->winner != WINNER_NONE) {
478 clear_screen();
479 //rectangle(0, 0, WIDTH, HEIGHT, 80, 80, 80);
480 store_screen();
481 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);
482 /*sprintf( s->game_score_str, "player %d wins the match with %s", s->winner, format_sets( s));
483 font_draw_string(FONT_LARGE, s->game_score_str, (WIDTH-font_get_string_width(FONT_LARGE, s->game_score_str))/2, HEIGHT/2 + 30);*/
484 updatescr();
485 return;
487 if ((r->referee != s->referee) ||
488 (r->ec_game != s->ec_game) ||
489 (r->ec_sets != s->ec_sets) ||
490 (r->status_message != s->status_message)) {
491 /* Update status message text */
492 if (r->status_message != s->status_message) {
493 r->text_status = format_status(s);
494 r->status_message = s->status_message;
496 /* Update game status text */
497 if (r->ec_game != s->ec_game) {
498 r->text_game = format_game(s);
499 r->ec_game = s->ec_game;
501 /* Update set status text */
502 if (r->ec_sets != s->ec_sets) {
503 r->text_sets = format_sets(s);
504 if (r->status_message == STATUSMSG_DEFAULT) {
505 r->text_status = format_status(s);
507 r->ec_sets = s->ec_sets;
509 clear_screen();
510 //rectangle(0, 0, WIDTH, HEIGHT, 80, 80, 80);
511 fill_image(s->location->court_type, 120, 120, 400, 250);
512 show_image(GR_COURT, 0, 0, 255);
513 font_draw_string(FONT_XLARGE, r->text_game, 14, 14);
514 font_draw_string(FONT_XLARGE, r->text_sets,
515 (WIDTH-font_get_string_width(FONT_XLARGE, r->text_sets))-14, 14);
516 if (s->location->has_referee) {
517 switch (s->referee) {
518 case REFEREE_NORMAL:
519 t = 1000;
520 break;
521 case REFEREE_OUT:
522 t = 200;
523 break;
524 case REFEREE_PLAYER1:
525 case REFEREE_PLAYER2:
526 t = 400;
527 break;
529 t = (time/t)%4;
530 switch (t) {
531 case 0:
532 t=0;
533 break;
534 case 1:
535 t=1;
536 break;
537 case 2:
538 t=0;
539 break;
540 case 3:
541 t=2;
542 break;
544 show_sprite( GR_REFEREE, s->referee*3+t, 12, 250, 10, 255);
545 if (voice_finished_flag == 0) {
546 show_sprite(GR_TALK, (time/150)%2, 2, 280, 45, 255);
549 r->referee = s->referee;
550 store_screen();
553 /* show_sprite( GR_RACKET, (!PLAYER(s, 1).state), 4, PLAYER(s, 1).x-RACKET_X_MID, PLAYER(s, 1).y-RACKET_Y_MID, 255);
554 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);*/
555 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);
556 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);
558 rectangle(10, HEIGHT-30, PLAYER_POWER_MAX, 10, 50, 50, 50);
559 rectangle(WIDTH-PLAYER_POWER_MAX-10, HEIGHT-30, PLAYER_POWER_MAX, 10, 50, 50, 50);
561 rectangle(10, HEIGHT-30, (int)(PLAYER(s, 1).power), 10, 200, 200, 200);
562 rectangle(WIDTH-PLAYER_POWER_MAX-10, HEIGHT-30, (int)(PLAYER(s, 2).power), 10, 200, 200, 200);
564 rotate = 0.0;
565 zoom = fmaxf(0.5, fminf(1.0, (float)(30.-(s->ball.z))/10.));
566 show_image_rotozoom(GR_SHADOW, s->ball.x, s->ball.y+3, rotate, zoom);
568 rotate = (-s->ball.move_x * (float)((float)time/10.));
569 zoom = 1.0 + fmaxf(0.0, (s->ball.z-10.)/100.);
570 show_image_rotozoom(GR_BALL, s->ball.x, s->ball.y - s->ball.z, rotate, zoom);
572 /* Player 1's mouse rectangle */
573 if (PLAYER(s, 1).input != NULL && PLAYER(s, 1).input->type == INPUT_TYPE_MOUSE) {
574 rectangle(PLAYER(s, 1).x-2+5, PLAYER(s, 1).input->my-2, 4, 4, 255, 255, 255);
575 rectangle(PLAYER(s, 1).x-1+5, PLAYER(s, 1).input->my-1, 2, 2, 0, 0, 0);
578 /* Player 2's mouse rectangle */
579 if (PLAYER(s, 2).input != NULL && PLAYER(s, 2).input->type == INPUT_TYPE_MOUSE) {
580 rectangle(PLAYER(s, 2).x-2+5, PLAYER(s, 2).input->my-2, 4, 4, 255, 255, 255);
581 rectangle(PLAYER(s, 2).x-1+5, PLAYER(s, 2).input->my-1, 2, 2, 0, 0, 0);
584 font_draw_string(FONT_MEDIUM, r->text_status,
585 (WIDTH-font_get_string_width(FONT_MEDIUM, r->text_status))/2, HEIGHT-50);
587 for (i=0; i<s->location->rain; i++) {
588 x = rand()%WIDTH;
589 y = rand()%HEIGHT;
590 draw_line_faded(x, y, x+10, y+30, 0, 0, 255, 100, 200, 255);
592 if (s->location->rain) {
594 * Cheap-ish update of the whole screen. This can
595 * probably be optimized.
597 update_rect(0, 0, WIDTH, HEIGHT);
600 #ifdef DEBUG
601 line_horiz( PLAYER(s, 1).y, 255, 0, 0);
602 line_horiz( PLAYER(s, 2).y, 0, 0, 255);
603 line_horiz( s->ball.y, 0, 255, 0);
605 line_vert( PLAYER(s, 1).x, 255, 0, 0);
606 line_vert( PLAYER(s, 2).x, 0, 0, 255);
607 line_vert( s->ball.x, 0, 255, 0);
609 line_horiz( GAME_Y_MIN, 100, 100, 100);
610 line_horiz( GAME_Y_MAX, 100, 100, 100);
611 #endif
612 switch (s->location->fog) {
613 default:
614 case 4:
615 fill_image_offset(GR_FOG2, 0, 0, WIDTH, HEIGHT, -time/150, 0);
616 case 3:
617 fill_image_offset(GR_FOG, 0, 0, WIDTH, HEIGHT, -time/100, 20);
618 case 2:
619 fill_image_offset(GR_FOG2, 0, 0, WIDTH, HEIGHT, -time/180, 80);
620 case 1:
621 fill_image_offset(GR_FOG, 0, 0, WIDTH, HEIGHT, time/200, 0);
622 case 0:
623 break;
625 if (s->location->night) {
626 show_image(GR_NIGHT, 0, 0, 255);
629 updatescr();
632 float get_move_y( GameState* s, unsigned char player) {
633 float pct, dest, x_len, y_len;
634 float py, by, pa, move_x;
636 py = (player==1)?(PLAYER(s, 1).y):(PLAYER(s, 2).y);
637 by = s->ball.y - s->ball.z;
638 pa = RACKET_Y_MID*2;
639 move_x = s->ball.move_x;
641 /* -1.0 .. 1.0 for racket hit position */
642 pct = fmaxf(-1.0, fminf(1.0, (by-py)/(pa/2)));
644 /* Y destination for ball */
645 dest = GAME_Y_MID + pct*(GAME_Y_MAX-GAME_Y_MIN);
647 /* lengths for the ball's journey */
648 if( player == 1) {
649 x_len = GAME_X_MAX - s->ball.x;
650 } else {
651 x_len = s->ball.x - GAME_X_MIN;
653 y_len = dest - by;
655 /* return the should-be value for move_y */
656 return (y_len*move_x)/(x_len);
659 void input_human(GameState* s, int player) {
660 bool hit, topspin, smash;
661 float move_y;
663 /* For mouse input, hand the player coordinates to the InputDevice */
664 if (PLAYER(s, player).input->type == INPUT_TYPE_MOUSE) {
665 PLAYER(s, player).input->player_x = (int)(PLAYER(s, player).x);
666 PLAYER(s, player).input->player_y = (int)(PLAYER(s, player).y);
669 move_y = PLAYER_MOVE_Y*input_device_get_axis(PLAYER(s, player).input, INPUT_AXIS_Y);
671 hit = input_device_get_key(PLAYER(s, player).input, INPUT_KEY_HIT);
672 topspin = input_device_get_key(PLAYER(s, player).input, INPUT_KEY_TOPSPIN);
673 smash = input_device_get_key(PLAYER(s, player).input, INPUT_KEY_SMASH);
675 if (move_y != 0) {
676 if (fabsf(move_y) > fabsf(move_y*PLAYER(s, player).accelerate)) {
677 move_y *= PLAYER(s, player).accelerate;
679 PLAYER(s, player).y += move_y;
680 PLAYER(s, player).accelerate *= PLAYER_ACCEL_INCREASE;
681 if (PLAYER(s, player).accelerate > 190) {
682 PLAYER(s, player).accelerate = 190;
684 } else {
685 PLAYER(s, player).accelerate = PLAYER_ACCEL_DEFAULT;
688 if(hit || topspin || smash) {
689 PLAYER(s, player).desire = (topspin)?(DESIRE_TOPSPIN):(DESIRE_NORMAL);
690 PLAYER(s, player).desire = (smash)?(DESIRE_SMASH):(PLAYER(s, player).desire);
692 PLAYER(s, player).power = fmaxf(10.0, fminf(PLAYER(s, player).power*POWER_UP_FACTOR, PLAYER_POWER_MAX));
693 PLAYER(s, player).use_power = false;
694 } else {
695 PLAYER(s, player).use_power = true;
699 void input_ai(GameState* s, int player) {
700 float fact = 1.7;
701 int ball_approaching = 0;
703 if ((PLAYER(s, player).x < GAME_X_MID && s->ball.move_x <= 0) ||
704 (PLAYER(s, player).x > GAME_X_MID && s->ball.move_x >= 0)) {
705 ball_approaching = 1;
708 /* FIXME - this is broken since the new physics model has been introduced */
710 if (fabsf(PLAYER(s, player).y - (s->ball.y-s->ball.z)) > RACKET_Y_MID*5) {
711 fact = 3.5;
714 if(1) {
715 if( PLAYER(s, player).desire == DESIRE_NORMAL && !IS_NEAR_Y_AI(PLAYER(s, player).y, (s->ball.y-s->ball.z)) && ball_approaching) {
716 if( PLAYER(s, player).y < (s->ball.y-s->ball.z)) {
717 PLAYER(s, player).y += fminf(2*fact, (s->ball.y-s->ball.z) - PLAYER(s, player).y);
718 } else if( PLAYER(s, player).y > (s->ball.y-s->ball.z)) {
719 PLAYER(s, player).y -= fminf(2*fact, PLAYER(s, player).y - (s->ball.y-s->ball.z));
723 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.) {
724 PLAYER(s, player).use_power = true;
725 } else if (ball_approaching) {
726 PLAYER(s, player).power = fmaxf(10., fminf(PLAYER(s, player).power*POWER_UP_FACTOR, PLAYER_POWER_MAX));
727 PLAYER(s, player).use_power = false;
732 void game_setup_serve( GameState* s) {
733 s->ball.z = 10.0;
734 s->ball.y = GAME_Y_MID;
735 s->ball.move_x = 0.0;
736 s->ball.move_y = 0.0;
737 s->ball.move_z = 0.0;
738 s->ball.last_hit_by = -1;
739 s->ball.inhibit_gravity = true;
741 if( s->serving_player == 1) {
742 s->ball.x = GAME_X_MIN-RACKET_X_MID*1.5;
743 } else {
744 s->ball.x = GAME_X_MAX+RACKET_X_MID*1.5;
748 int score_game(GameState* s) {
749 Player *last_hit_by = NULL;
750 Player *other = NULL;
752 Player* winner = NULL;
753 Player* loser = NULL;
755 /* determine "last hit by" and "other" */
756 if (s->ball.last_hit_by == 1) {
757 last_hit_by = &(PLAYER(s, 1));
758 other = &(PLAYER(s, 2));
759 } else {
760 last_hit_by = &(PLAYER(s, 2));
761 other = &(PLAYER(s, 1));
764 switch (s->score_event) {
765 case SCORE_EVENT_NET:
766 winner = other;
767 break;
768 case SCORE_EVENT_OUT:
769 case SCORE_EVENT_GROUND_INVALID:
770 case SCORE_EVENT_OFFSCREEN:
771 case SCORE_EVENT_GROUND_VALID:
772 if (s->ball.ground_hit) {
773 winner = last_hit_by;
774 } else {
775 winner = other;
777 break;
778 default:
779 break;
782 /* determine loser based on winner */
783 if (winner == last_hit_by) {
784 loser = other;
785 } else {
786 loser = last_hit_by;
789 /* we cannot be in an "impossibly high" set */
790 tnx_assert(s->current_set < SETS_TO_WIN*2-1);
792 winner->game++;
793 s->ec_game++;
794 if( loser->game < winner->game-1) {
795 if( winner->game >= 4) {
796 winner->game = loser->game = 0;
797 winner->sets[s->current_set]++;
798 s->ec_sets++;
800 /* serving is changed when the "game" is over */
801 s->serving_player = (s->serving_player==1)?(2):(1);
803 #ifdef HAVE_VOICE_FILES
804 /* speak the current score */
805 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);
806 #endif
808 /* scoring the set.. */
809 if( (winner->sets[s->current_set] == 6 && loser->sets[s->current_set] < 5) ||
810 winner->sets[s->current_set] == 7) {
811 s->current_set++;
812 s->winner = game_get_winner( s);
817 /* forget this event - we've handled it */
818 s->score_event = SCORE_UNDECIDED;
820 if (winner == &PLAYER(s, 1)) {
821 return WINNER_PLAYER1;
822 } else {
823 return WINNER_PLAYER2;
827 const char* format_sets(const GameState* s) {
828 static char sets[100];
829 static char tmp[100];
830 int i, max = s->current_set;
832 sets[0] = '\0';
834 if( s->winner != WINNER_NONE) {
835 max--;
837 for( i=0; i<=max; i++) {
838 sprintf( tmp, "%d:%d, ", PLAYER(s, 1).sets[i], PLAYER(s, 2).sets[i]);
839 strcat( sets, tmp);
842 sets[strlen(sets)-2] = '\0';
844 return sets;
847 const char* format_game(const GameState* s) {
848 static char game[100];
849 static const int game_scoring[] = { 0, 15, 30, 40 };
851 if( PLAYER(s, 1).game < 4 && PLAYER(s, 2).game < 4) {
852 #ifdef HAVE_VOICE_FILES
853 if (PLAYER(s, 1).game > 0 || PLAYER(s, 2).game > 0) {
854 if (PLAYER(s, 1).game == PLAYER(s, 2).game) {
855 voice_say_list(2, VOICE_LOVE_IN + 2*(PLAYER(s, 1).game), VOICE_ALL);
856 } else {
857 voice_say_list(2, VOICE_LOVE_IN + 2*(PLAYER(s, 1).game), VOICE_LOVE_OUT + 2*(PLAYER(s, 2).game));
860 #endif
861 sprintf( game, "%d - %d", game_scoring[PLAYER(s, 1).game], game_scoring[PLAYER(s, 2).game]);
862 } else if( PLAYER(s, 1).game > PLAYER(s, 2).game) {
863 #ifdef HAVE_VOICE_FILES
864 voice_say_list(1, VOICE_ADVANTAGE_PLAYER_ONE);
865 #endif
866 strcpy( game, "advantage player 1");
867 } else if( PLAYER(s, 1).game < PLAYER(s, 2).game) {
868 #ifdef HAVE_VOICE_FILES
869 voice_say_list(1, VOICE_ADVANTAGE_PLAYER_TWO);
870 #endif
871 strcpy( game, "advantage player 2");
872 } else {
873 #ifdef HAVE_VOICE_FILES
874 voice_say_list(1, VOICE_DEUCE);
875 #endif
876 strcpy( game, "deuce");
879 return game;
882 const char* format_status(const GameState* s) {
883 static char status[100];
884 static const char* set_names[] = { "first", "second", "third", "fourth", "fifth" };
886 switch (s->status_message) {
887 case STATUSMSG_NONE:
888 return "";
889 case STATUSMSG_WELCOME:
890 return "welcome to tennix " VERSION;
891 case STATUSMSG_DEFAULT:
892 sprintf(status, "%d:%d in %s set",
893 PLAYER(s, 1).sets[s->current_set],
894 PLAYER(s, 2).sets[s->current_set],
895 set_names[s->current_set]);
896 return status;
897 case STATUSMSG_NET:
898 return "net!";
899 case STATUSMSG_OUT:
900 return "out!";
901 case STATUSMSG_FAULT:
902 return "fault!";
903 case STATUSMSG_P1SCORES:
904 return "player 1 scores.";
905 case STATUSMSG_P2SCORES:
906 return "player 2 scores.";
907 case STATUSMSG_VOLLEY:
908 return "volley!";
909 case STATUSMSG_DIDNTCATCH:
910 return "did not catch the ball.";
911 default:
912 return "";
916 winner_t game_get_winner(const GameState* s) {
917 unsigned int i;
918 int sets[2] = {0};
920 for( i=0; i<s->current_set; i++) {
921 if( PLAYER(s, 1).sets[i] > PLAYER(s, 2).sets[i]) {
922 sets[0]++;
923 } else {
924 sets[1]++;
928 if( sets[0] == SETS_TO_WIN) return WINNER_PLAYER1;
929 if( sets[1] == SETS_TO_WIN) return WINNER_PLAYER2;
931 return WINNER_NONE;