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