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