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