Clear "paused" flag on delete/keep at Replay Paused screen.
[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->status, 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->status);
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 /*---------------------------------------------------------------------------*/
205
206 /* Scan another file (used by demo_scan). */
207
208 static void demo_scan_file(const char *filename)
209 {
210     FILE *fp;
211     struct demo *d = &demos[count];
212
213     if ((fp = fopen(config_user(filename), FMODE_RB)))
214     {
215         if (demo_header_read(fp, d))
216         {
217             strncpy(d->filename, config_user(filename),       MAXSTR);
218             strncpy(d->name,     bname(filename, REPLAY_EXT), PATHMAX);
219             d->name[PATHMAX - 1] = '\0';
220
221             count++;
222         }
223         fclose(fp);
224     }
225 }
226
227 #ifdef _WIN32
228
229 int demo_scan(void)
230 {
231     WIN32_FIND_DATA d;
232     HANDLE h;
233
234     count = 0;
235
236     /* Scan the user directory for files. */
237
238     if ((h = FindFirstFile(config_user("*"), &d)) != INVALID_HANDLE_VALUE)
239     {
240         do
241             demo_scan_file(d.cFileName);
242         while (count < MAXDEMO && FindNextFile(h, &d));
243
244         FindClose(h);
245     }
246     return count;
247 }
248
249 #else /* _WIN32 */
250 #include <dirent.h>
251
252 int demo_scan(void)
253 {
254     struct dirent *ent;
255     DIR  *dp;
256
257     count = 0;
258
259     /* Scan the user directory for files. */
260
261     if ((dp = opendir(config_user(""))))
262     {
263         while (count < MAXDEMO && (ent = readdir(dp)))
264             demo_scan_file(ent->d_name);
265
266         closedir(dp);
267     }
268     return count;
269 }
270 #endif /* _WIN32 */
271
272 const char *demo_pick(void)
273 {
274     int n = demo_scan();
275
276     return (n > 0) ? demos[(rand() >> 4) % n].filename : NULL;
277 }
278
279 const struct demo *demo_get(int i)
280 {
281     return (0 <= i && i < count) ? &demos[i] : NULL;
282 }
283
284 const char *date_to_str(time_t i)
285 {
286     static char str[MAXSTR];
287
288     /* TRANSLATORS:  here is the format of the date shown at the
289        replay selection screen.  The default will work in most cases, so
290        you should only change it if something's horribly wrong, like,
291        for instance, the GUI layout is broken.  See strftime(3) for
292        details on the format.
293      */
294
295     strftime(str, MAXSTR, /* xgettext:no-c-format */ _("%c"), localtime(&i));
296
297     return str;
298 }
299
300 /*---------------------------------------------------------------------------*/
301
302 int demo_exists(const char *name)
303 {
304     FILE *fp;
305     char buf[MAXSTR];
306
307     strcpy(buf, config_user(name));
308     strcat(buf, REPLAY_EXT);
309     if ((fp = fopen(buf, "r")))
310     {
311         fclose(fp);
312         return 1;
313     }
314     return 0;
315 }
316
317 void demo_unique(char *name)
318 {
319     int i;
320
321     /* Generate a unique name for a new replay save. */
322
323     for (i = 1; i < 100; i++)
324     {
325         sprintf(name, "replay%02d", i);
326
327         if (!demo_exists(name))
328             return;
329     }
330 }
331
332 /*---------------------------------------------------------------------------*/
333
334 int demo_play_init(const char *name,
335                    const struct level *level,
336                    const struct level_game *lg)
337 {
338     struct demo demo;
339
340     memset(&demo, 0, sizeof (demo));
341
342     strncpy(demo.filename, config_user(name), MAXSTR);
343     strcat(demo.filename, REPLAY_EXT);
344
345     demo.mode = lg->mode;
346     demo.date = time(NULL);
347
348     config_get_s(CONFIG_PLAYER, demo.player, MAXNAM);
349
350     strncpy(demo.shot, level->shot, PATHMAX);
351     strncpy(demo.file, level->file, PATHMAX);
352     strncpy(demo.back, level->back, PATHMAX);
353     strncpy(demo.grad, level->grad, PATHMAX);
354     strncpy(demo.song, level->song, PATHMAX);
355
356     demo.time  = lg->time;
357     demo.goal  = lg->goal;
358     demo.score = lg->score;
359     demo.balls = lg->balls;
360     demo.times = lg->times;
361
362     if ((demo_fp = fopen(demo.filename, FMODE_WB)))
363     {
364         demo_header_write(demo_fp, &demo);
365         audio_music_fade_to(2.0f, level->song);
366         return game_init(level, lg->time, lg->goal);
367     }
368     return 0;
369 }
370
371 void demo_play_step(float dt)
372 {
373     if (demo_fp)
374     {
375         put_float(demo_fp, &dt);
376         put_game_state(demo_fp);
377     }
378 }
379
380 void demo_play_stat(const struct level_game *lg)
381 {
382     if (demo_fp)
383     {
384         long pos = ftell(demo_fp);
385
386         fseek(demo_fp, 8, SEEK_SET);
387
388         put_index(demo_fp, &lg->timer);
389         put_index(demo_fp, &lg->coins);
390         put_index(demo_fp, &lg->status);
391
392         fseek(demo_fp, pos, SEEK_SET);
393     }
394 }
395
396 void demo_play_stop(void)
397 {
398     if (demo_fp)
399     {
400         fclose(demo_fp);
401         demo_fp = NULL;
402     }
403 }
404
405 int demo_saved(void)
406 {
407     return demo_exists(USER_REPLAY_FILE);
408 }
409
410 void demo_rename(const char *name)
411 {
412     char src[MAXSTR];
413     char dst[MAXSTR];
414
415     if (name &&
416         demo_exists(USER_REPLAY_FILE) &&
417         strcmp(name, USER_REPLAY_FILE) != 0)
418     {
419         strcpy(src, config_user(USER_REPLAY_FILE));
420         strcat(src, REPLAY_EXT);
421
422         strcpy(dst, config_user(name));
423         strcat(dst, REPLAY_EXT);
424
425         rename(src, dst);
426     }
427 }
428
429 /*---------------------------------------------------------------------------*/
430
431 static int demo_load_level(const struct demo *demo, struct level *level)
432 {
433     if (level_load(demo->file, level))
434     {
435         level->time = demo->time;
436         level->goal = demo->goal;
437         return 1;
438     }
439     return 0;
440 }
441
442 static struct demo  demo_replay;       /* The current demo */
443 static struct level demo_level_replay; /* The current level demo-ed*/
444
445 const struct demo *curr_demo_replay(void)
446 {
447     return &demo_replay;
448 }
449
450 static int demo_status = GAME_NONE;
451
452 int demo_replay_init(const char *name, struct level_game *lg)
453 {
454     demo_status = GAME_NONE;
455     demo_fp     = fopen(name, FMODE_RB);
456
457     if (demo_fp && demo_header_read(demo_fp, &demo_replay))
458     {
459         strncpy(demo_replay.filename, name,                    MAXSTR);
460         strncpy(demo_replay.name,     bname(name, REPLAY_EXT), PATHMAX);
461
462         if (!demo_load_level(&demo_replay, &demo_level_replay))
463             return 0;
464
465         if (lg)
466         {
467             lg->mode = demo_replay.mode;
468             lg->score = demo_replay.score;
469             lg->times = demo_replay.times;
470             lg->time = demo_replay.time;
471             lg->goal = demo_replay.goal;
472
473             /* A normal replay demo */
474             audio_music_fade_to(0.5f, demo_level_replay.song);
475             return game_init(&demo_level_replay, demo_replay.time,
476                              demo_replay.goal);
477         }
478         else /* A title screen demo */
479             return game_init(&demo_level_replay, demo_replay.time, 0);
480     }
481     return 0;
482 }
483
484 int demo_replay_step(float *dt)
485 {
486     const float g[3] = { 0.0f, -9.8f, 0.0f };
487
488     if (demo_fp)
489     {
490         get_float(demo_fp, dt);
491
492         if (feof(demo_fp) == 0)
493         {
494             /* Play out current game state for particles, clock, etc. */
495
496             if (demo_status == GAME_NONE)
497                 demo_status = game_step(g, *dt, 1);
498             else
499                 game_step(g, *dt, 0);
500
501             /* Load real current game state from file. */
502
503             if (get_game_state(demo_fp))
504                 return 1;
505         }
506     }
507     return 0;
508 }
509
510 void demo_replay_stop(int d)
511 {
512     if (demo_fp)
513     {
514         fclose(demo_fp);
515         demo_fp = NULL;
516
517         if (d) remove(demo_replay.filename);
518     }
519 }
520
521 void demo_replay_dump_info(void)
522 {
523     demo_dump_info(&demo_replay);
524 }
525
526 /*---------------------------------------------------------------------------*/