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 /*---------------------------------------------------------------------------*/
29 #define MAGIC 0x52424EAF
30 #define DEMO_VERSION 3
36 static struct demo demos[MAXDEMO]; /* Array of scanned demos */
37 static int count; /* Number of scanned demos */
39 /*---------------------------------------------------------------------------*/
41 void demo_dump_info(const struct demo *d)
62 d->timer, d->coins, d->mode, d->state, ctime(&d->date),
64 d->shot, d->file, d->back, d->grad, d->song,
65 d->time, d->goal, d->score, d->balls, d->times);
68 static time_t make_time_from_utc(struct tm *tm)
70 struct tm local, *utc;
75 local = *localtime(&t);
78 local.tm_year += local.tm_year - utc->tm_year;
79 local.tm_mon += local.tm_mon - utc->tm_mon ;
80 local.tm_mday += local.tm_mday - utc->tm_mday;
81 local.tm_hour += local.tm_hour - utc->tm_hour;
82 local.tm_min += local.tm_min - utc->tm_min ;
83 local.tm_sec += local.tm_sec - utc->tm_sec ;
85 return mktime(&local);
88 static int demo_header_read(FILE *fp, struct demo *d)
95 char datestr[DATELEN];
97 get_index(fp, &magic);
98 get_index(fp, &version);
102 if (magic == MAGIC && version == DEMO_VERSION && t)
106 get_index(fp, &d->coins);
107 get_index(fp, &d->state);
108 get_index(fp, &d->mode);
110 fread(d->player, 1, MAXNAM, fp);
112 fread(datestr, 1, DATELEN, fp);
122 /* Convert certain values to valid structure member values. */
124 date.tm_year -= 1900;
127 d->date = make_time_from_utc(&date);
129 fread(d->shot, 1, PATHMAX, fp);
130 fread(d->file, 1, PATHMAX, fp);
132 get_index(fp, &d->time);
133 get_index(fp, &d->goal);
134 get_index(fp, &d->score);
135 get_index(fp, &d->balls);
136 get_index(fp, &d->times);
143 static char *bname(const char *name, const char *suffix)
145 static char buf[MAXSTR];
150 /* Remove the directory delimiter */
152 base = strrchr(name, '/');
155 base = strrchr(name, '\\');
159 if ((tmp = strrchr(base, '\\')))
163 strncpy(buf, base ? base + 1 : name, MAXSTR);
165 /* Remove the extension */
167 l = strlen(buf) - strlen(suffix);
168 if ((l > 1) && (strcmp(buf + l, suffix) == 0))
174 static void demo_header_write(FILE *fp, struct demo *d)
177 int version = DEMO_VERSION;
180 char datestr[DATELEN];
182 strftime(datestr, DATELEN, "%Y-%m-%dT%H:%M:%S", gmtime(&d->date));
184 put_index(fp, &magic);
185 put_index(fp, &version);
186 put_index(fp, &zero);
187 put_index(fp, &zero);
188 put_index(fp, &zero);
189 put_index(fp, &d->mode);
191 fwrite(d->player, 1, MAXNAM, fp);
192 fwrite(datestr, 1, DATELEN, fp);
194 fwrite(d->shot, 1, PATHMAX, fp);
195 fwrite(d->file, 1, PATHMAX, fp);
197 put_index(fp, &d->time);
198 put_index(fp, &d->goal);
199 put_index(fp, &d->score);
200 put_index(fp, &d->balls);
201 put_index(fp, &d->times);
204 /* Update the demo header using the final level state. */
206 void demo_header_stop(FILE *fp, int coins, int timer, int state)
208 long pos = ftell(fp);
210 fseek(fp, 8, SEEK_SET);
211 put_index(fp, &timer);
212 put_index(fp, &coins);
213 put_index(fp, &state);
214 fseek(fp, pos, SEEK_SET);
217 /*---------------------------------------------------------------------------*/
219 /* Scan another file (used by demo_scan). */
221 static void demo_scan_file(const char *filename)
224 struct demo *d = &demos[count];
226 if ((fp = fopen(config_user(filename), FMODE_RB)))
228 if (demo_header_read(fp, d))
230 strncpy(d->filename, config_user(filename), MAXSTR);
231 strncpy(d->name, bname(filename, REPLAY_EXT), PATHMAX);
232 d->name[PATHMAX - 1] = '\0';
249 /* Scan the user directory for files. */
251 if ((h = FindFirstFile(config_user("*"), &d)) != INVALID_HANDLE_VALUE)
254 demo_scan_file(d.cFileName);
255 while (count < MAXDEMO && FindNextFile(h, &d));
272 /* Scan the user directory for files. */
274 if ((dp = opendir(config_user(""))))
276 while (count < MAXDEMO && (ent = readdir(dp)))
277 demo_scan_file(ent->d_name);
285 const char *demo_pick(void)
289 return (n > 0) ? demos[(rand() >> 4) % n].filename : NULL;
292 const struct demo *demo_get(int i)
294 return (0 <= i && i < count) ? &demos[i] : NULL;
297 const char *date_to_str(time_t i)
299 static char str[MAXSTR];
301 /* TRANSLATORS: here is the format of the date shown at the
302 replay selection screen. The default will work in most cases, so
303 you should only change it if something's horribly wrong, like,
304 for instance, the GUI layout is broken. See strftime(3) for
305 details on the format.
308 strftime(str, MAXSTR, /* xgettext:no-c-format */ _("%c"), localtime(&i));
313 /*---------------------------------------------------------------------------*/
315 int demo_exists(const char *name)
320 strcpy(buf, config_user(name));
321 strcat(buf, REPLAY_EXT);
322 if ((fp = fopen(buf, "r")))
330 void demo_unique(char *name)
334 /* Generate a unique name for a new replay save. */
336 for (i = 1; i < 100; i++)
338 sprintf(name, "replay%02d", i);
340 if (!demo_exists(name))
345 /*---------------------------------------------------------------------------*/
347 int demo_play_init(const char *name,
348 const struct level *level,
349 const struct level_game *lg)
353 memset(&demo, 0, sizeof (demo));
355 strncpy(demo.filename, config_user(name), MAXSTR);
356 strcat(demo.filename, REPLAY_EXT);
358 demo.mode = lg->mode;
359 demo.date = time(NULL);
361 config_get_s(CONFIG_PLAYER, demo.player, MAXNAM);
363 strncpy(demo.shot, level->shot, PATHMAX);
364 strncpy(demo.file, level->file, PATHMAX);
365 strncpy(demo.back, level->back, PATHMAX);
366 strncpy(demo.grad, level->grad, PATHMAX);
367 strncpy(demo.song, level->song, PATHMAX);
369 demo.time = lg->time;
370 demo.goal = lg->goal;
371 demo.score = lg->score;
372 demo.balls = lg->balls;
373 demo.times = lg->times;
375 if ((demo_fp = fopen(demo.filename, FMODE_WB)))
377 demo_header_write(demo_fp, &demo);
378 audio_music_fade_to(2.0f, level->song);
379 return game_init(level, lg->time, lg->goal);
384 void demo_play_step(float dt)
388 put_float(demo_fp, &dt);
389 put_game_state(demo_fp);
393 /* Update the demo header using the final level state. */
395 void demo_play_stop(const struct level_game *lg)
399 demo_header_stop(demo_fp, lg->coins, lg->timer, lg->state);
405 int demo_play_saved(void)
407 return demo_exists(USER_REPLAY_FILE);
410 void demo_play_save(const char *name)
416 demo_exists(USER_REPLAY_FILE) &&
417 strcmp(name, USER_REPLAY_FILE) != 0)
419 strcpy(src, config_user(USER_REPLAY_FILE));
420 strcat(src, REPLAY_EXT);
422 strcpy(dst, config_user(name));
423 strcat(dst, REPLAY_EXT);
429 /*---------------------------------------------------------------------------*/
431 static int demo_load_level(const struct demo *demo, struct level *level)
433 if (level_load(demo->file, level))
435 level->time = demo->time;
436 level->goal = demo->goal;
442 static struct demo demo_replay; /* The current demo */
443 static struct level demo_level_replay; /* The current level demo-ed*/
445 const struct demo *curr_demo_replay(void)
450 /* Internally load a replay and fill the lg structure (if not NULL) */
452 int demo_replay_init(const char *name, struct level_game *lg)
454 demo_fp = fopen(name, FMODE_RB);
456 if (demo_fp && demo_header_read(demo_fp, &demo_replay))
458 strncpy(demo_replay.filename, name, MAXSTR);
459 strncpy(demo_replay.name, bname(name, REPLAY_EXT), PATHMAX);
461 if (!demo_load_level(&demo_replay, &demo_level_replay))
466 lg->mode = demo_replay.mode;
467 lg->score = demo_replay.score;
468 lg->times = demo_replay.times;
469 lg->time = demo_replay.time;
470 lg->goal = demo_replay.goal;
472 /* A normal replay demo */
473 audio_music_fade_to(0.5f, demo_level_replay.song);
474 return game_init(&demo_level_replay, demo_replay.time,
477 else /* A title screen demo */
478 return game_init(&demo_level_replay, demo_replay.time, 0);
483 int demo_replay_step(float *dt)
485 const float g[3] = { 0.0f, -9.8f, 0.0f };
489 get_float(demo_fp, dt);
491 if (feof(demo_fp) == 0)
493 /* Play out current game state for particles, clock, etc. */
495 game_step(g, *dt, 1);
497 /* Load real current game state from file. */
499 if (get_game_state(demo_fp))
506 void demo_replay_stop(int d)
513 if (d) remove(demo_replay.filename);
517 void demo_replay_dump_info(void)
519 demo_dump_info(&demo_replay);
522 /*---------------------------------------------------------------------------*/