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"
34 #include "game_common.h"
36 /*---------------------------------------------------------------------------*/
38 #define DEMO_MAGIC (0xAF | 'N' << 8 | 'B' << 16 | 'R' << 24)
39 #define DEMO_VERSION 9
41 #define DATELEN sizeof ("YYYY-MM-DDTHH:MM:SS")
45 /*---------------------------------------------------------------------------*/
47 static int demo_header_read(fs_file fp, struct demo *d)
54 char datestr[DATELEN];
56 get_index(fp, &magic);
57 get_index(fp, &version);
61 if (magic == DEMO_MAGIC && version == DEMO_VERSION && t)
65 get_index(fp, &d->coins);
66 get_index(fp, &d->status);
67 get_index(fp, &d->mode);
69 get_string(fp, d->player, sizeof (d->player));
70 get_string(fp, datestr, sizeof (datestr));
85 d->date = make_time_from_utc(&date);
87 get_string(fp, d->shot, PATHMAX);
88 get_string(fp, d->file, PATHMAX);
90 get_index(fp, &d->time);
91 get_index(fp, &d->goal);
92 get_index(fp, &d->goal_e);
93 get_index(fp, &d->score);
94 get_index(fp, &d->balls);
95 get_index(fp, &d->times);
102 static void demo_header_write(fs_file fp, struct demo *d)
104 int magic = DEMO_MAGIC;
105 int version = DEMO_VERSION;
108 char datestr[DATELEN];
110 strftime(datestr, sizeof (datestr), "%Y-%m-%dT%H:%M:%S", gmtime(&d->date));
112 put_index(fp, magic);
113 put_index(fp, version);
117 put_index(fp, d->mode);
119 put_string(fp, d->player);
120 put_string(fp, datestr);
122 put_string(fp, d->shot);
123 put_string(fp, d->file);
125 put_index(fp, d->time);
126 put_index(fp, d->goal);
127 put_index(fp, d->goal_e);
128 put_index(fp, d->score);
129 put_index(fp, d->balls);
130 put_index(fp, d->times);
133 /*---------------------------------------------------------------------------*/
135 struct demo *demo_load(const char *path)
142 if ((fp = fs_open(path, "r")))
144 d = calloc(1, sizeof (struct demo));
146 if (demo_header_read(fp, d))
148 strncpy(d->filename, path, MAXSTR - 1);
149 strncpy(d->name, base_name_sans(d->filename, ".nbr"), PATHMAX - 1);
150 d->name[PATHMAX - 1] = '\0';
164 void demo_free(struct demo *d)
169 /*---------------------------------------------------------------------------*/
171 static const char *demo_path(const char *name)
173 static char path[MAXSTR];
174 sprintf(path, "Replays/%s.nbr", name);
178 /*---------------------------------------------------------------------------*/
180 int demo_exists(const char *name)
182 return fs_exists(demo_path(name));
185 #define MAXSTRLEN(a) (sizeof ((a)) - 1)
187 const char *demo_format_name(const char *fmt,
191 static char name[MAXSTR];
199 memset(name, 0, sizeof (name));
200 space_left = MAXSTRLEN(name);
202 /* Construct name, replacing each format sequence as appropriate. */
204 while (*fmt && space_left > 0)
215 strncat(name, set, space_left);
216 space_left -= strlen(set);
223 strncat(name, level, space_left);
224 space_left -= strlen(level);
229 strncat(name, "%", space_left);
234 fputs(L_("Missing format character in replay name\n"), stderr);
239 fprintf(stderr, L_("Invalid format character in "
240 "replay name: \"%%%c\"\n"), *fmt);
246 strncat(name, fmt, 1);
254 * Append a unique 2-digit number preceded by an underscore to the
255 * file name, discarding characters if there's not enough space
256 * left in the buffer.
259 if (space_left < strlen("_23"))
260 numpart = name + MAXSTRLEN(name) - strlen("_23");
262 numpart = name + MAXSTRLEN(name) - space_left;
264 for (i = 1; i < 100; i++)
266 sprintf(numpart, "_%02d", i);
268 if (!demo_exists(name))
277 /*---------------------------------------------------------------------------*/
279 int demo_play_init(const char *name, const struct level *level,
280 int mode, int t, int g, int e, int s, int b, int tt)
284 memset(&demo, 0, sizeof (demo));
286 strncpy(demo.filename, demo_path(name), sizeof (demo.filename) - 1);
289 demo.date = time(NULL);
291 strncpy(demo.player, config_get_s(CONFIG_PLAYER), sizeof (demo.player) - 1);
293 strncpy(demo.shot, level->shot, PATHMAX - 1);
294 strncpy(demo.file, level->file, PATHMAX - 1);
303 if ((demo_fp = fs_open(demo.filename, "w")))
305 demo_header_write(demo_fp, &demo);
311 void demo_play_stat(int status, int coins, int timer)
315 long pos = fs_tell(demo_fp);
317 fs_seek(demo_fp, 8, SEEK_SET);
319 put_index(demo_fp, timer);
320 put_index(demo_fp, coins);
321 put_index(demo_fp, status);
323 fs_seek(demo_fp, pos, SEEK_SET);
327 void demo_play_stop(void)
338 return demo_exists(USER_REPLAY_FILE);
341 void demo_rename(const char *name)
347 demo_exists(USER_REPLAY_FILE) &&
348 strcmp(name, USER_REPLAY_FILE) != 0)
350 strncpy(src, demo_path(USER_REPLAY_FILE), sizeof (src) - 1);
351 strncpy(dst, demo_path(name), sizeof (dst) - 1);
357 void demo_rename_player(const char *name, const char *player)
360 char filename[MAXSTR];
361 FILE *old_fp, *new_fp;
367 /* TODO: make this reusable. */
369 filename[sizeof (filename) - 1] = '\0';
370 strncpy(filename, name, sizeof (filename) - 1);
371 strncat(filename, REPLAY_EXT, sizeof (filename) - 1 - strlen(name));
374 * Write out a temporary file containing the original replay data with a
375 * new player name, then copy the resulting contents back to the original
378 * (It is believed that the ugliness found here is outweighed by the
379 * benefits of preserving the arbitrary-length property of all strings in
380 * the replay. In case of doubt, FIXME.)
383 if ((old_fp = fopen(config_user(filename), FMODE_RB)))
385 if ((new_fp = tmpfile()))
387 if (demo_header_read(old_fp, &d))
391 /* Modify and write the header. */
393 strncpy(d.player, player, sizeof (d.player));
395 demo_header_write(new_fp, &d);
398 * Restore the last three fields not written by the above call.
401 /* Hack, hack, hack. */
406 demo_play_stat(d.status, d.coins, d.timer);
410 /* Copy the remaining data. */
412 file_copy(old_fp, new_fp);
414 /* Then copy everything back. */
416 if (freopen(config_user(filename), FMODE_WB, old_fp))
418 fseek(new_fp, 0L, SEEK_SET);
419 file_copy(new_fp, old_fp);
429 /*---------------------------------------------------------------------------*/
431 static struct lockstep update_step;
433 static void demo_update_read(float dt)
439 while (cmd_get(demo_fp, &cmd))
441 game_proxy_enq(&cmd);
443 if (cmd.type == CMD_UPDATES_PER_SECOND)
444 update_step.dt = 1.0f / cmd.ups.n;
446 if (cmd.type == CMD_END_OF_UPDATE)
448 game_client_sync(NULL);
456 static struct lockstep update_step = { demo_update_read, DT };
458 /*---------------------------------------------------------------------------*/
460 static struct demo demo_replay;
462 const char *curr_demo(void)
464 return demo_replay.filename;
467 int demo_replay_init(const char *name, int *g, int *m, int *b, int *s, int *tt)
469 lockstep_clr(&update_step);
471 if ((demo_fp = fs_open(name, "r")))
473 if (demo_header_read(demo_fp, &demo_replay))
477 strncpy(demo_replay.filename, name, MAXSTR - 1);
478 strncpy(demo_replay.name,
479 base_name_sans(demo_replay.filename, ".nbr"),
482 if (level_load(demo_replay.file, &level))
484 if (g) *g = demo_replay.goal;
485 if (m) *m = demo_replay.mode;
486 if (b) *b = demo_replay.balls;
487 if (s) *s = demo_replay.score;
488 if (tt) *tt = demo_replay.times;
491 * Init client and then read and process the first batch of
492 * commands from the replay file.
495 if (game_client_init(demo_replay.file))
499 audio_music_fade_to(0.5f, level.song);
504 cmd.type = CMD_GOAL_OPEN;
505 game_proxy_enq(&cmd);
510 if (!fs_eof(demo_fp))
523 int demo_replay_step(float dt)
527 lockstep_run(&update_step, dt);
528 return !fs_eof(demo_fp);
533 void demo_replay_stop(int d)
540 if (d) fs_remove(demo_replay.filename);
544 /*---------------------------------------------------------------------------*/