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