Implement a Quake-like virtual file system layer
[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[PATHMAX]; /* User high-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 static int set_state = 0;
55
56 static int set;
57 static int count;
58
59 static struct set   set_v[MAXSET];
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_v[set];
75     fs_file fout;
76     int i;
77     const struct level *l;
78     char states[MAXLVL + 1];
79
80     if ((fout = fs_open(s->user_scores, "w")))
81     {
82         for (i = 0; i < s->count; i++)
83         {
84             if (level_v[i].is_locked)
85                 states[i] = 'L';
86             else if (level_v[i].is_completed)
87                 states[i] = 'C';
88             else
89                 states[i] = 'O';
90         }
91         states[s->count] = '\0';
92         fs_printf(fout, "%s\n",states);
93
94         put_score(fout, &s->time_score);
95         put_score(fout, &s->coin_score);
96
97         for (i = 0; i < s->count; i++)
98         {
99             l = &level_v[i];
100
101             put_score(fout, &l->score.best_times);
102             put_score(fout, &l->score.fast_unlock);
103             put_score(fout, &l->score.most_coins);
104         }
105
106         fs_close(fout);
107     }
108 }
109
110 static int get_score(fs_file fp, struct score *s)
111 {
112     int j;
113     int res = 1;
114     char line[MAXSTR];
115
116     for (j = 0; j < NSCORE && res; j++)
117     {
118         res = (fs_gets(line, sizeof (line), fp) &&
119                sscanf(line, "%d %d %s\n",
120                       &s->timer[j],
121                       &s->coins[j],
122                       s->player[j]) == 3);
123     }
124     return res;
125 }
126
127 /* Get the score of the set. */
128 static void set_load_hs(void)
129 {
130     struct set *s = &set_v[set];
131     fs_file fin;
132     int i;
133     int res = 0;
134     struct level *l;
135     const char *fn = 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->score.best_times) &&
175                 get_score(fin, &l->score.fast_unlock) &&
176                 get_score(fin, &l->score.most_coins);
177         }
178
179         fs_close(fin);
180     }
181
182     if (!res && errno != ENOENT)
183     {
184         fprintf(stderr,
185                 L_("Error while loading user high-score file '%s': %s\n"),
186                 fn, errno ? strerror(errno) : L_("Incorrect format"));
187     }
188 }
189
190 /*---------------------------------------------------------------------------*/
191
192 static int set_load(struct set *s, const char *filename)
193 {
194     fs_file fin;
195     char *scores, *level_name;
196
197     fin = fs_open(filename, "r");
198
199     if (!fin)
200     {
201         fprintf(stderr, L_("Cannot load the set file '%s': %s\n"),
202                 filename, strerror(errno));
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     strncpy(s->file, filename, PATHMAX - 1);
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[0],
223                &s->time_score.timer[1],
224                &s->time_score.timer[2],
225                &s->coin_score.coins[0],
226                &s->coin_score.coins[1],
227                &s->coin_score.coins[2]);
228
229         free(scores);
230
231         strncpy(s->user_scores, "neverballhs-", PATHMAX - 1);
232         strncat(s->user_scores, s->id, PATHMAX - 1 - strlen("neverballhs-"));
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 int set_init()
258 {
259     fs_file fin;
260     char *name;
261
262     if (set_state)
263         set_free();
264
265     set   = 0;
266     count = 0;
267
268     if ((fin = fs_open(SET_FILE, "r")))
269     {
270         while (count < MAXSET && read_line(&name, fin))
271         {
272             /* Skip "Misc" set when not in dev mode. */
273
274             if (strcmp(name, SET_MISC) == 0 && !config_cheat())
275             {
276                 free(name);
277                 continue;
278             }
279
280             if (set_load(&set_v[count], name))
281                 count++;
282
283             free(name);
284         }
285         fs_close(fin);
286
287         set_state = 1;
288     }
289
290     return count;
291 }
292
293 void set_free(void)
294 {
295     int i, j;
296
297     for (i = 0; i < count; i++)
298     {
299         free(set_v[i].name);
300         free(set_v[i].desc);
301         free(set_v[i].id);
302         free(set_v[i].shot);
303
304         for (j = 0; j < set_v[i].count; j++)
305             free(set_v[i].level_name_v[j]);
306     }
307
308     set_state = 0;
309 }
310
311 /*---------------------------------------------------------------------------*/
312
313 int set_exists(int i)
314 {
315     return (0 <= i && i < count);
316 }
317
318 const char *set_id(int i)
319 {
320     return set_exists(i) ? set_v[i].id : NULL;
321 }
322
323 const char *set_name(int i)
324 {
325     return set_exists(i) ? _(set_v[i].name) : NULL;
326 }
327
328 const char *set_desc(int i)
329 {
330     return set_exists(i) ? _(set_v[i].desc) : NULL;
331 }
332
333 const char *set_shot(int i)
334 {
335     return set_exists(i) ? set_v[i].shot : NULL;
336 }
337
338 const struct score *set_time_score(int i)
339 {
340     return set_exists(i) ? &set_v[i].time_score : NULL;
341 }
342
343 const struct score *set_coin_score(int i)
344 {
345     return set_exists(i) ? &set_v[i].coin_score : NULL;
346 }
347
348 /*---------------------------------------------------------------------------*/
349
350 int set_level_exists(int s, int i)
351 {
352     return (i >= 0 && i < set_v[s].count);
353 }
354
355 static void set_load_levels(void)
356 {
357     struct level *l;
358     int nb = 1, bnb = 1;
359
360     int i;
361
362     const char *roman[] = {
363         "",
364         "I",   "II",   "III",   "IV",   "V",
365         "VI",  "VII",  "VIII",  "IX",   "X",
366         "XI",  "XII",  "XIII",  "XIV",  "XV",
367         "XVI", "XVII", "XVIII", "XIX",  "XX",
368         "XXI", "XXII", "XXIII", "XXIV", "XXV"
369     };
370
371     for (i = 0; i < set_v[set].count; i++)
372     {
373         l = &level_v[i];
374
375         level_load(set_v[set].level_name_v[i], l);
376
377         l->set    = &set_v[set];
378         l->number = i;
379
380         if (l->is_bonus)
381             sprintf(l->name, "%s",   roman[bnb++]);
382         else
383             sprintf(l->name, "%02d", nb++);
384
385         l->is_locked    = 1;
386         l->is_completed = 0;
387     }
388
389     /* Unlock first level. */
390
391     level_v[0].is_locked = 0;
392 }
393
394 void set_goto(int i)
395 {
396     set = i;
397
398     set_load_levels();
399     set_load_hs();
400 }
401
402 int curr_set(void)
403 {
404     return set;
405 }
406
407 struct level *get_level(int i)
408 {
409     return (i >= 0 && i < set_v[set].count) ? &level_v[i] : NULL;
410 }
411
412 /*---------------------------------------------------------------------------*/
413
414 int set_score_update(int timer, int coins, int *score_rank, int *times_rank)
415 {
416     struct set *s = &set_v[set];
417     char player[MAXSTR] = "";
418
419     config_get_s(CONFIG_PLAYER, player, MAXSTR);
420
421     if (score_rank)
422         *score_rank = score_coin_insert(&s->coin_score, player, timer, coins);
423
424     if (times_rank)
425         *times_rank = score_time_insert(&s->time_score, player, timer, coins);
426
427     if ((score_rank && *score_rank < 3) || (times_rank && *times_rank < 3))
428         return 1;
429     else
430         return 0;
431 }
432
433 void set_rename_player(int score_rank, int times_rank, const char *player)
434 {
435     struct set *s = &set_v[set];
436
437     strncpy(s->coin_score.player[score_rank], player, MAXNAM);
438     strncpy(s->time_score.player[times_rank], player, MAXNAM);
439 }
440
441 /*---------------------------------------------------------------------------*/
442
443 void level_snap(int i)
444 {
445     char filename[MAXSTR];
446     char *ext;
447
448     /* Convert the level name to a PNG filename. */
449
450     memset(filename, 0, MAXSTR);
451
452     ext = strrchr(level_v[i].file, '.');
453     strncpy(filename, level_v[i].file,
454             ext ? ext - level_v[i].file : strlen(level_v[i].file));
455     strcat(filename, ".png");
456
457     /* Initialize the game for a snapshot. */
458
459     if (game_client_init(level_v[i].file))
460     {
461         union cmd cmd;
462
463         cmd.type = CMD_GOAL_OPEN;
464         game_proxy_enq(&cmd);
465
466         /* Render the level and grab the screen. */
467
468         video_clear();
469         game_set_fly(1.f, game_client_file());
470         game_kill_fade();
471         game_client_step(NULL);
472         game_draw(1, 0);
473
474         image_snap(filename);
475
476         SDL_GL_SwapBuffers();
477     }
478 }
479
480 void set_cheat(void)
481 {
482     int i;
483
484     for (i = 0; i < set_v[set].count; i++)
485     {
486         level_v[i].is_locked    = 0;
487         level_v[i].is_completed = 1;
488     }
489 }
490
491 /*---------------------------------------------------------------------------*/