New challenge/practice modes
[neverball] / ball / level.c
1 /*   
2  * Copyright (C) 2003 Robert Kooima
3  *
4  * NEVERBALL is  free software; you can redistribute  it and/or modify
5  * it under the  terms of the GNU General  Public License as published
6  * by the Free  Software Foundation; either version 2  of the License,
7  * or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT  ANY  WARRANTY;  without   even  the  implied  warranty  of
11  * MERCHANTABILITY or  FITNESS FOR A PARTICULAR PURPOSE.   See the GNU
12  * General Public License for more details.
13  */
14
15 #include <stdio.h>
16 #include <string.h>
17 #include <math.h>
18
19 #include "level.h"
20 #include "glext.h"
21 #include "image.h"
22 #include "game.h"
23 #include "geom.h"
24 #include "demo.h"
25 #include "hud.h"
26 #include "audio.h"
27 #include "config.h"
28
29 /*#define CHEATER 1*/
30
31 /*---------------------------------------------------------------------------*/
32
33 struct score
34 {
35     char time_n[4][MAXNAM];
36     int  time_t[4];
37     int  time_c[4];
38
39     char coin_n[4][MAXNAM];
40     int  coin_t[4];
41     int  coin_c[4];
42 };
43
44 struct level
45 {
46     char file[MAXSTR];
47     char back[MAXSTR];
48     char grad[MAXSTR];
49     char shot[MAXSTR];
50     char song[MAXSTR];
51     int  time;
52     int  goal;
53
54     GLuint text;
55 };
56
57 static int score;                       /* Current coin total         */
58 static int coins;                       /* Current coin count         */
59 static int balls;                       /* Current life count         */
60 static int goal;                        /* Current goal count         */
61
62 static int level;                       /* Current level number       */
63 static int count;                       /* Number of levels           */
64 static int limit;                       /* Last opened (locked) level */
65 static int status;                      /* Status of current level    */
66
67 static int mode;                        /* Current play mode          */
68
69 static int level_total;
70 static int coins_total;
71 static int times_total;
72
73 static struct level level_v[MAXLVL];
74 static struct score score_v[MAXLVL];
75
76 static char scores_file[MAXSTR];
77
78 /*---------------------------------------------------------------------------*/
79
80 static void level_store_hs(const char *filename)
81 {
82     FILE *fout;
83
84     if ((fout = fopen(config_user(filename), "w")))
85     {
86         int i;
87         int j;
88
89         for (i = 0; i < limit; i++)
90             for (j = 0; j < 3; j++)
91             {
92                 if (strlen(score_v[i].time_n[j]) == 0)
93                     strcpy(score_v[i].time_n[j], DEFAULT_PLAYER);
94                 if (strlen(score_v[i].coin_n[j]) == 0)
95                     strcpy(score_v[i].coin_n[j], DEFAULT_PLAYER);
96
97                 fprintf(fout, "%d %d %s\n",
98                         score_v[i].time_t[j],
99                         score_v[i].time_c[j],
100                         score_v[i].time_n[j]);
101                 fprintf(fout, "%d %d %s\n",
102                         score_v[i].coin_t[j],
103                         score_v[i].coin_c[j],
104                         score_v[i].coin_n[j]);
105             }
106             
107         fclose(fout);
108     }
109 }
110
111 static void level_load_hs(const char *filename)
112 {
113     FILE *fin;
114
115     limit = 1;
116
117     if ((fin = fopen(config_user(filename), "r")))
118     {
119         int i;
120
121         for (i = 0; i < count; i++)
122         {
123             if (fscanf(fin, "%d %d %s",
124                        &score_v[i].time_t[0],
125                        &score_v[i].time_c[0],
126                        score_v[i].time_n[0]) == 3 &&
127                 fscanf(fin, "%d %d %s",
128                        &score_v[i].coin_t[0],
129                        &score_v[i].coin_c[0],
130                        score_v[i].coin_n[0]) == 3 &&
131                 fscanf(fin, "%d %d %s",
132                        &score_v[i].time_t[1],
133                        &score_v[i].time_c[1],
134                        score_v[i].time_n[1]) == 3 &&
135                 fscanf(fin, "%d %d %s",
136                        &score_v[i].coin_t[1],
137                        &score_v[i].coin_c[1],
138                        score_v[i].coin_n[1]) == 3 &&
139                 fscanf(fin, "%d %d %s",
140                        &score_v[i].time_t[2],
141                        &score_v[i].time_c[2],
142                        score_v[i].time_n[2]) == 3 &&
143                 fscanf(fin, "%d %d %s",
144                        &score_v[i].coin_t[2],
145                        &score_v[i].coin_c[2],
146                        score_v[i].coin_n[2]) == 3)
147                 limit = i + 1;
148         }
149
150         fclose(fin);
151     }
152 }
153
154 /*---------------------------------------------------------------------------*/
155
156 static void level_init_rc(const char *filename)
157 {
158     FILE *fin;
159     char buf[MAXSTR];
160
161     count = 0;
162     level = 0;
163     coins = 0;
164     score = 0;
165     balls = 0;
166
167     /* Load the levels list. */
168
169     if ((fin = fopen(config_data(filename), "r")))
170     {
171         while (count < MAXLVL && fgets(buf, MAXSTR, fin))
172         {
173             sscanf(buf, "%s %s %s %s %d %d %s",
174                     level_v[count].file,
175                     level_v[count].back,
176                     level_v[count].shot,
177                     level_v[count].grad,
178                    &level_v[count].time,
179                    &level_v[count].goal,
180                     level_v[count].song);
181             count++;
182         }
183         fclose(fin);
184     }
185 }
186
187 static void level_init_hs(const char *filename)
188 {
189     char buf[MAXSTR];
190     FILE *fin;
191     int i = 0;
192
193     /* Set some sane values in case the scores file is missing. */
194
195     for (i = 0; i < MAXLVL; i++)
196     {
197         strcpy(score_v[i].time_n[0], "Hard");
198         strcpy(score_v[i].time_n[1], "Medium");
199         strcpy(score_v[i].time_n[2], "Easy");
200
201         score_v[i].time_t[0] = i ? 59999 : 359999;
202         score_v[i].time_t[1] = i ? 59999 : 359999;
203         score_v[i].time_t[2] = i ? 59999 : 359999;
204
205         score_v[i].time_c[0] = 0;
206         score_v[i].time_c[1] = 0;
207         score_v[i].time_c[2] = 0;
208
209         strcpy(score_v[i].coin_n[0], "Hard");
210         strcpy(score_v[i].coin_n[1], "Medium");
211         strcpy(score_v[i].coin_n[2], "Easy");
212
213         score_v[i].coin_t[0] = i ? 59999 : 359999;
214         score_v[i].coin_t[1] = i ? 59999 : 359999;
215         score_v[i].coin_t[2] = i ? 59999 : 359999;
216
217         score_v[i].coin_c[0] = 0;
218         score_v[i].coin_c[1] = 0;
219         score_v[i].coin_c[2] = 0;
220     }
221
222     /* Load the default high scores file. */
223
224     if ((fin = fopen(config_data(filename), "r")))
225     {
226         for (i = 0; i < MAXLVL && fgets(buf, MAXSTR, fin); i++)
227             sscanf(buf, "%d %d %d %d %d %d",
228                    &score_v[i].time_t[0], &score_v[i].coin_c[0],
229                    &score_v[i].time_t[1], &score_v[i].coin_c[1],
230                    &score_v[i].time_t[2], &score_v[i].coin_c[2]);
231
232         fclose(fin);
233     }
234 }
235
236 /*---------------------------------------------------------------------------*/
237
238 const char *level_shot(int i)
239 {
240     return level_v[i].shot;
241 }
242
243 const char *level_time_n(int i, int j)
244 {
245     return score_v[i].time_n[j];
246 }
247
248 const char *level_coin_n(int i, int j)
249 {
250     return score_v[i].coin_n[j];
251 }
252
253 /*---------------------------------------------------------------------------*/
254 /* Return the coin count for the Most Coins or Best Time score.              */
255
256 int level_coin_c(int i, int j)
257 {
258     if (j < 0)
259         return score;
260     else
261         return score_v[i].coin_c[j];
262 }
263
264 int level_time_c(int i, int j)
265 {
266     return score_v[i].time_c[j];
267 }
268
269 /*---------------------------------------------------------------------------*/
270 /* Return the time for the Most Coins or Best Time score.                    */
271
272 int level_coin_t(int i, int j)
273 {
274     return score_v[i].coin_t[j];
275 }
276
277 int level_time_t(int i, int j)
278 {
279     if (j < 0)
280         return level_v[i].time - curr_clock();
281     else
282         return score_v[i].time_t[j];
283 }
284
285 /*---------------------------------------------------------------------------*/
286
287 void level_init(const char *init_levels,
288                 const char *init_scores,
289                 const char *user_scores)
290 {
291     memset(level_v, 0, sizeof (struct level) * MAXLVL);
292     memset(score_v, 0, sizeof (struct score) * MAXLVL);
293
294     level_init_rc(init_levels);
295     level_init_hs(init_scores);
296     level_load_hs(user_scores);
297
298     strncpy(scores_file, user_scores, MAXSTR);
299
300     score = 0;
301     coins = 0;
302     balls = 2;
303     level = 0;
304
305     level_total = 0;
306     coins_total = 0;
307     times_total = 0;
308
309 #ifdef CHEATER
310     limit = count;
311     level_store_hs(user_scores);
312 #endif
313 }
314
315 void level_free(void)
316 {
317     int i;
318
319     level_store_hs(scores_file);
320
321     for (i = 0; i < count; i++)
322         if (glIsTexture(level_v[i].text))
323             glDeleteTextures(1, &level_v[i].text);
324
325     count = 0;
326 }
327
328 int level_exists(int i)
329 {
330     return (0 < i && i < count);
331 }
332
333 int level_opened(int i)
334 {
335     return level_exists(i) && (0 < i && i < count && i <= limit);
336 }
337
338 int level_locked(int i)
339 {
340     return level_opened(i) && (i == limit) && (level_v[i].goal > 0);
341 }
342
343 /*---------------------------------------------------------------------------*/
344
345 int curr_times_total(void) { return times_total; }
346 int curr_coins_total(void) { return coins_total; }
347
348 int curr_count(void) { return count; }
349 int curr_score(void) { return score; }
350 int curr_coins(void) { return coins; }
351 int curr_balls(void) { return balls; }
352 int curr_level(void) { return level; }
353 int curr_goal (void) { return goal;  }
354
355 /*---------------------------------------------------------------------------*/
356
357 static int score_time_comp(const struct score *S, int i, int j)
358 {
359     if (S->time_t[i] <  S->time_t[j])
360         return 1;
361
362     if (S->time_t[i] == S->time_t[j] &&
363         S->time_c[i] >  S->time_c[j])
364         return 1;
365
366     return 0;
367 }
368
369 static int score_coin_comp(const struct score *S, int i, int j)
370 {
371     if (S->coin_c[i] >  S->coin_c[j])
372         return 1;
373
374     if (S->coin_c[i] == S->coin_c[j] &&
375         S->coin_t[i] <  S->coin_t[j])
376         return 1;
377
378     return 0;
379 }
380
381 /*---------------------------------------------------------------------------*/
382
383 static void score_time_swap(struct score *S, int i, int j)
384 {
385     char n[MAXNAM];
386     int  t;
387     int  c;
388
389     strncpy(n,            S->time_n[i], MAXNAM);
390     strncpy(S->time_n[i], S->time_n[j], MAXNAM);
391     strncpy(S->time_n[j], n,            MAXNAM);
392
393     t            = S->time_t[i];
394     S->time_t[i] = S->time_t[j];
395     S->time_t[j] = t;
396
397     c            = S->time_c[i];
398     S->time_c[i] = S->time_c[j];
399     S->time_c[j] = c;
400 }
401
402 static void score_coin_swap(struct score *S, int i, int j)
403 {
404     char n[MAXNAM];
405     int  t;
406     int  c;
407
408     strncpy(n,            S->coin_n[i], MAXNAM);
409     strncpy(S->coin_n[i], S->coin_n[j], MAXNAM);
410     strncpy(S->coin_n[j], n,            MAXNAM);
411
412     t            = S->coin_t[i];
413     S->coin_t[i] = S->coin_t[j];
414     S->coin_t[j] = t;
415
416     c            = S->coin_c[i];
417     S->coin_c[i] = S->coin_c[j];
418     S->coin_c[j] = c;
419 }
420
421 /*---------------------------------------------------------------------------*/
422
423 int level_replay(const char *filename)
424 {
425     status = GAME_NONE;
426
427     return demo_replay_init(filename, &score, &coins, &balls, &goal);
428 }
429
430 int level_play(const char *filename, int i, int m)
431 {
432     status = GAME_NONE;
433     mode = m;
434
435     if (i >= 0)
436     {
437         level = i;
438         if (m == MODE_CHALLENGE)
439                 goal =  level_v[level].goal;
440         else
441                 goal = (level == limit) ? level_v[level].goal : 0;
442     }
443     return demo_play_init(USER_REPLAY_FILE,
444                           level_v[level].file,
445                           level_v[level].back,
446                           level_v[level].grad,
447                           level_v[level].song,
448                           level_v[level].shot,
449                           level_v[level].time,
450                           goal, score, coins, balls);
451 }
452
453 /*---------------------------------------------------------------------------*/
454
455 void level_stat(int s)
456 {
457     if ((status = s) == GAME_GOAL)
458     {
459         coins_total += coins;
460         level_total += 1;
461     }
462
463     demo_play_stat(curr_coins(), level_v[level].time - curr_clock());
464 }
465
466 int level_dead(void)
467 {
468     return (balls <= 0);
469 }
470
471 int level_last(void)
472 {
473     return (level + 1 == count);
474 }
475
476 int level_exit(const char *filename, int next)
477 {
478     times_total += level_v[level].time - curr_clock();
479
480     demo_play_stop(filename);
481
482     switch (status)
483     {
484     case GAME_GOAL:
485         if (next) level++;
486         if (limit < level)
487             limit = level;
488
489         level_store_hs(scores_file);
490         break;
491
492     case GAME_TIME:
493     case GAME_FALL:
494         if (mode == MODE_CHALLENGE)
495             balls--;
496         break;
497     }
498     
499     /* Load the next level. */
500
501     if (status && level < count && balls >= 0)
502     {
503         if (mode == MODE_CHALLENGE)
504             goal = level_v[level].goal;
505         else
506             goal = (level == limit) ? level_v[level].goal : 0;
507         coins  = 0;
508         status = GAME_NONE;
509
510         return demo_play_init(USER_REPLAY_FILE,
511                               level_v[level].file,
512                               level_v[level].back,
513                               level_v[level].grad,
514                               level_v[level].song,
515                               level_v[level].shot,
516                               level_v[level].time,
517                               goal, score, coins, balls);
518     }
519
520     return 0;
521 }
522
523 int level_sort(int *time_i, int *coin_i)
524 {
525     int i, clock = level_v[level].time - curr_clock();
526     char player[MAXNAM];
527
528     config_get_s(CONFIG_PLAYER, player, MAXNAM);
529
530     /* Insert the time record into the high score list. */
531
532     strncpy(score_v[level].time_n[3], player, MAXNAM);
533     score_v[level].time_c[3] = coins;
534     score_v[level].time_t[3] = clock;
535
536     for (i = 2; i >= 0 && score_time_comp(score_v + level, i + 1, i); i--)
537     {
538         score_time_swap(score_v + level, i + 1, i);
539         *time_i = i;
540     }
541
542     /* Insert the coin record into the high score list. */
543
544     strncpy(score_v[level].coin_n[3], player, MAXNAM);
545     score_v[level].coin_c[3] = coins;
546     score_v[level].coin_t[3] = clock;
547
548     for (i = 2; i >= 0 && score_coin_comp(score_v + level, i + 1, i); i--)
549     {
550         score_coin_swap(score_v + level, i + 1, i);
551         *coin_i = i;
552     }
553
554     return (*time_i < 3 || *coin_i < 3);
555 }
556
557 int level_done(int *time_i, int *coin_i)
558 {
559     int i;
560     char player[MAXNAM];
561
562     config_get_s(CONFIG_PLAYER, player, MAXNAM);
563
564     /* Note a global high score. */
565
566     strncpy(score_v[0].time_n[3], player, MAXNAM);
567     score_v[0].time_c[3] = coins_total;
568     score_v[0].time_t[3] = times_total;
569
570     strncpy(score_v[0].coin_n[3], player, MAXNAM);
571     score_v[0].coin_c[3] = coins_total;
572     score_v[0].coin_t[3] = times_total;
573
574     if (level == count && level_total == count - 1)
575     {
576         /* Insert the time record into the global high score list. */
577
578         for (i = 2; i >= 0 && score_time_comp(score_v, i + 1, i); i--)
579         {
580             score_time_swap(score_v, i + 1, i);
581             *time_i = i;
582         }
583
584         /* Insert the coin record into the global high score list. */
585
586         for (i = 2; i >= 0 && score_coin_comp(score_v, i + 1, i); i--)
587         {
588             score_coin_swap(score_v, i + 1, i);
589             *coin_i = i;
590         }
591     }
592
593     return (*time_i < 3 || *coin_i < 3);
594 }
595
596 int level_score(int n)
597 {
598     int sound = AUD_COIN;
599     int value = 0;
600
601     coins += n;
602
603     /* Pulse the coin counter based on the value of the grabbed coin. */
604
605     if      (n >= 10) hud_coin_pulse(2.00f);
606     else if (n >=  5) hud_coin_pulse(1.50f);
607     else              hud_coin_pulse(1.25f);
608
609     /* Check for goal open. */
610
611     if (goal > 0)
612     {
613         if      (n >= 10) hud_goal_pulse(2.00f);
614         else if (n >=  5) hud_goal_pulse(1.50f);
615         else              hud_goal_pulse(1.25f);
616
617         if (goal - n <= 0)
618         {
619             sound = AUD_SWITCH;
620             value = 1;
621             hud_goal_pulse(2.0f);
622         }
623
624         goal = (goal > n) ? (goal - n) : 0;
625     }
626
627     audio_play(sound, 1.f);
628     return value;
629 }
630
631 int level_count(void)
632 {
633     if (mode != MODE_CHALLENGE)
634         return 0;
635     if (coins > 0)
636     {
637         score++;
638         coins--;
639
640         if (score % 100 == 0)
641         {
642             balls += 1;
643             audio_play(AUD_BALL, 1.0f);
644         }
645         return 1;
646     }
647     return 0;
648 }
649
650 /*---------------------------------------------------------------------------*/
651
652 void level_name(int i, const char *name, int time_i, int coin_i)
653 {
654     strncpy(score_v[i].time_n[time_i], name, MAXNAM);
655     strncpy(score_v[i].coin_n[coin_i], name, MAXNAM);
656 }
657
658 void level_snap(int i)
659 {
660     char filename[MAXSTR];
661
662     /* Convert the level name to a BMP filename. */
663
664     memset(filename, 0, MAXSTR);
665     strncpy(filename, level_v[i].file, strcspn(level_v[i].file, "."));
666     strcat(filename, ".bmp");
667
668     /* Initialize the game for a snapshot. */
669
670     if (game_init(level_v[i].file, level_v[i].back, level_v[i].grad, 0, 1))
671     {
672         /* Render the level and grab the screen. */
673
674         config_clear();
675         game_set_fly(1.f);
676         game_kill_fade();
677         game_draw(1, 0);
678         SDL_GL_SwapBuffers();
679
680         image_snap(filename);
681     }
682 }
683
684 /*---------------------------------------------------------------------------*/
685
686 int level_mode(void)
687 {
688     return mode;
689 }
690