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