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