Fix redundant glTexEnv calls
[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 #include <assert.h>
20
21 #include "demo.h"
22 #include "audio.h"
23 #include "config.h"
24 #include "binary.h"
25 #include "common.h"
26 #include "level.h"
27 #include "array.h"
28 #include "dir.h"
29 #include "speed.h"
30
31 #include "game_server.h"
32 #include "game_client.h"
33 #include "game_proxy.h"
34 #include "game_common.h"
35
36 /*---------------------------------------------------------------------------*/
37
38 #define DEMO_MAGIC (0xAF | 'N' << 8 | 'B' << 16 | 'R' << 24)
39 #define DEMO_VERSION 9
40
41 #define DATELEN sizeof ("YYYY-MM-DDTHH:MM:SS")
42
43 fs_file demo_fp;
44
45 /*---------------------------------------------------------------------------*/
46
47 static int demo_header_read(fs_file fp, struct demo *d)
48 {
49     int magic;
50     int version;
51     int t, unused;
52
53     struct tm date;
54     char datestr[DATELEN];
55
56     get_index(fp, &magic);
57     get_index(fp, &version);
58
59     get_index(fp, &t);
60
61     if (magic == DEMO_MAGIC && version == DEMO_VERSION && t)
62     {
63         d->timer = t;
64
65         get_index(fp, &d->coins);
66         get_index(fp, &d->status);
67         get_index(fp, &d->mode);
68
69         get_string(fp, d->player, sizeof (d->player));
70         get_string(fp, datestr, sizeof (datestr));
71
72         sscanf(datestr,
73                "%d-%d-%dT%d:%d:%d",
74                &date.tm_year,
75                &date.tm_mon,
76                &date.tm_mday,
77                &date.tm_hour,
78                &date.tm_min,
79                &date.tm_sec);
80
81         date.tm_year -= 1900;
82         date.tm_mon  -= 1;
83         date.tm_isdst = -1;
84
85         d->date = make_time_from_utc(&date);
86
87         get_string(fp, d->shot, PATHMAX);
88         get_string(fp, d->file, PATHMAX);
89
90         get_index(fp, &d->time);
91         get_index(fp, &d->goal);
92         get_index(fp, &unused);
93         get_index(fp, &d->score);
94         get_index(fp, &d->balls);
95         get_index(fp, &d->times);
96
97         return 1;
98     }
99     return 0;
100 }
101
102 static void demo_header_write(fs_file fp, struct demo *d)
103 {
104     char datestr[DATELEN];
105
106     strftime(datestr, sizeof (datestr), "%Y-%m-%dT%H:%M:%S", gmtime(&d->date));
107
108     put_index(fp, DEMO_MAGIC);
109     put_index(fp, DEMO_VERSION);
110     put_index(fp, 0);
111     put_index(fp, 0);
112     put_index(fp, 0);
113     put_index(fp, d->mode);
114
115     put_string(fp, d->player);
116     put_string(fp, datestr);
117
118     put_string(fp, d->shot);
119     put_string(fp, d->file);
120
121     put_index(fp, d->time);
122     put_index(fp, d->goal);
123     put_index(fp, 0);                   /* Unused (was goal enabled flag).   */
124     put_index(fp, d->score);
125     put_index(fp, d->balls);
126     put_index(fp, d->times);
127 }
128
129 /*---------------------------------------------------------------------------*/
130
131 struct demo *demo_load(const char *path)
132 {
133     fs_file fp;
134     struct demo *d;
135
136     d = NULL;
137
138     if ((fp = fs_open(path, "r")))
139     {
140         d = calloc(1, sizeof (struct demo));
141
142         if (demo_header_read(fp, d))
143         {
144             SAFECPY(d->filename, path);
145             SAFECPY(d->name, base_name_sans(d->filename, ".nbr"));
146         }
147         else
148         {
149             free(d);
150             d = NULL;
151         }
152
153         fs_close(fp);
154     }
155
156     return d;
157 }
158
159 void demo_free(struct demo *d)
160 {
161     free(d);
162 }
163
164 /*---------------------------------------------------------------------------*/
165
166 static const char *demo_path(const char *name)
167 {
168     static char path[MAXSTR];
169     sprintf(path, "Replays/%s.nbr", name);
170     return path;
171 }
172
173 /*---------------------------------------------------------------------------*/
174
175 int demo_exists(const char *name)
176 {
177     return fs_exists(demo_path(name));
178 }
179
180 const char *demo_format_name(const char *fmt,
181                              const char *set,
182                              const char *level)
183 {
184     static char name[MAXSTR];
185     int space_left;
186     char *numpart;
187     int i;
188
189     if (!fmt)
190         return NULL;
191
192     if (!set)
193         set = "none";
194
195     if (!level)
196         level = "00";
197
198     memset(name, 0, sizeof (name));
199     space_left = MAXSTRLEN(name);
200
201     /* Construct name, replacing each format sequence as appropriate. */
202
203     while (*fmt && space_left > 0)
204     {
205         if (*fmt == '%')
206         {
207             fmt++;
208
209             switch (*fmt)
210             {
211             case 's':
212                 strncat(name, set, space_left);
213                 space_left -= strlen(set);
214                 break;
215
216             case 'l':
217                 strncat(name, level, space_left);
218                 space_left -= strlen(level);
219                 break;
220
221             case '%':
222                 strncat(name, "%", space_left);
223                 space_left--;
224                 break;
225
226             case '\0':
227                 fputs(L_("Missing format character in replay name\n"), stderr);
228                 fmt--;
229                 break;
230
231             default:
232                 fprintf(stderr, L_("Invalid format character in "
233                                    "replay name: \"%%%c\"\n"), *fmt);
234                 break;
235             }
236         }
237         else
238         {
239             strncat(name, fmt, 1);
240             space_left--;
241         }
242
243         fmt++;
244     }
245
246     /*
247      * Append a unique 2-digit number preceded by an underscore to the
248      * file name, discarding characters if there's not enough space
249      * left in the buffer.
250      */
251
252     if (space_left < strlen("_23"))
253         numpart = name + MAXSTRLEN(name) - strlen("_23");
254     else
255         numpart = name + MAXSTRLEN(name) - space_left;
256
257     for (i = 1; i < 100; i++)
258     {
259         sprintf(numpart, "_%02d", i);
260
261         if (!demo_exists(name))
262             break;
263     }
264
265     return name;
266 }
267
268 /*---------------------------------------------------------------------------*/
269
270 int demo_play_init(const char *name, const struct level *level,
271                    int mode, int scores, int balls, int times)
272 {
273     struct demo demo;
274
275     memset(&demo, 0, sizeof (demo));
276
277     SAFECPY(demo.filename, demo_path(name));
278     SAFECPY(demo.player, config_get_s(CONFIG_PLAYER));
279     SAFECPY(demo.shot, level_shot(level));
280     SAFECPY(demo.file, level_file(level));
281
282     demo.mode  = mode;
283     demo.date  = time(NULL);
284     demo.time  = level_time(level);
285     demo.goal  = level_goal(level);
286     demo.score = scores;
287     demo.balls = balls;
288     demo.times = times;
289
290     if ((demo_fp = fs_open(demo.filename, "w")))
291     {
292         demo_header_write(demo_fp, &demo);
293         return 1;
294     }
295     return 0;
296 }
297
298 void demo_play_stat(int status, int coins, int timer)
299 {
300     if (demo_fp)
301     {
302         long pos = fs_tell(demo_fp);
303
304         fs_seek(demo_fp, 8, SEEK_SET);
305
306         put_index(demo_fp, timer);
307         put_index(demo_fp, coins);
308         put_index(demo_fp, status);
309
310         fs_seek(demo_fp, pos, SEEK_SET);
311     }
312 }
313
314 void demo_play_stop(void)
315 {
316     if (demo_fp)
317     {
318         fs_close(demo_fp);
319         demo_fp = NULL;
320     }
321 }
322
323 int demo_saved(void)
324 {
325     return demo_exists(USER_REPLAY_FILE);
326 }
327
328 void demo_rename(const char *name)
329 {
330     char src[MAXSTR];
331     char dst[MAXSTR];
332
333     if (name &&
334         demo_exists(USER_REPLAY_FILE) &&
335         strcmp(name, USER_REPLAY_FILE) != 0)
336     {
337         SAFECPY(src, demo_path(USER_REPLAY_FILE));
338         SAFECPY(dst, demo_path(name));
339
340         fs_rename(src, dst);
341     }
342 }
343
344 void demo_rename_player(const char *name, const char *player)
345 {
346     /* TODO */
347     return;
348 }
349
350 /*---------------------------------------------------------------------------*/
351
352 static struct lockstep update_step;
353
354 static void demo_update_read(float dt)
355 {
356     if (demo_fp)
357     {
358         union cmd cmd;
359
360         while (cmd_get(demo_fp, &cmd))
361         {
362             game_proxy_enq(&cmd);
363
364             if (cmd.type == CMD_UPDATES_PER_SECOND)
365                 update_step.dt = 1.0f / cmd.ups.n;
366
367             if (cmd.type == CMD_END_OF_UPDATE)
368             {
369                 game_client_sync(NULL);
370                 break;
371             }
372         }
373
374     }
375 }
376
377 static struct lockstep update_step = { demo_update_read, DT };
378
379 float demo_replay_blend(void)
380 {
381     return lockstep_blend(&update_step);
382 }
383
384 /*---------------------------------------------------------------------------*/
385
386 static struct demo demo_replay;
387
388 const char *curr_demo(void)
389 {
390     return demo_replay.filename;
391 }
392
393 int demo_replay_init(const char *name, int *g, int *m, int *b, int *s, int *tt)
394 {
395     lockstep_clr(&update_step);
396
397     if ((demo_fp = fs_open(name, "r")))
398     {
399         if (demo_header_read(demo_fp, &demo_replay))
400         {
401             struct level level;
402
403             SAFECPY(demo_replay.filename, name);
404             SAFECPY(demo_replay.name,
405                     base_name_sans(demo_replay.filename, ".nbr"));
406
407             if (level_load(demo_replay.file, &level))
408             {
409                 if (g)  *g  = demo_replay.goal;
410                 if (m)  *m  = demo_replay.mode;
411                 if (b)  *b  = demo_replay.balls;
412                 if (s)  *s  = demo_replay.score;
413                 if (tt) *tt = demo_replay.times;
414
415                 /*
416                  * Init client and then read and process the first batch of
417                  * commands from the replay file.
418                  */
419
420                 if (game_client_init(demo_replay.file))
421                 {
422                     if (g)
423                     {
424                         audio_music_fade_to(0.5f, level.song);
425                     }
426                     else
427                     {
428                         union cmd cmd;
429                         cmd.type = CMD_GOAL_OPEN;
430                         game_proxy_enq(&cmd);
431                     }
432
433                     demo_update_read(0);
434
435                     if (!fs_eof(demo_fp))
436                         return 1;
437                 }
438             }
439         }
440
441         fs_close(demo_fp);
442         demo_fp = NULL;
443     }
444
445     return 0;
446 }
447
448 int demo_replay_step(float dt)
449 {
450     if (demo_fp)
451     {
452         lockstep_run(&update_step, dt);
453         return !fs_eof(demo_fp);
454     }
455     return 0;
456 }
457
458 void demo_replay_stop(int d)
459 {
460     if (demo_fp)
461     {
462         fs_close(demo_fp);
463         demo_fp = NULL;
464
465         if (d) fs_remove(demo_replay.filename);
466     }
467 }
468
469 void demo_speed_set(int speed)
470 {
471     if (SPEED_NONE <= speed && speed < SPEED_MAX)
472         lockstep_scl(&update_step, SPEED_FACTORS[speed]);
473 }
474
475 /*---------------------------------------------------------------------------*/