Call the standalone level 00, not 99
[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 "util.h"
26 #include "common.h"
27 #include "demo_dir.h"
28 #include "speed.h"
29
30 #include "game_common.h"
31 #include "game_server.h"
32 #include "game_client.h"
33
34 #include "st_demo.h"
35 #include "st_title.h"
36 #include "st_shared.h"
37
38 /*---------------------------------------------------------------------------*/
39
40 #define DEMO_LINE 4
41 #define DEMO_STEP 8
42
43 static Array items;
44
45 static int first = 0;
46 static int total = 0;
47 static int last  = 0;
48
49 static int last_viewed = 0;
50
51 /*---------------------------------------------------------------------------*/
52
53 static int demo_action(int i)
54 {
55     audio_play(AUD_MENU, 1.0f);
56
57     switch (i)
58     {
59     case GUI_BACK:
60         return goto_state(&st_title);
61
62     case GUI_NEXT:
63         first += DEMO_STEP;
64         return goto_state(&st_demo);
65         break;
66
67     case GUI_PREV:
68         first -= DEMO_STEP;
69         return goto_state(&st_demo);
70         break;
71
72     case GUI_NULL:
73         return 1;
74         break;
75
76     default:
77         if (progress_replay(DIR_ITEM_GET(items, i)->path))
78         {
79             last_viewed = i;
80             demo_play_goto(0);
81             return goto_state(&st_demo_play);
82         }
83         break;
84     }
85     return 1;
86 }
87
88 /*---------------------------------------------------------------------------*/
89
90 static struct thumb
91 {
92     int item;
93     int shot;
94     int name;
95 } thumbs[DEMO_STEP];
96
97 static int gui_demo_thumbs(int id)
98 {
99     int w = config_get_d(CONFIG_WIDTH);
100     int h = config_get_d(CONFIG_HEIGHT);
101
102     int jd, kd, ld;
103     int i, j;
104
105     struct thumb *thumb;
106
107     if ((jd = gui_varray(id)))
108         for (i = first; i < first + DEMO_STEP; i += DEMO_LINE)
109             if ((kd = gui_harray(jd)))
110             {
111                 for (j = i + DEMO_LINE - 1; j >= i; j--)
112                 {
113                     thumb = &thumbs[j % DEMO_STEP];
114
115                     thumb->item = j;
116
117                     if (j < total)
118                     {
119                         if ((ld = gui_vstack(kd)))
120                         {
121                             gui_space(ld);
122
123                             thumb->shot = gui_image(ld, " ", w / 6, h / 6);
124                             thumb->name = gui_state(ld, " ", GUI_SML, j, 0);
125
126                             gui_set_trunc(thumb->name, TRUNC_TAIL);
127
128                             gui_active(ld, j, 0);
129                         }
130                     }
131                     else
132                     {
133                         gui_space(kd);
134
135                         thumb->shot = 0;
136                         thumb->name = 0;
137                     }
138                 }
139             }
140
141     return jd;
142 }
143
144 static void gui_demo_update_thumbs(void)
145 {
146     struct dir_item *item;
147     struct demo *demo;
148     int i;
149
150     for (i = 0; i < ARRAYSIZE(thumbs) && thumbs[i].shot && thumbs[i].name; i++)
151     {
152         item = DIR_ITEM_GET(items, thumbs[i].item);
153         demo = item->data;
154
155         gui_set_image(thumbs[i].shot, demo ? demo->shot : "");
156         gui_set_label(thumbs[i].name, demo ? demo->name : base_name(item->path));
157     }
158 }
159
160 static int name_id;
161 static int time_id;
162 static int coin_id;
163 static int date_id;
164 static int status_id;
165 static int player_id;
166
167 static int gui_demo_status(int id)
168 {
169     const char *status;
170     int jd, kd, ld;
171     int s;
172
173     /* Find the longest status string. */
174
175     for (status = "", s = GAME_NONE; s < GAME_MAX; s++)
176         if (strlen(status_to_str(s)) > strlen(status))
177             status = status_to_str(s);
178
179     /* Build info bar with dummy values. */
180
181     if ((jd = gui_hstack(id)))
182     {
183         gui_filler(jd);
184
185         if ((kd = gui_hstack(jd)))
186         {
187             if ((ld = gui_vstack(kd)))
188             {
189                 gui_filler(ld);
190
191                 time_id   = gui_clock(ld, 35000,  GUI_SML, GUI_NE);
192                 coin_id   = gui_count(ld, 100,    GUI_SML, 0);
193                 status_id = gui_label(ld, status, GUI_SML, GUI_SE,
194                                       gui_red, gui_red);
195
196                 gui_filler(ld);
197             }
198
199             if ((ld = gui_vstack(kd)))
200             {
201                 gui_filler(ld);
202
203                 gui_label(ld, _("Time"),   GUI_SML, GUI_NW, gui_wht, gui_wht);
204                 gui_label(ld, _("Coins"),  GUI_SML, 0,      gui_wht, gui_wht);
205                 gui_label(ld, _("Status"), GUI_SML, GUI_SW, gui_wht, gui_wht);
206
207                 gui_filler(ld);
208             }
209         }
210
211         gui_space(jd);
212
213         if ((kd = gui_vstack(jd)))
214         {
215             gui_filler(kd);
216
217             name_id   = gui_label(kd, " ", GUI_SML, GUI_NE, 0, 0);
218             player_id = gui_label(kd, " ", GUI_SML, 0,      0, 0);
219             date_id   = gui_label(kd, date_to_str(time(NULL)),
220                                   GUI_SML, GUI_SE, 0, 0);
221
222             gui_filler(kd);
223
224             gui_set_trunc(name_id,   TRUNC_TAIL);
225             gui_set_trunc(player_id, TRUNC_TAIL);
226         }
227
228         if ((kd = gui_vstack(jd)))
229         {
230             gui_filler(kd);
231
232             gui_label(kd, _("Replay"), GUI_SML, GUI_NW, gui_wht, gui_wht);
233             gui_label(kd, _("Player"), GUI_SML, 0,      gui_wht, gui_wht);
234             gui_label(kd, _("Date"),   GUI_SML, GUI_SW, gui_wht, gui_wht);
235
236             gui_filler(kd);
237         }
238
239         gui_filler(jd);
240     }
241
242     return jd;
243 }
244
245 static void gui_demo_update_status(int i)
246 {
247     const struct demo *d;
248
249     if (!total)
250         return;
251
252     d = DEMO_GET(items, i < total ? i : 0);
253
254     if (!d)
255         return;
256
257     gui_set_label(name_id,   d->name);
258     gui_set_label(date_id,   date_to_str(d->date));
259     gui_set_label(player_id, d->player);
260
261     if (d->status == GAME_GOAL)
262         gui_set_color(status_id, gui_grn, gui_grn);
263     else
264         gui_set_color(status_id, gui_red, gui_red);
265
266     gui_set_label(status_id, status_to_str(d->status));
267     gui_set_count(coin_id, d->coins);
268     gui_set_clock(time_id, d->timer);
269 }
270
271 /*---------------------------------------------------------------------------*/
272
273 static int demo_gui(void)
274 {
275     int id, jd;
276
277     id = gui_vstack(0);
278
279     if (total)
280     {
281         if ((jd = gui_hstack(id)))
282         {
283
284             gui_label(jd, _("Select Replay"), GUI_SML, GUI_ALL, 0,0);
285             gui_filler(jd);
286             gui_navig(jd, first > 0, first + DEMO_STEP < total);
287         }
288
289         gui_demo_thumbs(id);
290         gui_space(id);
291         gui_demo_status(id);
292
293         gui_layout(id, 0, 0);
294
295         gui_demo_update_thumbs();
296         gui_demo_update_status(last_viewed);
297     }
298     else
299     {
300         gui_label(id, _("No Replays"), GUI_MED, GUI_ALL, 0, 0);
301         gui_layout(id, 0, 0);
302     }
303
304     return id;
305 }
306
307 static int demo_enter(struct state *st, struct state *prev)
308 {
309     if (!items || (prev == &st_demo_del))
310     {
311         if (items)
312         {
313             demo_dir_free(items);
314             items = NULL;
315         }
316
317         items = demo_dir_scan();
318         total = array_len(items);
319     }
320
321     first       = first < total ? first : 0;
322     last        = MIN(first + DEMO_STEP - 1, total - 1);
323     last_viewed = MIN(MAX(first, last_viewed), last);
324
325     if (total)
326         demo_dir_load(items, first, last);
327
328     audio_music_fade_to(0.5f, "bgm/inter.ogg");
329
330     return demo_gui();
331 }
332
333 static void demo_leave(struct state *st, struct state *next, int id)
334 {
335     if (next == &st_title)
336     {
337         demo_dir_free(items);
338         items = NULL;
339     }
340
341     gui_delete(id);
342 }
343
344 static void demo_timer(int id, float dt)
345 {
346     if (total == 0 && time_state() > 4.0f)
347         goto_state(&st_title);
348
349     gui_timer(id, dt);
350 }
351
352 static void demo_point(int id, int x, int y, int dx, int dy)
353 {
354     int jd = shared_point_basic(id, x, y);
355     int i  = gui_token(jd);
356
357     if (jd && i >= 0 && !GUI_ISMSK(i))
358         gui_demo_update_status(i);
359 }
360
361 static void demo_stick(int id, int a, float v, int bump)
362 {
363     int jd = shared_stick_basic(id, a, v, bump);
364     int i  = gui_token(jd);
365
366     if (jd && i >= 0 && !GUI_ISMSK(i))
367         gui_demo_update_status(i);
368 }
369
370 static int demo_buttn(int b, int d)
371 {
372     if (d)
373     {
374         if (config_tst_d(CONFIG_JOYSTICK_BUTTON_A, b))
375             return demo_action(total ? gui_token(gui_click()) : GUI_BACK);
376         if (config_tst_d(CONFIG_JOYSTICK_BUTTON_EXIT, b))
377             return demo_action(GUI_BACK);
378     }
379     return 1;
380 }
381
382 /*---------------------------------------------------------------------------*/
383
384 static int standalone;
385 static int demo_paused;
386 static int show_hud;
387 static int check_compat;
388 static int speed;
389
390 static float prelude;
391
392 void demo_play_goto(int s)
393 {
394     standalone   = s;
395     check_compat = 1;
396 }
397
398 static int demo_play_gui(void)
399 {
400     int id;
401
402     if ((id = gui_vstack(0)))
403     {
404         gui_label(id, _("Replay"), GUI_LRG, GUI_ALL, gui_blu, gui_grn);
405         gui_layout(id, 0, 0);
406         gui_pulse(id, 1.2f);
407     }
408
409     return id;
410 }
411
412 static int demo_play_enter(struct state *st, struct state *prev)
413 {
414     if (demo_paused)
415     {
416         demo_paused = 0;
417         prelude = 0;
418         audio_music_fade_in(0.5f);
419         return 0;
420     }
421
422     /*
423      * Post-1.5.1 replays include view data in the first update, this
424      * line is currently left in for compatibility with older replays.
425      */
426     game_client_fly(0.0f);
427
428     if (check_compat && !game_compat_map)
429     {
430         goto_state(&st_demo_compat);
431         return 0;
432     }
433
434     prelude = 1.0f;
435
436     speed = SPEED_NORMAL;
437     demo_speed_set(speed);
438
439     show_hud = 1;
440     hud_update(0);
441
442     return demo_play_gui();
443 }
444
445 static void demo_play_paint(int id, float t)
446 {
447     game_client_draw(0, t);
448
449     if (show_hud)
450         hud_paint();
451
452     if (time_state() < prelude)
453         gui_paint(id);
454 }
455
456 static void demo_play_timer(int id, float dt)
457 {
458     game_step_fade(dt);
459     gui_timer(id, dt);
460     hud_timer(dt);
461
462     /* Pause briefly before starting playback. */
463
464     if (time_state() < prelude)
465         return;
466
467     if (!demo_replay_step(dt))
468     {
469         demo_paused = 0;
470         goto_state(&st_demo_end);
471     }
472     else
473         progress_step();
474 }
475
476 static void set_speed(int d)
477 {
478     if (d > 0) speed = SPEED_UP(speed);
479     if (d < 0) speed = SPEED_DN(speed);
480
481     demo_speed_set(speed);
482     hud_speed_pulse(speed);
483 }
484
485 static void demo_play_stick(int id, int a, float v, int bump)
486 {
487     if (!bump)
488         return;
489
490     if (config_tst_d(CONFIG_JOYSTICK_AXIS_Y, a))
491     {
492         if (v < 0) set_speed(+1);
493         if (v > 0) set_speed(-1);
494     }
495 }
496
497 static int demo_play_click(int b, int d)
498 {
499     if (d)
500     {
501         if (b == SDL_BUTTON_WHEELUP)   set_speed(+1);
502         if (b == SDL_BUTTON_WHEELDOWN) set_speed(-1);
503     }
504
505     return 1;
506 }
507
508 static int demo_play_keybd(int c, int d)
509 {
510     if (d)
511     {
512         if (config_tst_d(CONFIG_KEY_PAUSE, c))
513         {
514             demo_paused = 1;
515             return goto_state(&st_demo_end);
516         }
517
518         if (c == SDLK_F6)
519             show_hud = !show_hud;
520     }
521     return 1;
522 }
523
524 static int demo_play_buttn(int b, int d)
525 {
526     if (d)
527     {
528         if (config_tst_d(CONFIG_JOYSTICK_BUTTON_EXIT, b))
529         {
530             if (config_tst_d(CONFIG_KEY_PAUSE, SDLK_ESCAPE))
531                 demo_paused = 1;
532
533             return goto_state(&st_demo_end);
534         }
535     }
536     return 1;
537 }
538
539 /*---------------------------------------------------------------------------*/
540
541 #define DEMO_KEEP      0
542 #define DEMO_DEL       1
543 #define DEMO_QUIT      2
544 #define DEMO_REPLAY    3
545 #define DEMO_CONTINUE  4
546
547 static int demo_end_action(int i)
548 {
549     audio_play(AUD_MENU, 1.0f);
550
551     switch (i)
552     {
553     case DEMO_DEL:
554         demo_paused = 0;
555         return goto_state(&st_demo_del);
556     case DEMO_KEEP:
557         demo_paused = 0;
558         demo_replay_stop(0);
559         return goto_state(&st_demo);
560     case DEMO_QUIT:
561         demo_replay_stop(0);
562         return 0;
563     case DEMO_REPLAY:
564         demo_replay_stop(0);
565         progress_replay(curr_demo());
566         return goto_state(&st_demo_play);
567     case DEMO_CONTINUE:
568         return goto_state(&st_demo_play);
569     }
570     return 1;
571 }
572
573 static int demo_end_gui(void)
574 {
575     int id, jd, kd;
576
577     if ((id = gui_vstack(0)))
578     {
579         if (demo_paused)
580             kd = gui_label(id, _("Replay Paused"), GUI_LRG, GUI_ALL,
581                            gui_gry, gui_red);
582         else
583             kd = gui_label(id, _("Replay Ends"),   GUI_LRG, GUI_ALL,
584                            gui_gry, gui_red);
585
586         if ((jd = gui_harray(id)))
587         {
588             int start_id = 0;
589
590             if (standalone)
591             {
592                 start_id = gui_start(jd, _("Quit"), GUI_SML, DEMO_QUIT, 1);
593             }
594             else
595             {
596                 start_id = gui_start(jd, _("Keep"), GUI_SML, DEMO_KEEP, 1);
597                 gui_state(jd, _("Delete"), GUI_SML, DEMO_DEL, 0);
598             }
599
600             if (demo_paused)
601             {
602                 gui_start(jd, _("Continue"), GUI_SML, DEMO_CONTINUE, 1);
603                 gui_toggle(start_id);
604             }
605             else
606                 gui_state(jd, _("Repeat"),   GUI_SML, DEMO_REPLAY,   0);
607         }
608
609         gui_pulse(kd, 1.2f);
610         gui_layout(id, 0, 0);
611     }
612
613     return id;
614 }
615
616 static int demo_end_enter(struct state *st, struct state *prev)
617 {
618     audio_music_fade_out(demo_paused ? 0.2f : 2.0f);
619
620     return demo_end_gui();
621 }
622
623 static void demo_end_paint(int id, float t)
624 {
625     game_client_draw(0, t);
626     gui_paint(id);
627
628     if (demo_paused)
629         hud_paint();
630 }
631
632 static int demo_end_keybd(int c, int d)
633 {
634     if (d)
635     {
636         if (demo_paused && config_tst_d(CONFIG_KEY_PAUSE, c))
637             return demo_end_action(DEMO_CONTINUE);
638     }
639     return 1;
640 }
641
642 static int demo_end_buttn(int b, int d)
643 {
644     if (d)
645     {
646         if (config_tst_d(CONFIG_JOYSTICK_BUTTON_A, b))
647             return demo_end_action(gui_token(gui_click()));
648
649         if (config_tst_d(CONFIG_JOYSTICK_BUTTON_EXIT, b))
650         {
651             if (demo_paused)
652                 return demo_end_action(DEMO_CONTINUE);
653             else
654                 return demo_end_action(standalone ? DEMO_QUIT : DEMO_KEEP);
655         }
656     }
657     return 1;
658 }
659
660 /*---------------------------------------------------------------------------*/
661
662 static int demo_del_action(int i)
663 {
664     audio_play(AUD_MENU, 1.0f);
665     demo_replay_stop(i == DEMO_DEL);
666     return goto_state(&st_demo);
667 }
668
669 static int demo_del_gui(void)
670 {
671     int id, jd, kd;
672
673     if ((id = gui_vstack(0)))
674     {
675         kd = gui_label(id, _("Delete Replay?"), GUI_MED, GUI_ALL, gui_red, gui_red);
676
677         if ((jd = gui_harray(id)))
678         {
679             gui_start(jd, _("No"),  GUI_SML, DEMO_KEEP, 1);
680             gui_state(jd, _("Yes"), GUI_SML, DEMO_DEL,  0);
681         }
682
683         gui_pulse(kd, 1.2f);
684         gui_layout(id, 0, 0);
685     }
686
687     return id;
688 }
689
690 static int demo_del_enter(struct state *st, struct state *prev)
691 {
692     audio_music_fade_out(2.0f);
693
694     return demo_del_gui();
695 }
696
697 static int demo_del_buttn(int b, int d)
698 {
699     if (d)
700     {
701         if (config_tst_d(CONFIG_JOYSTICK_BUTTON_A, b))
702             return demo_del_action(gui_token(gui_click()));
703         if (config_tst_d(CONFIG_JOYSTICK_BUTTON_EXIT, b))
704             return demo_del_action(DEMO_KEEP);
705     }
706     return 1;
707 }
708
709 /*---------------------------------------------------------------------------*/
710
711 static int demo_compat_gui(void)
712 {
713     int id;
714
715     if ((id = gui_vstack(0)))
716     {
717         gui_label(id, _("Warning!"), GUI_MED, GUI_ALL, 0, 0);
718         gui_space(id);
719         gui_multi(id, _("The current replay was recorded with a\\"
720                         "different (or unknown) version of this level.\\"
721                         "Be prepared to encounter visual errors.\\"),
722                   GUI_SML, GUI_ALL, gui_wht, gui_wht);
723
724         gui_layout(id, 0, 0);
725     }
726
727     return id;
728 }
729
730 static int demo_compat_enter(struct state *st, struct state *prev)
731 {
732     check_compat = 0;
733
734     return demo_compat_gui();
735 }
736
737 static void demo_compat_timer(int id, float dt)
738 {
739     game_step_fade(dt);
740     gui_timer(id, dt);
741 }
742
743 static int demo_compat_buttn(int b, int d)
744 {
745     if (d)
746     {
747         if (config_tst_d(CONFIG_JOYSTICK_BUTTON_A, b))
748             return goto_state(&st_demo_play);
749         if (config_tst_d(CONFIG_JOYSTICK_BUTTON_EXIT, b))
750             return goto_state(&st_demo_end);
751     }
752     return 1;
753 }
754
755 /*---------------------------------------------------------------------------*/
756
757 struct state st_demo = {
758     demo_enter,
759     demo_leave,
760     shared_paint,
761     demo_timer,
762     demo_point,
763     demo_stick,
764     shared_angle,
765     shared_click,
766     NULL,
767     demo_buttn
768 };
769
770 struct state st_demo_play = {
771     demo_play_enter,
772     shared_leave,
773     demo_play_paint,
774     demo_play_timer,
775     NULL,
776     demo_play_stick,
777     NULL,
778     demo_play_click,
779     demo_play_keybd,
780     demo_play_buttn
781 };
782
783 struct state st_demo_end = {
784     demo_end_enter,
785     shared_leave,
786     demo_end_paint,
787     shared_timer,
788     shared_point,
789     shared_stick,
790     shared_angle,
791     shared_click,
792     demo_end_keybd,
793     demo_end_buttn
794 };
795
796 struct state st_demo_del = {
797     demo_del_enter,
798     shared_leave,
799     shared_paint,
800     shared_timer,
801     shared_point,
802     shared_stick,
803     shared_angle,
804     shared_click,
805     NULL,
806     demo_del_buttn
807 };
808
809 struct state st_demo_compat = {
810     demo_compat_enter,
811     shared_leave,
812     shared_paint,
813     demo_compat_timer,
814     shared_point,
815     shared_stick,
816     shared_angle,
817     shared_click,
818     NULL,
819     demo_compat_buttn
820 };