Eliminated some duplicate code in ball/set.
[neverball] / ball / set.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 <assert.h>
18 #include <errno.h>
19
20 #include "glext.h"
21 #include "config.h"
22 #include "image.h"
23 #include "text.h"
24 #include "set.h"
25 #include "game.h"
26 #include "common.h"
27
28 /*---------------------------------------------------------------------------*/
29
30 struct set
31 {
32     char file[PATHMAX];
33
34     char *id;                  /* Internal set identifier    */
35     char *name;                /* Set name                   */
36     char *desc;                /* Set description            */
37     char *shot;                /* Set screen-shot            */
38
39     char user_scores[PATHMAX]; /* User high-score file       */
40
41     struct score time_score;   /* Challenge score            */
42     struct score coin_score;   /* Challenge score            */
43
44     /* Level info */
45
46     int   count;                /* Number of levels           */
47     char *level_name_v[MAXLVL]; /* List of level file names   */
48 };
49
50 static int set_state = 0;
51
52 static int set;
53 static int count;
54
55 static struct set   set_v[MAXSET];
56 static struct level level_v[MAXLVL];
57
58 /*---------------------------------------------------------------------------*/
59
60 static void put_score(FILE *fp, const struct score *s)
61 {
62     int j;
63
64     for (j = 0; j < NSCORE; j++)
65        fprintf(fp, "%d %d %s\n", s->timer[j], s->coins[j], s->player[j]);
66 }
67
68 /* Store the score of the set. */
69 static void set_store_hs(void)
70 {
71     const struct set *s = &set_v[set];
72     FILE *fout;
73     int i;
74     const struct level *l;
75     char states[MAXLVL + 1];
76
77     if ((fout = fopen(config_user(s->user_scores), "w")))
78     {
79         for (i = 0; i < s->count; i++)
80         {
81             if (level_v[i].is_locked)
82                 states[i] = 'L';
83             else if (level_v[i].is_completed)
84                 states[i] = 'C';
85             else
86                 states[i] = 'O';
87         }
88         states[s->count] = '\0';
89         fprintf(fout, "%s\n",states);
90
91         put_score(fout, &s->time_score);
92         put_score(fout, &s->coin_score);
93
94         for (i = 0; i < s->count; i++)
95         {
96             l = &level_v[i];
97
98             put_score(fout, &l->score.best_times);
99             put_score(fout, &l->score.unlock_goal);
100             put_score(fout, &l->score.most_coins);
101         }
102
103         fclose(fout);
104     }
105 }
106
107 static int get_score(FILE *fp, struct score *s)
108 {
109     int j;
110     int res = 1;
111
112     for (j = 0; j < NSCORE && res; j++)
113     {
114         res = fscanf(fp, "%d %d %s\n",
115                      &s->timer[j],
116                      &s->coins[j],
117                      s->player[j]) == 3;
118     }
119     return res;
120 }
121
122 /* Get the score of the set. */
123 static void set_load_hs(void)
124 {
125     struct set *s = &set_v[set];
126     FILE *fin;
127     int i;
128     int res = 0;
129     struct level *l;
130     const char *fn = config_user(s->user_scores);
131     char states[MAXLVL + 1];
132
133     if ((fin = fopen(fn, "r")))
134     {
135         res = fscanf(fin, "%s\n", states) == 1 && strlen(states) == s->count;
136
137         for (i = 0; i < s->count && res; i++)
138         {
139             switch (states[i])
140             {
141             case 'L':
142                 level_v[i].is_locked = 1;
143                 level_v[i].is_completed = 0;
144                 break;
145
146             case 'C':
147                 level_v[i].is_locked = 0;
148                 level_v[i].is_completed = 1;
149                 break;
150
151             case 'O':
152                 level_v[i].is_locked = 0;
153                 level_v[i].is_completed = 0;
154                 break;
155
156             default:
157                 res = 0;
158             }
159         }
160
161         res = res &&
162             get_score(fin, &s->time_score) &&
163             get_score(fin, &s->coin_score);
164
165         for (i = 0; i < s->count && res; i++)
166         {
167             l = &level_v[i];
168             res = get_score(fin, &l->score.best_times) &&
169                   get_score(fin, &l->score.unlock_goal) &&
170                   get_score(fin, &l->score.most_coins);
171         }
172
173         fclose(fin);
174     }
175
176     if (!res && errno != ENOENT)
177     {
178         fprintf(stderr,
179                 L_("Error while loading user high-score file '%s': %s\n"),
180                 fn, errno ? strerror(errno) : L_("Incorrect format"));
181     }
182 }
183
184 /*---------------------------------------------------------------------------*/
185
186 static int set_load(struct set *s, const char *filename)
187 {
188     FILE *fin;
189     char *scores, *level_name;
190
191     fin = fopen(config_data(filename), "r");
192
193     if (!fin)
194     {
195         fprintf(stderr, L_("Cannot load the set file '%s': %s\n"),
196                 filename, strerror(errno));
197         return 0;
198     }
199
200     memset(s, 0, sizeof (struct set));
201
202     /* Set some sane values in case the scores are missing. */
203
204     score_init_hs(&s->time_score, 359999, 0);
205     score_init_hs(&s->coin_score, 359999, 0);
206
207     strncpy(s->file, filename, PATHMAX - 1);
208
209     if (read_line(&s->name, fin) &&
210         read_line(&s->desc, fin) &&
211         read_line(&s->id,   fin) &&
212         read_line(&s->shot, fin) &&
213         read_line(&scores,  fin))
214     {
215         sscanf(scores, "%d %d %d %d %d %d",
216                &s->time_score.timer[0],
217                &s->time_score.timer[1],
218                &s->time_score.timer[2],
219                &s->coin_score.coins[0],
220                &s->coin_score.coins[1],
221                &s->coin_score.coins[2]);
222
223         free(scores);
224
225         strncpy(s->user_scores, "neverballhs-", PATHMAX - 1);
226         strncat(s->user_scores, s->id, PATHMAX - 1 - strlen("neverballhs-"));
227
228         s->count = 0;
229
230         while (s->count < MAXLVL && read_line(&level_name, fin))
231         {
232             s->level_name_v[s->count] = level_name;
233             s->count++;
234         }
235
236         fclose(fin);
237
238         return 1;
239     }
240
241     free(s->name);
242     free(s->desc);
243     free(s->id);
244     free(s->shot);
245
246     fclose(fin);
247
248     return 0;
249 }
250
251 int set_init()
252 {
253     FILE *fin;
254     char *name;
255
256     if (set_state)
257         set_free();
258
259     set   = 0;
260     count = 0;
261
262     if ((fin = fopen(config_data(SET_FILE), "r")))
263     {
264         while (count < MAXSET && read_line(&name, fin))
265         {
266             if (set_load(&set_v[count], name))
267                 count++;
268
269             free(name);
270         }
271         fclose(fin);
272
273         set_state = 1;
274     }
275
276     return count;
277 }
278
279 void set_free(void)
280 {
281     int i, j;
282
283     for (i = 0; i < count; i++)
284     {
285         free(set_v[i].name);
286         free(set_v[i].desc);
287         free(set_v[i].id);
288         free(set_v[i].shot);
289
290         for (j = 0; j < set_v[i].count; j++)
291             free(set_v[i].level_name_v[j]);
292     }
293
294     set_state = 0;
295 }
296
297 /*---------------------------------------------------------------------------*/
298
299 int set_exists(int i)
300 {
301     return (0 <= i && i < count);
302 }
303
304 const char *set_name(int i)
305 {
306     return set_exists(i) ? _(set_v[i].name) : NULL;
307 }
308
309 const char *set_desc(int i)
310 {
311     return set_exists(i) ? _(set_v[i].desc) : NULL;
312 }
313
314 const char *set_shot(int i)
315 {
316     return set_exists(i) ? set_v[i].shot : NULL;
317 }
318
319 const struct score *set_time_score(int i)
320 {
321     return set_exists(i) ? &set_v[i].time_score : NULL;
322 }
323
324 const struct score *set_coin_score(int i)
325 {
326     return set_exists(i) ? &set_v[i].coin_score : NULL;
327 }
328
329 /*---------------------------------------------------------------------------*/
330
331 int set_level_exists(int s, int i)
332 {
333     return (i >= 0 && i < set_v[s].count);
334 }
335
336 static void set_load_levels(void)
337 {
338     struct level *l;
339     int nb = 1, bnb = 1;
340
341     int i;
342
343     const char *roman[] = {
344         "",
345         "I",   "II",   "III",   "IV",   "V",
346         "VI",  "VII",  "VIII",  "IX",   "X",
347         "XI",  "XII",  "XIII",  "XIV",  "XV",
348         "XVI", "XVII", "XVIII", "XIX",  "XX",
349         "XXI", "XXII", "XXIII", "XXIV", "XXV"
350     };
351
352     for (i = 0; i < set_v[set].count; i++)
353     {
354         l = &level_v[i];
355
356         level_load(set_v[set].level_name_v[i], l);
357
358         l->set    = &set_v[set];
359         l->number = i;
360
361         if (l->is_bonus)
362             sprintf(l->repr, "%s",   roman[bnb++]);
363         else
364             sprintf(l->repr, "%02d", nb++);
365
366         l->is_locked    = 1;
367         l->is_completed = 0;
368     }
369
370     /* Unlock first level. */
371
372     level_v[0].is_locked = 0;
373 }
374
375 void set_goto(int i)
376 {
377     set = i;
378
379     set_load_levels();
380     set_load_hs();
381 }
382
383 int curr_set(void)
384 {
385     return set;
386 }
387
388 const struct level *get_level(int i)
389 {
390     return (i >= 0 && i < set_v[set].count) ? &level_v[i] : NULL;
391 }
392
393 /*---------------------------------------------------------------------------*/
394
395 /* Update the level score rank according to coins and timer. */
396 static int level_score_update(struct level_game *lg, const char *player)
397 {
398     int timer = lg->timer;
399     int coins = lg->coins;
400     struct level *l = &level_v[lg->level->number];
401
402     lg->time_rank = score_time_insert(&l->score.best_times,
403                                       player, timer, coins);
404
405     if (lg->mode == MODE_CHALLENGE || lg->mode == MODE_NORMAL)
406         lg->goal_rank = score_time_insert(&l->score.unlock_goal,
407                                           player, timer, coins);
408     else
409         lg->goal_rank = 3;
410
411     lg->coin_rank = score_coin_insert(&l->score.most_coins,
412                                       player, timer, coins);
413
414     return (lg->time_rank < 3 || lg->goal_rank < 3 || lg->coin_rank < 3);
415 }
416
417 /* Update the set score rank according to score and times. */
418 static int set_score_update(struct level_game *lg, const char *player)
419 {
420     int timer = lg->times;
421     int coins = lg->score;
422     struct set *s = &set_v[set];
423
424     lg->score_rank = score_time_insert(&s->time_score, player, timer, coins);
425     lg->times_rank = score_time_insert(&s->coin_score, player, timer, coins);
426
427     return (lg->score_rank < 3 || lg->times_rank < 3);
428 }
429
430 /* Update the player name for set and level high-score. */
431 void score_change_name(struct level_game *lg, const char *player)
432 {
433     struct set   *s = &set_v[set];
434     struct level *l = &level_v[lg->level->number];
435
436     strncpy(l->score.best_times.player [lg->time_rank], player, MAXNAM);
437     strncpy(l->score.unlock_goal.player[lg->goal_rank], player, MAXNAM);
438     strncpy(l->score.most_coins.player [lg->coin_rank], player, MAXNAM);
439
440     strncpy(s->coin_score.player[lg->score_rank], player, MAXNAM);
441     strncpy(s->time_score.player[lg->times_rank], player, MAXNAM);
442
443     set_store_hs();
444 }
445
446 static struct level *next_level(int i)
447 {
448     return set_level_exists(set, i + 1) ? &level_v[i + 1] : NULL;
449 }
450
451 static struct level *next_normal_level(int i)
452 {
453     for (i++; i < set_v[set].count; i++)
454         if (!level_v[i].is_bonus)
455             return &level_v[i];
456
457     return NULL;
458 }
459
460 /*---------------------------------------------------------------------------*/
461
462 void set_finish_level(struct level_game *lg, const char *player)
463 {
464     struct set *s = &set_v[set];
465     int ln = lg->level->number;      /* Current level number       */
466     struct level *cl = &level_v[ln]; /* Current level              */
467     struct level *nl = NULL;         /* Next level                 */
468     int dirty = 0;                   /* Should the score be saved? */
469
470     assert(s == cl->set);
471
472     /* if no set, no next level */
473     if (s == NULL)
474     {
475         /* if no set, return */
476         lg->next_level = NULL;
477         return;
478     }
479
480     /* On level completed */
481     if (lg->status == GAME_GOAL)
482     {
483         /* Update level scores */
484         dirty = level_score_update(lg, player);
485
486         /* Complete the level */
487         if (lg->mode == MODE_CHALLENGE || lg->mode == MODE_NORMAL)
488         {
489             /* Complete the level */
490             if (!cl->is_completed)
491             {
492                 cl->is_completed = 1;
493                 dirty = 1;
494             }
495         }
496     }
497
498     /* On goal reached */
499     if (lg->status == GAME_GOAL)
500     {
501         /* Identify the following level */
502
503         nl = next_level(ln);
504
505         if (nl != NULL)
506         {
507             /* Skip bonuses if unlocked in any mode */
508
509             if (nl->is_bonus)
510             {
511                 if (lg->mode == MODE_CHALLENGE && nl->is_locked)
512                 {
513                     nl->is_locked = 0;
514
515                     lg->bonus = 1;
516                     lg->bonus_repr = nl->repr;
517                 }
518
519                 nl = next_normal_level(nl->number);
520
521                 if (nl == NULL && lg->mode == MODE_CHALLENGE)
522                 {
523                     lg->win = 1;
524                 }
525             }
526         }
527         else if (lg->mode == MODE_CHALLENGE)
528             lg->win = 1;
529     }
530     else if (cl->is_bonus || lg->mode != MODE_CHALLENGE)
531     {
532         /* On fail, identify the next level (only in bonus for challenge) */
533         nl = next_normal_level(ln);
534         /* Next level may be unavailable */
535         if (!cl->is_bonus && nl != NULL && nl->is_locked)
536             nl = NULL;
537         /* Fail a bonus level but win the set! */
538         else if (nl == NULL && lg->mode == MODE_CHALLENGE)
539             lg->win = 1;
540     }
541
542     /* Win ! */
543     if (lg->win)
544     {
545         /* update set score */
546         set_score_update(lg, player);
547         /* unlock all levels */
548         set_cheat();
549         dirty = 1;
550     }
551
552     /* unlock the next level if needed */
553     if (nl != NULL && nl->is_locked)
554     {
555         if (lg->mode == MODE_CHALLENGE || lg->mode == MODE_NORMAL)
556         {
557             lg->unlock = 1;
558             nl->is_locked = 0;
559             dirty = 1;
560         }
561         else
562             nl = NULL;
563     }
564
565     /* got the next level */
566     lg->next_level = nl;
567
568     /* Update file */
569     if (dirty)
570         set_store_hs();
571 }
572
573 /*---------------------------------------------------------------------------*/
574
575 void level_snap(int i)
576 {
577     char filename[MAXSTR];
578     char *ext;
579
580     /* Convert the level name to a PNG filename. */
581
582     memset(filename, 0, MAXSTR);
583
584     ext = strrchr(level_v[i].file, '.');
585     strncpy(filename, level_v[i].file,
586             ext ? ext - level_v[i].file : strlen(level_v[i].file));
587     strcat(filename, ".png");
588
589     /* Initialize the game for a snapshot. */
590
591     if (game_init(&level_v[i], 0, 0))
592     {
593         /* Render the level and grab the screen. */
594
595         config_clear();
596         game_set_fly(1.f);
597         game_kill_fade();
598         game_draw(1, 0);
599         SDL_GL_SwapBuffers();
600
601         image_snap(filename);
602     }
603 }
604
605 void set_cheat(void)
606 {
607     int i;
608
609     for (i = 0; i < set_v[set].count; i++)
610         level_v[i].is_locked = 0;
611 }
612
613 /*---------------------------------------------------------------------------*/