Kill trailing whitespace.
[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             basename = strrchr(filename, '/');
107
108             if (basename != NULL)
109                 strncpy(buf, basename + 1, MAXSTR);
110             else
111                 strncpy(buf, filename, MAXSTR);
112
113             /* Remove the extension */
114             t = strlen(buf) - strlen(REPLAY_EXT);
115             if ((t > 1) && (strcmp(buf + t, REPLAY_EXT) == 0))
116                 buf[t] = '\0';
117             strncpy(d->name, buf, PATHMAX);
118             d->name[PATHMAX - 1] = '\0';
119
120             get_index (fp, &d->coins);
121             get_index (fp, &d->state);
122             get_index (fp, &d->mode);
123             get_index (fp, (int *)&d->date);
124             get_string(fp, d->player, MAXNAM);
125             get_string(fp, d->shot,   PATHMAX);
126             get_string(fp, d->file,   PATHMAX);
127             get_string(fp, d->back,   PATHMAX);
128             get_string(fp, d->grad,   PATHMAX);
129             get_string(fp, d->song,   PATHMAX);
130             get_index (fp, &d->time);
131             get_index (fp, &d->goal);
132             get_index (fp, &d->score);
133             get_index (fp, &d->balls);
134             get_index (fp, &d->times);
135             get_string(fp, d->nb_version, 20);
136
137             return fp;
138         }
139         fclose(fp);
140     }
141     return NULL;
142 }
143
144 static FILE *demo_header_write(struct demo *d)
145 /* Create a new demo file, write the demo information structure
146  * If success, return the file pointer positioned after the header
147  * If fail, return null
148  * */
149 {
150     int magic = MAGIC;
151     int version = REPLAY_VERSION;
152     int zero  = 0;
153     FILE *fp;
154
155     if (d->filename && (fp = fopen(d->filename, FMODE_WB)))
156     {
157         put_index (fp, &magic);
158         put_index (fp, &version);
159         put_index (fp, &zero);
160         put_index (fp, &zero);
161         put_index (fp, &zero);
162         put_index (fp, &d->mode);
163         put_index (fp, (int *)&d->date);
164         put_string(fp, d->player);
165         put_string(fp, d->shot);
166         put_string(fp, d->file);
167         put_string(fp, d->back);
168         put_string(fp, d->grad);
169         put_string(fp, d->song);
170         put_index (fp, &d->time);
171         put_index (fp, &d->goal);
172         put_index (fp, &d->score);
173         put_index (fp, &d->balls);
174         put_index (fp, &d->times);
175         put_string(fp, VERSION);
176
177         return fp;
178     }
179     return NULL;
180 }
181
182 void demo_header_stop(FILE *fp, int coins, int timer, int state)
183 /* Update the demo header using the final level state. */
184 {
185     long pos = ftell(fp);
186     fseek(fp, 8, SEEK_SET);
187     put_index(fp, &timer);
188     put_index(fp, &coins);
189     put_index(fp, &state);
190     fseek(fp, pos, SEEK_SET);
191 }
192
193 /*---------------------------------------------------------------------------*/
194
195 static void demo_scan_file(const char *filename)
196 /* Scan another file (used by demo_scan */
197 {
198     FILE *fp;
199     if ((fp = demo_header_read(config_user(filename), &demos[count])))
200     {
201         count++;
202         fclose(fp);
203     }
204 }
205
206 #ifdef _WIN32
207
208 int demo_scan(void)
209 {
210     WIN32_FIND_DATA d;
211     HANDLE h;
212
213     count = 0;
214
215     /* Scan the user directory for files. */
216
217     if ((h = FindFirstFile(config_user("*"), &d)) != INVALID_HANDLE_VALUE)
218     {
219         do
220             demo_scan_file(d.cFileName);
221         while (count < MAXDEMO && FindNextFile(h, &d));
222
223         FindClose(h);
224     }
225     return count;
226 }
227
228 #else /* _WIN32 */
229 #include <dirent.h>
230
231 int demo_scan(void)
232 {
233     struct dirent *ent;
234     DIR  *dp;
235
236     count = 0;
237
238     /* Scan the user directory for files. */
239
240     if ((dp = opendir(config_user(""))))
241     {
242         while (count < MAXDEMO && (ent = readdir(dp)))
243             demo_scan_file(ent->d_name);
244
245         closedir(dp);
246     }
247     return count;
248 }
249 #endif /* _WIN32 */
250
251 const char *demo_pick(void)
252 {
253     int n = demo_scan();
254
255     return (n > 0) ? demos[(rand() >> 4) % n].filename : NULL;
256 }
257
258 const struct demo *get_demo(int i)
259 {
260     return (0 <= i && i < count) ? &demos[i] : NULL;
261 }
262
263 const char *date_to_str(time_t i)
264 {
265     static char str[MAXSTR];
266     struct tm *tm = localtime(&i);
267     strftime (str, MAXSTR, "%c", tm);
268     return str;
269 }
270
271 /*---------------------------------------------------------------------------*/
272
273 int demo_exists(char *name)
274 {
275     FILE *fp;
276     char buf[MAXSTR];
277
278     strcpy(buf, config_user(name));
279     strcat(buf, REPLAY_EXT);
280     if ((fp = fopen(buf, "r")))
281     {
282         fclose(fp);
283         return 1;
284     }
285     return 0;
286 }
287
288 void demo_unique(char *name)
289 {
290     int i;
291
292     /* Generate a unique name for a new replay save. */
293
294     for (i = 1; i < 100; i++)
295     {
296         sprintf(name, _("replay%02d"), i);
297
298         if (!demo_exists(name))
299             return;
300     }
301 }
302
303 /*---------------------------------------------------------------------------*/
304
305 int demo_play_init(const char *name,
306                    const struct level *level,
307                    const struct level_game *lg)
308 {
309     struct demo demo;
310
311     /* file structure */
312     strncpy(demo.name, name, MAXNAM);
313     strncpy(demo.filename, config_user(name), PATHMAX);
314     strcat(demo.filename, REPLAY_EXT);
315     demo.time = demo.coins = demo.state = 0;
316     demo.mode = lg->mode;
317     demo.date = time(NULL);
318     config_get_s(CONFIG_PLAYER, demo.player, MAXNAM);
319     strncpy(demo.shot, level->shot, PATHMAX);
320     strncpy(demo.file, level->file, PATHMAX);
321     strncpy(demo.back, level->back, PATHMAX);
322     strncpy(demo.grad, level->grad, PATHMAX);
323     strncpy(demo.song, level->song, PATHMAX);
324     demo.time  = lg->time;
325     demo.goal  = lg->goal;
326     demo.score = lg->score;
327     demo.balls = lg->balls;
328     demo.times = lg->times;
329
330     demo_fp = demo_header_write(&demo);
331     if (demo_fp == NULL)
332         return 0;
333     else
334     {
335         audio_music_fade_to(2.0f, level->song);
336         return game_init(level, lg->time, lg->goal);
337     }
338 }
339
340 void demo_play_step(float dt)
341 {
342     static float fps_track = 0.0f;
343     static float fps_cap   = 1.0f / (float) DEMO_FPS_CAP;
344
345     if (demo_fp)
346     {
347         fps_track += dt;
348         if (fps_track > fps_cap)
349         {
350             put_float(demo_fp, &fps_track);
351             put_game_state(demo_fp);
352             fps_track = 0.0f;
353         }
354     }
355 }
356
357 void demo_play_stop(const struct level_game *lg)
358 /* Update the demo header using the final level state. */
359 {
360     if (demo_fp)
361     {
362         demo_header_stop(demo_fp, lg->coins, lg->timer, lg->state);
363         fclose(demo_fp);
364         demo_fp = NULL;
365     }
366 }
367
368 int demo_play_saved(void)
369 {
370     return demo_exists(USER_REPLAY_FILE);
371 }
372
373 void demo_play_save(const char *name)
374 {
375     char src[PATHMAX];
376     char dst[PATHMAX];
377
378     if (name && demo_exists(USER_REPLAY_FILE)
379         && strcmp(name, USER_REPLAY_FILE) != 0)
380     {
381         strncpy(src, config_user(USER_REPLAY_FILE), PATHMAX);
382         strcat(src, REPLAY_EXT);
383         strncpy(dst, config_user(name), PATHMAX);
384         strcat(dst, REPLAY_EXT);
385
386         rename(src, dst);
387     }
388 }
389
390 /*---------------------------------------------------------------------------*/
391
392 static int demo_load_level(const struct demo *demo, struct level *level)
393 /* Load the level of the demo and fill the level structure */
394 {
395     if (level_load(demo->file, level))
396     {
397         level->time = demo->time;
398         level->goal = demo->goal;
399         return 1;
400     }
401     else
402         return 0;
403 }
404
405 static struct demo  demo_replay;       /* The current demo */
406 static struct level demo_level_replay; /* The current level demo-ed*/
407
408 const struct demo *curr_demo_replay(void)
409 {
410     return &demo_replay;
411 }
412
413
414 int demo_replay_init(const char *name, struct level_game *lg)
415 /* Internally load a replay an fill the lg structure (if not NULL) */
416 {
417     if ((demo_fp = demo_header_read(name, &demo_replay)))
418     {
419         if (!demo_load_level(&demo_replay, &demo_level_replay))
420             return 0;
421
422         if (lg)
423         {
424             lg->mode = demo_replay.mode;
425             lg->score = demo_replay.score;
426             lg->times = demo_replay.times;
427             lg->time = demo_replay.time;
428             lg->goal = demo_replay.goal;
429
430             /* A normal replay demo */
431             audio_music_fade_to(0.5f, demo_replay.song);
432             return game_init(&demo_level_replay, demo_replay.time,
433                              demo_replay.goal);
434         }
435         else                    /* A title screen demo */
436             return game_init(&demo_level_replay, demo_replay.time, 0);
437     }
438
439     return 0;
440 }
441
442 int demo_replay_step(float *dt)
443 {
444     const float g[3] = { 0.0f, -9.8f, 0.0f };
445     int sv;
446
447     if (demo_fp)
448     {
449         get_float(demo_fp, dt);
450
451         if (feof(demo_fp) == 0)
452         {
453             /* Play out current game state for particles, clock, etc. */
454
455             game_step(g, *dt, &sv);
456
457             /* Load real current game state from file. */
458
459             if (get_game_state(demo_fp))
460                 return 1;
461         }
462     }
463     return 0;
464 }
465
466 void demo_replay_stop(int d)
467 {
468     if (demo_fp)
469     {
470         fclose(demo_fp);
471         demo_fp = NULL;
472
473         if (d) unlink(demo_replay.filename);
474     }
475 }
476
477 void demo_replay_dump_info(void)
478 {
479     demo_dump_info(&demo_replay);
480 }
481
482 /*---------------------------------------------------------------------------*/