Clean up some macro defs and usage
[neverball] / ball / demo.c
1 /*
2  * Copyright (C) 2003 Robert Kooima
3  *
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.
8  *
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.
13  */
14
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <string.h>
18 #include <time.h>
19 #include <assert.h>
20
21 #include "demo.h"
22 #include "audio.h"
23 #include "solid.h"
24 #include "config.h"
25 #include "binary.h"
26 #include "common.h"
27 #include "level.h"
28 #include "array.h"
29 #include "dir.h"
30
31 #include "game_server.h"
32 #include "game_client.h"
33 #include "game_proxy.h"
34 #include "game_common.h"
35
36 /*---------------------------------------------------------------------------*/
37
38 #define DEMO_MAGIC (0xAF | 'N' << 8 | 'B' << 16 | 'R' << 24)
39 #define DEMO_VERSION 9
40
41 #define DATELEN sizeof ("YYYY-MM-DDTHH:MM:SS")
42
43 fs_file demo_fp;
44
45 /*---------------------------------------------------------------------------*/
46
47 static int demo_header_read(fs_file fp, struct demo *d)
48 {
49     int magic;
50     int version;
51     int t;
52
53     struct tm date;
54     char datestr[DATELEN];
55
56     get_index(fp, &magic);
57     get_index(fp, &version);
58
59     get_index(fp, &t);
60
61     if (magic == DEMO_MAGIC && version == DEMO_VERSION && t)
62     {
63         d->timer = t;
64
65         get_index(fp, &d->coins);
66         get_index(fp, &d->status);
67         get_index(fp, &d->mode);
68
69         get_string(fp, d->player, sizeof (d->player));
70         get_string(fp, datestr, sizeof (datestr));
71
72         sscanf(datestr,
73                "%d-%d-%dT%d:%d:%d",
74                &date.tm_year,
75                &date.tm_mon,
76                &date.tm_mday,
77                &date.tm_hour,
78                &date.tm_min,
79                &date.tm_sec);
80
81         date.tm_year -= 1900;
82         date.tm_mon  -= 1;
83         date.tm_isdst = -1;
84
85         d->date = make_time_from_utc(&date);
86
87         get_string(fp, d->shot, PATHMAX);
88         get_string(fp, d->file, PATHMAX);
89
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);
96
97         return 1;
98     }
99     return 0;
100 }
101
102 static void demo_header_write(fs_file fp, struct demo *d)
103 {
104     int magic = DEMO_MAGIC;
105     int version = DEMO_VERSION;
106     int zero  = 0;
107
108     char datestr[DATELEN];
109
110     strftime(datestr, sizeof (datestr), "%Y-%m-%dT%H:%M:%S", gmtime(&d->date));
111
112     put_index(fp, magic);
113     put_index(fp, version);
114     put_index(fp, zero);
115     put_index(fp, zero);
116     put_index(fp, zero);
117     put_index(fp, d->mode);
118
119     put_string(fp, d->player);
120     put_string(fp, datestr);
121
122     put_string(fp, d->shot);
123     put_string(fp, d->file);
124
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);
131 }
132
133 /*---------------------------------------------------------------------------*/
134
135 struct demo *demo_load(const char *path)
136 {
137     fs_file fp;
138     struct demo *d;
139
140     d = NULL;
141
142     if ((fp = fs_open(path, "r")))
143     {
144         d = calloc(1, sizeof (struct demo));
145
146         if (demo_header_read(fp, d))
147         {
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';
151         }
152         else
153         {
154             free(d);
155             d = NULL;
156         }
157
158         fs_close(fp);
159     }
160
161     return d;
162 }
163
164 void demo_free(struct demo *d)
165 {
166     free(d);
167 }
168
169 /*---------------------------------------------------------------------------*/
170
171 static const char *demo_path(const char *name)
172 {
173     static char path[MAXSTR];
174     sprintf(path, "Replays/%s.nbr", name);
175     return path;
176 }
177
178 /*---------------------------------------------------------------------------*/
179
180 int demo_exists(const char *name)
181 {
182     return fs_exists(demo_path(name));
183 }
184
185 #define MAXSTRLEN(a) (sizeof ((a)) - 1)
186
187 const char *demo_format_name(const char *fmt,
188                              const char *set,
189                              const char *level)
190 {
191     static char name[MAXSTR];
192     int space_left;
193     char *numpart;
194     int i;
195
196     if (!fmt)
197         return NULL;
198
199     memset(name, 0, sizeof (name));
200     space_left = MAXSTRLEN(name);
201
202     /* Construct name, replacing each format sequence as appropriate. */
203
204     while (*fmt && space_left > 0)
205     {
206         if (*fmt == '%')
207         {
208             fmt++;
209
210             switch (*fmt)
211             {
212             case 's':
213                 if (set)
214                 {
215                     strncat(name, set, space_left);
216                     space_left -= strlen(set);
217                 }
218                 break;
219
220             case 'l':
221                 if (level)
222                 {
223                     strncat(name, level, space_left);
224                     space_left -= strlen(level);
225                 }
226                 break;
227
228             case '%':
229                 strncat(name, "%", space_left);
230                 space_left--;
231                 break;
232
233             case '\0':
234                 fputs(L_("Missing format character in replay name\n"), stderr);
235                 fmt--;
236                 break;
237
238             default:
239                 fprintf(stderr, L_("Invalid format character in "
240                                    "replay name: \"%%%c\"\n"), *fmt);
241                 break;
242             }
243         }
244         else
245         {
246             strncat(name, fmt, 1);
247             space_left--;
248         }
249
250         fmt++;
251     }
252
253     /*
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.
257      */
258
259     if (space_left < strlen("_23"))
260         numpart = name + MAXSTRLEN(name) - strlen("_23");
261     else
262         numpart = name + MAXSTRLEN(name) - space_left;
263
264     for (i = 1; i < 100; i++)
265     {
266         sprintf(numpart, "_%02d", i);
267
268         if (!demo_exists(name))
269             break;
270     }
271
272     return name;
273 }
274
275 #undef MAXSTRLEN
276
277 /*---------------------------------------------------------------------------*/
278
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)
281 {
282     struct demo demo;
283
284     memset(&demo, 0, sizeof (demo));
285
286     strncpy(demo.filename, demo_path(name), sizeof (demo.filename) - 1);
287
288     demo.mode = mode;
289     demo.date = time(NULL);
290
291     strncpy(demo.player, config_get_s(CONFIG_PLAYER), sizeof (demo.player) - 1);
292
293     strncpy(demo.shot, level->shot, PATHMAX - 1);
294     strncpy(demo.file, level->file, PATHMAX - 1);
295
296     demo.time   = t;
297     demo.goal   = g;
298     demo.goal_e = e;
299     demo.score  = s;
300     demo.balls  = b;
301     demo.times  = tt;
302
303     if ((demo_fp = fs_open(demo.filename, "w")))
304     {
305         demo_header_write(demo_fp, &demo);
306         return 1;
307     }
308     return 0;
309 }
310
311 void demo_play_stat(int status, int coins, int timer)
312 {
313     if (demo_fp)
314     {
315         long pos = fs_tell(demo_fp);
316
317         fs_seek(demo_fp, 8, SEEK_SET);
318
319         put_index(demo_fp, timer);
320         put_index(demo_fp, coins);
321         put_index(demo_fp, status);
322
323         fs_seek(demo_fp, pos, SEEK_SET);
324     }
325 }
326
327 void demo_play_stop(void)
328 {
329     if (demo_fp)
330     {
331         fs_close(demo_fp);
332         demo_fp = NULL;
333     }
334 }
335
336 int demo_saved(void)
337 {
338     return demo_exists(USER_REPLAY_FILE);
339 }
340
341 void demo_rename(const char *name)
342 {
343     char src[MAXSTR];
344     char dst[MAXSTR];
345
346     if (name &&
347         demo_exists(USER_REPLAY_FILE) &&
348         strcmp(name, USER_REPLAY_FILE) != 0)
349     {
350         strncpy(src, demo_path(USER_REPLAY_FILE), sizeof (src) - 1);
351         strncpy(dst, demo_path(name),             sizeof (dst) - 1);
352
353         fs_rename(src, dst);
354     }
355 }
356
357 void demo_rename_player(const char *name, const char *player)
358 {
359 #if 0
360     char filename[MAXSTR];
361     FILE *old_fp, *new_fp;
362     struct demo d;
363
364     assert(name);
365     assert(player);
366
367     /* TODO: make this reusable. */
368
369     filename[sizeof (filename) - 1] = '\0';
370     strncpy(filename, name, sizeof (filename) - 1);
371     strncat(filename, REPLAY_EXT, sizeof (filename) - 1 - strlen(name));
372
373     /*
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
376      * file.
377      *
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.)
381      */
382
383     if ((old_fp = fopen(config_user(filename), FMODE_RB)))
384     {
385         if ((new_fp = tmpfile()))
386         {
387             if (demo_header_read(old_fp, &d))
388             {
389                 FILE *save_fp;
390
391                 /* Modify and write the header. */
392
393                 strncpy(d.player, player, sizeof (d.player));
394
395                 demo_header_write(new_fp, &d);
396
397                 /*
398                  * Restore the last three fields not written by the above call.
399                  */
400
401                 /* Hack, hack, hack. */
402
403                 save_fp = demo_fp;
404                 demo_fp = new_fp;
405
406                 demo_play_stat(d.status, d.coins, d.timer);
407
408                 demo_fp = save_fp;
409
410                 /* Copy the remaining data. */
411
412                 file_copy(old_fp, new_fp);
413
414                 /* Then copy everything back. */
415
416                 if (freopen(config_user(filename), FMODE_WB, old_fp))
417                 {
418                     fseek(new_fp, 0L, SEEK_SET);
419                     file_copy(new_fp, old_fp);
420                 }
421             }
422             fclose(new_fp);
423         }
424         fclose(old_fp);
425     }
426 #endif
427 }
428
429 /*---------------------------------------------------------------------------*/
430
431 static struct lockstep update_step;
432
433 static void demo_update_read(float dt)
434 {
435     if (demo_fp)
436     {
437         union cmd cmd;
438
439         while (cmd_get(demo_fp, &cmd))
440         {
441             game_proxy_enq(&cmd);
442
443             if (cmd.type == CMD_UPDATES_PER_SECOND)
444                 update_step.dt = 1.0f / cmd.ups.n;
445
446             if (cmd.type == CMD_END_OF_UPDATE)
447             {
448                 game_client_sync(NULL);
449                 break;
450             }
451         }
452
453     }
454 }
455
456 static struct lockstep update_step = { demo_update_read, DT };
457
458 /*---------------------------------------------------------------------------*/
459
460 static struct demo demo_replay;
461
462 const char *curr_demo(void)
463 {
464     return demo_replay.filename;
465 }
466
467 int demo_replay_init(const char *name, int *g, int *m, int *b, int *s, int *tt)
468 {
469     lockstep_clr(&update_step);
470
471     if ((demo_fp = fs_open(name, "r")))
472     {
473         if (demo_header_read(demo_fp, &demo_replay))
474         {
475             struct level level;
476
477             strncpy(demo_replay.filename, name, MAXSTR - 1);
478             strncpy(demo_replay.name,
479                     base_name_sans(demo_replay.filename, ".nbr"),
480                     PATHMAX - 1);
481
482             if (level_load(demo_replay.file, &level))
483             {
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;
489
490                 /*
491                  * Init client and then read and process the first batch of
492                  * commands from the replay file.
493                  */
494
495                 if (game_client_init(demo_replay.file))
496                 {
497                     if (g)
498                     {
499                         audio_music_fade_to(0.5f, level.song);
500                     }
501                     else
502                     {
503                         union cmd cmd;
504                         cmd.type = CMD_GOAL_OPEN;
505                         game_proxy_enq(&cmd);
506                     }
507
508                     demo_update_read(0);
509
510                     if (!fs_eof(demo_fp))
511                         return 1;
512                 }
513             }
514         }
515
516         fs_close(demo_fp);
517         demo_fp = NULL;
518     }
519
520     return 0;
521 }
522
523 int demo_replay_step(float dt)
524 {
525     if (demo_fp)
526     {
527         lockstep_run(&update_step, dt);
528         return !fs_eof(demo_fp);
529     }
530     return 0;
531 }
532
533 void demo_replay_stop(int d)
534 {
535     if (demo_fp)
536     {
537         fs_close(demo_fp);
538         demo_fp = NULL;
539
540         if (d) fs_remove(demo_replay.filename);
541     }
542 }
543
544 /*---------------------------------------------------------------------------*/