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