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