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.
31 /*---------------------------------------------------------------------------*/
33 #define MAGIC 0x52424EAF
34 #define DEMO_VERSION 6
40 static struct demo demos[MAXDEMO]; /* Array of scanned demos */
41 static int count; /* Number of scanned demos */
43 /*---------------------------------------------------------------------------*/
45 void demo_dump_info(const struct demo *d)
64 d->timer, d->coins, d->mode, d->status, ctime(&d->date),
67 d->time, d->goal, d->goal_e, d->score, d->balls, d->times);
70 static int demo_header_read(FILE *fp, struct demo *d)
77 char datestr[DATELEN];
79 get_index(fp, &magic);
80 get_index(fp, &version);
84 if (magic == MAGIC && version == DEMO_VERSION && t)
88 get_index(fp, &d->coins);
89 get_index(fp, &d->status);
90 get_index(fp, &d->mode);
92 get_string(fp, d->player, MAXNAM);
93 get_string(fp, datestr, DATELEN);
104 date.tm_year -= 1900;
108 d->date = make_time_from_utc(&date);
110 get_string(fp, d->shot, PATHMAX);
111 get_string(fp, d->file, PATHMAX);
113 get_index(fp, &d->time);
114 get_index(fp, &d->goal);
115 get_index(fp, &d->goal_e);
116 get_index(fp, &d->score);
117 get_index(fp, &d->balls);
118 get_index(fp, &d->times);
125 static void demo_header_write(FILE *fp, struct demo *d)
128 int version = DEMO_VERSION;
131 char datestr[DATELEN];
133 strftime(datestr, DATELEN, "%Y-%m-%dT%H:%M:%S", gmtime(&d->date));
135 put_index(fp, &magic);
136 put_index(fp, &version);
137 put_index(fp, &zero);
138 put_index(fp, &zero);
139 put_index(fp, &zero);
140 put_index(fp, &d->mode);
142 put_string(fp, d->player);
143 put_string(fp, datestr);
145 put_string(fp, d->shot);
146 put_string(fp, d->file);
148 put_index(fp, &d->time);
149 put_index(fp, &d->goal);
150 put_index(fp, &d->goal_e);
151 put_index(fp, &d->score);
152 put_index(fp, &d->balls);
153 put_index(fp, &d->times);
156 /*---------------------------------------------------------------------------*/
158 /* Scan another file (used by demo_scan). */
160 static void demo_scan_file(const char *filename)
163 struct demo *d = &demos[count];
165 if ((fp = fopen(config_user(filename), FMODE_RB)))
167 if (demo_header_read(fp, d))
169 strncpy(d->filename, config_user(filename), MAXSTR);
171 base_name(text_from_locale(d->filename), REPLAY_EXT),
173 d->name[PATHMAX - 1] = '\0';
190 /* Scan the user directory for files. */
192 if ((h = FindFirstFile(config_user("*"), &d)) != INVALID_HANDLE_VALUE)
195 demo_scan_file(d.cFileName);
196 while (count < MAXDEMO && FindNextFile(h, &d));
213 /* Scan the user directory for files. */
215 if ((dp = opendir(config_user(""))))
217 while (count < MAXDEMO && (ent = readdir(dp)))
218 demo_scan_file(ent->d_name);
226 const char *demo_pick(void)
230 return (n > 0) ? demos[(rand() >> 4) % n].filename : NULL;
233 const struct demo *demo_get(int i)
235 return (0 <= i && i < count) ? &demos[i] : NULL;
238 /*---------------------------------------------------------------------------*/
240 int demo_exists(const char *name)
244 strcpy(buf, config_user(name));
245 strcat(buf, REPLAY_EXT);
247 return file_exists(buf);
250 void demo_unique(char *name)
254 /* Generate a unique name for a new replay save. */
256 for (i = 1; i < 100; i++)
258 sprintf(name, "replay%02d", i);
260 if (!demo_exists(name))
265 /*---------------------------------------------------------------------------*/
267 int demo_play_init(const char *name, const struct level *level,
268 int mode, int t, int g, int e, int s, int b, int tt)
272 memset(&demo, 0, sizeof (demo));
274 strncpy(demo.filename, config_user(name), MAXSTR);
275 strcat(demo.filename, REPLAY_EXT);
278 demo.date = time(NULL);
280 config_get_s(CONFIG_PLAYER, demo.player, MAXNAM);
282 strncpy(demo.shot, level->shot, PATHMAX);
283 strncpy(demo.file, level->file, PATHMAX);
292 if ((demo_fp = fopen(demo.filename, FMODE_WB)))
294 demo_header_write(demo_fp, &demo);
295 audio_music_fade_to(2.0f, level->song);
296 return game_init(level->file, t, e);
301 void demo_play_step()
307 void demo_play_stat(int status, int coins, int timer)
311 long pos = ftell(demo_fp);
313 fseek(demo_fp, 8, SEEK_SET);
315 put_index(demo_fp, &timer);
316 put_index(demo_fp, &coins);
317 put_index(demo_fp, &status);
319 fseek(demo_fp, pos, SEEK_SET);
323 void demo_play_stop(void)
334 return demo_exists(USER_REPLAY_FILE);
337 void demo_rename(const char *name)
343 demo_exists(USER_REPLAY_FILE) &&
344 strcmp(name, USER_REPLAY_FILE) != 0)
346 strcpy(src, config_user(USER_REPLAY_FILE));
347 strcat(src, REPLAY_EXT);
349 strcpy(dst, config_user(name));
350 strcat(dst, REPLAY_EXT);
352 file_rename(src, dst);
356 void demo_rename_player(const char *name, const char *player)
358 char filename[MAXSTR];
359 FILE *old_fp, *new_fp;
365 /* TODO: make this reusable. */
367 filename[sizeof (filename) - 1] = '\0';
368 strncpy(filename, name, sizeof (filename) - 1);
369 strncat(filename, REPLAY_EXT, sizeof (filename) - 1 - strlen(name));
372 * Write out a temporary file containing the original replay data with a
373 * new player name, then copy the resulting contents back to the original
376 * (It is believed that the ugliness found here is outweighed by the
377 * benefits of preserving the arbitrary-length property of all strings in
378 * the replay. In case of doubt, FIXME.)
381 if ((old_fp = fopen(config_user(filename), FMODE_RB)))
383 if ((new_fp = tmpfile()))
385 if (demo_header_read(old_fp, &d))
389 /* Modify and write the header. */
391 strncpy(d.player, player, sizeof (d.player));
393 demo_header_write(new_fp, &d);
395 /* Restore the last three fields not written by the above call. */
397 /* Hack, hack, hack. */
402 demo_play_stat(d.status, d.coins, d.timer);
406 /* Copy the remaining data. */
408 file_copy(old_fp, new_fp);
410 /* Then copy everything back. */
412 if (freopen(config_user(filename), FMODE_WB, old_fp))
414 fseek(new_fp, 0L, SEEK_SET);
415 file_copy(new_fp, old_fp);
424 /*---------------------------------------------------------------------------*/
426 static struct demo demo_replay; /* The current demo */
427 static struct level demo_level_replay; /* The current level demo-ed*/
429 const struct demo *curr_demo_replay(void)
434 static int demo_status = GAME_NONE;
436 int demo_replay_init(const char *name, int *g, int *m, int *b, int *s, int *tt)
438 demo_status = GAME_NONE;
439 demo_fp = fopen(name, FMODE_RB);
441 if (demo_fp && demo_header_read(demo_fp, &demo_replay))
443 strncpy(demo_replay.filename, name, MAXSTR);
444 strncpy(demo_replay.name,
445 base_name(text_from_locale(demo_replay.filename), REPLAY_EXT),
448 if (level_load(demo_replay.file, &demo_level_replay))
450 demo_level_replay.time = demo_replay.time;
451 demo_level_replay.goal = demo_replay.goal;
456 if (g) *g = demo_replay.goal;
457 if (m) *m = demo_replay.mode;
458 if (b) *b = demo_replay.balls;
459 if (s) *s = demo_replay.score;
460 if (tt) *tt = demo_replay.times;
464 audio_music_fade_to(0.5f, demo_level_replay.song);
466 return game_init(demo_level_replay.file,
467 demo_level_replay.time,
471 return game_init(demo_level_replay.file,
472 demo_level_replay.time, 1);
477 int demo_replay_step(float dt)
479 const float gdn[3] = { 0.0f, -9.8f, 0.0f };
480 const float gup[3] = { 0.0f, +9.8f, 0.0f };
484 if (input_get(demo_fp))
486 /* Play out current game state. */
491 demo_status = game_step(gdn, dt, 1); break;
493 (void) game_step(gup, dt, 0); break;
495 (void) game_step(gdn, dt, 0); break;
504 void demo_replay_stop(int d)
511 if (d) remove(demo_replay.filename);
515 void demo_replay_dump_info(void)
517 demo_dump_info(&demo_replay);
520 /*---------------------------------------------------------------------------*/