Add a basic ball configuration screen
[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
35 /*---------------------------------------------------------------------------*/
36
37 #define MAGIC           0x52424EAF
38 #define DEMO_VERSION    9
39
40 #define DATELEN 20
41
42 static fs_file demo_fp;
43
44 /*---------------------------------------------------------------------------*/
45
46 void demo_dump_info(const struct demo *d)
47 {
48     printf("Name:         %s\n"
49            "File:         %s\n"
50            "Time:         %d\n"
51            "Coins:        %d\n"
52            "Mode:         %d\n"
53            "State:        %d\n"
54            "Date:         %s"
55            "Player:       %s\n"
56            "Shot:         %s\n"
57            "Level:        %s\n"
58            "Time:         %d\n"
59            "Goal:         %d\n"
60            "Goal enabled: %d\n"
61            "Score:        %d\n"
62            "Balls:        %d\n"
63            "Total Time:   %d\n",
64            d->name, d->filename,
65            d->timer, d->coins, d->mode, d->status, ctime(&d->date),
66            d->player,
67            d->shot, d->file,
68            d->time, d->goal, d->goal_e, d->score, d->balls, d->times);
69 }
70
71 static int demo_header_read(fs_file fp, struct demo *d)
72 {
73     int magic;
74     int version;
75     int t;
76
77     struct tm date;
78     char datestr[DATELEN];
79
80     get_index(fp, &magic);
81     get_index(fp, &version);
82
83     get_index(fp, &t);
84
85     if (magic == MAGIC && version == DEMO_VERSION && t)
86     {
87         d->timer = t;
88
89         get_index(fp, &d->coins);
90         get_index(fp, &d->status);
91         get_index(fp, &d->mode);
92
93         get_string(fp, d->player, sizeof (d->player));
94         get_string(fp, datestr, DATELEN);
95
96         sscanf(datestr,
97                "%d-%d-%dT%d:%d:%d",
98                &date.tm_year,
99                &date.tm_mon,
100                &date.tm_mday,
101                &date.tm_hour,
102                &date.tm_min,
103                &date.tm_sec);
104
105         date.tm_year -= 1900;
106         date.tm_mon  -= 1;
107         date.tm_isdst = -1;
108
109         d->date = make_time_from_utc(&date);
110
111         get_string(fp, d->shot, PATHMAX);
112         get_string(fp, d->file, PATHMAX);
113
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);
120
121         return 1;
122     }
123     return 0;
124 }
125
126 static void demo_header_write(fs_file fp, struct demo *d)
127 {
128     int magic = MAGIC;
129     int version = DEMO_VERSION;
130     int zero  = 0;
131
132     char datestr[DATELEN];
133
134     strftime(datestr, DATELEN, "%Y-%m-%dT%H:%M:%S", gmtime(&d->date));
135
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);
142
143     put_string(fp, d->player);
144     put_string(fp, datestr);
145
146     put_string(fp, d->shot);
147     put_string(fp, d->file);
148
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);
155 }
156
157 /*---------------------------------------------------------------------------*/
158
159 struct demo *demo_load(const char *path)
160 {
161     fs_file fp;
162     struct demo *d;
163
164     d = NULL;
165
166     if ((fp = fs_open(path, "r")))
167     {
168         d = malloc(sizeof (struct demo));
169
170         if (demo_header_read(fp, d))
171         {
172             strncpy(d->filename, path, MAXSTR);
173             strncpy(d->name, base_name(d->filename, ".nbr"), PATHMAX);
174             d->name[PATHMAX - 1] = '\0';
175         }
176         else
177         {
178             free(d);
179             d = NULL;
180         }
181
182         fs_close(fp);
183     }
184
185     return d;
186 }
187
188 void demo_free(struct demo *d)
189 {
190     free(d);
191 }
192
193 /*---------------------------------------------------------------------------*/
194
195 static const char *demo_path(const char *name)
196 {
197     static char path[MAXSTR];
198     sprintf(path, "Replays/%s.nbr", name);
199     return path;
200 }
201
202 /*---------------------------------------------------------------------------*/
203
204 int demo_exists(const char *name)
205 {
206     return fs_exists(demo_path(name));
207 }
208
209 #define MAXSTRLEN(a) (sizeof ((a)) - 1)
210
211 const char *demo_format_name(const char *fmt,
212                              const char *set,
213                              const char *level)
214 {
215     static char name[MAXSTR];
216     int space_left;
217     char *numpart;
218     int i;
219
220     if (!fmt)
221         return NULL;
222
223     memset(name, 0, sizeof (name));
224     space_left = MAXSTRLEN(name);
225
226     /* Construct name, replacing each format sequence as appropriate. */
227
228     while (*fmt && space_left > 0)
229     {
230         if (*fmt == '%')
231         {
232             fmt++;
233
234             switch (*fmt)
235             {
236             case 's':
237                 if (set)
238                 {
239                     strncat(name, set, space_left);
240                     space_left -= strlen(set);
241                 }
242                 break;
243
244             case 'l':
245                 if (level)
246                 {
247                     strncat(name, level, space_left);
248                     space_left -= strlen(level);
249                 }
250                 break;
251
252             case '%':
253                 strncat(name, "%", space_left);
254                 space_left--;
255                 break;
256
257             case '\0':
258                 fputs(L_("Missing format character in replay name\n"), stderr);
259                 fmt--;
260                 break;
261
262             default:
263                 fprintf(stderr, L_("Invalid format character in "
264                                    "replay name: \"%%%c\"\n"), *fmt);
265                 break;
266             }
267         }
268         else
269         {
270             strncat(name, fmt, 1);
271             space_left--;
272         }
273
274         fmt++;
275     }
276
277     /*
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.
281      */
282
283     if (space_left < strlen("_23"))
284         numpart = name + MAXSTRLEN(name) - strlen("_23");
285     else
286         numpart = name + MAXSTRLEN(name) - space_left;
287
288     for (i = 1; i < 100; i++)
289     {
290         sprintf(numpart, "_%02d", i);
291
292         if (!demo_exists(name))
293             break;
294     }
295
296     return name;
297 }
298
299 #undef MAXSTRLEN
300
301 /*---------------------------------------------------------------------------*/
302
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)
305 {
306     struct demo demo;
307
308     memset(&demo, 0, sizeof (demo));
309
310     strncpy(demo.filename, demo_path(name), sizeof (demo.filename) - 1);
311
312     demo.mode = mode;
313     demo.date = time(NULL);
314
315     config_get_s(CONFIG_PLAYER, demo.player, MAXNAM);
316
317     strncpy(demo.shot, level->shot, PATHMAX);
318     strncpy(demo.file, level->file, PATHMAX);
319
320     demo.time   = t;
321     demo.goal   = g;
322     demo.goal_e = e;
323     demo.score  = s;
324     demo.balls  = b;
325     demo.times  = tt;
326
327     if ((demo_fp = fs_open(demo.filename, "w")))
328     {
329         demo_header_write(demo_fp, &demo);
330         audio_music_fade_to(2.0f, level->song);
331
332         /*
333          * Init both client and server, then process the first batch
334          * of commands generated by the server to sync client to
335          * server.
336          */
337
338         if (game_client_init(level->file) &&
339             game_server_init(level->file, t, e))
340         {
341             game_client_step(demo_fp);
342             return 1;
343         }
344     }
345     return 0;
346 }
347
348 void demo_play_stat(int status, int coins, int timer)
349 {
350     if (demo_fp)
351     {
352         long pos = fs_tell(demo_fp);
353
354         fs_seek(demo_fp, 8, SEEK_SET);
355
356         put_index(demo_fp, &timer);
357         put_index(demo_fp, &coins);
358         put_index(demo_fp, &status);
359
360         fs_seek(demo_fp, pos, SEEK_SET);
361     }
362 }
363
364 void demo_play_stop(void)
365 {
366     if (demo_fp)
367     {
368         fs_close(demo_fp);
369         demo_fp = NULL;
370     }
371 }
372
373 int demo_saved(void)
374 {
375     return demo_exists(USER_REPLAY_FILE);
376 }
377
378 void demo_rename(const char *name)
379 {
380     char src[MAXSTR];
381     char dst[MAXSTR];
382
383     if (name &&
384         demo_exists(USER_REPLAY_FILE) &&
385         strcmp(name, USER_REPLAY_FILE) != 0)
386     {
387         strncpy(src, demo_path(USER_REPLAY_FILE), sizeof (src) - 1);
388         strncpy(dst, demo_path(name),             sizeof (dst) - 1);
389
390         fs_rename(src, dst);
391     }
392 }
393
394 void demo_rename_player(const char *name, const char *player)
395 {
396 #if 0
397     char filename[MAXSTR];
398     FILE *old_fp, *new_fp;
399     struct demo d;
400
401     assert(name);
402     assert(player);
403
404     /* TODO: make this reusable. */
405
406     filename[sizeof (filename) - 1] = '\0';
407     strncpy(filename, name, sizeof (filename) - 1);
408     strncat(filename, REPLAY_EXT, sizeof (filename) - 1 - strlen(name));
409
410     /*
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
413      * file.
414      *
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.)
418      */
419
420     if ((old_fp = fopen(config_user(filename), FMODE_RB)))
421     {
422         if ((new_fp = tmpfile()))
423         {
424             if (demo_header_read(old_fp, &d))
425             {
426                 FILE *save_fp;
427
428                 /* Modify and write the header. */
429
430                 strncpy(d.player, player, sizeof (d.player));
431
432                 demo_header_write(new_fp, &d);
433
434                 /*
435                  * Restore the last three fields not written by the above call.
436                  */
437
438                 /* Hack, hack, hack. */
439
440                 save_fp = demo_fp;
441                 demo_fp = new_fp;
442
443                 demo_play_stat(d.status, d.coins, d.timer);
444
445                 demo_fp = save_fp;
446
447                 /* Copy the remaining data. */
448
449                 file_copy(old_fp, new_fp);
450
451                 /* Then copy everything back. */
452
453                 if (freopen(config_user(filename), FMODE_WB, old_fp))
454                 {
455                     fseek(new_fp, 0L, SEEK_SET);
456                     file_copy(new_fp, old_fp);
457                 }
458             }
459             fclose(new_fp);
460         }
461         fclose(old_fp);
462     }
463 #endif
464 }
465
466 /*---------------------------------------------------------------------------*/
467
468 static struct demo  demo_replay;       /* The current demo */
469 static struct level demo_level_replay; /* The current level demo-ed*/
470
471 const struct demo *curr_demo_replay(void)
472 {
473     return &demo_replay;
474 }
475
476 int demo_replay_init(const char *name, int *g, int *m, int *b, int *s, int *tt)
477 {
478     demo_fp = fs_open(name, "r");
479
480     if (demo_fp && demo_header_read(demo_fp, &demo_replay))
481     {
482         strncpy(demo_replay.filename, name, MAXSTR);
483         strncpy(demo_replay.name,
484                 base_name(demo_replay.filename, ".nbr"),
485                 PATHMAX);
486
487         if (level_load(demo_replay.file, &demo_level_replay))
488         {
489             demo_level_replay.time = demo_replay.time;
490             demo_level_replay.goal = demo_replay.goal;
491         }
492         else
493             return 0;
494
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;
500
501         /*
502          * Init client and then read and process the first batch of
503          * commands from the replay file.
504          */
505
506         if (g)
507         {
508             audio_music_fade_to(0.5f, demo_level_replay.song);
509
510             return (game_client_init(demo_level_replay.file) &&
511                     demo_replay_step(0.0f));
512         }
513
514         /* Likewise, but also queue a command to open the goal. */
515
516         else if (game_client_init(demo_level_replay.file))
517         {
518             union cmd cmd;
519
520             cmd.type = CMD_GOAL_OPEN;
521             game_proxy_enq(&cmd);
522
523             return demo_replay_step(0.0f);
524         }
525     }
526     return 0;
527 }
528
529 /*
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.
533  */
534 int demo_replay_step(float dt)
535 {
536     if (demo_fp)
537     {
538         union cmd cmd;
539
540         while (cmd_get(demo_fp, &cmd))
541         {
542             game_proxy_enq(&cmd);
543
544             if (cmd.type == CMD_END_OF_UPDATE)
545                 break;
546         }
547
548         if (!fs_eof(demo_fp))
549         {
550             game_client_step(NULL);
551             return 1;
552         }
553     }
554     return 0;
555 }
556
557 void demo_replay_stop(int d)
558 {
559     if (demo_fp)
560     {
561         fs_close(demo_fp);
562         demo_fp = NULL;
563
564         if (d) fs_remove(demo_replay.filename);
565     }
566 }
567
568 void demo_replay_dump_info(void)
569 {
570     demo_dump_info(&demo_replay);
571 }
572
573 /*---------------------------------------------------------------------------*/
574
575 fs_file demo_file(void) { return demo_fp; }
576
577 /*---------------------------------------------------------------------------*/