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 static int get_score(fs_file fp, struct score *s)
76 for (i = RANK_HARD; i <= RANK_EASY; i++)
80 if (!fs_gets(line, sizeof (line), fp))
85 if (sscanf(line, "%d %d %n", &s->timer[i], &s->coins[i], &n) < 2)
91 SAFECPY(s->player[i], line + n);
97 void set_store_hs(void)
99 const struct set *s = SET_GET(sets, curr);
102 if ((fp = fs_open(config_cheat() ? s->cheat_scores : s->user_scores, "w")))
104 const struct level *l;
106 char states[MAXLVL + 2] = "";
109 for (i = 0; i < s->count; i++)
115 else if (l->is_completed)
121 states[s->count] = '\n';
125 put_score(fp, &s->time_score);
126 put_score(fp, &s->coin_score);
128 for (i = 0; i < s->count; i++)
130 const struct level *l = &level_v[i];
132 put_score(fp, &l->scores[SCORE_TIME]);
133 put_score(fp, &l->scores[SCORE_GOAL]);
134 put_score(fp, &l->scores[SCORE_COIN]);
141 static void set_load_hs(void)
143 struct set *s = SET_GET(sets, curr);
146 if ((fp = fs_open(config_cheat() ? s->cheat_scores : s->user_scores, "r")))
148 char states[MAXLVL + 2];
150 if (fs_gets(states, sizeof (states), fp) &&
151 strlen(states) - 1 == s->count)
156 for (i = 0; i < s->count; i++)
160 l->is_locked = (states[i] == 'L');
161 l->is_completed = (states[i] == 'C');
164 get_score(fp, &s->time_score);
165 get_score(fp, &s->coin_score);
167 for (i = 0; i < s->count; i++)
171 get_score(fp, &l->scores[SCORE_TIME]);
172 get_score(fp, &l->scores[SCORE_GOAL]);
173 get_score(fp, &l->scores[SCORE_COIN]);
181 /*---------------------------------------------------------------------------*/
183 static int set_load(struct set *s, const char *filename)
186 char *scores, *level_name;
188 /* Skip "Misc" set when not in dev mode. */
190 if (strcmp(filename, SET_MISC) == 0 && !config_cheat())
193 fin = fs_open(filename, "r");
197 fprintf(stderr, L_("Failure to load set file '%s'\n"), filename);
201 memset(s, 0, sizeof (struct set));
203 /* Set some sane values in case the scores are missing. */
205 score_init_hs(&s->time_score, 359999, 0);
206 score_init_hs(&s->coin_score, 359999, 0);
208 SAFECPY(s->file, filename);
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))
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]);
226 s->user_scores = concat_string("Scores/", s->id, ".txt", NULL);
227 s->cheat_scores = concat_string("Scores/", s->id, "-cheat.txt", NULL);
231 while (s->count < MAXLVL && read_line(&level_name, fin))
233 s->level_name_v[s->count] = level_name;
252 static void set_free(struct set *s)
261 free(s->user_scores);
262 free(s->cheat_scores);
264 for (i = 0; i < s->count; i++)
265 free(s->level_name_v[i]);
268 /*---------------------------------------------------------------------------*/
270 static int cmp_dir_items(const void *A, const void *B)
272 const struct dir_item *a = A, *b = B;
273 return strcmp(a->path, b->path);
276 static int set_is_loaded(const char *path)
280 for (i = 0; i < array_len(sets); i++)
281 if (strcmp(SET_GET(sets, i)->file, path) == 0)
287 static int is_unseen_set(struct dir_item *item)
289 return (str_starts_with(base_name(item->path), "set-") &&
290 str_ends_with(item->path, ".txt") &&
291 !set_is_loaded(item->path));
305 sets = array_new(sizeof (struct set));
309 * First, load the sets listed in the set file, preserving order.
312 if ((fin = fs_open(SET_FILE, "r")))
314 while (read_line(&name, fin))
316 struct set *s = array_add(sets);
318 if (!set_load(s, name))
327 * Then, scan for any remaining set description files, and add
328 * them after the first group in alphabetic order.
331 if ((items = fs_dir_scan("", is_unseen_set)))
333 array_sort(items, cmp_dir_items);
335 for (i = 0; i < array_len(items); i++)
337 struct set *s = array_add(sets);
339 if (!set_load(s, DIR_ITEM_GET(items, i)->path))
346 return array_len(sets);
353 for (i = 0; i < array_len(sets); i++)
354 set_free(array_get(sets, i));
360 /*---------------------------------------------------------------------------*/
362 int set_exists(int i)
364 return sets ? 0 <= i && i < array_len(sets) : 0;
367 const char *set_id(int i)
369 return set_exists(i) ? SET_GET(sets, i)->id : NULL;
372 const char *set_name(int i)
374 return set_exists(i) ? _(SET_GET(sets, i)->name) : NULL;
377 const char *set_desc(int i)
379 return set_exists(i) ? _(SET_GET(sets, i)->desc) : NULL;
382 const char *set_shot(int i)
384 return set_exists(i) ? SET_GET(sets, i)->shot : NULL;
387 const struct score *set_score(int i, int s)
391 if (s == SCORE_TIME) return &SET_GET(sets, i)->time_score;
392 if (s == SCORE_COIN) return &SET_GET(sets, i)->coin_score;
397 /*---------------------------------------------------------------------------*/
399 static void set_load_levels(void)
401 static const char *roman[] = {
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"
410 struct set *s = SET_GET(sets, curr);
411 int regular = 1, bonus = 1;
414 for (i = 0; i < s->count; i++)
416 struct level *l = &level_v[i];
418 level_load(s->level_name_v[i], l);
423 SAFECPY(l->name, roman[bonus++]);
425 sprintf(l->name, "%02d", regular++);
427 l->is_locked = (i > 0);
431 level_v[i - 1].next = l;
448 struct level *get_level(int i)
450 return (i >= 0 && i < SET_GET(sets, curr)->count) ? &level_v[i] : NULL;
453 /*---------------------------------------------------------------------------*/
455 int set_score_update(int timer, int coins, int *score_rank, int *times_rank)
457 struct set *s = SET_GET(sets, curr);
458 const char *player = config_get_s(CONFIG_PLAYER);
460 score_coin_insert(&s->coin_score, score_rank, player, timer, coins);
461 score_time_insert(&s->time_score, times_rank, player, timer, coins);
463 if ((score_rank && *score_rank < RANK_LAST) ||
464 (times_rank && *times_rank < RANK_LAST))
470 void set_rename_player(int score_rank, int times_rank, const char *player)
472 struct set *s = SET_GET(sets, curr);
474 SAFECPY(s->coin_score.player[score_rank], player);
475 SAFECPY(s->time_score.player[times_rank], player);
478 /*---------------------------------------------------------------------------*/
480 void level_snap(int i, const char *path)
484 /* Convert the level name to a PNG filename. */
486 filename = concat_string(path,
488 base_name_sans(level_v[i].file, ".sol"),
492 /* Initialize the game for a snapshot. */
494 if (game_client_init(level_v[i].file))
497 cmd.type = CMD_GOAL_OPEN;
498 game_proxy_enq(&cmd);
499 game_client_sync(NULL);
501 /* Render the level and grab the screen. */
504 game_client_fly(1.0f);
506 game_client_draw(POSE_LEVEL, 0);
507 SDL_GL_SwapBuffers();
509 image_snap(filename);
519 for (i = 0; i < SET_GET(sets, curr)->count; i++)
521 level_v[i].is_locked = 0;
522 level_v[i].is_completed = 1;
526 /*---------------------------------------------------------------------------*/