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 (j = RANK_HARD; j < RANK_LAST; j++)
68 fs_printf(fp, "%d %d %s\n", s->timer[j], s->coins[j], s->player[j]);
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)
117 for (j = RANK_HARD; j < RANK_LAST && res; j++)
119 res = (fs_gets(line, sizeof (line), fp) &&
120 sscanf(line, "%d %d %s\n",
128 static void set_load_hs(void)
130 struct set *s = SET_GET(sets, curr);
135 const char *fn = config_cheat() ? s->cheat_scores : s->user_scores;
136 char states[MAXLVL + sizeof ("\n")];
138 if ((fin = fs_open(fn, "r")))
140 res = (fs_gets(states, sizeof (states), fin) &&
141 strlen(states) - 1 == s->count);
143 for (i = 0; i < s->count && res; i++)
148 level_v[i].is_locked = 1;
149 level_v[i].is_completed = 0;
153 level_v[i].is_locked = 0;
154 level_v[i].is_completed = 1;
158 level_v[i].is_locked = 0;
159 level_v[i].is_completed = 0;
168 get_score(fin, &s->time_score) &&
169 get_score(fin, &s->coin_score);
171 for (i = 0; i < s->count && res; i++)
174 res = (get_score(fin, &l->scores[SCORE_TIME]) &&
175 get_score(fin, &l->scores[SCORE_GOAL]) &&
176 get_score(fin, &l->scores[SCORE_COIN]));
182 fprintf(stderr, L_("Failure to load user score file '%s'\n"), fn);
186 /*---------------------------------------------------------------------------*/
188 static int set_load(struct set *s, const char *filename)
191 char *scores, *level_name;
193 /* Skip "Misc" set when not in dev mode. */
195 if (strcmp(filename, SET_MISC) == 0 && !config_cheat())
198 fin = fs_open(filename, "r");
202 fprintf(stderr, L_("Failure to load set file '%s'\n"), filename);
206 memset(s, 0, sizeof (struct set));
208 /* Set some sane values in case the scores are missing. */
210 score_init_hs(&s->time_score, 359999, 0);
211 score_init_hs(&s->coin_score, 359999, 0);
213 SAFECPY(s->file, filename);
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))
221 sscanf(scores, "%d %d %d %d %d %d",
222 &s->time_score.timer[RANK_HARD],
223 &s->time_score.timer[RANK_MEDM],
224 &s->time_score.timer[RANK_EASY],
225 &s->coin_score.coins[RANK_HARD],
226 &s->coin_score.coins[RANK_MEDM],
227 &s->coin_score.coins[RANK_EASY]);
231 s->user_scores = concat_string("Scores/", s->id, ".txt", NULL);
232 s->cheat_scores = concat_string("Scores/", s->id, "-cheat.txt", NULL);
236 while (s->count < MAXLVL && read_line(&level_name, fin))
238 s->level_name_v[s->count] = level_name;
257 static void set_free(struct set *s)
266 free(s->user_scores);
267 free(s->cheat_scores);
269 for (i = 0; i < s->count; i++)
270 free(s->level_name_v[i]);
273 /*---------------------------------------------------------------------------*/
275 static int cmp_dir_items(const void *A, const void *B)
277 const struct dir_item *a = A, *b = B;
278 return strcmp(a->path, b->path);
281 static int set_is_loaded(const char *path)
285 for (i = 0; i < array_len(sets); i++)
286 if (strcmp(SET_GET(sets, i)->file, path) == 0)
292 static int is_unseen_set(struct dir_item *item)
294 return (str_starts_with(base_name(item->path), "set-") &&
295 str_ends_with(item->path, ".txt") &&
296 !set_is_loaded(item->path));
310 sets = array_new(sizeof (struct set));
314 * First, load the sets listed in the set file, preserving order.
317 if ((fin = fs_open(SET_FILE, "r")))
319 while (read_line(&name, fin))
321 struct set *s = array_add(sets);
323 if (!set_load(s, name))
332 * Then, scan for any remaining set description files, and add
333 * them after the first group in alphabetic order.
336 if ((items = fs_dir_scan("", is_unseen_set)))
338 array_sort(items, cmp_dir_items);
340 for (i = 0; i < array_len(items); i++)
342 struct set *s = array_add(sets);
344 if (!set_load(s, DIR_ITEM_GET(items, i)->path))
351 return array_len(sets);
358 for (i = 0; i < array_len(sets); i++)
359 set_free(array_get(sets, i));
365 /*---------------------------------------------------------------------------*/
367 int set_exists(int i)
369 return (0 <= i && i < array_len(sets));
372 const char *set_id(int i)
374 return set_exists(i) ? SET_GET(sets, i)->id : NULL;
377 const char *set_name(int i)
379 return set_exists(i) ? _(SET_GET(sets, i)->name) : NULL;
382 const char *set_desc(int i)
384 return set_exists(i) ? _(SET_GET(sets, i)->desc) : NULL;
387 const char *set_shot(int i)
389 return set_exists(i) ? SET_GET(sets, i)->shot : NULL;
392 const struct score *set_score(int i, int s)
396 if (s == SCORE_TIME) return &SET_GET(sets, i)->time_score;
397 if (s == SCORE_COIN) return &SET_GET(sets, i)->coin_score;
402 /*---------------------------------------------------------------------------*/
404 static void set_load_levels(void)
411 const char *roman[] = {
413 "I", "II", "III", "IV", "V",
414 "VI", "VII", "VIII", "IX", "X",
415 "XI", "XII", "XIII", "XIV", "XV",
416 "XVI", "XVII", "XVIII", "XIX", "XX",
417 "XXI", "XXII", "XXIII", "XXIV", "XXV"
420 for (i = 0; i < SET_GET(sets, curr)->count; i++)
424 level_load(SET_GET(sets, curr)->level_name_v[i], l);
429 sprintf(l->name, "%s", roman[bnb++]);
431 sprintf(l->name, "%02d", nb++);
436 if (i > 0) level_v[i - 1].next = l;
439 /* Unlock first level. */
441 level_v[0].is_locked = 0;
457 struct level *get_level(int i)
459 return (i >= 0 && i < SET_GET(sets, curr)->count) ? &level_v[i] : NULL;
462 /*---------------------------------------------------------------------------*/
464 int set_score_update(int timer, int coins, int *score_rank, int *times_rank)
466 struct set *s = SET_GET(sets, curr);
467 const char *player = config_get_s(CONFIG_PLAYER);
469 score_coin_insert(&s->coin_score, score_rank, player, timer, coins);
470 score_time_insert(&s->time_score, times_rank, player, timer, coins);
472 if ((score_rank && *score_rank < RANK_LAST) ||
473 (times_rank && *times_rank < RANK_LAST))
479 void set_rename_player(int score_rank, int times_rank, const char *player)
481 struct set *s = SET_GET(sets, curr);
483 SAFECPY(s->coin_score.player[score_rank], player);
484 SAFECPY(s->time_score.player[times_rank], player);
487 /*---------------------------------------------------------------------------*/
489 void level_snap(int i, const char *path)
493 /* Convert the level name to a PNG filename. */
495 filename = concat_string(path,
497 base_name_sans(level_v[i].file, ".sol"),
501 /* Initialize the game for a snapshot. */
503 if (game_client_init(level_v[i].file))
506 cmd.type = CMD_GOAL_OPEN;
507 game_proxy_enq(&cmd);
508 game_client_sync(NULL);
510 /* Render the level and grab the screen. */
513 game_client_fly(1.0f);
515 game_client_draw(POSE_LEVEL, 0);
516 SDL_GL_SwapBuffers();
518 image_snap(filename);
528 for (i = 0; i < SET_GET(sets, curr)->count; i++)
530 level_v[i].is_locked = 0;
531 level_v[i].is_completed = 1;
535 /*---------------------------------------------------------------------------*/