2 * Copyright (C) 2003 Robert Kooima
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.
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.
27 #include "game_server.h"
28 #include "game_client.h"
29 #include "game_proxy.h"
31 /*---------------------------------------------------------------------------*/
37 char *id; /* Internal set identifier */
38 char *name; /* Set name */
39 char *desc; /* Set description */
40 char *shot; /* Set screen-shot */
42 char *user_scores; /* User high-score file */
43 char *cheat_scores; /* Cheat mode score file */
45 struct score coin_score; /* Challenge score */
46 struct score time_score; /* Challenge score */
50 int count; /* Number of levels */
51 char *level_name_v[MAXLVL]; /* List of level file names */
54 #define SET_GET(a, i) ((struct set *) array_get((a), (i)))
59 static struct level level_v[MAXLVL];
61 /*---------------------------------------------------------------------------*/
63 static void put_score(fs_file fp, const struct score *s)
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]);
71 void set_store_hs(void)
73 const struct set *s = SET_GET(sets, curr);
76 const struct level *l;
77 char states[MAXLVL + 1];
79 if ((fout = fs_open(config_cheat() ?
81 s->user_scores, "w")))
83 for (i = 0; i < s->count; i++)
85 if (level_v[i].is_locked)
87 else if (level_v[i].is_completed)
92 states[s->count] = '\0';
93 fs_printf(fout, "%s\n",states);
95 put_score(fout, &s->time_score);
96 put_score(fout, &s->coin_score);
98 for (i = 0; i < s->count; i++)
102 put_score(fout, &l->scores[SCORE_TIME]);
103 put_score(fout, &l->scores[SCORE_GOAL]);
104 put_score(fout, &l->scores[SCORE_COIN]);
111 static int get_score(fs_file fp, struct score *s)
116 for (i = RANK_HARD; i <= RANK_EASY; i++)
120 if (!fs_gets(line, sizeof (line), fp))
125 if (sscanf(line, "%d %d %n", &s->timer[i], &s->coins[i], &n) < 2)
131 SAFECPY(s->player[i], line + n);
137 static void set_load_hs(void)
139 struct set *s = SET_GET(sets, curr);
144 const char *fn = config_cheat() ? s->cheat_scores : s->user_scores;
145 char states[MAXLVL + sizeof ("\n")];
147 if ((fin = fs_open(fn, "r")))
149 res = (fs_gets(states, sizeof (states), fin) &&
150 strlen(states) - 1 == s->count);
152 for (i = 0; i < s->count && res; i++)
157 level_v[i].is_locked = 1;
158 level_v[i].is_completed = 0;
162 level_v[i].is_locked = 0;
163 level_v[i].is_completed = 1;
167 level_v[i].is_locked = 0;
168 level_v[i].is_completed = 0;
177 get_score(fin, &s->time_score) &&
178 get_score(fin, &s->coin_score);
180 for (i = 0; i < s->count && res; i++)
183 res = (get_score(fin, &l->scores[SCORE_TIME]) &&
184 get_score(fin, &l->scores[SCORE_GOAL]) &&
185 get_score(fin, &l->scores[SCORE_COIN]));
191 fprintf(stderr, L_("Failure to load user score file '%s'\n"), fn);
195 /*---------------------------------------------------------------------------*/
197 static int set_load(struct set *s, const char *filename)
200 char *scores, *level_name;
202 /* Skip "Misc" set when not in dev mode. */
204 if (strcmp(filename, SET_MISC) == 0 && !config_cheat())
207 fin = fs_open(filename, "r");
211 fprintf(stderr, L_("Failure to load set file '%s'\n"), filename);
215 memset(s, 0, sizeof (struct set));
217 /* Set some sane values in case the scores are missing. */
219 score_init_hs(&s->time_score, 359999, 0);
220 score_init_hs(&s->coin_score, 359999, 0);
222 SAFECPY(s->file, filename);
224 if (read_line(&s->name, fin) &&
225 read_line(&s->desc, fin) &&
226 read_line(&s->id, fin) &&
227 read_line(&s->shot, fin) &&
228 read_line(&scores, fin))
230 sscanf(scores, "%d %d %d %d %d %d",
231 &s->time_score.timer[RANK_HARD],
232 &s->time_score.timer[RANK_MEDM],
233 &s->time_score.timer[RANK_EASY],
234 &s->coin_score.coins[RANK_HARD],
235 &s->coin_score.coins[RANK_MEDM],
236 &s->coin_score.coins[RANK_EASY]);
240 s->user_scores = concat_string("Scores/", s->id, ".txt", NULL);
241 s->cheat_scores = concat_string("Scores/", s->id, "-cheat.txt", NULL);
245 while (s->count < MAXLVL && read_line(&level_name, fin))
247 s->level_name_v[s->count] = level_name;
266 static void set_free(struct set *s)
275 free(s->user_scores);
276 free(s->cheat_scores);
278 for (i = 0; i < s->count; i++)
279 free(s->level_name_v[i]);
282 /*---------------------------------------------------------------------------*/
284 static int cmp_dir_items(const void *A, const void *B)
286 const struct dir_item *a = A, *b = B;
287 return strcmp(a->path, b->path);
290 static int set_is_loaded(const char *path)
294 for (i = 0; i < array_len(sets); i++)
295 if (strcmp(SET_GET(sets, i)->file, path) == 0)
301 static int is_unseen_set(struct dir_item *item)
303 return (str_starts_with(base_name(item->path), "set-") &&
304 str_ends_with(item->path, ".txt") &&
305 !set_is_loaded(item->path));
319 sets = array_new(sizeof (struct set));
323 * First, load the sets listed in the set file, preserving order.
326 if ((fin = fs_open(SET_FILE, "r")))
328 while (read_line(&name, fin))
330 struct set *s = array_add(sets);
332 if (!set_load(s, name))
341 * Then, scan for any remaining set description files, and add
342 * them after the first group in alphabetic order.
345 if ((items = fs_dir_scan("", is_unseen_set)))
347 array_sort(items, cmp_dir_items);
349 for (i = 0; i < array_len(items); i++)
351 struct set *s = array_add(sets);
353 if (!set_load(s, DIR_ITEM_GET(items, i)->path))
360 return array_len(sets);
367 for (i = 0; i < array_len(sets); i++)
368 set_free(array_get(sets, i));
374 /*---------------------------------------------------------------------------*/
376 int set_exists(int i)
378 return (0 <= i && i < array_len(sets));
381 const char *set_id(int i)
383 return set_exists(i) ? SET_GET(sets, i)->id : NULL;
386 const char *set_name(int i)
388 return set_exists(i) ? _(SET_GET(sets, i)->name) : NULL;
391 const char *set_desc(int i)
393 return set_exists(i) ? _(SET_GET(sets, i)->desc) : NULL;
396 const char *set_shot(int i)
398 return set_exists(i) ? SET_GET(sets, i)->shot : NULL;
401 const struct score *set_score(int i, int s)
405 if (s == SCORE_TIME) return &SET_GET(sets, i)->time_score;
406 if (s == SCORE_COIN) return &SET_GET(sets, i)->coin_score;
411 /*---------------------------------------------------------------------------*/
413 static void set_load_levels(void)
420 const char *roman[] = {
422 "I", "II", "III", "IV", "V",
423 "VI", "VII", "VIII", "IX", "X",
424 "XI", "XII", "XIII", "XIV", "XV",
425 "XVI", "XVII", "XVIII", "XIX", "XX",
426 "XXI", "XXII", "XXIII", "XXIV", "XXV"
429 for (i = 0; i < SET_GET(sets, curr)->count; i++)
433 level_load(SET_GET(sets, curr)->level_name_v[i], l);
438 sprintf(l->name, "%s", roman[bnb++]);
440 sprintf(l->name, "%02d", nb++);
445 if (i > 0) level_v[i - 1].next = l;
448 /* Unlock first level. */
450 level_v[0].is_locked = 0;
466 struct level *get_level(int i)
468 return (i >= 0 && i < SET_GET(sets, curr)->count) ? &level_v[i] : NULL;
471 /*---------------------------------------------------------------------------*/
473 int set_score_update(int timer, int coins, int *score_rank, int *times_rank)
475 struct set *s = SET_GET(sets, curr);
476 const char *player = config_get_s(CONFIG_PLAYER);
478 score_coin_insert(&s->coin_score, score_rank, player, timer, coins);
479 score_time_insert(&s->time_score, times_rank, player, timer, coins);
481 if ((score_rank && *score_rank < RANK_LAST) ||
482 (times_rank && *times_rank < RANK_LAST))
488 void set_rename_player(int score_rank, int times_rank, const char *player)
490 struct set *s = SET_GET(sets, curr);
492 SAFECPY(s->coin_score.player[score_rank], player);
493 SAFECPY(s->time_score.player[times_rank], player);
496 /*---------------------------------------------------------------------------*/
498 void level_snap(int i, const char *path)
502 /* Convert the level name to a PNG filename. */
504 filename = concat_string(path,
506 base_name_sans(level_v[i].file, ".sol"),
510 /* Initialize the game for a snapshot. */
512 if (game_client_init(level_v[i].file))
515 cmd.type = CMD_GOAL_OPEN;
516 game_proxy_enq(&cmd);
517 game_client_sync(NULL);
519 /* Render the level and grab the screen. */
522 game_client_fly(1.0f);
524 game_client_draw(POSE_LEVEL, 0);
525 SDL_GL_SwapBuffers();
527 image_snap(filename);
537 for (i = 0; i < SET_GET(sets, curr)->count; i++)
539 level_v[i].is_locked = 0;
540 level_v[i].is_completed = 1;
544 /*---------------------------------------------------------------------------*/