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