Replace forgettable view numbers with symbols and clean up view name lookup
[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 "video.h"
23 #include "image.h"
24 #include "set.h"
25 #include "common.h"
26 #include "fs.h"
27
28 #include "game_server.h"
29 #include "game_client.h"
30 #include "game_proxy.h"
31
32 /*---------------------------------------------------------------------------*/
33
34 struct set
35 {
36     char file[PATHMAX];
37
38     char *id;                  /* Internal set identifier    */
39     char *name;                /* Set name                   */
40     char *desc;                /* Set description            */
41     char *shot;                /* Set screen-shot            */
42
43     char *user_scores;         /* User high-score file       */
44     char *cheat_scores;        /* Cheat mode score file      */
45
46     struct score coin_score;   /* Challenge score            */
47     struct score time_score;   /* Challenge score            */
48
49     /* Level info */
50
51     int   count;                /* Number of levels           */
52     char *level_name_v[MAXLVL]; /* List of level file names   */
53 };
54
55 static int set_state = 0;
56
57 static int set;
58 static int count;
59
60 static struct set   set_v[MAXSET];
61 static struct level level_v[MAXLVL];
62
63 /*---------------------------------------------------------------------------*/
64
65 static void put_score(fs_file fp, const struct score *s)
66 {
67     int j;
68
69     for (j = 0; j < NSCORE; j++)
70         fs_printf(fp, "%d %d %s\n", s->timer[j], s->coins[j], s->player[j]);
71 }
72
73 void set_store_hs(void)
74 {
75     const struct set *s = &set_v[set];
76     fs_file fout;
77     int i;
78     const struct level *l;
79     char states[MAXLVL + 1];
80
81     if ((fout = fs_open(config_cheat() ?
82                         s->cheat_scores :
83                         s->user_scores, "w")))
84     {
85         for (i = 0; i < s->count; i++)
86         {
87             if (level_v[i].is_locked)
88                 states[i] = 'L';
89             else if (level_v[i].is_completed)
90                 states[i] = 'C';
91             else
92                 states[i] = 'O';
93         }
94         states[s->count] = '\0';
95         fs_printf(fout, "%s\n",states);
96
97         put_score(fout, &s->time_score);
98         put_score(fout, &s->coin_score);
99
100         for (i = 0; i < s->count; i++)
101         {
102             l = &level_v[i];
103
104             put_score(fout, &l->score.best_times);
105             put_score(fout, &l->score.fast_unlock);
106             put_score(fout, &l->score.most_coins);
107         }
108
109         fs_close(fout);
110     }
111 }
112
113 static int get_score(fs_file fp, struct score *s)
114 {
115     int j;
116     int res = 1;
117     char line[MAXSTR];
118
119     for (j = 0; j < NSCORE && res; j++)
120     {
121         res = (fs_gets(line, sizeof (line), fp) &&
122                sscanf(line, "%d %d %s\n",
123                       &s->timer[j],
124                       &s->coins[j],
125                       s->player[j]) == 3);
126     }
127     return res;
128 }
129
130 /* Get the score of the set. */
131 static void set_load_hs(void)
132 {
133     struct set *s = &set_v[set];
134     fs_file fin;
135     int i;
136     int res = 0;
137     struct level *l;
138     const char *fn = config_cheat() ? s->cheat_scores : s->user_scores;
139     char states[MAXLVL + sizeof ("\n")];
140
141     if ((fin = fs_open(fn, "r")))
142     {
143         res = (fs_gets(states, sizeof (states), fin) &&
144                strlen(states) - 1 == s->count);
145
146         for (i = 0; i < s->count && res; i++)
147         {
148             switch (states[i])
149             {
150             case 'L':
151                 level_v[i].is_locked = 1;
152                 level_v[i].is_completed = 0;
153                 break;
154
155             case 'C':
156                 level_v[i].is_locked = 0;
157                 level_v[i].is_completed = 1;
158                 break;
159
160             case 'O':
161                 level_v[i].is_locked = 0;
162                 level_v[i].is_completed = 0;
163                 break;
164
165             default:
166                 res = 0;
167             }
168         }
169
170         res = res &&
171             get_score(fin, &s->time_score) &&
172             get_score(fin, &s->coin_score);
173
174         for (i = 0; i < s->count && res; i++)
175         {
176             l = &level_v[i];
177             res = get_score(fin, &l->score.best_times) &&
178                 get_score(fin, &l->score.fast_unlock) &&
179                 get_score(fin, &l->score.most_coins);
180         }
181
182         fs_close(fin);
183     }
184
185     if (!res && errno != ENOENT)
186     {
187         fprintf(stderr,
188                 L_("Error while loading user high-score file '%s': %s\n"),
189                 fn, errno ? strerror(errno) : L_("Incorrect format"));
190     }
191 }
192
193 /*---------------------------------------------------------------------------*/
194
195 static int set_load(struct set *s, const char *filename)
196 {
197     fs_file fin;
198     char *scores, *level_name;
199
200     /* Skip "Misc" set when not in dev mode. */
201
202     if (strcmp(filename, SET_MISC) == 0 && !config_cheat())
203         return 0;
204
205     fin = fs_open(filename, "r");
206
207     if (!fin)
208     {
209         fprintf(stderr, L_("Cannot load the set file '%s': %s\n"),
210                 filename, strerror(errno));
211         return 0;
212     }
213
214     memset(s, 0, sizeof (struct set));
215
216     /* Set some sane values in case the scores are missing. */
217
218     score_init_hs(&s->time_score, 359999, 0);
219     score_init_hs(&s->coin_score, 359999, 0);
220
221     strncpy(s->file, filename, PATHMAX - 1);
222
223     if (read_line(&s->name, fin) &&
224         read_line(&s->desc, fin) &&
225         read_line(&s->id,   fin) &&
226         read_line(&s->shot, fin) &&
227         read_line(&scores,  fin))
228     {
229         sscanf(scores, "%d %d %d %d %d %d",
230                &s->time_score.timer[0],
231                &s->time_score.timer[1],
232                &s->time_score.timer[2],
233                &s->coin_score.coins[0],
234                &s->coin_score.coins[1],
235                &s->coin_score.coins[2]);
236
237         free(scores);
238
239         s->user_scores  = concat_string("Scores/", s->id, ".txt",       NULL);
240         s->cheat_scores = concat_string("Scores/", s->id, "-cheat.txt", NULL);
241
242         s->count = 0;
243
244         while (s->count < MAXLVL && read_line(&level_name, fin))
245         {
246             s->level_name_v[s->count] = level_name;
247             s->count++;
248         }
249
250         fs_close(fin);
251
252         return 1;
253     }
254
255     free(s->name);
256     free(s->desc);
257     free(s->id);
258     free(s->shot);
259
260     fs_close(fin);
261
262     return 0;
263 }
264
265 static int cmp_dir_items(const void *A, const void *B)
266 {
267     const struct dir_item *a = A, *b = B;
268     return strcmp(a->path, b->path);
269 }
270
271 static int set_is_loaded(const char *path)
272 {
273     int i;
274
275     for (i = 0; i < count; i++)
276         if (strcmp(set_v[i].file, path) == 0)
277             return 1;
278
279     return 0;
280 }
281
282 static int is_unseen_set(struct dir_item *item)
283 {
284     return (strncmp(base_name(item->path, NULL), "set-", 4) == 0 &&
285             !set_is_loaded(item->path));
286 }
287
288 int set_init()
289 {
290     fs_file fin;
291     char *name;
292
293     Array items;
294     int i;
295
296     if (set_state)
297         set_free();
298
299     set   = 0;
300     count = 0;
301
302      /*
303       * First, load the sets listed in the set file, preserving order.
304       */
305
306     if ((fin = fs_open(SET_FILE, "r")))
307     {
308         while (count < MAXSET && read_line(&name, fin))
309         {
310             if (set_load(&set_v[count], name))
311                 count++;
312
313             free(name);
314         }
315         fs_close(fin);
316
317         set_state = 1;
318     }
319
320     /*
321      * Then, scan for any remaining set description files, and add
322      * them after the first group in alphabetic order.
323      */
324
325     if ((items = fs_dir_scan("", is_unseen_set)))
326     {
327         array_sort(items, cmp_dir_items);
328
329         for (i = 0; i < array_len(items) && count < MAXSET; i++)
330             if (set_load(&set_v[count], DIR_ITEM_GET(items, i)->path))
331                 count++;
332
333         fs_dir_free(items);
334
335         set_state = 1;
336     }
337
338     return count;
339 }
340
341 void set_free(void)
342 {
343     int i, j;
344
345     for (i = 0; i < count; i++)
346     {
347         free(set_v[i].name);
348         free(set_v[i].desc);
349         free(set_v[i].id);
350         free(set_v[i].shot);
351
352         free(set_v[i].user_scores);
353         free(set_v[i].cheat_scores);
354
355         for (j = 0; j < set_v[i].count; j++)
356             free(set_v[i].level_name_v[j]);
357     }
358
359     set_state = 0;
360 }
361
362 /*---------------------------------------------------------------------------*/
363
364 int set_exists(int i)
365 {
366     return (0 <= i && i < count);
367 }
368
369 const char *set_id(int i)
370 {
371     return set_exists(i) ? set_v[i].id : NULL;
372 }
373
374 const char *set_name(int i)
375 {
376     return set_exists(i) ? _(set_v[i].name) : NULL;
377 }
378
379 const char *set_desc(int i)
380 {
381     return set_exists(i) ? _(set_v[i].desc) : NULL;
382 }
383
384 const char *set_shot(int i)
385 {
386     return set_exists(i) ? set_v[i].shot : NULL;
387 }
388
389 const struct score *set_time_score(int i)
390 {
391     return set_exists(i) ? &set_v[i].time_score : NULL;
392 }
393
394 const struct score *set_coin_score(int i)
395 {
396     return set_exists(i) ? &set_v[i].coin_score : NULL;
397 }
398
399 /*---------------------------------------------------------------------------*/
400
401 int set_level_exists(int s, int i)
402 {
403     return (i >= 0 && i < set_v[s].count);
404 }
405
406 static void set_load_levels(void)
407 {
408     struct level *l;
409     int nb = 1, bnb = 1;
410
411     int i;
412
413     const char *roman[] = {
414         "",
415         "I",   "II",   "III",   "IV",   "V",
416         "VI",  "VII",  "VIII",  "IX",   "X",
417         "XI",  "XII",  "XIII",  "XIV",  "XV",
418         "XVI", "XVII", "XVIII", "XIX",  "XX",
419         "XXI", "XXII", "XXIII", "XXIV", "XXV"
420     };
421
422     for (i = 0; i < set_v[set].count; i++)
423     {
424         l = &level_v[i];
425
426         level_load(set_v[set].level_name_v[i], l);
427
428         l->set    = &set_v[set];
429         l->number = i;
430
431         if (l->is_bonus)
432             sprintf(l->name, "%s",   roman[bnb++]);
433         else
434             sprintf(l->name, "%02d", nb++);
435
436         l->is_locked    = 1;
437         l->is_completed = 0;
438     }
439
440     /* Unlock first level. */
441
442     level_v[0].is_locked = 0;
443 }
444
445 void set_goto(int i)
446 {
447     set = i;
448
449     set_load_levels();
450     set_load_hs();
451 }
452
453 int curr_set(void)
454 {
455     return set;
456 }
457
458 struct level *get_level(int i)
459 {
460     return (i >= 0 && i < set_v[set].count) ? &level_v[i] : NULL;
461 }
462
463 /*---------------------------------------------------------------------------*/
464
465 int set_score_update(int timer, int coins, int *score_rank, int *times_rank)
466 {
467     struct set *s = &set_v[set];
468     char player[MAXSTR] = "";
469
470     config_get_s(CONFIG_PLAYER, player, MAXSTR);
471
472     if (score_rank)
473         *score_rank = score_coin_insert(&s->coin_score, player, timer, coins);
474
475     if (times_rank)
476         *times_rank = score_time_insert(&s->time_score, player, timer, coins);
477
478     if ((score_rank && *score_rank < 3) || (times_rank && *times_rank < 3))
479         return 1;
480     else
481         return 0;
482 }
483
484 void set_rename_player(int score_rank, int times_rank, const char *player)
485 {
486     struct set *s = &set_v[set];
487
488     strncpy(s->coin_score.player[score_rank], player, MAXNAM);
489     strncpy(s->time_score.player[times_rank], player, MAXNAM);
490 }
491
492 /*---------------------------------------------------------------------------*/
493
494 void level_snap(int i, const char *path)
495 {
496     char filename[MAXSTR];
497
498     /* Convert the level name to a PNG filename. */
499
500     sprintf(filename, "%s/%s.png", path, base_name(level_v[i].file, ".sol"));
501
502     /* Initialize the game for a snapshot. */
503
504     if (game_client_init(level_v[i].file))
505     {
506         union cmd cmd;
507
508         cmd.type = CMD_GOAL_OPEN;
509         game_proxy_enq(&cmd);
510
511         /* Render the level and grab the screen. */
512
513         video_clear();
514         game_set_fly(1.f, game_client_file());
515         game_kill_fade();
516         game_client_step(NULL);
517         game_draw(1, 0);
518         SDL_GL_SwapBuffers();
519
520         image_snap(filename);
521     }
522 }
523
524 void set_cheat(void)
525 {
526     int i;
527
528     for (i = 0; i < set_v[set].count; i++)
529     {
530         level_v[i].is_locked    = 0;
531         level_v[i].is_completed = 1;
532     }
533 }
534
535 /*---------------------------------------------------------------------------*/