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 #include "game_server.h"
32 #include "game_client.h"
33 #include "game_proxy.h"
35 /*---------------------------------------------------------------------------*/
37 #define MAGIC 0x52424EAF
38 #define DEMO_VERSION 9
42 static fs_file demo_fp;
44 /*---------------------------------------------------------------------------*/
46 void demo_dump_info(const struct demo *d)
65 d->timer, d->coins, d->mode, d->status, ctime(&d->date),
68 d->time, d->goal, d->goal_e, d->score, d->balls, d->times);
71 static int demo_header_read(fs_file fp, struct demo *d)
78 char datestr[DATELEN];
80 get_index(fp, &magic);
81 get_index(fp, &version);
85 if (magic == MAGIC && version == DEMO_VERSION && t)
89 get_index(fp, &d->coins);
90 get_index(fp, &d->status);
91 get_index(fp, &d->mode);
93 get_string(fp, d->player, sizeof (d->player));
94 get_string(fp, datestr, DATELEN);
105 date.tm_year -= 1900;
109 d->date = make_time_from_utc(&date);
111 get_string(fp, d->shot, PATHMAX);
112 get_string(fp, d->file, PATHMAX);
114 get_index(fp, &d->time);
115 get_index(fp, &d->goal);
116 get_index(fp, &d->goal_e);
117 get_index(fp, &d->score);
118 get_index(fp, &d->balls);
119 get_index(fp, &d->times);
126 static void demo_header_write(fs_file fp, struct demo *d)
129 int version = DEMO_VERSION;
132 char datestr[DATELEN];
134 strftime(datestr, DATELEN, "%Y-%m-%dT%H:%M:%S", gmtime(&d->date));
136 put_index(fp, &magic);
137 put_index(fp, &version);
138 put_index(fp, &zero);
139 put_index(fp, &zero);
140 put_index(fp, &zero);
141 put_index(fp, &d->mode);
143 put_string(fp, d->player);
144 put_string(fp, datestr);
146 put_string(fp, d->shot);
147 put_string(fp, d->file);
149 put_index(fp, &d->time);
150 put_index(fp, &d->goal);
151 put_index(fp, &d->goal_e);
152 put_index(fp, &d->score);
153 put_index(fp, &d->balls);
154 put_index(fp, &d->times);
157 /*---------------------------------------------------------------------------*/
159 struct demo *demo_load(const char *path)
166 if ((fp = fs_open(path, "r")))
168 d = malloc(sizeof (struct demo));
170 if (demo_header_read(fp, d))
172 strncpy(d->filename, path, MAXSTR);
173 strncpy(d->name, base_name(d->filename, ".nbr"), PATHMAX);
174 d->name[PATHMAX - 1] = '\0';
188 void demo_free(struct demo *d)
193 /*---------------------------------------------------------------------------*/
195 static const char *demo_path(const char *name)
197 static char path[MAXSTR];
198 sprintf(path, "Replays/%s.nbr", name);
202 /*---------------------------------------------------------------------------*/
204 int demo_exists(const char *name)
206 return fs_exists(demo_path(name));
209 #define MAXSTRLEN(a) (sizeof ((a)) - 1)
211 const char *demo_format_name(const char *fmt,
215 static char name[MAXSTR];
223 memset(name, 0, sizeof (name));
224 space_left = MAXSTRLEN(name);
226 /* Construct name, replacing each format sequence as appropriate. */
228 while (*fmt && space_left > 0)
239 strncat(name, set, space_left);
240 space_left -= strlen(set);
247 strncat(name, level, space_left);
248 space_left -= strlen(level);
253 strncat(name, "%", space_left);
258 fputs(L_("Missing format character in replay name\n"), stderr);
263 fprintf(stderr, L_("Invalid format character in "
264 "replay name: \"%%%c\"\n"), *fmt);
270 strncat(name, fmt, 1);
278 * Append a unique 2-digit number preceded by an underscore to the
279 * file name, discarding characters if there's not enough space
280 * left in the buffer.
283 if (space_left < strlen("_23"))
284 numpart = name + MAXSTRLEN(name) - strlen("_23");
286 numpart = name + MAXSTRLEN(name) - space_left;
288 for (i = 1; i < 100; i++)
290 sprintf(numpart, "_%02d", i);
292 if (!demo_exists(name))
301 /*---------------------------------------------------------------------------*/
303 int demo_play_init(const char *name, const struct level *level,
304 int mode, int t, int g, int e, int s, int b, int tt)
308 memset(&demo, 0, sizeof (demo));
310 strncpy(demo.filename, demo_path(name), sizeof (demo.filename) - 1);
313 demo.date = time(NULL);
315 config_get_s(CONFIG_PLAYER, demo.player, MAXNAM);
317 strncpy(demo.shot, level->shot, PATHMAX);
318 strncpy(demo.file, level->file, PATHMAX);
327 if ((demo_fp = fs_open(demo.filename, "w")))
329 demo_header_write(demo_fp, &demo);
330 audio_music_fade_to(2.0f, level->song);
333 * Init both client and server, then process the first batch
334 * of commands generated by the server to sync client to
338 if (game_client_init(level->file) &&
339 game_server_init(level->file, t, e))
341 game_client_step(demo_fp);
348 void demo_play_stat(int status, int coins, int timer)
352 long pos = fs_tell(demo_fp);
354 fs_seek(demo_fp, 8, SEEK_SET);
356 put_index(demo_fp, &timer);
357 put_index(demo_fp, &coins);
358 put_index(demo_fp, &status);
360 fs_seek(demo_fp, pos, SEEK_SET);
364 void demo_play_stop(void)
375 return demo_exists(USER_REPLAY_FILE);
378 void demo_rename(const char *name)
384 demo_exists(USER_REPLAY_FILE) &&
385 strcmp(name, USER_REPLAY_FILE) != 0)
387 strncpy(src, demo_path(USER_REPLAY_FILE), sizeof (src) - 1);
388 strncpy(dst, demo_path(name), sizeof (dst) - 1);
394 void demo_rename_player(const char *name, const char *player)
397 char filename[MAXSTR];
398 FILE *old_fp, *new_fp;
404 /* TODO: make this reusable. */
406 filename[sizeof (filename) - 1] = '\0';
407 strncpy(filename, name, sizeof (filename) - 1);
408 strncat(filename, REPLAY_EXT, sizeof (filename) - 1 - strlen(name));
411 * Write out a temporary file containing the original replay data with a
412 * new player name, then copy the resulting contents back to the original
415 * (It is believed that the ugliness found here is outweighed by the
416 * benefits of preserving the arbitrary-length property of all strings in
417 * the replay. In case of doubt, FIXME.)
420 if ((old_fp = fopen(config_user(filename), FMODE_RB)))
422 if ((new_fp = tmpfile()))
424 if (demo_header_read(old_fp, &d))
428 /* Modify and write the header. */
430 strncpy(d.player, player, sizeof (d.player));
432 demo_header_write(new_fp, &d);
435 * Restore the last three fields not written by the above call.
438 /* Hack, hack, hack. */
443 demo_play_stat(d.status, d.coins, d.timer);
447 /* Copy the remaining data. */
449 file_copy(old_fp, new_fp);
451 /* Then copy everything back. */
453 if (freopen(config_user(filename), FMODE_WB, old_fp))
455 fseek(new_fp, 0L, SEEK_SET);
456 file_copy(new_fp, old_fp);
466 /*---------------------------------------------------------------------------*/
468 static struct demo demo_replay; /* The current demo */
469 static struct level demo_level_replay; /* The current level demo-ed*/
471 const struct demo *curr_demo_replay(void)
476 int demo_replay_init(const char *name, int *g, int *m, int *b, int *s, int *tt)
478 demo_fp = fs_open(name, "r");
480 if (demo_fp && demo_header_read(demo_fp, &demo_replay))
482 strncpy(demo_replay.filename, name, MAXSTR);
483 strncpy(demo_replay.name,
484 base_name(demo_replay.filename, ".nbr"),
487 if (level_load(demo_replay.file, &demo_level_replay))
489 demo_level_replay.time = demo_replay.time;
490 demo_level_replay.goal = demo_replay.goal;
495 if (g) *g = demo_replay.goal;
496 if (m) *m = demo_replay.mode;
497 if (b) *b = demo_replay.balls;
498 if (s) *s = demo_replay.score;
499 if (tt) *tt = demo_replay.times;
502 * Init client and then read and process the first batch of
503 * commands from the replay file.
508 audio_music_fade_to(0.5f, demo_level_replay.song);
510 return (game_client_init(demo_level_replay.file) &&
511 demo_replay_step(0.0f));
514 /* Likewise, but also queue a command to open the goal. */
516 else if (game_client_init(demo_level_replay.file))
520 cmd.type = CMD_GOAL_OPEN;
521 game_proxy_enq(&cmd);
523 return demo_replay_step(0.0f);
530 * Read and enqueue commands from the replay file, up to and including
531 * CMD_END_OF_UPDATE. Return 0 on EOF. Otherwise, run the commands
532 * on the client and return 1.
534 int demo_replay_step(float dt)
540 while (cmd_get(demo_fp, &cmd))
542 game_proxy_enq(&cmd);
544 if (cmd.type == CMD_END_OF_UPDATE)
548 if (!fs_eof(demo_fp))
550 game_client_step(NULL);
557 void demo_replay_stop(int d)
564 if (d) fs_remove(demo_replay.filename);
568 void demo_replay_dump_info(void)
570 demo_dump_info(&demo_replay);
573 /*---------------------------------------------------------------------------*/
575 fs_file demo_file(void) { return demo_fp; }
577 /*---------------------------------------------------------------------------*/