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