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