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))
153 int i, n = MIN(strlen(states) - 1, s->count);
155 for (i = 0; i < n; i++)
159 l->is_locked = (states[i] == 'L');
160 l->is_completed = (states[i] == 'C');
163 get_score(fp, &s->time_score);
164 get_score(fp, &s->coin_score);
166 for (i = 0; i < n; i++)
170 get_score(fp, &l->scores[SCORE_TIME]);
171 get_score(fp, &l->scores[SCORE_GOAL]);
172 get_score(fp, &l->scores[SCORE_COIN]);
180 /*---------------------------------------------------------------------------*/
182 static int set_load(struct set *s, const char *filename)
185 char *scores, *level_name;
187 /* Skip "Misc" set when not in dev mode. */
189 if (strcmp(filename, SET_MISC) == 0 && !config_cheat())
192 fin = fs_open(filename, "r");
196 fprintf(stderr, L_("Failure to load set file '%s'\n"), filename);
200 memset(s, 0, sizeof (struct set));
202 /* Set some sane values in case the scores are missing. */
204 score_init_hs(&s->time_score, 359999, 0);
205 score_init_hs(&s->coin_score, 359999, 0);
207 SAFECPY(s->file, filename);
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))
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]);
225 s->user_scores = concat_string("Scores/", s->id, ".txt", NULL);
226 s->cheat_scores = concat_string("Scores/", s->id, "-cheat.txt", NULL);
230 while (s->count < MAXLVL && read_line(&level_name, fin))
232 s->level_name_v[s->count] = level_name;
251 static void set_free(struct set *s)
260 free(s->user_scores);
261 free(s->cheat_scores);
263 for (i = 0; i < s->count; i++)
264 free(s->level_name_v[i]);
267 /*---------------------------------------------------------------------------*/
269 static int cmp_dir_items(const void *A, const void *B)
271 const struct dir_item *a = A, *b = B;
272 return strcmp(a->path, b->path);
275 static int set_is_loaded(const char *path)
279 for (i = 0; i < array_len(sets); i++)
280 if (strcmp(SET_GET(sets, i)->file, path) == 0)
286 static int is_unseen_set(struct dir_item *item)
288 return (str_starts_with(base_name(item->path), "set-") &&
289 str_ends_with(item->path, ".txt") &&
290 !set_is_loaded(item->path));
304 sets = array_new(sizeof (struct set));
308 * First, load the sets listed in the set file, preserving order.
311 if ((fin = fs_open(SET_FILE, "r")))
313 while (read_line(&name, fin))
315 struct set *s = array_add(sets);
317 if (!set_load(s, name))
326 * Then, scan for any remaining set description files, and add
327 * them after the first group in alphabetic order.
330 if ((items = fs_dir_scan("", is_unseen_set)))
332 array_sort(items, cmp_dir_items);
334 for (i = 0; i < array_len(items); i++)
336 struct set *s = array_add(sets);
338 if (!set_load(s, DIR_ITEM_GET(items, i)->path))
345 return array_len(sets);
352 for (i = 0; i < array_len(sets); i++)
353 set_free(array_get(sets, i));
359 /*---------------------------------------------------------------------------*/
361 int set_exists(int i)
363 return sets ? 0 <= i && i < array_len(sets) : 0;
366 const char *set_id(int i)
368 return set_exists(i) ? SET_GET(sets, i)->id : NULL;
371 const char *set_name(int i)
373 return set_exists(i) ? _(SET_GET(sets, i)->name) : NULL;
376 const char *set_desc(int i)
378 return set_exists(i) ? _(SET_GET(sets, i)->desc) : NULL;
381 const char *set_shot(int i)
383 return set_exists(i) ? SET_GET(sets, i)->shot : NULL;
386 const struct score *set_score(int i, int s)
390 if (s == SCORE_TIME) return &SET_GET(sets, i)->time_score;
391 if (s == SCORE_COIN) return &SET_GET(sets, i)->coin_score;
396 /*---------------------------------------------------------------------------*/
398 static void set_load_levels(void)
400 static const char *roman[] = {
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"
409 struct set *s = SET_GET(sets, curr);
410 int regular = 1, bonus = 1;
413 for (i = 0; i < s->count; i++)
415 struct level *l = &level_v[i];
417 level_load(s->level_name_v[i], l);
422 SAFECPY(l->name, roman[bonus++]);
424 sprintf(l->name, "%02d", regular++);
426 l->is_locked = (i > 0);
430 level_v[i - 1].next = l;
447 struct level *get_level(int i)
449 return (i >= 0 && i < SET_GET(sets, curr)->count) ? &level_v[i] : NULL;
452 /*---------------------------------------------------------------------------*/
454 int set_score_update(int timer, int coins, int *score_rank, int *times_rank)
456 struct set *s = SET_GET(sets, curr);
457 const char *player = config_get_s(CONFIG_PLAYER);
459 score_coin_insert(&s->coin_score, score_rank, player, timer, coins);
460 score_time_insert(&s->time_score, times_rank, player, timer, coins);
462 if ((score_rank && *score_rank < RANK_LAST) ||
463 (times_rank && *times_rank < RANK_LAST))
469 void set_rename_player(int score_rank, int times_rank, const char *player)
471 struct set *s = SET_GET(sets, curr);
473 SAFECPY(s->coin_score.player[score_rank], player);
474 SAFECPY(s->time_score.player[times_rank], player);
477 /*---------------------------------------------------------------------------*/
479 void level_snap(int i, const char *path)
483 /* Convert the level name to a PNG filename. */
485 filename = concat_string(path,
487 base_name_sans(level_v[i].file, ".sol"),
491 /* Initialize the game for a snapshot. */
493 if (game_client_init(level_v[i].file))
496 cmd.type = CMD_GOAL_OPEN;
497 game_proxy_enq(&cmd);
498 game_client_sync(NULL);
500 /* Render the level and grab the screen. */
503 game_client_fly(1.0f);
505 game_client_draw(POSE_LEVEL, 0);
506 image_snap(filename);
508 SDL_GL_SwapBuffers();
518 for (i = 0; i < SET_GET(sets, curr)->count; i++)
520 level_v[i].is_locked = 0;
521 level_v[i].is_completed = 1;
525 /*---------------------------------------------------------------------------*/