Correct logic of BSP back/front tests
[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 "game.h"
23 #include "audio.h"
24 #include "solid.h"
25 #include "config.h"
26 #include "binary.h"
27 #include "text.h"
28 #include "common.h"
29 #include "level.h"
30
31 /*---------------------------------------------------------------------------*/
32
33 #define MAGIC           0x52424EAF
34 #define DEMO_VERSION    6
35
36 #define DATELEN 20
37
38 static FILE *demo_fp;
39
40 static struct demo demos[MAXDEMO]; /* Array of scanned demos  */
41 static int         count;          /* Number of scanned demos */
42
43 /*---------------------------------------------------------------------------*/
44
45 void demo_dump_info(const struct demo *d)
46 {
47     printf("Name:         %s\n"
48            "File:         %s\n"
49            "Time:         %d\n"
50            "Coins:        %d\n"
51            "Mode:         %d\n"
52            "State:        %d\n"
53            "Date:         %s"
54            "Player:       %s\n"
55            "Shot:         %s\n"
56            "Level:        %s\n"
57            "Time:         %d\n"
58            "Goal:         %d\n"
59            "Goal enabled: %d\n"
60            "Score:        %d\n"
61            "Balls:        %d\n"
62            "Total Time:   %d\n",
63            d->name, d->filename,
64            d->timer, d->coins, d->mode, d->status, ctime(&d->date),
65            d->player,
66            d->shot, d->file,
67            d->time, d->goal, d->goal_e, d->score, d->balls, d->times);
68 }
69
70 static int demo_header_read(FILE *fp, struct demo *d)
71 {
72     int magic;
73     int version;
74     int t;
75
76     struct tm date;
77     char datestr[DATELEN];
78
79     get_index(fp, &magic);
80     get_index(fp, &version);
81
82     get_index(fp, &t);
83
84     if (magic == MAGIC && version == DEMO_VERSION && t)
85     {
86         d->timer = t;
87
88         get_index(fp, &d->coins);
89         get_index(fp, &d->status);
90         get_index(fp, &d->mode);
91
92         get_string(fp, d->player, MAXNAM);
93         get_string(fp, datestr, DATELEN);
94
95         sscanf(datestr,
96                "%d-%d-%dT%d:%d:%d",
97                &date.tm_year,
98                &date.tm_mon,
99                &date.tm_mday,
100                &date.tm_hour,
101                &date.tm_min,
102                &date.tm_sec);
103
104         date.tm_year -= 1900;
105         date.tm_mon  -= 1;
106         date.tm_isdst = -1;
107
108         d->date = make_time_from_utc(&date);
109
110         get_string(fp, d->shot, PATHMAX);
111         get_string(fp, d->file, PATHMAX);
112
113         get_index(fp, &d->time);
114         get_index(fp, &d->goal);
115         get_index(fp, &d->goal_e);
116         get_index(fp, &d->score);
117         get_index(fp, &d->balls);
118         get_index(fp, &d->times);
119
120         return 1;
121     }
122     return 0;
123 }
124
125 static void demo_header_write(FILE *fp, struct demo *d)
126 {
127     int magic = MAGIC;
128     int version = DEMO_VERSION;
129     int zero  = 0;
130
131     char datestr[DATELEN];
132
133     strftime(datestr, DATELEN, "%Y-%m-%dT%H:%M:%S", gmtime(&d->date));
134
135     put_index(fp, &magic);
136     put_index(fp, &version);
137     put_index(fp, &zero);
138     put_index(fp, &zero);
139     put_index(fp, &zero);
140     put_index(fp, &d->mode);
141
142     put_string(fp, d->player);
143     put_string(fp, datestr);
144
145     put_string(fp, d->shot);
146     put_string(fp, d->file);
147
148     put_index(fp, &d->time);
149     put_index(fp, &d->goal);
150     put_index(fp, &d->goal_e);
151     put_index(fp, &d->score);
152     put_index(fp, &d->balls);
153     put_index(fp, &d->times);
154 }
155
156 /*---------------------------------------------------------------------------*/
157
158 /* Scan another file (used by demo_scan). */
159
160 static void demo_scan_file(const char *filename)
161 {
162     FILE *fp;
163     struct demo *d = &demos[count];
164
165     if ((fp = fopen(config_user(filename), FMODE_RB)))
166     {
167         if (demo_header_read(fp, d))
168         {
169             strncpy(d->filename, config_user(filename), MAXSTR);
170             strncpy(d->name,
171                     base_name(text_from_locale(d->filename), REPLAY_EXT),
172                     PATHMAX);
173             d->name[PATHMAX - 1] = '\0';
174
175             count++;
176         }
177         fclose(fp);
178     }
179 }
180
181 #ifdef _WIN32
182
183 int demo_scan(void)
184 {
185     WIN32_FIND_DATA d;
186     HANDLE h;
187
188     count = 0;
189
190     /* Scan the user directory for files. */
191
192     if ((h = FindFirstFile(config_user("*"), &d)) != INVALID_HANDLE_VALUE)
193     {
194         do
195             demo_scan_file(d.cFileName);
196         while (count < MAXDEMO && FindNextFile(h, &d));
197
198         FindClose(h);
199     }
200     return count;
201 }
202
203 #else /* _WIN32 */
204 #include <dirent.h>
205
206 int demo_scan(void)
207 {
208     struct dirent *ent;
209     DIR  *dp;
210
211     count = 0;
212
213     /* Scan the user directory for files. */
214
215     if ((dp = opendir(config_user(""))))
216     {
217         while (count < MAXDEMO && (ent = readdir(dp)))
218             demo_scan_file(ent->d_name);
219
220         closedir(dp);
221     }
222     return count;
223 }
224 #endif /* _WIN32 */
225
226 const char *demo_pick(void)
227 {
228     int n = demo_scan();
229
230     return (n > 0) ? demos[(rand() >> 4) % n].filename : NULL;
231 }
232
233 const struct demo *demo_get(int i)
234 {
235     return (0 <= i && i < count) ? &demos[i] : NULL;
236 }
237
238 /*---------------------------------------------------------------------------*/
239
240 int demo_exists(const char *name)
241 {
242     char buf[MAXSTR];
243
244     strcpy(buf, config_user(name));
245     strcat(buf, REPLAY_EXT);
246
247     return file_exists(buf);
248 }
249
250 #define MAXSTRLEN(a) (sizeof ((a)) - 1)
251
252 const char *demo_format_name(const char *fmt,
253                              const char *set,
254                              const char *level)
255 {
256     static char name[MAXSTR];
257     int space_left;
258     char *numpart;
259     int i;
260
261     if (!fmt)
262         return NULL;
263
264     memset(name, 0, sizeof (name));
265     space_left = MAXSTRLEN(name);
266
267     /* Construct name, replacing each format sequence as appropriate. */
268
269     while (*fmt && space_left > 0)
270     {
271         if (*fmt == '%')
272         {
273             fmt++;
274
275             switch (*fmt)
276             {
277             case 's':
278                 if (set)
279                 {
280                     strncat(name, set, space_left);
281                     space_left -= strlen(set);
282                 }
283                 break;
284
285             case 'l':
286                 if (level)
287                 {
288                     strncat(name, level, space_left);
289                     space_left -= strlen(level);
290                 }
291                 break;
292
293             case '%':
294                 strncat(name, "%", space_left);
295                 space_left--;
296                 break;
297
298             case '\0':
299                 fputs(L_("Missing format character in replay name\n"), stderr);
300                 fmt--;
301                 break;
302
303             default:
304                 fprintf(stderr, L_("Invalid format character in "
305                                    "replay name: \"%%%c\"\n"), *fmt);
306                 break;
307             }
308         }
309         else
310         {
311             strncat(name, fmt, 1);
312             space_left--;
313         }
314
315         fmt++;
316     }
317
318     /*
319      * Append a unique 2-digit number preceded by an underscore to the
320      * file name, discarding characters if there's not enough space
321      * left in the buffer.
322      */
323
324     if (space_left < strlen("_23"))
325         numpart = name + MAXSTRLEN(name) - strlen("_23");
326     else
327         numpart = name + MAXSTRLEN(name) - space_left;
328
329     for (i = 1; i < 100; i++)
330     {
331         sprintf(numpart, "_%02d", i);
332
333         if (!demo_exists(name))
334             break;
335     }
336
337     return name;
338 }
339
340 #undef MAXSTRLEN
341
342 /*---------------------------------------------------------------------------*/
343
344 int demo_play_init(const char *name, const struct level *level,
345                    int mode, int t, int g, int e, int s, int b, int tt)
346 {
347     struct demo demo;
348
349     memset(&demo, 0, sizeof (demo));
350
351     strncpy(demo.filename, config_user(name), MAXSTR);
352     strcat(demo.filename, REPLAY_EXT);
353
354     demo.mode = mode;
355     demo.date = time(NULL);
356
357     config_get_s(CONFIG_PLAYER, demo.player, MAXNAM);
358
359     strncpy(demo.shot, level->shot, PATHMAX);
360     strncpy(demo.file, level->file, PATHMAX);
361
362     demo.time   = t;
363     demo.goal   = g;
364     demo.goal_e = e;
365     demo.score  = s;
366     demo.balls  = b;
367     demo.times  = tt;
368
369     if ((demo_fp = fopen(demo.filename, FMODE_WB)))
370     {
371         demo_header_write(demo_fp, &demo);
372         audio_music_fade_to(2.0f, level->song);
373         return game_init(level->file, t, e);
374     }
375     return 0;
376 }
377
378 void demo_play_step()
379 {
380     if (demo_fp)
381         input_put(demo_fp);
382 }
383
384 void demo_play_stat(int status, int coins, int timer)
385 {
386     if (demo_fp)
387     {
388         long pos = ftell(demo_fp);
389
390         fseek(demo_fp, 8, SEEK_SET);
391
392         put_index(demo_fp, &timer);
393         put_index(demo_fp, &coins);
394         put_index(demo_fp, &status);
395
396         fseek(demo_fp, pos, SEEK_SET);
397     }
398 }
399
400 void demo_play_stop(void)
401 {
402     if (demo_fp)
403     {
404         fclose(demo_fp);
405         demo_fp = NULL;
406     }
407 }
408
409 int demo_saved(void)
410 {
411     return demo_exists(USER_REPLAY_FILE);
412 }
413
414 void demo_rename(const char *name)
415 {
416     char src[MAXSTR];
417     char dst[MAXSTR];
418
419     if (name &&
420         demo_exists(USER_REPLAY_FILE) &&
421         strcmp(name, USER_REPLAY_FILE) != 0)
422     {
423         strcpy(src, config_user(USER_REPLAY_FILE));
424         strcat(src, REPLAY_EXT);
425
426         strcpy(dst, config_user(name));
427         strcat(dst, REPLAY_EXT);
428
429         file_rename(src, dst);
430     }
431 }
432
433 void demo_rename_player(const char *name, const char *player)
434 {
435     char filename[MAXSTR];
436     FILE *old_fp, *new_fp;
437     struct demo d;
438
439     assert(name);
440     assert(player);
441
442     /* TODO: make this reusable. */
443
444     filename[sizeof (filename) - 1] = '\0';
445     strncpy(filename, name, sizeof (filename) - 1);
446     strncat(filename, REPLAY_EXT, sizeof (filename) - 1 - strlen(name));
447
448     /*
449      * Write out a temporary file containing the original replay data with a
450      * new player name, then copy the resulting contents back to the original
451      * file.
452      *
453      * (It is believed that the ugliness found here is outweighed by the
454      * benefits of preserving the arbitrary-length property of all strings in
455      * the replay.  In case of doubt, FIXME.)
456      */
457
458     if ((old_fp = fopen(config_user(filename), FMODE_RB)))
459     {
460         if ((new_fp = tmpfile()))
461         {
462             if (demo_header_read(old_fp, &d))
463             {
464                 FILE *save_fp;
465
466                 /* Modify and write the header. */
467
468                 strncpy(d.player, player, sizeof (d.player));
469
470                 demo_header_write(new_fp, &d);
471
472                 /* Restore the last three fields not written by the above call. */
473
474                 /* Hack, hack, hack. */
475
476                 save_fp = demo_fp;
477                 demo_fp = new_fp;
478
479                 demo_play_stat(d.status, d.coins, d.timer);
480
481                 demo_fp = save_fp;
482
483                 /* Copy the remaining data. */
484
485                 file_copy(old_fp, new_fp);
486
487                 /* Then copy everything back. */
488
489                 if (freopen(config_user(filename), FMODE_WB, old_fp))
490                 {
491                     fseek(new_fp, 0L, SEEK_SET);
492                     file_copy(new_fp, old_fp);
493                 }
494             }
495             fclose(new_fp);
496         }
497         fclose(old_fp);
498     }
499 }
500
501 /*---------------------------------------------------------------------------*/
502
503 static struct demo  demo_replay;       /* The current demo */
504 static struct level demo_level_replay; /* The current level demo-ed*/
505
506 const struct demo *curr_demo_replay(void)
507 {
508     return &demo_replay;
509 }
510
511 static int demo_status = GAME_NONE;
512
513 int demo_replay_init(const char *name, int *g, int *m, int *b, int *s, int *tt)
514 {
515     demo_status = GAME_NONE;
516     demo_fp     = fopen(name, FMODE_RB);
517
518     if (demo_fp && demo_header_read(demo_fp, &demo_replay))
519     {
520         strncpy(demo_replay.filename, name, MAXSTR);
521         strncpy(demo_replay.name,
522                 base_name(text_from_locale(demo_replay.filename), REPLAY_EXT),
523                 PATHMAX);
524
525         if (level_load(demo_replay.file, &demo_level_replay))
526         {
527             demo_level_replay.time = demo_replay.time;
528             demo_level_replay.goal = demo_replay.goal;
529         }
530         else
531             return 0;
532
533         if (g)  *g  = demo_replay.goal;
534         if (m)  *m  = demo_replay.mode;
535         if (b)  *b  = demo_replay.balls;
536         if (s)  *s  = demo_replay.score;
537         if (tt) *tt = demo_replay.times;
538
539         if (g)
540         {
541             audio_music_fade_to(0.5f, demo_level_replay.song);
542
543             return game_init(demo_level_replay.file,
544                              demo_level_replay.time,
545                              demo_replay.goal_e);
546         }
547         else
548             return game_init(demo_level_replay.file,
549                              demo_level_replay.time, 1);
550     }
551     return 0;
552 }
553
554 int demo_replay_step(float dt)
555 {
556     const float gdn[3] = { 0.0f, -9.8f, 0.0f };
557     const float gup[3] = { 0.0f, +9.8f, 0.0f };
558
559     if (demo_fp)
560     {
561         if (input_get(demo_fp))
562         {
563             /* Play out current game state. */
564
565             switch (demo_status)
566             {
567             case GAME_NONE:
568                 demo_status = game_step(gdn, dt, 1); break;
569             case GAME_GOAL:
570                 (void)        game_step(gup, dt, 0); break;
571             default:
572                 (void)        game_step(gdn, dt, 0); break;
573             }
574
575             return 1;
576         }
577     }
578     return 0;
579 }
580
581 void demo_replay_stop(int d)
582 {
583     if (demo_fp)
584     {
585         fclose(demo_fp);
586         demo_fp = NULL;
587
588         if (d) remove(demo_replay.filename);
589     }
590 }
591
592 void demo_replay_dump_info(void)
593 {
594     demo_dump_info(&demo_replay);
595 }
596
597 /*---------------------------------------------------------------------------*/