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