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