Assume map compatibility by default if client's map version is 1
[neverball] / ball / st_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 <string.h>
16
17 #include "gui.h"
18 #include "hud.h"
19 #include "set.h"
20 #include "demo.h"
21 #include "progress.h"
22 #include "audio.h"
23 #include "solid.h"
24 #include "config.h"
25 #include "st_shared.h"
26 #include "util.h"
27 #include "common.h"
28
29 #include "game_common.h"
30 #include "game_server.h"
31 #include "game_client.h"
32
33 #include "st_demo.h"
34 #include "st_title.h"
35
36 /*---------------------------------------------------------------------------*/
37
38 #define DEMO_LINE 4
39 #define DEMO_STEP 8
40
41 static int first = 0;
42 static int total = 0;
43
44 static int last_viewed = 0;
45
46 /*---------------------------------------------------------------------------*/
47
48 static int demo_action(int i)
49 {
50     audio_play(AUD_MENU, 1.0f);
51
52     switch (i)
53     {
54     case GUI_BACK:
55         return goto_state(&st_title);
56
57     case GUI_NEXT:
58         first += DEMO_STEP;
59         return goto_state(&st_demo);
60         break;
61
62     case GUI_PREV:
63         first -= DEMO_STEP;
64         return goto_state(&st_demo);
65         break;
66
67     case GUI_NULL:
68         return 1;
69         break;
70
71     default:
72         if (progress_replay(demo_get(i)->filename))
73         {
74             last_viewed = i;
75             demo_play_goto(0);
76             return goto_state(&st_demo_play);
77         }
78         break;
79     }
80     return 1;
81 }
82
83 /*---------------------------------------------------------------------------*/
84
85 static void demo_replay(int id, int i)
86 {
87     int w = config_get_d(CONFIG_WIDTH);
88     int h = config_get_d(CONFIG_HEIGHT);
89     int jd;
90
91     char nam[MAXNAM + 3];
92
93     if ((jd = gui_vstack(id)))
94     {
95         gui_space(jd);
96         gui_image(jd, demo_get(i)->shot, w / 6, h / 6);
97
98         nam[MAXNAM - 1] = '\0';
99         strncpy(nam, demo_get(i)->name, MAXNAM);
100         if (nam[MAXNAM - 1] != '\0')
101         {
102             nam[MAXNAM - 2] = '.';
103             nam[MAXNAM - 1] = '.';
104             nam[MAXNAM + 0] = '.';
105             nam[MAXNAM + 1] = '\0';
106         }
107         gui_state(jd, nam, GUI_SML, i, 0);
108
109         gui_active(jd, i, 0);
110     }
111 }
112
113 static int name_id;
114 static int time_id;
115 static int coin_id;
116 static int date_id;
117 static int status_id;
118 static int player_id;
119
120 static int gui_demo_status(int id, const struct demo *d)
121 {
122     char noname[MAXNAM];
123     const char *status;
124     int i, j, k;
125     int jd, kd, ld;
126
127     if (d == NULL)
128     {
129         /* Build a long name */
130         memset(noname, 'M', MAXNAM - 1);
131         noname[MAXNAM - 1] = '\0';
132
133         /* Get a long status */
134         status = status_to_str(0);
135         j = strlen(status);
136         for (i = 1; i <= GAME_FALL; i++)
137         {
138             k = strlen(status_to_str(i));
139             if (k > j)
140             {
141                 j = k;
142                 status = status_to_str(i);
143             }
144         }
145     }
146     else
147     {
148         status = status_to_str(d->status);
149     }
150
151     if ((jd = gui_hstack(id)))
152     {
153         gui_filler(jd);
154
155         if ((kd = gui_hstack(jd)))
156         {
157             if ((ld = gui_vstack(kd)))
158             {
159                 gui_filler(ld);
160
161                 time_id = gui_clock(ld, (d ? d->timer : 35000),
162                                     GUI_SML, GUI_NE);
163                 coin_id = gui_count(ld, (d ? d->coins : 100),
164                                     GUI_SML, 0);
165                 status_id = gui_label(ld, status, GUI_SML, GUI_SE,
166                                       gui_red, gui_red);
167
168                 if (d && d->status == GAME_GOAL)
169                     gui_set_color(status_id, gui_grn, gui_grn);
170
171                 gui_filler(ld);
172             }
173
174             if ((ld = gui_vstack(kd)))
175             {
176                 gui_filler(ld);
177
178                 gui_label(ld, _("Time"),  GUI_SML, GUI_NW,
179                           gui_wht, gui_wht);
180                 gui_label(ld, _("Coins"), GUI_SML, 0,
181                           gui_wht, gui_wht);
182                 gui_label(ld, _("Status"), GUI_SML, GUI_SW,
183                           gui_wht, gui_wht);
184
185                 gui_filler(ld);
186             }
187         }
188
189         gui_space(jd);
190
191         if ((kd = gui_vstack(jd)))
192         {
193             gui_filler(kd);
194
195             name_id   = gui_label(kd, (d ? d->name : noname),
196                                   GUI_SML, GUI_NE, 0, 0);
197             player_id = gui_label(kd, (d ? d->player : noname),
198                                   GUI_SML, 0,      0, 0);
199             date_id   = gui_label(kd, (d ? date_to_str(d->date) :
200                                        date_to_str(time(NULL))),
201                                   GUI_SML, GUI_SE, 0, 0);
202
203             gui_filler(kd);
204         }
205
206         if ((kd = gui_vstack(jd)))
207         {
208             gui_filler(kd);
209
210             gui_label(kd, _("Replay"), GUI_SML, GUI_NW, gui_wht, gui_wht);
211             gui_label(kd, _("Player"), GUI_SML, 0,      gui_wht, gui_wht);
212             gui_label(kd, _("Date"),   GUI_SML, GUI_SW, gui_wht, gui_wht);
213
214             gui_filler(kd);
215         }
216
217         gui_filler(jd);
218     }
219
220     return jd;
221 }
222
223 static void gui_demo_update_status(int i)
224 {
225     const struct demo *d;
226
227     if ((d = demo_get(i)) == NULL && (d = demo_get(0)) == NULL)
228         return;
229
230     gui_set_label(name_id,   d->name);
231     gui_set_label(date_id,   date_to_str(d->date));
232     gui_set_label(player_id, d->player);
233
234     if (d->status == GAME_GOAL)
235         gui_set_color(status_id, gui_grn, gui_grn);
236     else
237         gui_set_color(status_id, gui_red, gui_red);
238
239     gui_set_label(status_id, status_to_str(d->status));
240     gui_set_count(coin_id, d->coins);
241     gui_set_clock(time_id, d->timer);
242 }
243
244 /*---------------------------------------------------------------------------*/
245
246 static int demo_enter(void)
247 {
248     int i, j;
249     int id, jd, kd;
250
251     id = gui_vstack(0);
252
253     if ((total = demo_scan()))
254     {
255         if ((jd = gui_hstack(id)))
256         {
257
258             gui_label(jd, _("Select Replay"), GUI_SML, GUI_ALL, 0,0);
259             gui_filler(jd);
260             gui_navig(jd, first > 0, first + DEMO_STEP < total);
261         }
262
263         if ((jd = gui_varray(id)))
264             for (i = first; i < first + DEMO_STEP ; i += DEMO_LINE)
265                 if ((kd = gui_harray(jd)))
266                 {
267                     for (j = i + DEMO_LINE - 1; j >= i; j--)
268                         if (j < total)
269                             demo_replay(kd, j);
270                         else
271                             gui_space(kd);
272                 }
273         gui_filler(id);
274         gui_demo_status(id, NULL);
275         gui_layout(id, 0, 0);
276         gui_demo_update_status(last_viewed);
277     }
278     else
279     {
280         gui_label(id, _("No Replays"), GUI_MED, GUI_ALL, 0, 0);
281         gui_layout(id, 0, 0);
282     }
283
284     audio_music_fade_to(0.5f, "bgm/inter.ogg");
285
286     return id;
287 }
288
289 static void demo_timer(int id, float dt)
290 {
291     if (total == 0 && time_state() > 4.0f)
292         goto_state(&st_title);
293
294     gui_timer(id, dt);
295 }
296
297 static void demo_point(int id, int x, int y, int dx, int dy)
298 {
299     int jd = shared_point_basic(id, x, y);
300     int i  = gui_token(jd);
301
302     if (jd && i >= 0 && !GUI_ISMSK(i))
303         gui_demo_update_status(i);
304 }
305
306 static void demo_stick(int id, int a, int v)
307 {
308     int jd = shared_stick_basic(id, a, v);
309     int i  = gui_token(jd);
310
311     if (jd && i >= 0 && !GUI_ISMSK(i))
312         gui_demo_update_status(i);
313 }
314
315 static int demo_buttn(int b, int d)
316 {
317     if (d)
318     {
319         if (config_tst_d(CONFIG_JOYSTICK_BUTTON_A, b))
320             return demo_action(total ? gui_token(gui_click()) : GUI_BACK);
321         if (config_tst_d(CONFIG_JOYSTICK_BUTTON_EXIT, b))
322             return demo_action(GUI_BACK);
323     }
324     return 1;
325 }
326
327 /*---------------------------------------------------------------------------*/
328
329 static int standalone;
330 static int demo_paused;
331 static int show_hud;
332 static int check_compat;
333
334 void demo_play_goto(int s)
335 {
336     standalone   = s;
337     check_compat = 1;
338 }
339
340 static int demo_play_enter(void)
341 {
342     int id;
343
344     if (demo_paused)
345     {
346         demo_paused = 0;
347         audio_music_fade_in(0.5f);
348         return 0;
349     }
350
351     if (check_compat && !game_compat_map)
352     {
353         goto_state(&st_demo_compat);
354         return 0;
355     }
356
357     if ((id = gui_vstack(0)))
358     {
359         gui_label(id, _("Replay"), GUI_LRG, GUI_ALL, gui_blu, gui_grn);
360         gui_layout(id, 0, 0);
361         gui_pulse(id, 1.2f);
362     }
363
364     show_hud = 1;
365     hud_update(0);
366     game_set_fly(0.f, game_client_file());
367     game_client_step(NULL);
368
369     return id;
370 }
371
372 static void demo_play_paint(int id, float t)
373 {
374     game_draw(0, t);
375
376     if (show_hud)
377         hud_paint();
378
379     if (time_state() < 1.f)
380         gui_paint(id);
381 }
382
383 static void demo_play_timer(int id, float dt)
384 {
385     game_step_fade(dt);
386     gui_timer(id, dt);
387     hud_timer(dt);
388
389     /*
390      * Introduce a one-second pause at the start of replay playback.  (One
391      * second is the time during which the "Replay" label is being displayed.)
392      * HACK ALERT!  "id == 0" means we got here from the pause screen, so no
393      * label has been created and there's no need to wait.
394      */
395
396     if (id != 0 && time_state() < 1.0f)
397         return;
398
399     /* Spin or skip depending on how fast the demo wants to run. */
400
401     if (!demo_replay_step(dt))
402     {
403         demo_paused = 0;
404         goto_state(&st_demo_end);
405     }
406     else
407         progress_step();
408 }
409
410 static int demo_play_keybd(int c, int d)
411 {
412     if (d)
413     {
414         if (config_tst_d(CONFIG_KEY_PAUSE, c))
415         {
416             demo_paused = 1;
417             return goto_state(&st_demo_end);
418         }
419
420         if (c == SDLK_F6)
421             show_hud = !show_hud;
422     }
423     return 1;
424 }
425
426 static int demo_play_buttn(int b, int d)
427 {
428     if (d)
429     {
430         if (config_tst_d(CONFIG_JOYSTICK_BUTTON_EXIT, b))
431         {
432             if (config_tst_d(CONFIG_KEY_PAUSE, SDLK_ESCAPE))
433                 demo_paused = 1;
434
435             return goto_state(&st_demo_end);
436         }
437     }
438     return 1;
439 }
440
441 /*---------------------------------------------------------------------------*/
442
443 #define DEMO_KEEP      0
444 #define DEMO_DEL       1
445 #define DEMO_QUIT      2
446 #define DEMO_REPLAY    3
447 #define DEMO_CONTINUE  4
448
449 static int demo_end_action(int i)
450 {
451     audio_play(AUD_MENU, 1.0f);
452
453     switch (i)
454     {
455     case DEMO_DEL:
456         demo_paused = 0;
457         return goto_state(&st_demo_del);
458     case DEMO_KEEP:
459         demo_paused = 0;
460         demo_replay_stop(0);
461         return goto_state(&st_demo);
462     case DEMO_QUIT:
463         demo_replay_stop(0);
464         return 0;
465     case DEMO_REPLAY:
466         demo_replay_stop(0);
467         progress_replay(curr_demo_replay()->filename);
468         return goto_state(&st_demo_play);
469     case DEMO_CONTINUE:
470         return goto_state(&st_demo_play);
471     }
472     return 1;
473 }
474
475 static int demo_end_enter(void)
476 {
477     int id, jd, kd;
478
479     if ((id = gui_vstack(0)))
480     {
481         if (demo_paused)
482             kd = gui_label(id, _("Replay Paused"), GUI_LRG, GUI_ALL,
483                            gui_gry, gui_red);
484         else
485             kd = gui_label(id, _("Replay Ends"),   GUI_LRG, GUI_ALL,
486                            gui_gry, gui_red);
487
488         if ((jd = gui_harray(id)))
489         {
490             int start_id = 0;
491
492             if (standalone)
493             {
494                 start_id = gui_start(jd, _("Quit"), GUI_SML, DEMO_QUIT, 1);
495             }
496             else
497             {
498                 start_id = gui_start(jd, _("Keep"), GUI_SML, DEMO_KEEP, 1);
499                 gui_state(jd, _("Delete"), GUI_SML, DEMO_DEL, 0);
500             }
501
502             if (demo_paused)
503             {
504                 gui_start(jd, _("Continue"), GUI_SML, DEMO_CONTINUE, 1);
505                 gui_toggle(start_id);
506             }
507             else
508                 gui_state(jd, _("Repeat"),   GUI_SML, DEMO_REPLAY,   0);
509         }
510
511         gui_pulse(kd, 1.2f);
512         gui_layout(id, 0, 0);
513     }
514
515     audio_music_fade_out(demo_paused ? 0.2f : 2.0f);
516
517     return id;
518 }
519
520 static void demo_end_paint(int id, float t)
521 {
522     game_draw(0, t);
523     gui_paint(id);
524
525     if (demo_paused)
526         hud_paint();
527 }
528
529 static int demo_end_keybd(int c, int d)
530 {
531     if (d)
532     {
533         if (demo_paused && config_tst_d(CONFIG_KEY_PAUSE, c))
534             return demo_end_action(DEMO_CONTINUE);
535     }
536     return 1;
537 }
538
539 static int demo_end_buttn(int b, int d)
540 {
541     if (d)
542     {
543         if (config_tst_d(CONFIG_JOYSTICK_BUTTON_A, b))
544             return demo_end_action(gui_token(gui_click()));
545
546         if (config_tst_d(CONFIG_JOYSTICK_BUTTON_EXIT, b))
547         {
548             if (demo_paused)
549                 return demo_end_action(DEMO_CONTINUE);
550             else
551                 return demo_end_action(standalone ? DEMO_QUIT : DEMO_KEEP);
552         }
553     }
554     return 1;
555 }
556
557 /*---------------------------------------------------------------------------*/
558
559 static int demo_del_action(int i)
560 {
561     audio_play(AUD_MENU, 1.0f);
562
563     demo_replay_stop(i == DEMO_DEL);
564     return goto_state(&st_demo);
565 }
566
567 static int demo_del_enter(void)
568 {
569     int id, jd, kd;
570
571     if ((id = gui_vstack(0)))
572     {
573         kd = gui_label(id, _("Delete Replay?"), GUI_MED, GUI_ALL, gui_red, gui_red);
574
575         if ((jd = gui_harray(id)))
576         {
577             gui_start(jd, _("No"),  GUI_SML, DEMO_KEEP, 1);
578             gui_state(jd, _("Yes"), GUI_SML, DEMO_DEL,  0);
579         }
580
581         gui_pulse(kd, 1.2f);
582         gui_layout(id, 0, 0);
583     }
584     audio_music_fade_out(2.0f);
585
586     return id;
587 }
588
589 static int demo_del_buttn(int b, int d)
590 {
591     if (d)
592     {
593         if (config_tst_d(CONFIG_JOYSTICK_BUTTON_A, b))
594             return demo_del_action(gui_token(gui_click()));
595         if (config_tst_d(CONFIG_JOYSTICK_BUTTON_EXIT, b))
596             return demo_del_action(DEMO_KEEP);
597     }
598     return 1;
599 }
600
601 /*---------------------------------------------------------------------------*/
602
603 static int demo_compat_enter(void)
604 {
605     int id;
606
607     check_compat = 0;
608
609     if ((id = gui_vstack(0)))
610     {
611         gui_label(id, _("Warning!"), GUI_MED, GUI_ALL, 0, 0);
612         gui_space(id);
613         gui_multi(id, _("The replay you're about to view was\\"
614                         "recorded with a different (or unknown)\\"
615                         "version of this map. Be prepared to\\"
616                         "encounter visual errors.\\"),
617                   GUI_SML, GUI_ALL, gui_wht, gui_wht);
618
619         gui_layout(id, 0, 0);
620     }
621
622     game_set_fly(0.f, game_client_file());
623     game_client_step(NULL);
624
625     return id;
626 }
627
628 static void demo_compat_timer(int id, float dt)
629 {
630     game_step_fade(dt);
631     gui_timer(id, dt);
632 }
633
634 static int demo_compat_buttn(int b, int d)
635 {
636     if (d)
637     {
638         if (config_tst_d(CONFIG_JOYSTICK_BUTTON_A, b))
639             return goto_state(&st_demo_play);
640         if (config_tst_d(CONFIG_JOYSTICK_BUTTON_EXIT, b))
641             return goto_state(&st_demo_end);
642     }
643     return 1;
644 }
645
646 /*---------------------------------------------------------------------------*/
647
648 struct state st_demo = {
649     demo_enter,
650     shared_leave,
651     shared_paint,
652     demo_timer,
653     demo_point,
654     demo_stick,
655     shared_angle,
656     shared_click,
657     NULL,
658     demo_buttn,
659     1, 0
660 };
661
662 struct state st_demo_play = {
663     demo_play_enter,
664     shared_leave,
665     demo_play_paint,
666     demo_play_timer,
667     NULL,
668     NULL,
669     NULL,
670     NULL,
671     demo_play_keybd,
672     demo_play_buttn,
673     1, 0
674 };
675
676 struct state st_demo_end = {
677     demo_end_enter,
678     shared_leave,
679     demo_end_paint,
680     shared_timer,
681     shared_point,
682     shared_stick,
683     shared_angle,
684     shared_click,
685     demo_end_keybd,
686     demo_end_buttn,
687     1, 0
688 };
689
690 struct state st_demo_del = {
691     demo_del_enter,
692     shared_leave,
693     shared_paint,
694     shared_timer,
695     shared_point,
696     shared_stick,
697     shared_angle,
698     shared_click,
699     NULL,
700     demo_del_buttn,
701     1, 0
702 };
703
704 struct state st_demo_compat = {
705     demo_compat_enter,
706     shared_leave,
707     shared_paint,
708     demo_compat_timer,
709     shared_point,
710     shared_stick,
711     shared_angle,
712     shared_click,
713     NULL,
714     demo_compat_buttn,
715     1, 0
716 };