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