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