Minor code clean-up.
[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
20 #ifndef _WIN32
21 #include <unistd.h>
22 #endif
23
24 #include "demo.h"
25 #include "game.h"
26 #include "audio.h"
27 #include "solid.h"
28 #include "config.h"
29 #include "binary.h"
30
31 /*---------------------------------------------------------------------------*/
32
33 #define MAGIC     0x4E425251 /* Replay file magic number (should not change) */
34 #define OLD_MAGIC 0x4E425250 /* Replay file magic number for neverball 1.4.0 */
35 #define REPLAY_VERSION  1    /* Replay file format version (can change)      */
36
37 #define DEMO_FPS_CAP 200 /* FPS replay limit, keeps size down on monster systems */
38
39 static FILE *demo_fp;
40
41 static struct demo demos[MAXDEMO]; /* Array of scanned demos */
42
43 static int count; /* number of scanned demos */
44
45 /*---------------------------------------------------------------------------*/
46
47 void demo_dump_info(const struct demo *d)
48 /* This function dump the info of a demo structure
49  * It's only a function for debugging, no need of I18N */
50 {
51     printf("Name:         %s\n"
52            "File:         %s\n"
53            "NB Version:   %s\n"
54            "Time:         %d\n"
55            "Coins:        %d\n"
56            "Mode:         %d\n"
57            "State:        %d\n"
58            "Date:         %s"
59            "Player:       %s\n"
60            "Shot:         %s\n"
61            "Level:        %s\n"
62            "  Back:       %s\n"
63            "  Grad:       %s\n"
64            "  Song:       %s\n"
65            "Time:         %d\n"
66            "Goal:         %d\n"
67            "Score:        %d\n"
68            "Balls:        %d\n"
69            "Total Time:   %d\n",
70            d->name, d->filename,
71            d->nb_version,
72            d->timer, d->coins, d->mode, d->state, ctime(&d->date),
73            d->player,
74            d->shot, d->file, d->back, d->grad, d->song,
75            d->time, d->goal, d->score, d->balls, d->times);
76 }
77
78 FILE *demo_header_read(const char *filename, struct demo *d)
79 /* Open a demo file, fill the demo information structure
80  * If success, return the file pointer positioned after the header
81  * If fail, return null
82  */
83 {
84     FILE *fp;
85     char *basename;
86     char buf[MAXSTR];
87
88     if ((fp = fopen(filename, FMODE_RB)))
89     {
90         int magic;
91         int version;
92         int t;
93
94         get_index(fp, &magic);
95         get_index(fp, &version);
96
97         /* if time is 0, it means the replay was not finished */
98         get_index(fp, &t);
99
100         if (magic == MAGIC && version == REPLAY_VERSION && t)
101         {
102             d->timer = t;
103             strncpy(d->filename, filename, PATHMAX);
104
105             /* Remove the directory delimiter */
106
107             basename = strrchr(filename, '/');
108 #ifdef _WIN32
109             if (!basename)
110                 basename = strrchr(filename, '\\');
111             else
112             {
113                 char *tmp;
114                 if ((tmp = strrchr(basename, '\\')))
115                     basename = tmp;
116             }
117 #endif
118
119             if (basename != NULL)
120                 strncpy(buf, basename + 1, MAXSTR);
121             else
122                 strncpy(buf, filename, MAXSTR);
123
124             /* Remove the extension */
125             t = strlen(buf) - strlen(REPLAY_EXT);
126             if ((t > 1) && (strcmp(buf + t, REPLAY_EXT) == 0))
127                 buf[t] = '\0';
128             strncpy(d->name, buf, PATHMAX);
129             d->name[PATHMAX - 1] = '\0';
130
131             get_index (fp, &d->coins);
132             get_index (fp, &d->state);
133             get_index (fp, &d->mode);
134             get_index (fp, (int *)&d->date);
135
136             fread(d->player, 1, MAXNAM, fp);
137
138             fread(d->shot, 1, PATHMAX, fp);
139             fread(d->file, 1, PATHMAX, fp);
140             fread(d->back, 1, PATHMAX, fp);
141             fread(d->grad, 1, PATHMAX, fp);
142             fread(d->song, 1, PATHMAX, fp);
143
144             get_index (fp, &d->time);
145             get_index (fp, &d->goal);
146             get_index (fp, &d->score);
147             get_index (fp, &d->balls);
148             get_index (fp, &d->times);
149
150             fread(d->nb_version, 1, 20, fp);
151
152             return fp;
153         }
154         fclose(fp);
155     }
156     return NULL;
157 }
158
159 static FILE *demo_header_write(struct demo *d)
160 /* Create a new demo file, write the demo information structure. If
161  * success, return the file pointer positioned after the header. If
162  * fail, return null.
163  */
164 {
165     int magic = MAGIC;
166     int version = REPLAY_VERSION;
167     int zero  = 0;
168
169     FILE *fp;
170
171     if (d->filename && (fp = fopen(d->filename, FMODE_WB)))
172     {
173         put_index(fp, &magic);
174         put_index(fp, &version);
175         put_index(fp, &zero);
176         put_index(fp, &zero);
177         put_index(fp, &zero);
178         put_index(fp, &d->mode);
179         put_index(fp, (int *) &d->date);
180
181         fwrite(d->player, 1, MAXNAM, fp);
182
183         fwrite(d->shot, 1, PATHMAX, fp);
184         fwrite(d->file, 1, PATHMAX, fp);
185         fwrite(d->back, 1, PATHMAX, fp);
186         fwrite(d->grad, 1, PATHMAX, fp);
187         fwrite(d->song, 1, PATHMAX, fp);
188
189         put_index(fp, &d->time);
190         put_index(fp, &d->goal);
191         put_index(fp, &d->score);
192         put_index(fp, &d->balls);
193         put_index(fp, &d->times);
194
195         fwrite(d->nb_version, 1, 20, fp);
196
197         return fp;
198     }
199     return NULL;
200 }
201
202 void demo_header_stop(FILE *fp, int coins, int timer, int state)
203 /* Update the demo header using the final level state. */
204 {
205     long pos = ftell(fp);
206     fseek(fp, 8, SEEK_SET);
207     put_index(fp, &timer);
208     put_index(fp, &coins);
209     put_index(fp, &state);
210     fseek(fp, pos, SEEK_SET);
211 }
212
213 /*---------------------------------------------------------------------------*/
214
215 static void demo_scan_file(const char *filename)
216 /* Scan another file (used by demo_scan */
217 {
218     FILE *fp;
219     if ((fp = demo_header_read(config_user(filename), &demos[count])))
220     {
221         count++;
222         fclose(fp);
223     }
224 }
225
226 #ifdef _WIN32
227
228 int demo_scan(void)
229 {
230     WIN32_FIND_DATA d;
231     HANDLE h;
232
233     count = 0;
234
235     /* Scan the user directory for files. */
236
237     if ((h = FindFirstFile(config_user("*"), &d)) != INVALID_HANDLE_VALUE)
238     {
239         do
240             demo_scan_file(d.cFileName);
241         while (count < MAXDEMO && FindNextFile(h, &d));
242
243         FindClose(h);
244     }
245     return count;
246 }
247
248 #else /* _WIN32 */
249 #include <dirent.h>
250
251 int demo_scan(void)
252 {
253     struct dirent *ent;
254     DIR  *dp;
255
256     count = 0;
257
258     /* Scan the user directory for files. */
259
260     if ((dp = opendir(config_user(""))))
261     {
262         while (count < MAXDEMO && (ent = readdir(dp)))
263             demo_scan_file(ent->d_name);
264
265         closedir(dp);
266     }
267     return count;
268 }
269 #endif /* _WIN32 */
270
271 const char *demo_pick(void)
272 {
273     int n = demo_scan();
274
275     return (n > 0) ? demos[(rand() >> 4) % n].filename : NULL;
276 }
277
278 const struct demo *get_demo(int i)
279 {
280     return (0 <= i && i < count) ? &demos[i] : NULL;
281 }
282
283 const char *date_to_str(time_t i)
284 {
285     static char str[MAXSTR];
286     struct tm *tm = localtime(&i);
287     strftime (str, MAXSTR, "%c", tm);
288     return str;
289 }
290
291 /*---------------------------------------------------------------------------*/
292
293 int demo_exists(char *name)
294 {
295     FILE *fp;
296     char buf[MAXSTR];
297
298     strcpy(buf, config_user(name));
299     strcat(buf, REPLAY_EXT);
300     if ((fp = fopen(buf, "r")))
301     {
302         fclose(fp);
303         return 1;
304     }
305     return 0;
306 }
307
308 void demo_unique(char *name)
309 {
310     int i;
311
312     /* Generate a unique name for a new replay save. */
313
314     for (i = 1; i < 100; i++)
315     {
316         sprintf(name, _("replay%02d"), i);
317
318         if (!demo_exists(name))
319             return;
320     }
321 }
322
323 /*---------------------------------------------------------------------------*/
324
325 int demo_play_init(const char *name,
326                    const struct level *level,
327                    const struct level_game *lg)
328 {
329     struct demo demo;
330
331     memset(&demo, 0, sizeof(demo));
332
333     strncpy(demo.filename, config_user(name), PATHMAX);
334     strcat(demo.filename, REPLAY_EXT);
335
336     demo.mode = lg->mode;
337     demo.date = time(NULL);
338
339     config_get_s(CONFIG_PLAYER, demo.player, MAXNAM);
340
341     strncpy(demo.shot, level->shot, PATHMAX);
342     strncpy(demo.file, level->file, PATHMAX);
343     strncpy(demo.back, level->back, PATHMAX);
344     strncpy(demo.grad, level->grad, PATHMAX);
345     strncpy(demo.song, level->song, PATHMAX);
346
347     demo.time  = lg->time;
348     demo.goal  = lg->goal;
349     demo.score = lg->score;
350     demo.balls = lg->balls;
351     demo.times = lg->times;
352
353     strncpy(demo.nb_version, VERSION, 20);
354
355     demo_fp = demo_header_write(&demo);
356
357     if (demo_fp == NULL)
358         return 0;
359     else
360     {
361         audio_music_fade_to(2.0f, level->song);
362         return game_init(level, lg->time, lg->goal);
363     }
364 }
365
366 void demo_play_step(float dt)
367 {
368     static float fps_track = 0.0f;
369     static float fps_cap   = 1.0f / (float) DEMO_FPS_CAP;
370
371     if (demo_fp)
372     {
373         fps_track += dt;
374         if (fps_track > fps_cap)
375         {
376             put_float(demo_fp, &fps_track);
377             put_game_state(demo_fp);
378             fps_track = 0.0f;
379         }
380     }
381 }
382
383 void demo_play_stop(const struct level_game *lg)
384 /* Update the demo header using the final level state. */
385 {
386     if (demo_fp)
387     {
388         demo_header_stop(demo_fp, lg->coins, lg->timer, lg->state);
389         fclose(demo_fp);
390         demo_fp = NULL;
391     }
392 }
393
394 int demo_play_saved(void)
395 {
396     return demo_exists(USER_REPLAY_FILE);
397 }
398
399 void demo_play_save(const char *name)
400 {
401     char src[PATHMAX];
402     char dst[PATHMAX];
403
404     if (name && demo_exists(USER_REPLAY_FILE)
405         && strcmp(name, USER_REPLAY_FILE) != 0)
406     {
407         strncpy(src, config_user(USER_REPLAY_FILE), PATHMAX);
408         strcat(src, REPLAY_EXT);
409         strncpy(dst, config_user(name), PATHMAX);
410         strcat(dst, REPLAY_EXT);
411
412         rename(src, dst);
413     }
414 }
415
416 /*---------------------------------------------------------------------------*/
417
418 static int demo_load_level(const struct demo *demo, struct level *level)
419 /* Load the level of the demo and fill the level structure */
420 {
421     if (level_load(demo->file, level))
422     {
423         level->time = demo->time;
424         level->goal = demo->goal;
425         return 1;
426     }
427     else
428         return 0;
429 }
430
431 static struct demo  demo_replay;       /* The current demo */
432 static struct level demo_level_replay; /* The current level demo-ed*/
433
434 const struct demo *curr_demo_replay(void)
435 {
436     return &demo_replay;
437 }
438
439
440 int demo_replay_init(const char *name, struct level_game *lg)
441 /* Internally load a replay an fill the lg structure (if not NULL) */
442 {
443     if ((demo_fp = demo_header_read(name, &demo_replay)))
444     {
445         if (!demo_load_level(&demo_replay, &demo_level_replay))
446             return 0;
447
448         if (lg)
449         {
450             lg->mode = demo_replay.mode;
451             lg->score = demo_replay.score;
452             lg->times = demo_replay.times;
453             lg->time = demo_replay.time;
454             lg->goal = demo_replay.goal;
455
456             /* A normal replay demo */
457             audio_music_fade_to(0.5f, demo_replay.song);
458             return game_init(&demo_level_replay, demo_replay.time,
459                              demo_replay.goal);
460         }
461         else                    /* A title screen demo */
462             return game_init(&demo_level_replay, demo_replay.time, 0);
463     }
464
465     return 0;
466 }
467
468 int demo_replay_step(float *dt)
469 {
470     const float g[3] = { 0.0f, -9.8f, 0.0f };
471     int sv;
472
473     if (demo_fp)
474     {
475         get_float(demo_fp, dt);
476
477         if (feof(demo_fp) == 0)
478         {
479             /* Play out current game state for particles, clock, etc. */
480
481             game_step(g, *dt, &sv);
482
483             /* Load real current game state from file. */
484
485             if (get_game_state(demo_fp))
486                 return 1;
487         }
488     }
489     return 0;
490 }
491
492 void demo_replay_stop(int d)
493 {
494     if (demo_fp)
495     {
496         fclose(demo_fp);
497         demo_fp = NULL;
498
499         if (d) unlink(demo_replay.filename);
500     }
501 }
502
503 void demo_replay_dump_info(void)
504 {
505     demo_dump_info(&demo_replay);
506 }
507
508 /*---------------------------------------------------------------------------*/