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