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.
30 #include "game_server.h"
31 #include "game_client.h"
32 #include "game_proxy.h"
34 /*---------------------------------------------------------------------------*/
36 #define MAGIC 0x52424EAF
37 #define DEMO_VERSION 9
43 static struct demo demos[MAXDEMO]; /* Array of scanned demos */
44 static int count; /* Number of scanned demos */
46 /*---------------------------------------------------------------------------*/
48 void demo_dump_info(const struct demo *d)
67 d->timer, d->coins, d->mode, d->status, ctime(&d->date),
70 d->time, d->goal, d->goal_e, d->score, d->balls, d->times);
73 static int demo_header_read(FILE *fp, struct demo *d)
80 char datestr[DATELEN];
82 get_index(fp, &magic);
83 get_index(fp, &version);
87 if (magic == MAGIC && version == DEMO_VERSION && t)
91 get_index(fp, &d->coins);
92 get_index(fp, &d->status);
93 get_index(fp, &d->mode);
95 get_string(fp, d->player, MAXNAM);
96 get_string(fp, datestr, DATELEN);
107 date.tm_year -= 1900;
111 d->date = make_time_from_utc(&date);
113 get_string(fp, d->shot, PATHMAX);
114 get_string(fp, d->file, PATHMAX);
116 get_index(fp, &d->time);
117 get_index(fp, &d->goal);
118 get_index(fp, &d->goal_e);
119 get_index(fp, &d->score);
120 get_index(fp, &d->balls);
121 get_index(fp, &d->times);
128 static void demo_header_write(FILE *fp, struct demo *d)
131 int version = DEMO_VERSION;
134 char datestr[DATELEN];
136 strftime(datestr, DATELEN, "%Y-%m-%dT%H:%M:%S", gmtime(&d->date));
138 put_index(fp, &magic);
139 put_index(fp, &version);
140 put_index(fp, &zero);
141 put_index(fp, &zero);
142 put_index(fp, &zero);
143 put_index(fp, &d->mode);
145 put_string(fp, d->player);
146 put_string(fp, datestr);
148 put_string(fp, d->shot);
149 put_string(fp, d->file);
151 put_index(fp, &d->time);
152 put_index(fp, &d->goal);
153 put_index(fp, &d->goal_e);
154 put_index(fp, &d->score);
155 put_index(fp, &d->balls);
156 put_index(fp, &d->times);
159 /*---------------------------------------------------------------------------*/
161 /* Scan another file (used by demo_scan). */
163 static void demo_scan_file(const char *filename)
166 struct demo *d = &demos[count];
168 if ((fp = fopen(config_user(filename), FMODE_RB)))
170 if (demo_header_read(fp, d))
172 strncpy(d->filename, config_user(filename), MAXSTR);
174 base_name(text_from_locale(d->filename), REPLAY_EXT),
176 d->name[PATHMAX - 1] = '\0';
193 /* Scan the user directory for files. */
195 if ((h = FindFirstFile(config_user("*"), &d)) != INVALID_HANDLE_VALUE)
198 demo_scan_file(d.cFileName);
199 while (count < MAXDEMO && FindNextFile(h, &d));
216 /* Scan the user directory for files. */
218 if ((dp = opendir(config_user(""))))
220 while (count < MAXDEMO && (ent = readdir(dp)))
221 demo_scan_file(ent->d_name);
229 const char *demo_pick(void)
233 return (n > 0) ? demos[(rand() >> 4) % n].filename : NULL;
236 const struct demo *demo_get(int i)
238 return (0 <= i && i < count) ? &demos[i] : NULL;
241 /*---------------------------------------------------------------------------*/
243 int demo_exists(const char *name)
247 strcpy(buf, config_user(name));
248 strcat(buf, REPLAY_EXT);
250 return file_exists(buf);
253 #define MAXSTRLEN(a) (sizeof ((a)) - 1)
255 const char *demo_format_name(const char *fmt,
259 static char name[MAXSTR];
267 memset(name, 0, sizeof (name));
268 space_left = MAXSTRLEN(name);
270 /* Construct name, replacing each format sequence as appropriate. */
272 while (*fmt && space_left > 0)
283 strncat(name, set, space_left);
284 space_left -= strlen(set);
291 strncat(name, level, space_left);
292 space_left -= strlen(level);
297 strncat(name, "%", space_left);
302 fputs(L_("Missing format character in replay name\n"), stderr);
307 fprintf(stderr, L_("Invalid format character in "
308 "replay name: \"%%%c\"\n"), *fmt);
314 strncat(name, fmt, 1);
322 * Append a unique 2-digit number preceded by an underscore to the
323 * file name, discarding characters if there's not enough space
324 * left in the buffer.
327 if (space_left < strlen("_23"))
328 numpart = name + MAXSTRLEN(name) - strlen("_23");
330 numpart = name + MAXSTRLEN(name) - space_left;
332 for (i = 1; i < 100; i++)
334 sprintf(numpart, "_%02d", i);
336 if (!demo_exists(name))
345 /*---------------------------------------------------------------------------*/
347 int demo_play_init(const char *name, const struct level *level,
348 int mode, int t, int g, int e, int s, int b, int tt)
352 memset(&demo, 0, sizeof (demo));
354 strncpy(demo.filename, config_user(name), MAXSTR);
355 strcat(demo.filename, REPLAY_EXT);
358 demo.date = time(NULL);
360 config_get_s(CONFIG_PLAYER, demo.player, MAXNAM);
362 strncpy(demo.shot, level->shot, PATHMAX);
363 strncpy(demo.file, level->file, PATHMAX);
372 if ((demo_fp = fopen(demo.filename, FMODE_WB)))
374 demo_header_write(demo_fp, &demo);
375 audio_music_fade_to(2.0f, level->song);
378 * Init both client and server, then process the first batch
379 * of commands generated by the server to sync client to
383 if (game_client_init(level->file) &&
384 game_server_init(level->file, t, e))
386 game_client_step(demo_fp);
393 void demo_play_stat(int status, int coins, int timer)
397 long pos = ftell(demo_fp);
399 fseek(demo_fp, 8, SEEK_SET);
401 put_index(demo_fp, &timer);
402 put_index(demo_fp, &coins);
403 put_index(demo_fp, &status);
405 fseek(demo_fp, pos, SEEK_SET);
409 void demo_play_stop(void)
420 return demo_exists(USER_REPLAY_FILE);
423 void demo_rename(const char *name)
429 demo_exists(USER_REPLAY_FILE) &&
430 strcmp(name, USER_REPLAY_FILE) != 0)
432 strcpy(src, config_user(USER_REPLAY_FILE));
433 strcat(src, REPLAY_EXT);
435 strcpy(dst, config_user(name));
436 strcat(dst, REPLAY_EXT);
438 file_rename(src, dst);
442 void demo_rename_player(const char *name, const char *player)
444 char filename[MAXSTR];
445 FILE *old_fp, *new_fp;
451 /* TODO: make this reusable. */
453 filename[sizeof (filename) - 1] = '\0';
454 strncpy(filename, name, sizeof (filename) - 1);
455 strncat(filename, REPLAY_EXT, sizeof (filename) - 1 - strlen(name));
458 * Write out a temporary file containing the original replay data with a
459 * new player name, then copy the resulting contents back to the original
462 * (It is believed that the ugliness found here is outweighed by the
463 * benefits of preserving the arbitrary-length property of all strings in
464 * the replay. In case of doubt, FIXME.)
467 if ((old_fp = fopen(config_user(filename), FMODE_RB)))
469 if ((new_fp = tmpfile()))
471 if (demo_header_read(old_fp, &d))
475 /* Modify and write the header. */
477 strncpy(d.player, player, sizeof (d.player));
479 demo_header_write(new_fp, &d);
482 * Restore the last three fields not written by the above call.
485 /* Hack, hack, hack. */
490 demo_play_stat(d.status, d.coins, d.timer);
494 /* Copy the remaining data. */
496 file_copy(old_fp, new_fp);
498 /* Then copy everything back. */
500 if (freopen(config_user(filename), FMODE_WB, old_fp))
502 fseek(new_fp, 0L, SEEK_SET);
503 file_copy(new_fp, old_fp);
512 /*---------------------------------------------------------------------------*/
514 static struct demo demo_replay; /* The current demo */
515 static struct level demo_level_replay; /* The current level demo-ed*/
517 const struct demo *curr_demo_replay(void)
522 int demo_replay_init(const char *name, int *g, int *m, int *b, int *s, int *tt)
524 demo_fp = fopen(name, FMODE_RB);
526 if (demo_fp && demo_header_read(demo_fp, &demo_replay))
528 strncpy(demo_replay.filename, name, MAXSTR);
529 strncpy(demo_replay.name,
530 base_name(text_from_locale(demo_replay.filename), REPLAY_EXT),
533 if (level_load(demo_replay.file, &demo_level_replay))
535 demo_level_replay.time = demo_replay.time;
536 demo_level_replay.goal = demo_replay.goal;
541 if (g) *g = demo_replay.goal;
542 if (m) *m = demo_replay.mode;
543 if (b) *b = demo_replay.balls;
544 if (s) *s = demo_replay.score;
545 if (tt) *tt = demo_replay.times;
548 * Init client and then read and process the first batch of
549 * commands from the replay file.
554 audio_music_fade_to(0.5f, demo_level_replay.song);
556 return (game_client_init(demo_level_replay.file) &&
557 demo_replay_step(0.0f));
560 /* Likewise, but also queue a command to open the goal. */
562 else if (game_client_init(demo_level_replay.file))
566 cmd.type = CMD_GOAL_OPEN;
567 game_proxy_enq(&cmd);
569 return demo_replay_step(0.0f);
576 * Read and enqueue commands from the replay file, up to and including
577 * CMD_END_OF_UPDATE. Return 0 on EOF. Otherwise, run the commands
578 * on the client and return 1.
580 int demo_replay_step(float dt)
586 while (cmd_get(demo_fp, &cmd))
588 game_proxy_enq(&cmd);
590 if (cmd.type == CMD_END_OF_UPDATE)
596 game_client_step(NULL);
603 void demo_replay_stop(int d)
610 if (d) remove(demo_replay.filename);
614 void demo_replay_dump_info(void)
616 demo_dump_info(&demo_replay);
619 /*---------------------------------------------------------------------------*/
621 FILE *demo_file(void) { return demo_fp; }
623 /*---------------------------------------------------------------------------*/