Add a macro for copying strings to static arrays
[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 = RANK_HARD; j < RANK_LAST; 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->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     int j;
114     int res = 1;
115     char line[MAXSTR];
116
117     for (j = RANK_HARD; j < RANK_LAST && 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 static void set_load_hs(void)
129 {
130     struct set *s = SET_GET(sets, curr);
131     fs_file fin;
132     int i;
133     int res = 0;
134     struct level *l;
135     const char *fn = config_cheat() ? s->cheat_scores : s->user_scores;
136     char states[MAXLVL + sizeof ("\n")];
137
138     if ((fin = fs_open(fn, "r")))
139     {
140         res = (fs_gets(states, sizeof (states), fin) &&
141                strlen(states) - 1 == s->count);
142
143         for (i = 0; i < s->count && res; i++)
144         {
145             switch (states[i])
146             {
147             case 'L':
148                 level_v[i].is_locked = 1;
149                 level_v[i].is_completed = 0;
150                 break;
151
152             case 'C':
153                 level_v[i].is_locked = 0;
154                 level_v[i].is_completed = 1;
155                 break;
156
157             case 'O':
158                 level_v[i].is_locked = 0;
159                 level_v[i].is_completed = 0;
160                 break;
161
162             default:
163                 res = 0;
164             }
165         }
166
167         res = res &&
168             get_score(fin, &s->time_score) &&
169             get_score(fin, &s->coin_score);
170
171         for (i = 0; i < s->count && res; i++)
172         {
173             l = &level_v[i];
174             res = (get_score(fin, &l->scores[SCORE_TIME])  &&
175                    get_score(fin, &l->scores[SCORE_GOAL]) &&
176                    get_score(fin, &l->scores[SCORE_COIN]));
177         }
178
179         fs_close(fin);
180
181         if (!res)
182             fprintf(stderr, L_("Failure to load user score file '%s'\n"), fn);
183     }
184 }
185
186 /*---------------------------------------------------------------------------*/
187
188 static int set_load(struct set *s, const char *filename)
189 {
190     fs_file fin;
191     char *scores, *level_name;
192
193     /* Skip "Misc" set when not in dev mode. */
194
195     if (strcmp(filename, SET_MISC) == 0 && !config_cheat())
196         return 0;
197
198     fin = fs_open(filename, "r");
199
200     if (!fin)
201     {
202         fprintf(stderr, L_("Failure to load set file '%s'\n"), filename);
203         return 0;
204     }
205
206     memset(s, 0, sizeof (struct set));
207
208     /* Set some sane values in case the scores are missing. */
209
210     score_init_hs(&s->time_score, 359999, 0);
211     score_init_hs(&s->coin_score, 359999, 0);
212
213     SAFECPY(s->file, filename);
214
215     if (read_line(&s->name, fin) &&
216         read_line(&s->desc, fin) &&
217         read_line(&s->id,   fin) &&
218         read_line(&s->shot, fin) &&
219         read_line(&scores,  fin))
220     {
221         sscanf(scores, "%d %d %d %d %d %d",
222                &s->time_score.timer[RANK_HARD],
223                &s->time_score.timer[RANK_MEDM],
224                &s->time_score.timer[RANK_EASY],
225                &s->coin_score.coins[RANK_HARD],
226                &s->coin_score.coins[RANK_MEDM],
227                &s->coin_score.coins[RANK_EASY]);
228
229         free(scores);
230
231         s->user_scores  = concat_string("Scores/", s->id, ".txt",       NULL);
232         s->cheat_scores = concat_string("Scores/", s->id, "-cheat.txt", NULL);
233
234         s->count = 0;
235
236         while (s->count < MAXLVL && read_line(&level_name, fin))
237         {
238             s->level_name_v[s->count] = level_name;
239             s->count++;
240         }
241
242         fs_close(fin);
243
244         return 1;
245     }
246
247     free(s->name);
248     free(s->desc);
249     free(s->id);
250     free(s->shot);
251
252     fs_close(fin);
253
254     return 0;
255 }
256
257 static void set_free(struct set *s)
258 {
259     int i;
260
261     free(s->name);
262     free(s->desc);
263     free(s->id);
264     free(s->shot);
265
266     free(s->user_scores);
267     free(s->cheat_scores);
268
269     for (i = 0; i < s->count; i++)
270         free(s->level_name_v[i]);
271 }
272
273 /*---------------------------------------------------------------------------*/
274
275 static int cmp_dir_items(const void *A, const void *B)
276 {
277     const struct dir_item *a = A, *b = B;
278     return strcmp(a->path, b->path);
279 }
280
281 static int set_is_loaded(const char *path)
282 {
283     int i;
284
285     for (i = 0; i < array_len(sets); i++)
286         if (strcmp(SET_GET(sets, i)->file, path) == 0)
287             return 1;
288
289     return 0;
290 }
291
292 static int is_unseen_set(struct dir_item *item)
293 {
294     return (str_starts_with(base_name(item->path), "set-") &&
295             str_ends_with(item->path, ".txt") &&
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_score(int i, int s)
393 {
394     if (set_exists(i))
395     {
396         if (s == SCORE_TIME) return &SET_GET(sets, i)->time_score;
397         if (s == SCORE_COIN) return &SET_GET(sets, i)->coin_score;
398     }
399     return NULL;
400 }
401
402 /*---------------------------------------------------------------------------*/
403
404 static void set_load_levels(void)
405 {
406     struct level *l;
407     int nb = 1, bnb = 1;
408
409     int i;
410
411     const char *roman[] = {
412         "",
413         "I",   "II",   "III",   "IV",   "V",
414         "VI",  "VII",  "VIII",  "IX",   "X",
415         "XI",  "XII",  "XIII",  "XIV",  "XV",
416         "XVI", "XVII", "XVIII", "XIX",  "XX",
417         "XXI", "XXII", "XXIII", "XXIV", "XXV"
418     };
419
420     for (i = 0; i < SET_GET(sets, curr)->count; i++)
421     {
422         l = &level_v[i];
423
424         level_load(SET_GET(sets, curr)->level_name_v[i], l);
425
426         l->number = i;
427
428         if (l->is_bonus)
429             sprintf(l->name, "%s",   roman[bnb++]);
430         else
431             sprintf(l->name, "%02d", nb++);
432
433         l->is_locked    = 1;
434         l->is_completed = 0;
435
436         if (i > 0) level_v[i - 1].next = l;
437     }
438
439     /* Unlock first level. */
440
441     level_v[0].is_locked = 0;
442 }
443
444 void set_goto(int i)
445 {
446     curr = i;
447
448     set_load_levels();
449     set_load_hs();
450 }
451
452 int curr_set(void)
453 {
454     return curr;
455 }
456
457 struct level *get_level(int i)
458 {
459     return (i >= 0 && i < SET_GET(sets, curr)->count) ? &level_v[i] : NULL;
460 }
461
462 /*---------------------------------------------------------------------------*/
463
464 int set_score_update(int timer, int coins, int *score_rank, int *times_rank)
465 {
466     struct set *s = SET_GET(sets, curr);
467     const char *player = config_get_s(CONFIG_PLAYER);
468
469     score_coin_insert(&s->coin_score, score_rank, player, timer, coins);
470     score_time_insert(&s->time_score, times_rank, player, timer, coins);
471
472     if ((score_rank && *score_rank < RANK_LAST) ||
473         (times_rank && *times_rank < RANK_LAST))
474         return 1;
475     else
476         return 0;
477 }
478
479 void set_rename_player(int score_rank, int times_rank, const char *player)
480 {
481     struct set *s = SET_GET(sets, curr);
482
483     SAFECPY(s->coin_score.player[score_rank], player);
484     SAFECPY(s->time_score.player[times_rank], player);
485 }
486
487 /*---------------------------------------------------------------------------*/
488
489 void level_snap(int i, const char *path)
490 {
491     char *filename;
492
493     /* Convert the level name to a PNG filename. */
494
495     filename = concat_string(path,
496                              "/",
497                              base_name_sans(level_v[i].file, ".sol"),
498                              ".png",
499                              NULL);
500
501     /* Initialize the game for a snapshot. */
502
503     if (game_client_init(level_v[i].file))
504     {
505         union cmd cmd;
506         cmd.type = CMD_GOAL_OPEN;
507         game_proxy_enq(&cmd);
508         game_client_sync(NULL);
509
510         /* Render the level and grab the screen. */
511
512         video_clear();
513         game_client_fly(1.0f);
514         game_kill_fade();
515         game_client_draw(POSE_LEVEL, 0);
516         SDL_GL_SwapBuffers();
517
518         image_snap(filename);
519     }
520
521     free(filename);
522 }
523
524 void set_cheat(void)
525 {
526     int i;
527
528     for (i = 0; i < SET_GET(sets, curr)->count; i++)
529     {
530         level_v[i].is_locked    = 0;
531         level_v[i].is_completed = 1;
532     }
533 }
534
535 /*---------------------------------------------------------------------------*/