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, &unused);
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 char datestr[DATELEN];
106 strftime(datestr, sizeof (datestr), "%Y-%m-%dT%H:%M:%S", gmtime(&d->date));
108 put_index(fp, DEMO_MAGIC);
109 put_index(fp, DEMO_VERSION);
113 put_index(fp, d->mode);
115 put_string(fp, d->player);
116 put_string(fp, datestr);
118 put_string(fp, d->shot);
119 put_string(fp, d->file);
121 put_index(fp, d->time);
122 put_index(fp, d->goal);
123 put_index(fp, 0); /* Unused (was goal enabled flag). */
124 put_index(fp, d->score);
125 put_index(fp, d->balls);
126 put_index(fp, d->times);
129 /*---------------------------------------------------------------------------*/
131 struct demo *demo_load(const char *path)
138 if ((fp = fs_open(path, "r")))
140 d = calloc(1, sizeof (struct demo));
142 if (demo_header_read(fp, d))
144 SAFECPY(d->filename, path);
145 SAFECPY(d->name, base_name_sans(d->filename, ".nbr"));
159 void demo_free(struct demo *d)
164 /*---------------------------------------------------------------------------*/
166 static const char *demo_path(const char *name)
168 static char path[MAXSTR];
169 sprintf(path, "Replays/%s.nbr", name);
173 /*---------------------------------------------------------------------------*/
175 int demo_exists(const char *name)
177 return fs_exists(demo_path(name));
180 const char *demo_format_name(const char *fmt,
184 static char name[MAXSTR];
198 memset(name, 0, sizeof (name));
199 space_left = MAXSTRLEN(name);
201 /* Construct name, replacing each format sequence as appropriate. */
203 while (*fmt && space_left > 0)
212 strncat(name, set, space_left);
213 space_left -= strlen(set);
217 strncat(name, level, space_left);
218 space_left -= strlen(level);
222 strncat(name, "%", space_left);
227 fputs(L_("Missing format character in replay name\n"), stderr);
232 fprintf(stderr, L_("Invalid format character in "
233 "replay name: \"%%%c\"\n"), *fmt);
239 strncat(name, fmt, 1);
247 * Append a unique 2-digit number preceded by an underscore to the
248 * file name, discarding characters if there's not enough space
249 * left in the buffer.
252 if (space_left < strlen("_23"))
253 numpart = name + MAXSTRLEN(name) - strlen("_23");
255 numpart = name + MAXSTRLEN(name) - space_left;
257 for (i = 1; i < 100; i++)
259 sprintf(numpart, "_%02d", i);
261 if (!demo_exists(name))
268 /*---------------------------------------------------------------------------*/
270 int demo_play_init(const char *name, const struct level *level,
271 int mode, int scores, int balls, int times)
275 memset(&demo, 0, sizeof (demo));
277 SAFECPY(demo.filename, demo_path(name));
278 SAFECPY(demo.player, config_get_s(CONFIG_PLAYER));
279 SAFECPY(demo.shot, level_shot(level));
280 SAFECPY(demo.file, level_file(level));
283 demo.date = time(NULL);
284 demo.time = level_time(level);
285 demo.goal = level_goal(level);
290 if ((demo_fp = fs_open(demo.filename, "w")))
292 demo_header_write(demo_fp, &demo);
298 void demo_play_stat(int status, int coins, int timer)
302 long pos = fs_tell(demo_fp);
304 fs_seek(demo_fp, 8, SEEK_SET);
306 put_index(demo_fp, timer);
307 put_index(demo_fp, coins);
308 put_index(demo_fp, status);
310 fs_seek(demo_fp, pos, SEEK_SET);
314 void demo_play_stop(void)
325 return demo_exists(USER_REPLAY_FILE);
328 void demo_rename(const char *name)
334 demo_exists(USER_REPLAY_FILE) &&
335 strcmp(name, USER_REPLAY_FILE) != 0)
337 SAFECPY(src, demo_path(USER_REPLAY_FILE));
338 SAFECPY(dst, demo_path(name));
344 void demo_rename_player(const char *name, const char *player)
350 /*---------------------------------------------------------------------------*/
352 static struct lockstep update_step;
354 static void demo_update_read(float dt)
360 while (cmd_get(demo_fp, &cmd))
362 game_proxy_enq(&cmd);
364 if (cmd.type == CMD_UPDATES_PER_SECOND)
365 update_step.dt = 1.0f / cmd.ups.n;
367 if (cmd.type == CMD_END_OF_UPDATE)
369 game_client_sync(NULL);
377 static struct lockstep update_step = { demo_update_read, DT };
379 float demo_replay_blend(void)
381 return lockstep_blend(&update_step);
384 /*---------------------------------------------------------------------------*/
386 static struct demo demo_replay;
388 const char *curr_demo(void)
390 return demo_replay.filename;
393 int demo_replay_init(const char *name, int *g, int *m, int *b, int *s, int *tt)
395 lockstep_clr(&update_step);
397 if ((demo_fp = fs_open(name, "r")))
399 if (demo_header_read(demo_fp, &demo_replay))
403 SAFECPY(demo_replay.filename, name);
404 SAFECPY(demo_replay.name,
405 base_name_sans(demo_replay.filename, ".nbr"));
407 if (level_load(demo_replay.file, &level))
409 if (g) *g = demo_replay.goal;
410 if (m) *m = demo_replay.mode;
411 if (b) *b = demo_replay.balls;
412 if (s) *s = demo_replay.score;
413 if (tt) *tt = demo_replay.times;
416 * Init client and then read and process the first batch of
417 * commands from the replay file.
420 if (game_client_init(demo_replay.file))
424 audio_music_fade_to(0.5f, level.song);
429 cmd.type = CMD_GOAL_OPEN;
430 game_proxy_enq(&cmd);
435 if (!fs_eof(demo_fp))
448 int demo_replay_step(float dt)
452 lockstep_run(&update_step, dt);
453 return !fs_eof(demo_fp);
458 void demo_replay_stop(int d)
465 if (d) fs_remove(demo_replay.filename);
469 void demo_speed_set(int speed)
471 if (SPEED_NONE <= speed && speed < SPEED_MAX)
472 lockstep_scl(&update_step, SPEED_FACTORS[speed]);
475 /*---------------------------------------------------------------------------*/