update copyright info, also add a copiright template to add at the top of sourcefiles
[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 #ifndef _WIN32
21 #include <unistd.h>
22 #endif
23
24 #include "set.h"
25 #include "demo.h"
26 #include "game.h"
27 #include "audio.h"
28 #include "solid.h"
29 #include "config.h"
30 #include "binary.h"
31
32 /*---------------------------------------------------------------------------*/
33
34 #define MAGIC 0x4E425252
35 #define DEMO_FPS_CAP 200 /* FPS replay limit, keeps size down on monster systems */
36
37 static FILE *demo_fp;
38
39
40 /* Demo information structure (header) */
41 struct demo
42 {
43     char   name[MAXNAM];    /* demo filename */
44
45     /* magic */
46     int    timer;           /* elapsed time */
47     int    coins;           /* coin number */
48     int    state;           /* how the replay end */
49     int    mode;            /* game mode */
50     time_t date;            /* date of creation */
51     char   player[MAXNAM];  /* player name */
52     char   shot[PATHMAX];   /* image filename */
53     char   file[PATHMAX];   /* level filename */
54     char   back[PATHMAX];   /* level bg filename */
55     char   grad[PATHMAX];   /* level gradiant filename */
56     char   song[PATHMAX];   /* level song filename */
57     int    time;            /* time limit (! training mode) */
58     int    goal;            /* coin to open the goal (! training mode) */
59     int    score;           /* sum of coins (challenge mode) */
60     int    balls;           /* number of balls (challenge mode) */
61     int    total_time;      /* total time (challenge mode) */
62 };
63
64
65 static struct demo demos[MAXDEMO]; /* Array of scanned demos */
66
67 static int count; /* number of scanned demos */
68
69 /*---------------------------------------------------------------------------*/
70
71 void demo_dump_info(struct demo * d)
72 /* This function dump the info of a demo structure*/
73 {
74     printf("Filename: %s\n"
75            "Time:     %d\n"
76            "Coins:    %d\n"
77            "Mode:     %d\n"
78            "State:    %d\n"
79            "Date:     %s"
80            "Player:   %s\n"
81            "Shot:     %s\n"
82            "Level:    %s\n"
83            "  Back:   %s\n"
84            "  Grad:   %s\n"
85            "  Song:   %s\n"
86            "Time:     %d\n"
87            "Goal:     %d\n"
88            "Score:    %d\n"
89            "Balls:    %d\n"
90            "Tot Time: %d\n",
91            d->name,
92            d->timer, d->coins, d->mode, d->state, ctime(&d->date),
93            d->player,
94            d->shot, d->file, d->back, d->grad, d->song,
95            d->time, d->goal, d->score, d->balls, d->total_time);
96 }
97
98 static FILE * demo_header(const char * filename, struct demo * d)
99 /* Open a demo file, fill the demo information structure
100 If success, return the file pointer positioned after the header
101 If fail, return null */
102 {
103     FILE *fp;
104     if ((fp = fopen(config_user(filename), FMODE_RB)))
105     {
106         int magic;
107         int t;
108
109         get_index(fp, &magic);
110         get_index(fp, &t);
111
112         if (magic == MAGIC && t)
113         {
114             strncpy(d->name, filename, MAXNAM);
115             d->timer = t;
116             get_index(fp, &d->coins);
117             get_index(fp, &d->state);
118             get_index(fp, &d->mode);
119             get_index(fp, (int*)&d->date);
120             fread(d->player, 1, MAXNAM,  fp);
121             fread(d->shot,   1, PATHMAX, fp);
122             fread(d->file,   1, PATHMAX, fp);
123             fread(d->back,   1, PATHMAX, fp);
124             fread(d->grad,   1, PATHMAX, fp);
125             fread(d->song,   1, PATHMAX, fp);
126             get_index(fp, &d->time);
127             get_index(fp, &d->goal);
128             get_index(fp, &d->score);
129             get_index(fp, &d->balls);
130             get_index(fp, &d->total_time);
131
132             return fp;
133         }
134         fclose(fp);
135     }
136     return NULL;
137 }
138
139 /*---------------------------------------------------------------------------*/
140
141 static void demo_scan_file(const char * filename)
142 /* Scan a other file (used by demo_scan */
143 {
144     FILE *fp;
145     if ((fp = demo_header(filename, &demos[count])))
146     {
147         count++;
148         fclose(fp);
149     }
150 }
151
152 #ifdef _WIN32
153
154 int demo_scan(void)
155 {
156     WIN32_FIND_DATA d;
157     HANDLE h;
158
159     count = 0;
160
161     /* Scan the user directory for files. */
162
163     if ((h = FindFirstFile(config_user("*"), &d)) != INVALID_HANDLE_VALUE)
164     {
165         do
166             demo_scan_file(d.cFileName);
167         while (count < MAXDEMO && FindNextFile(h, &d));
168
169         FindClose(h);
170     }
171     return count;
172 }
173
174 #else /* _WIN32 */
175 #include <dirent.h>
176
177 int demo_scan(void)
178 {
179     struct dirent *ent;
180     DIR  *dp;
181
182     count = 0;
183
184     /* Scan the user directory for files. */
185
186     if ((dp = opendir(config_user(""))))
187     {
188         while (count < MAXDEMO && (ent = readdir(dp)))
189             demo_scan_file(ent->d_name);
190
191         closedir(dp);
192     }
193     return count;
194 }
195 #endif /* _WIN32 */
196
197 const char *demo_pick(void)
198 {
199     int n = demo_scan();
200
201     return (n > 0) ? demos[(rand() >> 4) % n].name : NULL;
202 }
203
204 const char *demo_name(int i)
205 {
206     return (0 <= i && i < count) ? demos[i].name : NULL;
207 }
208
209 const char *demo_shot(int i)
210 {
211     return (0 <= i && i < count) ? demos[i].shot : NULL;
212 }
213
214 int demo_coins(int i)
215 {
216     return (0 <= i && i < count) ? demos[i].coins : 0;
217 }
218
219 int demo_state(int i)
220 {
221     return (0 <= i && i < count) ? demos[i].state : 0;
222 }
223
224 int demo_mode(int i)
225 {
226     return (0 <= i && i < count) ? demos[i].mode : 0;
227 }
228
229 int demo_clock(int i)
230 {
231     return (0 <= i && i < count) ? demos[i].timer : 0;
232 }
233
234 time_t demo_date(int i)
235 {
236     return (0 <= i && i < count) ? demos[i].date : 0;
237 }
238
239 void demo_str_date(int i, char * str, int len)
240 {
241     time_t d = demo_date(i);
242     struct tm * tm = localtime(&d);
243     strftime (str, len, "%c",tm);
244 }
245
246 const char * demo_player(int i)
247 {
248     return (0 <= i && i < count) ? demos[i].player : "";
249 }
250
251 /*---------------------------------------------------------------------------*/
252
253 int demo_exists(char *name)
254 {
255     FILE *fp;
256
257     if ((fp = fopen(config_user(name), "r")))
258     {
259         fclose(fp);
260         return 1;
261     }
262     return 0;
263 }
264
265 void demo_unique(char *name)
266 {
267     int i;
268
269     /* Generate a unique name for a new replay save. */
270
271     for (i = 1; i < 100; i++)
272     {
273         sprintf(name, _("replay%02d"), i);
274
275         if (!demo_exists(name))
276             return;
277     }
278 }
279
280 /*---------------------------------------------------------------------------*/
281
282 int demo_play_init(const char *name,
283                    const char *file,
284                    const char *back,
285                    const char *grad,
286                    const char *song,
287                    const char *shot,
288                    int mode, int tim, int goal, int score, int balls, int total_time)
289 {
290     int magic = MAGIC;
291     int zero  = 0;
292     int date  = time(NULL);
293
294     char player[MAXNAM];
295     config_get_s(CONFIG_PLAYER, player, MAXNAM);
296
297     /* Initialize the replay file.  Write the header. */
298
299     if (name && (demo_fp = fopen(config_user(name), FMODE_WB)))
300     {
301         put_index(demo_fp, &magic);
302         put_index(demo_fp, &zero);
303         put_index(demo_fp, &zero);
304         put_index(demo_fp, &zero);
305         put_index(demo_fp, &mode);
306         put_index(demo_fp, &date);
307
308         fwrite(player, 1, MAXNAM,  demo_fp);
309         
310         fwrite(shot,   1, PATHMAX, demo_fp);
311         fwrite(file,   1, PATHMAX, demo_fp);
312         fwrite(back,   1, PATHMAX, demo_fp);
313         fwrite(grad,   1, PATHMAX, demo_fp);
314         fwrite(song,   1, PATHMAX, demo_fp);
315
316         put_index(demo_fp, &tim);
317         put_index(demo_fp, &goal);
318         put_index(demo_fp, &score);
319         put_index(demo_fp, &balls);
320         put_index(demo_fp, &total_time);
321
322         audio_music_fade_to(2.0f, song);
323
324         return game_init(file, back, grad, tim, goal);
325     }
326     return 0;
327 }
328
329 void demo_play_step(float dt)
330 {
331     static float fps_track = 0.0f;
332     static float fps_cap   = 1.0f / (float) DEMO_FPS_CAP;
333
334     if (demo_fp)
335     {
336         fps_track += dt;
337         if (fps_track > fps_cap)
338         {            
339             put_float(demo_fp, &fps_track);
340             put_game_state(demo_fp); 
341             fps_track = 0.0f;
342         }
343     }
344 }
345
346 void demo_play_stop(int coins, int timer, int state)
347 {
348     if (demo_fp)
349     {
350         /* Update the demo header using the final coins and time. */
351
352         fseek(demo_fp, 4, SEEK_SET);
353
354         put_index(demo_fp, &timer);
355         put_index(demo_fp, &coins);
356         put_index(demo_fp, &state);
357
358         fclose(demo_fp);
359         demo_fp = NULL;
360     }
361 }
362
363 int demo_play_saved(void)
364 {
365     return demo_exists(USER_REPLAY_FILE);
366 }
367
368 void demo_play_save(const char *name)
369 {
370     char src[PATHMAX];
371     char dst[PATHMAX];
372
373     if (name && demo_exists(USER_REPLAY_FILE) && strcmp(name, USER_REPLAY_FILE) != 0)
374     {
375         strncpy(src, config_user(USER_REPLAY_FILE), PATHMAX);
376         strncpy(dst, config_user(name),             PATHMAX);
377
378         rename(src, dst);
379     }
380 }
381
382 /*---------------------------------------------------------------------------*/
383
384 static char demo_replay_name[MAXSTR];
385
386 int demo_replay_init(const char *name, int *m, int *s, int *c, int *t)
387 {
388     struct demo d;
389
390     if ((demo_fp = demo_header(name, &d)))
391     {
392         strncpy(demo_replay_name, name, MAXSTR);
393         if (m) *m = d.mode;
394         if (s) *s = d.score;
395         if (t) *t = d.total_time;
396
397         if (m)
398         {
399             /* A normal replay demo */
400             audio_music_fade_to(0.5f, d.song);
401             return game_init(d.file, d.back, d.grad, d.time, d.goal);
402         }
403         else /* A title screen demo */
404             return game_init(d.file, d.back, d.grad, d.time, 0);
405     }
406     
407     return 0;
408 }
409
410 int demo_replay_step(float *dt)
411 {
412     const float g[3] = { 0.0f, -9.8f, 0.0f };
413
414     if (demo_fp)
415     {
416         get_float(demo_fp, dt);
417
418         if (feof(demo_fp) == 0)
419         {
420             /* Play out current game state for particles, clock, etc. */
421
422             game_step(g, *dt, 1);
423
424             /* Load real current game state from file. */
425             
426             if (get_game_state(demo_fp))
427                 return 1;
428         }
429     }
430     return 0;
431 }
432
433 void demo_replay_stop(int d)
434 {
435     if (demo_fp)
436     {
437         fclose(demo_fp);
438         demo_fp = NULL;
439
440         if (d) unlink(config_user(demo_replay_name));
441     }
442 }
443
444 /*---------------------------------------------------------------------------*/