Removed all remntants of an old implementation of special/warp goals.
[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     /* TRANSLATORS:  here is the format of the date shown at the
302        replay selection screen.  The default will work in most cases, so
303        you should only change it if something's horribly wrong, like,
304        for instance, the GUI layout is broken.  See strftime(3) for
305        details on the format.
306      */
307
308     strftime(str, MAXSTR, /* xgettext:no-c-format */ _("%c"), localtime(&i));
309
310     return str;
311 }
312
313 /*---------------------------------------------------------------------------*/
314
315 int demo_exists(const char *name)
316 {
317     FILE *fp;
318     char buf[MAXSTR];
319
320     strcpy(buf, config_user(name));
321     strcat(buf, REPLAY_EXT);
322     if ((fp = fopen(buf, "r")))
323     {
324         fclose(fp);
325         return 1;
326     }
327     return 0;
328 }
329
330 void demo_unique(char *name)
331 {
332     int i;
333
334     /* Generate a unique name for a new replay save. */
335
336     for (i = 1; i < 100; i++)
337     {
338         sprintf(name, "replay%02d", i);
339
340         if (!demo_exists(name))
341             return;
342     }
343 }
344
345 /*---------------------------------------------------------------------------*/
346
347 int demo_play_init(const char *name,
348                    const struct level *level,
349                    const struct level_game *lg)
350 {
351     struct demo demo;
352
353     memset(&demo, 0, sizeof (demo));
354
355     strncpy(demo.filename, config_user(name), MAXSTR);
356     strcat(demo.filename, REPLAY_EXT);
357
358     demo.mode = lg->mode;
359     demo.date = time(NULL);
360
361     config_get_s(CONFIG_PLAYER, demo.player, MAXNAM);
362
363     strncpy(demo.shot, level->shot, PATHMAX);
364     strncpy(demo.file, level->file, PATHMAX);
365     strncpy(demo.back, level->back, PATHMAX);
366     strncpy(demo.grad, level->grad, PATHMAX);
367     strncpy(demo.song, level->song, PATHMAX);
368
369     demo.time  = lg->time;
370     demo.goal  = lg->goal;
371     demo.score = lg->score;
372     demo.balls = lg->balls;
373     demo.times = lg->times;
374
375     if ((demo_fp = fopen(demo.filename, FMODE_WB)))
376     {
377         demo_header_write(demo_fp, &demo);
378         audio_music_fade_to(2.0f, level->song);
379         return game_init(level, lg->time, lg->goal);
380     }
381     return 0;
382 }
383
384 void demo_play_step(float dt)
385 {
386     if (demo_fp)
387     {
388         put_float(demo_fp, &dt);
389         put_game_state(demo_fp);
390     }
391 }
392
393 /* Update the demo header using the final level state. */
394
395 void demo_play_stop(const struct level_game *lg)
396 {
397     if (demo_fp)
398     {
399         demo_header_stop(demo_fp, lg->coins, lg->timer, lg->state);
400         fclose(demo_fp);
401         demo_fp = NULL;
402     }
403 }
404
405 int demo_play_saved(void)
406 {
407     return demo_exists(USER_REPLAY_FILE);
408 }
409
410 void demo_play_save(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 /* Internally load a replay and fill the lg structure (if not NULL) */
451
452 int demo_replay_init(const char *name, struct level_game *lg)
453 {
454     demo_fp = fopen(name, FMODE_RB);
455
456     if (demo_fp && demo_header_read(demo_fp, &demo_replay))
457     {
458         strncpy(demo_replay.filename, name,                    MAXSTR);
459         strncpy(demo_replay.name,     bname(name, REPLAY_EXT), PATHMAX);
460
461         if (!demo_load_level(&demo_replay, &demo_level_replay))
462             return 0;
463
464         if (lg)
465         {
466             lg->mode = demo_replay.mode;
467             lg->score = demo_replay.score;
468             lg->times = demo_replay.times;
469             lg->time = demo_replay.time;
470             lg->goal = demo_replay.goal;
471
472             /* A normal replay demo */
473             audio_music_fade_to(0.5f, demo_level_replay.song);
474             return game_init(&demo_level_replay, demo_replay.time,
475                              demo_replay.goal);
476         }
477         else /* A title screen demo */
478             return game_init(&demo_level_replay, demo_replay.time, 0);
479     }
480     return 0;
481 }
482
483 int demo_replay_step(float *dt)
484 {
485     const float g[3] = { 0.0f, -9.8f, 0.0f };
486
487     if (demo_fp)
488     {
489         get_float(demo_fp, dt);
490
491         if (feof(demo_fp) == 0)
492         {
493             /* Play out current game state for particles, clock, etc. */
494
495             game_step(g, *dt, 1);
496
497             /* Load real current game state from file. */
498
499             if (get_game_state(demo_fp))
500                 return 1;
501         }
502     }
503     return 0;
504 }
505
506 void demo_replay_stop(int d)
507 {
508     if (demo_fp)
509     {
510         fclose(demo_fp);
511         demo_fp = NULL;
512
513         if (d) remove(demo_replay.filename);
514     }
515 }
516
517 void demo_replay_dump_info(void)
518 {
519     demo_dump_info(&demo_replay);
520 }
521
522 /*---------------------------------------------------------------------------*/