struct level: remove unused member
[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'\n"), 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             strcmp(item->path + strlen(item->path) - 4, ".txt") == 0 &&
297             !set_is_loaded(item->path));
298 }
299
300 int set_init()
301 {
302     fs_file fin;
303     char *name;
304
305     Array items;
306     int i;
307
308     if (sets)
309         set_quit();
310
311     sets = array_new(sizeof (struct set));
312     curr = 0;
313
314     /*
315      * First, load the sets listed in the set file, preserving order.
316      */
317
318     if ((fin = fs_open(SET_FILE, "r")))
319     {
320         while (read_line(&name, fin))
321         {
322             struct set *s = array_add(sets);
323
324             if (!set_load(s, name))
325                 array_del(sets);
326
327             free(name);
328         }
329         fs_close(fin);
330     }
331
332     /*
333      * Then, scan for any remaining set description files, and add
334      * them after the first group in alphabetic order.
335      */
336
337     if ((items = fs_dir_scan("", is_unseen_set)))
338     {
339         array_sort(items, cmp_dir_items);
340
341         for (i = 0; i < array_len(items); i++)
342         {
343             struct set *s = array_add(sets);
344
345             if (!set_load(s, DIR_ITEM_GET(items, i)->path))
346                 array_del(sets);
347         }
348
349         fs_dir_free(items);
350     }
351
352     return array_len(sets);
353 }
354
355 void set_quit(void)
356 {
357     int i;
358
359     for (i = 0; i < array_len(sets); i++)
360         set_free(array_get(sets, i));
361
362     array_free(sets);
363     sets = NULL;
364 }
365
366 /*---------------------------------------------------------------------------*/
367
368 int set_exists(int i)
369 {
370     return (0 <= i && i < array_len(sets));
371 }
372
373 const char *set_id(int i)
374 {
375     return set_exists(i) ? SET_GET(sets, i)->id : NULL;
376 }
377
378 const char *set_name(int i)
379 {
380     return set_exists(i) ? _(SET_GET(sets, i)->name) : NULL;
381 }
382
383 const char *set_desc(int i)
384 {
385     return set_exists(i) ? _(SET_GET(sets, i)->desc) : NULL;
386 }
387
388 const char *set_shot(int i)
389 {
390     return set_exists(i) ? SET_GET(sets, i)->shot : NULL;
391 }
392
393 const struct score *set_time_score(int i)
394 {
395     return set_exists(i) ? &SET_GET(sets, i)->time_score : NULL;
396 }
397
398 const struct score *set_coin_score(int i)
399 {
400     return set_exists(i) ? &SET_GET(sets, i)->coin_score : NULL;
401 }
402
403 /*---------------------------------------------------------------------------*/
404
405 int set_level_exists(int i, int l)
406 {
407     return (l >= 0 && l < SET_GET(sets, i)->count);
408 }
409
410 static void set_load_levels(void)
411 {
412     struct level *l;
413     int nb = 1, bnb = 1;
414
415     int i;
416
417     const char *roman[] = {
418         "",
419         "I",   "II",   "III",   "IV",   "V",
420         "VI",  "VII",  "VIII",  "IX",   "X",
421         "XI",  "XII",  "XIII",  "XIV",  "XV",
422         "XVI", "XVII", "XVIII", "XIX",  "XX",
423         "XXI", "XXII", "XXIII", "XXIV", "XXV"
424     };
425
426     for (i = 0; i < SET_GET(sets, curr)->count; i++)
427     {
428         l = &level_v[i];
429
430         level_load(SET_GET(sets, curr)->level_name_v[i], l);
431
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     score_coin_insert(&s->coin_score, score_rank, player, timer, coins);
474     score_time_insert(&s->time_score, times_rank, player, timer, coins);
475
476     if ((score_rank && *score_rank < 3) || (times_rank && *times_rank < 3))
477         return 1;
478     else
479         return 0;
480 }
481
482 void set_rename_player(int score_rank, int times_rank, const char *player)
483 {
484     struct set *s = SET_GET(sets, curr);
485
486     strncpy(s->coin_score.player[score_rank], player, MAXNAM - 1);
487     strncpy(s->time_score.player[times_rank], player, MAXNAM - 1);
488 }
489
490 /*---------------------------------------------------------------------------*/
491
492 void level_snap(int i, const char *path)
493 {
494     char filename[MAXSTR];
495
496     /* Convert the level name to a PNG filename. */
497
498     sprintf(filename, "%s/%s.png", path, base_name(level_v[i].file, ".sol"));
499
500     /* Initialize the game for a snapshot. */
501
502     if (game_client_init(level_v[i].file))
503     {
504         union cmd cmd;
505         cmd.type = CMD_GOAL_OPEN;
506         game_proxy_enq(&cmd);
507         game_client_sync(NULL);
508
509         /* Render the level and grab the screen. */
510
511         video_clear();
512         game_client_fly(1.0f);
513         game_kill_fade();
514         game_draw(POSE_LEVEL, 0);
515         SDL_GL_SwapBuffers();
516
517         image_snap(filename);
518     }
519 }
520
521 void set_cheat(void)
522 {
523     int i;
524
525     for (i = 0; i < SET_GET(sets, curr)->count; i++)
526     {
527         level_v[i].is_locked    = 0;
528         level_v[i].is_completed = 1;
529     }
530 }
531
532 /*---------------------------------------------------------------------------*/