23bfc0c44b7d4027c5e94b361466338dd6d5bede
[rocksndiamonds.git] / src / screens.c
1 /***********************************************************
2 * Rocks'n'Diamonds -- McDuffin Strikes Back!               *
3 *----------------------------------------------------------*
4 * (c) 1995-2002 Artsoft Entertainment                      *
5 *               Holger Schemel                             *
6 *               Detmolder Strasse 189                      *
7 *               33604 Bielefeld                            *
8 *               Germany                                    *
9 *               e-mail: info@artsoft.org                   *
10 *----------------------------------------------------------*
11 * screens.c                                                *
12 ***********************************************************/
13
14 #include "libgame/libgame.h"
15
16 #include "screens.h"
17 #include "events.h"
18 #include "game.h"
19 #include "tools.h"
20 #include "editor.h"
21 #include "files.h"
22 #include "tape.h"
23 #include "cartoons.h"
24 #include "network.h"
25 #include "init.h"
26
27 /* screens in the setup menu */
28 #define SETUP_MODE_MAIN                 0
29 #define SETUP_MODE_GAME                 1
30 #define SETUP_MODE_EDITOR               2
31 #define SETUP_MODE_INPUT                3
32 #define SETUP_MODE_SHORTCUT             4
33 #define SETUP_MODE_GRAPHICS             5
34 #define SETUP_MODE_SOUND                6
35 #define SETUP_MODE_ARTWORK              7
36 #define SETUP_MODE_CHOOSE_GRAPHICS      8
37 #define SETUP_MODE_CHOOSE_SOUNDS        9
38 #define SETUP_MODE_CHOOSE_MUSIC         10
39
40 #define MAX_SETUP_MODES                 11
41
42 /* for input setup functions */
43 #define SETUPINPUT_SCREEN_POS_START     0
44 #define SETUPINPUT_SCREEN_POS_END       (SCR_FIELDY - 4)
45 #define SETUPINPUT_SCREEN_POS_EMPTY1    (SETUPINPUT_SCREEN_POS_START + 3)
46 #define SETUPINPUT_SCREEN_POS_EMPTY2    (SETUPINPUT_SCREEN_POS_END - 1)
47
48 /* screens on the info screen */
49 #define INFO_MODE_MAIN                  0
50 #define INFO_MODE_ELEMENTS              1
51 #define INFO_MODE_MUSIC                 2
52 #define INFO_MODE_CREDITS               3
53 #define INFO_MODE_PROGRAM               4
54
55 #define MAX_INFO_MODES                  5
56
57 /* for various menu stuff  */
58 #define MAX_INFO_ELEMENTS_ON_SCREEN     10
59 #define MAX_MENU_ENTRIES_ON_SCREEN      (SCR_FIELDY - 2)
60 #define MENU_SCREEN_START_YPOS          2
61 #define MENU_SCREEN_VALUE_XPOS          14
62
63 /* buttons and scrollbars identifiers */
64 #define SCREEN_CTRL_ID_SCROLL_UP        0
65 #define SCREEN_CTRL_ID_SCROLL_DOWN      1
66 #define SCREEN_CTRL_ID_SCROLL_VERTICAL  2
67
68 #define NUM_SCREEN_SCROLLBUTTONS        2
69 #define NUM_SCREEN_SCROLLBARS           1
70 #define NUM_SCREEN_GADGETS              3
71
72 /* forward declarations of internal functions */
73 static void HandleScreenGadgets(struct GadgetInfo *);
74 static void HandleSetupScreen_Generic(int, int, int, int, int);
75 static void HandleSetupScreen_Input(int, int, int, int, int);
76 static void CustomizeKeyboard(int);
77 static void CalibrateJoystick(int);
78 static void execSetupArtwork(void);
79 static void HandleChooseTree(int, int, int, int, int, TreeInfo **);
80
81 static void DrawChooseLevel(void);
82 static void DrawInfoScreen(void);
83 static void DrawSetupScreen(void);
84
85 static void DrawInfoScreen_HelpAnim(int, int, boolean);
86 static void DrawInfoScreen_HelpText(int, int, int, int);
87 static void HandleInfoScreen_Main(int, int, int, int, int);
88 static void HandleInfoScreen_Elements(int);
89 static void HandleInfoScreen_Music(int);
90 static void HandleInfoScreen_Credits(int);
91 static void HandleInfoScreen_Program(int);
92
93 static void MapChooseTreeGadgets(TreeInfo *);
94
95 static struct GadgetInfo *screen_gadget[NUM_SCREEN_GADGETS];
96 static int setup_mode = SETUP_MODE_MAIN;
97 static int info_mode = INFO_MODE_MAIN;
98
99 #define mSX (SX + (game_status >= GAME_MODE_MAIN &&     \
100                    game_status <= GAME_MODE_SETUP ?     \
101                    menu.draw_xoffset[game_status] : menu.draw_xoffset_default))
102 #define mSY (SY + (game_status >= GAME_MODE_MAIN &&     \
103                    game_status <= GAME_MODE_SETUP ?     \
104                    menu.draw_yoffset[game_status] : menu.draw_yoffset_default))
105
106 #define NUM_MENU_ENTRIES_ON_SCREEN (menu.list_size[game_status] > 2 ?   \
107                                     menu.list_size[game_status] :       \
108                                     MAX_MENU_ENTRIES_ON_SCREEN)
109
110 #if defined(TARGET_X11_NATIVE_PERFORMANCE_WORKAROUND)
111 #define NUM_SCROLLBAR_BITMAPS           2
112 static Bitmap *scrollbar_bitmap[NUM_SCROLLBAR_BITMAPS];
113 #endif
114
115
116 static void drawCursorExt(int xpos, int ypos, int color, int graphic)
117 {
118   static int cursor_array[SCR_FIELDY];
119
120   if (xpos == 0)
121   {
122     if (graphic != 0)
123       cursor_array[ypos] = graphic;
124     else
125       graphic = cursor_array[ypos];
126   }
127
128   if (color == FC_RED)
129     graphic = (graphic == IMG_MENU_BUTTON_LEFT  ? IMG_MENU_BUTTON_LEFT_ACTIVE :
130                graphic == IMG_MENU_BUTTON_RIGHT ? IMG_MENU_BUTTON_RIGHT_ACTIVE:
131                IMG_MENU_BUTTON_ACTIVE);
132
133   ypos += MENU_SCREEN_START_YPOS;
134
135   DrawBackground(mSX + xpos * TILEX, mSY + ypos * TILEY, TILEX, TILEY);
136   DrawGraphicThruMaskExt(drawto, mSX + xpos * TILEX, mSY + ypos * TILEY,
137                          graphic, 0);
138 }
139
140 static void initCursor(int ypos, int graphic)
141 {
142   drawCursorExt(0, ypos, FC_BLUE, graphic);
143 }
144
145 static void drawCursor(int ypos, int color)
146 {
147   drawCursorExt(0, ypos, color, 0);
148 }
149
150 static void drawCursorXY(int xpos, int ypos, int graphic)
151 {
152   drawCursorExt(xpos, ypos, -1, graphic);
153 }
154
155 static void drawChooseTreeCursor(int ypos, int color)
156 {
157   int last_game_status = game_status;   /* save current game status */
158
159   /* force LEVELS draw offset on artwork setup screen */
160   game_status = GAME_MODE_LEVELS;
161
162   drawCursorExt(0, ypos, color, 0);
163
164   game_status = last_game_status;       /* restore current game status */
165 }
166
167 static void PlayMenuSound()
168 {
169   int sound = menu.sound[game_status];
170
171   if (sound == SND_UNDEFINED)
172     return;
173
174   if (sound_info[sound].loop)
175     PlaySoundLoop(sound);
176   else
177     PlaySound(sound);
178 }
179
180 static void PlayMenuSoundIfLoop()
181 {
182   int sound = menu.sound[game_status];
183
184   if (sound == SND_UNDEFINED)
185     return;
186
187   if (sound_info[sound].loop)
188     PlaySoundLoop(sound);
189 }
190
191 static void PlayMenuMusic()
192 {
193   int music = menu.music[game_status];
194
195   if (music == MUS_UNDEFINED)
196     return;
197
198   PlayMusic(music);
199 }
200
201 void DrawHeadline()
202 {
203   int text1_width = getTextWidth(PROGRAM_TITLE_STRING,     FONT_TITLE_1);
204   int text2_width = getTextWidth(PROGRAM_COPYRIGHT_STRING, FONT_TITLE_2);
205   int x1 = SX + (SXSIZE - text1_width) / 2;
206   int x2 = SX + (SXSIZE - text2_width) / 2;
207
208   DrawText(x1, SY + 8,  PROGRAM_TITLE_STRING,     FONT_TITLE_1);
209   DrawText(x2, SY + 46, PROGRAM_COPYRIGHT_STRING, FONT_TITLE_2);
210 }
211
212 static void ToggleFullscreenIfNeeded()
213 {
214   if (setup.fullscreen != video.fullscreen_enabled)
215   {
216     /* save old door content */
217     BlitBitmap(backbuffer, bitmap_db_door,
218                DX, DY, DXSIZE, DYSIZE, DOOR_GFX_PAGEX1, DOOR_GFX_PAGEY1);
219
220     /* toggle fullscreen */
221     ChangeVideoModeIfNeeded(setup.fullscreen);
222     setup.fullscreen = video.fullscreen_enabled;
223
224     /* redraw background to newly created backbuffer */
225     BlitBitmap(graphic_info[IMG_GLOBAL_BORDER].bitmap, backbuffer,
226                0,0, WIN_XSIZE,WIN_YSIZE, 0,0);
227
228     /* restore old door content */
229     BlitBitmap(bitmap_db_door, backbuffer,
230                DOOR_GFX_PAGEX1, DOOR_GFX_PAGEY1, DXSIZE, DYSIZE, DX, DY);
231
232     redraw_mask = REDRAW_ALL;
233   }
234 }
235
236 void DrawMainMenu()
237 {
238   static LevelDirTree *leveldir_last_valid = NULL;
239   char *name_text = (!options.network && setup.team_mode ? "Team:" : "Name:");
240   int name_width  = getTextWidth("Name:",  FONT_MENU_1);
241   int level_width = getTextWidth("Level:", FONT_MENU_1);
242   int i;
243
244   UnmapAllGadgets();
245   FadeSoundsAndMusic();
246
247   KeyboardAutoRepeatOn();
248   ActivateJoystick();
249
250   SetDrawDeactivationMask(REDRAW_NONE);
251   SetDrawBackgroundMask(REDRAW_FIELD);
252
253   audio.sound_deactivated = FALSE;
254
255   /* needed if last screen was the playing screen, invoked from level editor */
256   if (level_editor_test_game)
257   {
258     game_status = GAME_MODE_EDITOR;
259     DrawLevelEd();
260
261     return;
262   }
263
264   /* needed if last screen was the editor screen */
265   UndrawSpecialEditorDoor();
266
267   /* needed if last screen was the setup screen and fullscreen state changed */
268   ToggleFullscreenIfNeeded();
269
270   /* leveldir_current may be invalid (level group, parent link) */
271   if (!validLevelSeries(leveldir_current))
272     leveldir_current = getFirstValidTreeInfoEntry(leveldir_last_valid);
273
274   /* store valid level series information */
275   leveldir_last_valid = leveldir_current;
276
277   /* needed if last screen (level choice) changed graphics, sounds or music */
278   ReloadCustomArtwork();
279
280 #ifdef TARGET_SDL
281   SetDrawtoField(DRAW_BACKBUFFER);
282 #endif
283
284   /* map gadgets for main menu screen */
285   MapTapeButtons();
286
287   /* level_nr may have been set to value over handicap with level editor */
288   if (setup.handicap && level_nr > leveldir_current->handicap_level)
289     level_nr = leveldir_current->handicap_level;
290
291   GetPlayerConfig();
292   LoadLevel(level_nr);
293
294   SetMainBackgroundImage(IMG_BACKGROUND_MAIN);
295   ClearWindow();
296
297   DrawHeadline();
298
299   DrawText(mSX + 32, mSY + 2*32, name_text, FONT_MENU_1);
300   DrawText(mSX + 32, mSY + 3*32, "Level:", FONT_MENU_1);
301   DrawText(mSX + 32, mSY + 4*32, "Hall Of Fame", FONT_MENU_1);
302   DrawText(mSX + 32, mSY + 5*32, "Level Creator", FONT_MENU_1);
303   DrawText(mSX + 32, mSY + 6*32, "Info Screen", FONT_MENU_1);
304   DrawText(mSX + 32, mSY + 7*32, "Start Game", FONT_MENU_1);
305   DrawText(mSX + 32, mSY + 8*32, "Setup", FONT_MENU_1);
306   DrawText(mSX + 32, mSY + 9*32, "Quit", FONT_MENU_1);
307
308   DrawText(mSX + 32 + name_width, mSY + 2*32, setup.player_name, FONT_INPUT_1);
309   DrawText(mSX + level_width + 5 * 32, mSY + 3*32, int2str(level_nr,3),
310            FONT_VALUE_1);
311
312   DrawMicroLevel(MICROLEV_XPOS, MICROLEV_YPOS, TRUE);
313
314   DrawTextF(mSX + 32 + level_width - 2, mSY + 3*32 + 1, FONT_TEXT_3, "%d-%d",
315             leveldir_current->first_level, leveldir_current->last_level);
316
317   if (leveldir_current->readonly)
318   {
319     DrawTextS(mSX + level_width + 9 * 32 - 2,
320               mSY + 3 * 32 + 1 - 7, FONT_TEXT_3, "READ");
321     DrawTextS(mSX + level_width + 9 * 32 - 2,
322               mSY + 3 * 32 + 1 + 7, FONT_TEXT_3, "ONLY");
323   }
324
325   for (i = 0; i < 8; i++)
326     initCursor(i, (i == 1 || i == 6 ? IMG_MENU_BUTTON_RIGHT :IMG_MENU_BUTTON));
327
328   drawCursorXY(level_width/32 + 4, 1, IMG_MENU_BUTTON_LEFT);
329   drawCursorXY(level_width/32 + 8, 1, IMG_MENU_BUTTON_RIGHT);
330
331   DrawText(SX + 56, SY + 326, "A Game by Artsoft Entertainment", FONT_TITLE_2);
332
333   FadeToFront();
334   InitAnimation();
335
336   HandleMainMenu(0, 0, 0, 0, MB_MENU_INITIALIZE);
337
338   TapeStop();
339   if (TAPE_IS_EMPTY(tape))
340     LoadTape(level_nr);
341   DrawCompleteVideoDisplay();
342
343   PlayMenuSound();
344   PlayMenuMusic();
345
346   OpenDoor(DOOR_CLOSE_1 | DOOR_OPEN_2);
347 }
348
349 static void gotoTopLevelDir()
350 {
351   /* move upwards to top level directory */
352   while (leveldir_current->node_parent)
353   {
354     /* write a "path" into level tree for easy navigation to last level */
355     if (leveldir_current->node_parent->node_group->cl_first == -1)
356     {
357       int num_leveldirs = numTreeInfoInGroup(leveldir_current);
358       int leveldir_pos = posTreeInfo(leveldir_current);
359       int num_page_entries;
360       int cl_first, cl_cursor;
361
362       if (num_leveldirs <= NUM_MENU_ENTRIES_ON_SCREEN)
363         num_page_entries = num_leveldirs;
364       else
365         num_page_entries = NUM_MENU_ENTRIES_ON_SCREEN;
366
367       cl_first = MAX(0, leveldir_pos - num_page_entries + 1);
368       cl_cursor = leveldir_pos - cl_first;
369
370       leveldir_current->node_parent->node_group->cl_first = cl_first;
371       leveldir_current->node_parent->node_group->cl_cursor = cl_cursor;
372     }
373
374     leveldir_current = leveldir_current->node_parent;
375   }
376 }
377
378 void HandleMainMenu(int mx, int my, int dx, int dy, int button)
379 {
380   static int choice = 5;
381   int x = 0;
382   int y = choice;
383
384   if (button == MB_MENU_INITIALIZE)
385   {
386     drawCursor(choice, FC_RED);
387     return;
388   }
389
390   if (mx || my)         /* mouse input */
391   {
392     x = (mx - mSX) / 32;
393     y = (my - mSY) / 32 - MENU_SCREEN_START_YPOS;
394   }
395   else if (dx || dy)    /* keyboard input */
396   {
397     if (dx && choice == 1)
398       x = (dx < 0 ? 10 : 14);
399     else if (dy)
400       y = choice + dy;
401   }
402
403   if (y == 1 && ((x == 10 && level_nr > leveldir_current->first_level) ||
404                  (x == 14 && level_nr < leveldir_current->last_level)) &&
405       button)
406   {
407     static unsigned long level_delay = 0;
408     int step = (button == 1 ? 1 : button == 2 ? 5 : 10);
409     int old_level_nr = level_nr;
410     int new_level_nr;
411
412     new_level_nr = level_nr + (x == 10 ? -step : +step);
413     if (new_level_nr < leveldir_current->first_level)
414       new_level_nr = leveldir_current->first_level;
415     if (new_level_nr > leveldir_current->last_level)
416       new_level_nr = leveldir_current->last_level;
417
418     if (setup.handicap && new_level_nr > leveldir_current->handicap_level)
419       new_level_nr = leveldir_current->handicap_level;
420
421     if (new_level_nr != old_level_nr &&
422         DelayReached(&level_delay, GADGET_FRAME_DELAY))
423     {
424       level_nr = new_level_nr;
425
426       DrawText(mSX + 11 * 32, mSY + 3 * 32, int2str(level_nr, 3),
427                FONT_VALUE_1);
428
429       LoadLevel(level_nr);
430       DrawMicroLevel(MICROLEV_XPOS, MICROLEV_YPOS, TRUE);
431
432       TapeErase();
433       LoadTape(level_nr);
434       DrawCompleteVideoDisplay();
435
436       /* needed because DrawMicroLevel() takes some time */
437       BackToFront();
438       SyncDisplay();
439       DelayReached(&level_delay, 0);    /* reset delay counter */
440     }
441   }
442   else if (IN_GFX_SCREEN(mx, my) &&
443            y >= 0 && y <= 7 && (y != 1 || x < 10))
444   {
445     if (button)
446     {
447       if (y != choice)
448       {
449         drawCursor(y, FC_RED);
450         drawCursor(choice, FC_BLUE);
451         choice = y;
452       }
453     }
454     else
455     {
456       if (y == 0)
457       {
458         game_status = GAME_MODE_PSEUDO_TYPENAME;
459         HandleTypeName(strlen(setup.player_name), 0);
460       }
461       else if (y == 1)
462       {
463         if (leveldir_first)
464         {
465           game_status = GAME_MODE_LEVELS;
466           SaveLevelSetup_LastSeries();
467           SaveLevelSetup_SeriesInfo();
468
469           gotoTopLevelDir();
470
471           DrawChooseLevel();
472         }
473       }
474       else if (y == 2)
475       {
476         game_status = GAME_MODE_SCORES;
477         DrawHallOfFame(-1);
478       }
479       else if (y == 3)
480       {
481         if (leveldir_current->readonly &&
482             strcmp(setup.player_name, "Artsoft") != 0)
483           Request("This level is read only !", REQ_CONFIRM);
484         game_status = GAME_MODE_EDITOR;
485         DrawLevelEd();
486       }
487       else if (y == 4)
488       {
489         game_status = GAME_MODE_INFO;
490         info_mode = INFO_MODE_MAIN;
491         DrawInfoScreen();
492       }
493       else if (y == 5)
494       {
495         if (setup.autorecord)
496           TapeStartRecording();
497
498 #if defined(PLATFORM_UNIX)
499         if (options.network)
500           SendToServer_StartPlaying();
501         else
502 #endif
503         {
504           game_status = GAME_MODE_PLAYING;
505           StopAnimation();
506           InitGame();
507         }
508       }
509       else if (y == 6)
510       {
511         game_status = GAME_MODE_SETUP;
512         setup_mode = SETUP_MODE_MAIN;
513         DrawSetupScreen();
514       }
515       else if (y == 7)
516       {
517         SaveLevelSetup_LastSeries();
518         SaveLevelSetup_SeriesInfo();
519         if (Request("Do you really want to quit ?", REQ_ASK | REQ_STAY_CLOSED))
520           game_status = GAME_MODE_QUIT;
521       }
522     }
523   }
524
525   if (game_status == GAME_MODE_MAIN)
526   {
527     DrawMicroLevel(MICROLEV_XPOS, MICROLEV_YPOS, FALSE);
528     DoAnimation();
529   }
530 }
531
532
533 /* ========================================================================= */
534 /* info screen functions                                                     */
535 /* ========================================================================= */
536
537 static struct TokenInfo *info_info;
538 static int num_info_info;
539
540 static void execInfoElements()
541 {
542   info_mode = INFO_MODE_ELEMENTS;
543   DrawInfoScreen();
544 }
545
546 static void execInfoMusic()
547 {
548   info_mode = INFO_MODE_MUSIC;
549   DrawInfoScreen();
550 }
551
552 static void execInfoCredits()
553 {
554   info_mode = INFO_MODE_CREDITS;
555   DrawInfoScreen();
556 }
557
558 static void execInfoProgram()
559 {
560   info_mode = INFO_MODE_PROGRAM;
561   DrawInfoScreen();
562 }
563
564 static void execExitInfo()
565 {
566   game_status = GAME_MODE_MAIN;
567   DrawMainMenu();
568 }
569
570 static struct TokenInfo info_info_main[] =
571 {
572   { TYPE_ENTER_SCREEN,  execInfoElements,       "Elements Info"         },
573   { TYPE_ENTER_SCREEN,  execInfoMusic,          "Music Info"            },
574   { TYPE_ENTER_SCREEN,  execInfoCredits,        "Credits"               },
575   { TYPE_ENTER_SCREEN,  execInfoProgram,        "Program Info"          },
576   { TYPE_EMPTY,         NULL,                   ""                      },
577   { TYPE_LEAVE_MENU,    execExitInfo,           "Exit"                  },
578
579   { 0,                  NULL,                   NULL                    }
580 };
581
582 static void DrawInfoScreen_Main()
583 {
584   int i;
585
586   UnmapAllGadgets();
587   CloseDoor(DOOR_CLOSE_2);
588
589   ClearWindow();
590
591   DrawText(mSX + 16, mSY + 16, "Info Screen", FONT_TITLE_1);
592
593   info_info = info_info_main;
594   num_info_info = 0;
595
596   for (i = 0; info_info[i].type != 0 && i < NUM_MENU_ENTRIES_ON_SCREEN; i++)
597   {
598     int ypos = MENU_SCREEN_START_YPOS + i;
599     int font_nr = FONT_MENU_1;
600
601     DrawText(mSX + 32, mSY + ypos * 32, info_info[i].text, font_nr);
602
603     if (info_info[i].type & TYPE_ENTER_MENU)
604       initCursor(i, IMG_MENU_BUTTON_RIGHT);
605     else if (info_info[i].type & TYPE_LEAVE_MENU)
606       initCursor(i, IMG_MENU_BUTTON_LEFT);
607     else if (info_info[i].type & ~TYPE_SKIP_ENTRY)
608       initCursor(i, IMG_MENU_BUTTON);
609
610     num_info_info++;
611   }
612
613   FadeToFront();
614   InitAnimation();
615
616   PlayMenuSound();
617   PlayMenuMusic();
618
619   HandleInfoScreen_Main(0, 0, 0, 0, MB_MENU_INITIALIZE);
620 }
621
622 void HandleInfoScreen_Main(int mx, int my, int dx, int dy, int button)
623 {
624   static int choice_store[MAX_INFO_MODES];
625   int choice = choice_store[info_mode];         /* always starts with 0 */
626   int x = 0;
627   int y = choice;
628
629   if (button == MB_MENU_INITIALIZE)
630   {
631     /* advance to first valid menu entry */
632     while (choice < num_info_info &&
633            info_info[choice].type & TYPE_SKIP_ENTRY)
634       choice++;
635     choice_store[info_mode] = choice;
636
637     drawCursor(choice, FC_RED);
638     return;
639   }
640   else if (button == MB_MENU_LEAVE)
641   {
642     for (y = 0; y < num_info_info; y++)
643     {
644       if (info_info[y].type & TYPE_LEAVE_MENU)
645       {
646         void (*menu_callback_function)(void) = info_info[y].value;
647
648         menu_callback_function();
649         break;  /* absolutely needed because function changes 'info_info'! */
650       }
651     }
652
653     return;
654   }
655
656   if (mx || my)         /* mouse input */
657   {
658     x = (mx - mSX) / 32;
659     y = (my - mSY) / 32 - MENU_SCREEN_START_YPOS;
660   }
661   else if (dx || dy)    /* keyboard input */
662   {
663     if (dx)
664     {
665       int menu_navigation_type = (dx < 0 ? TYPE_LEAVE_MENU : TYPE_ENTER_MENU);
666
667       if (info_info[choice].type & menu_navigation_type ||
668           info_info[choice].type & TYPE_ENTER_SCREEN ||
669           info_info[choice].type & TYPE_BOOLEAN_STYLE)
670         button = MB_MENU_CHOICE;
671     }
672     else if (dy)
673       y = choice + dy;
674
675     /* jump to next non-empty menu entry (up or down) */
676     while (y > 0 && y < num_info_info - 1 &&
677            info_info[y].type & TYPE_SKIP_ENTRY)
678       y += dy;
679   }
680
681   if (IN_GFX_SCREEN(mx, my) &&
682       y >= 0 && y < num_info_info && info_info[y].type & ~TYPE_SKIP_ENTRY)
683   {
684     if (button)
685     {
686       if (y != choice)
687       {
688         drawCursor(y, FC_RED);
689         drawCursor(choice, FC_BLUE);
690         choice = choice_store[info_mode] = y;
691       }
692     }
693     else if (!(info_info[y].type & TYPE_GHOSTED))
694     {
695       if (info_info[y].type & TYPE_ENTER_OR_LEAVE_MENU)
696       {
697         void (*menu_callback_function)(void) = info_info[choice].value;
698
699         menu_callback_function();
700       }
701     }
702   }
703 }
704
705 void DrawInfoScreen_HelpAnim(int start, int max_anims, boolean init)
706 {
707   static int infoscreen_step[MAX_INFO_ELEMENTS_ON_SCREEN];
708   static int infoscreen_frame[MAX_INFO_ELEMENTS_ON_SCREEN];
709   int xstart = mSX + 16;
710   int ystart = mSY + 64 + 2 * 32;
711   int ystep = TILEY + 4;
712   int element, action, direction;
713   int graphic;
714   int delay;
715   int sync_frame;
716   int i, j;
717
718   if (init)
719   {
720     for (i = 0; i < MAX_INFO_ELEMENTS_ON_SCREEN; i++)
721       infoscreen_step[i] = infoscreen_frame[i] = 0;
722
723     SetMainBackgroundImage(IMG_BACKGROUND_INFO);
724     ClearWindow();
725     DrawHeadline();
726
727     DrawTextSCentered(100, FONT_TEXT_1, "The game elements:");
728
729     DrawTextSCentered(SYSIZE - 20, FONT_TEXT_4,
730                       "Press any key or button for next page");
731
732     FrameCounter = 0;
733   }
734
735   i = j = 0;
736   while (helpanim_info[j].element != HELPANIM_LIST_END)
737   {
738     if (i >= start + MAX_INFO_ELEMENTS_ON_SCREEN ||
739         i >= max_anims)
740       break;
741     else if (i < start)
742     {
743       while (helpanim_info[j].element != HELPANIM_LIST_NEXT)
744         j++;
745
746       j++;
747       i++;
748
749       continue;
750     }
751
752     j += infoscreen_step[i - start];
753
754     element = helpanim_info[j].element;
755     action = helpanim_info[j].action;
756     direction = helpanim_info[j].direction;
757
758     if (action != -1 && direction != -1)
759       graphic = el_act_dir2img(element, action, direction);
760     else if (action != -1)
761       graphic = el_act2img(element, action);
762     else if (direction != -1)
763       graphic = el_act2img(element, direction);
764     else
765       graphic = el2img(element);
766
767     delay = helpanim_info[j++].delay;
768
769     if (delay == -1)
770       delay = 1000000;
771
772     if (infoscreen_frame[i - start] == 0)
773     {
774       sync_frame = 0;
775       infoscreen_frame[i - start] = delay - 1;
776     }
777     else
778     {
779       sync_frame = delay - infoscreen_frame[i - start];
780       infoscreen_frame[i - start]--;
781     }
782
783     if (helpanim_info[j].element == -1)
784     {
785       if (!infoscreen_frame[i - start])
786         infoscreen_step[i - start] = 0;
787     }
788     else
789     {
790       if (!infoscreen_frame[i - start])
791         infoscreen_step[i - start]++;
792       while (helpanim_info[j].element != -1)
793         j++;
794     }
795
796     j++;
797
798     ClearRectangleOnBackground(drawto, xstart, ystart + (i - start) * ystep,
799                                TILEX, TILEY);
800     DrawGraphicAnimationExt(drawto, xstart, ystart + (i - start) * ystep,
801                             graphic, sync_frame, USE_MASKING);
802
803     if (init)
804       DrawInfoScreen_HelpText(element, action, direction, i - start);
805
806     i++;
807   }
808
809   redraw_mask |= REDRAW_FIELD;
810
811   FrameCounter++;
812 }
813
814 #if 1
815
816 static char *getHelpText(int element, int action, int direction)
817 {
818   char token[MAX_LINE_LEN];
819
820   strcpy(token, element_info[element].token_name);
821
822   if (action != -1)
823     strcat(token, element_action_info[action].suffix);
824
825   if (direction != -1)
826     strcat(token, element_direction_info[MV_DIR_BIT(direction)].suffix);
827
828   return getHashEntry(helptext_info, token);
829 }
830
831 void DrawInfoScreen_HelpText(int element, int action, int direction, int ypos)
832 {
833   int font_nr = FONT_TEXT_2;
834   int max_chars_per_line = 34;
835   int max_lines_per_text = 2;    
836   int sx = mSX + 56;
837   int sy = mSY + 65 + 2 * 32 + 1;
838   int ystep = TILEY + 4;
839   char *text = NULL;
840
841   if (action != -1 && direction != -1)          /* element.action.direction */
842     text = getHelpText(element, action, direction);
843
844   if (text == NULL && action != -1)             /* element.action */
845     text = getHelpText(element, action, -1);
846
847   if (text == NULL && direction != -1)          /* element.direction */
848     text = getHelpText(element, -1, direction);
849
850   if (text == NULL)                             /* base element */
851     text = getHelpText(element, -1, -1);
852
853   if (text == NULL)                             /* not found */
854     text = "No description available";
855
856   if (strlen(text) <= max_chars_per_line)       /* only one line of text */
857     sy += getFontHeight(font_nr) / 2;
858
859   DrawTextWrapped(sx, sy + ypos * ystep, text, font_nr,
860                   max_chars_per_line, max_lines_per_text);
861 }
862
863 #else
864
865 void DrawInfoScreen_HelpText(int element, int action, int direction, int ypos)
866 {
867   int font_nr = FONT_TEXT_2;
868   int max_chars_per_line = 34;
869   int max_lines_per_text = 2;    
870   int sx = mSX + 56;
871   int sy = mSY + 65 + 2 * 32 + 1;
872   int ystep = TILEY + 4;
873   char *text;
874
875   /* 1st try: get text for base element */
876   text = getHashEntry(helptext_info, element_info[element].token_name);
877
878   if (text == NULL)
879   {
880     /* 2nd try: get text for element/action/direction */
881     char token[MAX_LINE_LEN];
882
883     strcpy(token, element_info[element].token_name);
884
885     if (action != -1)
886       strcat(token, element_action_info[action].suffix);
887
888     if (direction != -1)
889       strcat(token, element_direction_info[MV_DIR_BIT(direction)].suffix);
890
891     text = getHashEntry(helptext_info, token);
892
893     if (text == NULL)
894       text = "No description available";
895   }
896
897   if (strlen(text) <= max_chars_per_line)       /* only one line of text */
898     sy += getFontHeight(font_nr) / 2;
899
900   DrawTextWrapped(sx, sy + ypos * ystep, text, font_nr,
901                   max_chars_per_line, max_lines_per_text);
902 }
903 #endif
904
905 void DrawInfoScreen_Elements()
906 {
907   LoadHelpAnimInfo();
908   LoadHelpTextInfo();
909
910   HandleInfoScreen_Elements(MB_MENU_INITIALIZE);
911
912   FadeToFront();
913   InitAnimation();
914 }
915
916 void HandleInfoScreen_Elements(int button)
917 {
918   static unsigned long info_delay = 0;
919   static int num_anims;
920   static int num_pages;
921   static int page;
922   int anims_per_page = MAX_INFO_ELEMENTS_ON_SCREEN;
923   int button_released = !button;
924   int i;
925
926   if (button == MB_MENU_INITIALIZE)
927   {
928     boolean new_element = TRUE;
929
930     num_anims = 0;
931     for (i = 0; helpanim_info[i].element != HELPANIM_LIST_END; i++)
932     {
933       if (helpanim_info[i].element == HELPANIM_LIST_NEXT)
934         new_element = TRUE;
935       else if (new_element)
936       {
937         num_anims++;
938         new_element = FALSE;
939       }
940     }
941
942     num_pages = (num_anims + anims_per_page - 1) / anims_per_page;
943     page = 0;
944   }
945   else if (button == MB_MENU_LEAVE)
946   {
947     info_mode = INFO_MODE_MAIN;
948     DrawInfoScreen();
949
950     return;
951   }
952
953   if (button_released || button == MB_MENU_INITIALIZE)
954   {
955     if (button != MB_MENU_INITIALIZE)
956       page++;
957
958     if (page >= num_pages)
959     {
960       FadeSoundsAndMusic();
961
962       info_mode = INFO_MODE_MAIN;
963       DrawInfoScreen();
964
965       return;
966     }
967
968     DrawInfoScreen_HelpAnim(page * anims_per_page, num_anims, TRUE);
969   }
970   else
971   {
972     if (DelayReached(&info_delay, GAME_FRAME_DELAY))
973       if (page < num_pages)
974         DrawInfoScreen_HelpAnim(page * anims_per_page, num_anims, FALSE);
975
976     PlayMenuSoundIfLoop();
977   }
978 }
979
980 void DrawInfoScreen_Music()
981 {
982   ClearWindow();
983   DrawHeadline();
984
985   LoadMusicInfo();
986
987   HandleInfoScreen_Music(MB_MENU_INITIALIZE);
988 }
989
990 void HandleInfoScreen_Music(int button)
991 {
992   static struct MusicFileInfo *list = NULL;
993   int ystart = 150, ystep = 30;
994   int ybottom = SYSIZE - 20;
995   int button_released = !button;
996
997   if (button == MB_MENU_INITIALIZE)
998   {
999     list = music_file_info;
1000
1001     if (list == NULL)
1002     {
1003       FadeSoundsAndMusic();
1004
1005       ClearWindow();
1006       DrawHeadline();
1007
1008       DrawTextSCentered(100, FONT_TEXT_1, "No music info for this level set.");
1009
1010       DrawTextSCentered(ybottom, FONT_TEXT_4,
1011                         "Press any key or button for info menu");
1012
1013       return;
1014     }
1015   }
1016   else if (button == MB_MENU_LEAVE)
1017   {
1018     info_mode = INFO_MODE_MAIN;
1019     DrawInfoScreen();
1020
1021     return;
1022   }
1023
1024   if (button_released || button == MB_MENU_INITIALIZE)
1025   {
1026     if (list == NULL)
1027     {
1028       info_mode = INFO_MODE_MAIN;
1029       DrawInfoScreen();
1030
1031       return;
1032     }
1033
1034     FadeSoundsAndMusic();
1035
1036     ClearWindow();
1037     DrawHeadline();
1038
1039     DrawTextSCentered(100, FONT_TEXT_1, "The game background music:");
1040
1041     DrawTextSCentered(ystart + 0 * ystep, FONT_TEXT_2, "Excerpt from");
1042     DrawTextFCentered(ystart + 1 * ystep, FONT_TEXT_3, "\"%s\"", list->title);
1043     DrawTextSCentered(ystart + 2 * ystep, FONT_TEXT_2, "by");
1044     DrawTextFCentered(ystart + 3 * ystep, FONT_TEXT_3, "%s", list->artist);
1045     DrawTextSCentered(ystart + 4 * ystep, FONT_TEXT_2, "from the album");
1046     DrawTextFCentered(ystart + 5 * ystep, FONT_TEXT_3, "\"%s\"", list->album);
1047
1048     DrawTextSCentered(ybottom, FONT_TEXT_4,
1049                       "Press any key or button for next page");
1050
1051     PlayMusic(list->music);
1052
1053     list = list->next;
1054   }
1055 }
1056
1057 void DrawInfoScreen_Credits()
1058 {
1059   int ystart = 150, ystep = 30;
1060   int ybottom = SYSIZE - 20;
1061
1062   FadeSoundsAndMusic();
1063
1064   ClearWindow();
1065   DrawHeadline();
1066
1067   DrawTextSCentered(100, FONT_TEXT_1, "Credits:");
1068   DrawTextSCentered(ystart + 0 * ystep, FONT_TEXT_2, "DOS port of the game:");
1069   DrawTextSCentered(ystart + 1 * ystep, FONT_TEXT_3, "Guido Schulz");
1070   DrawTextSCentered(ystart + 2 * ystep, FONT_TEXT_2, "Additional toons:");
1071   DrawTextSCentered(ystart + 3 * ystep, FONT_TEXT_3, "Karl Hörnell");
1072   DrawTextSCentered(ystart + 5 * ystep, FONT_TEXT_2,
1073                     "...and many thanks to all contributors");
1074   DrawTextSCentered(ystart + 6 * ystep, FONT_TEXT_2, "of new levels!");
1075
1076   DrawTextSCentered(ybottom, FONT_TEXT_4,
1077                     "Press any key or button for info menu");
1078 }
1079
1080 void HandleInfoScreen_Credits(int button)
1081 {
1082   int button_released = !button;
1083
1084   if (button == MB_MENU_LEAVE)
1085   {
1086     info_mode = INFO_MODE_MAIN;
1087     DrawInfoScreen();
1088
1089     return;
1090   }
1091
1092   if (button_released)
1093   {
1094     FadeSoundsAndMusic();
1095
1096     info_mode = INFO_MODE_MAIN;
1097     DrawInfoScreen();
1098   }
1099   else
1100   {
1101     PlayMenuSoundIfLoop();
1102   }
1103 }
1104
1105 void DrawInfoScreen_Program()
1106 {
1107   int ystart = 150, ystep = 30;
1108   int ybottom = SYSIZE - 20;
1109
1110   ClearWindow();
1111   DrawHeadline();
1112
1113   DrawTextSCentered(100, FONT_TEXT_1, "Program information:");
1114
1115   DrawTextSCentered(ystart + 0 * ystep, FONT_TEXT_2,
1116                     "This game is Freeware!");
1117   DrawTextSCentered(ystart + 1 * ystep, FONT_TEXT_2,
1118                     "If you like it, send e-mail to:");
1119   DrawTextSCentered(ystart + 2 * ystep, FONT_TEXT_3,
1120                     "info@artsoft.org");
1121   DrawTextSCentered(ystart + 3 * ystep, FONT_TEXT_2,
1122                     "or SnailMail to:");
1123   DrawTextSCentered(ystart + 4 * ystep + 0, FONT_TEXT_3,
1124                     "Holger Schemel");
1125   DrawTextSCentered(ystart + 4 * ystep + 20, FONT_TEXT_3,
1126                     "Detmolder Strasse 189");
1127   DrawTextSCentered(ystart + 4 * ystep + 40, FONT_TEXT_3,
1128                     "33604 Bielefeld");
1129   DrawTextSCentered(ystart + 4 * ystep + 60, FONT_TEXT_3,
1130                     "Germany");
1131
1132   DrawTextSCentered(ystart + 7 * ystep, FONT_TEXT_2,
1133                     "If you have created new levels,");
1134   DrawTextSCentered(ystart + 8 * ystep, FONT_TEXT_2,
1135                     "send them to me to include them!");
1136   DrawTextSCentered(ystart + 9 * ystep, FONT_TEXT_2,
1137                     ":-)");
1138
1139   DrawTextSCentered(ybottom, FONT_TEXT_4,
1140                     "Press any key or button for info menu");
1141 }
1142
1143 void HandleInfoScreen_Program(int button)
1144 {
1145   int button_released = !button;
1146
1147   if (button == MB_MENU_LEAVE)
1148   {
1149     info_mode = INFO_MODE_MAIN;
1150     DrawInfoScreen();
1151
1152     return;
1153   }
1154
1155   if (button_released)
1156   {
1157     FadeSoundsAndMusic();
1158
1159     info_mode = INFO_MODE_MAIN;
1160     DrawInfoScreen();
1161   }
1162   else
1163   {
1164     PlayMenuSoundIfLoop();
1165   }
1166 }
1167
1168 void DrawInfoScreen()
1169 {
1170   SetMainBackgroundImage(IMG_BACKGROUND_INFO);
1171
1172   if (info_mode == INFO_MODE_ELEMENTS)
1173     DrawInfoScreen_Elements();
1174   else if (info_mode == INFO_MODE_MUSIC)
1175     DrawInfoScreen_Music();
1176   else if (info_mode == INFO_MODE_CREDITS)
1177     DrawInfoScreen_Credits();
1178   else if (info_mode == INFO_MODE_PROGRAM)
1179     DrawInfoScreen_Program();
1180   else
1181     DrawInfoScreen_Main();
1182
1183   if (info_mode != INFO_MODE_MUSIC)
1184   {
1185     PlayMenuSound();
1186     PlayMenuMusic();
1187   }
1188 }
1189
1190 void HandleInfoScreen(int mx, int my, int dx, int dy, int button)
1191 {
1192   if (info_mode == INFO_MODE_ELEMENTS)
1193     HandleInfoScreen_Elements(button);
1194   else if (info_mode == INFO_MODE_MUSIC)
1195     HandleInfoScreen_Music(button);
1196   else if (info_mode == INFO_MODE_CREDITS)
1197     HandleInfoScreen_Credits(button);
1198   else if (info_mode == INFO_MODE_PROGRAM)
1199     HandleInfoScreen_Program(button);
1200   else
1201     HandleInfoScreen_Main(mx, my, dx, dy, button);
1202
1203   DoAnimation();
1204 }
1205
1206
1207 /* ========================================================================= */
1208 /* type name functions                                                       */
1209 /* ========================================================================= */
1210
1211 void HandleTypeName(int newxpos, Key key)
1212 {
1213   static int xpos = 0, ypos = 2;
1214   int font_width = getFontWidth(FONT_INPUT_1_ACTIVE);
1215   int name_width = getFontWidth(FONT_MENU_1) * strlen("Name:");
1216   int startx = mSX + 32 + name_width;
1217   int starty = mSY + ypos * 32;
1218
1219   if (newxpos)
1220   {
1221     xpos = newxpos;
1222
1223     DrawText(startx, starty, setup.player_name, FONT_INPUT_1_ACTIVE);
1224     DrawText(startx + xpos * font_width, starty, "_", FONT_INPUT_1_ACTIVE);
1225
1226     return;
1227   }
1228
1229   if (((key >= KSYM_A && key <= KSYM_Z) ||
1230        (key >= KSYM_a && key <= KSYM_z)) && 
1231       xpos < MAX_PLAYER_NAME_LEN)
1232   {
1233     char ascii;
1234
1235     if (key >= KSYM_A && key <= KSYM_Z)
1236       ascii = 'A' + (char)(key - KSYM_A);
1237     else
1238       ascii = 'a' + (char)(key - KSYM_a);
1239
1240     setup.player_name[xpos] = ascii;
1241     setup.player_name[xpos + 1] = 0;
1242     xpos++;
1243
1244     DrawText(startx, starty, setup.player_name, FONT_INPUT_1_ACTIVE);
1245     DrawText(startx + xpos * font_width, starty, "_", FONT_INPUT_1_ACTIVE);
1246   }
1247   else if ((key == KSYM_Delete || key == KSYM_BackSpace) && xpos > 0)
1248   {
1249     xpos--;
1250     setup.player_name[xpos] = 0;
1251
1252     DrawText(startx + xpos * font_width, starty, "_ ", FONT_INPUT_1_ACTIVE);
1253   }
1254   else if (key == KSYM_Return && xpos > 0)
1255   {
1256     DrawText(startx, starty, setup.player_name, FONT_INPUT_1);
1257     DrawText(startx + xpos * font_width, starty, " ", FONT_INPUT_1_ACTIVE);
1258
1259     SaveSetup();
1260     game_status = GAME_MODE_MAIN;
1261   }
1262 }
1263
1264
1265 /* ========================================================================= */
1266 /* tree menu functions                                                       */
1267 /* ========================================================================= */
1268
1269 static void DrawChooseTree(TreeInfo **ti_ptr)
1270 {
1271   UnmapAllGadgets();
1272
1273   FreeScreenGadgets();
1274   CreateScreenGadgets();
1275
1276   CloseDoor(DOOR_CLOSE_2);
1277
1278   ClearWindow();
1279
1280   HandleChooseTree(0, 0, 0, 0, MB_MENU_INITIALIZE, ti_ptr);
1281   MapChooseTreeGadgets(*ti_ptr);
1282
1283   FadeToFront();
1284   InitAnimation();
1285 }
1286
1287 static void AdjustChooseTreeScrollbar(int id, int first_entry, TreeInfo *ti)
1288 {
1289   struct GadgetInfo *gi = screen_gadget[id];
1290   int items_max, items_visible, item_position;
1291
1292   items_max = numTreeInfoInGroup(ti);
1293   items_visible = NUM_MENU_ENTRIES_ON_SCREEN;
1294   item_position = first_entry;
1295
1296   if (item_position > items_max - items_visible)
1297     item_position = items_max - items_visible;
1298
1299   ModifyGadget(gi, GDI_SCROLLBAR_ITEMS_MAX, items_max,
1300                GDI_SCROLLBAR_ITEMS_VISIBLE, items_visible,
1301                GDI_SCROLLBAR_ITEM_POSITION, item_position, GDI_END);
1302 }
1303
1304 static void drawChooseTreeList(int first_entry, int num_page_entries,
1305                                TreeInfo *ti)
1306 {
1307   int i;
1308   char buffer[SCR_FIELDX * 2];
1309   int max_buffer_len = (SCR_FIELDX - 2) * 2;
1310   char *title_string = NULL;
1311   int xoffset_setup = 16;
1312   int yoffset_setup = 16;
1313   int xoffset = (ti->type == TREE_TYPE_LEVEL_DIR ? 0 : xoffset_setup);
1314   int yoffset = (ti->type == TREE_TYPE_LEVEL_DIR ? 0 : yoffset_setup);
1315   int last_game_status = game_status;   /* save current game status */
1316
1317   title_string =
1318     (ti->type == TREE_TYPE_LEVEL_DIR ? "Level Directories" :
1319      ti->type == TREE_TYPE_GRAPHICS_DIR ? "Custom Graphics" :
1320      ti->type == TREE_TYPE_SOUNDS_DIR ? "Custom Sounds" :
1321      ti->type == TREE_TYPE_MUSIC_DIR ? "Custom Music" : "");
1322
1323   DrawText(SX + xoffset, SY + yoffset, title_string, FONT_TITLE_1);
1324
1325   /* force LEVELS font on artwork setup screen */
1326   game_status = GAME_MODE_LEVELS;
1327
1328   /* clear tree list area, but not title or scrollbar */
1329   DrawBackground(mSX, mSY + MENU_SCREEN_START_YPOS * 32,
1330                  SXSIZE - 32 + menu.scrollbar_xoffset,
1331                  MAX_MENU_ENTRIES_ON_SCREEN * 32);
1332
1333   for (i = 0; i < num_page_entries; i++)
1334   {
1335     TreeInfo *node, *node_first;
1336     int entry_pos = first_entry + i;
1337     int ypos = MENU_SCREEN_START_YPOS + i;
1338
1339     node_first = getTreeInfoFirstGroupEntry(ti);
1340     node = getTreeInfoFromPos(node_first, entry_pos);
1341
1342     strncpy(buffer, node->name , max_buffer_len);
1343     buffer[max_buffer_len] = '\0';
1344
1345     DrawText(mSX + 32, mSY + ypos * 32, buffer, FONT_TEXT_1 + node->color);
1346
1347     if (node->parent_link)
1348       initCursor(i, IMG_MENU_BUTTON_LEFT);
1349     else if (node->level_group)
1350       initCursor(i, IMG_MENU_BUTTON_RIGHT);
1351     else
1352       initCursor(i, IMG_MENU_BUTTON);
1353   }
1354
1355   game_status = last_game_status;       /* restore current game status */
1356
1357   redraw_mask |= REDRAW_FIELD;
1358 }
1359
1360 static void drawChooseTreeInfo(int entry_pos, TreeInfo *ti)
1361 {
1362   TreeInfo *node, *node_first;
1363   int x, last_redraw_mask = redraw_mask;
1364
1365   if (ti->type != TREE_TYPE_LEVEL_DIR)
1366     return;
1367
1368   node_first = getTreeInfoFirstGroupEntry(ti);
1369   node = getTreeInfoFromPos(node_first, entry_pos);
1370
1371   DrawBackground(SX + 32, SY + 32, SXSIZE - 64, 32);
1372
1373   if (node->parent_link)
1374     DrawTextFCentered(40, FONT_TITLE_2, "leave group \"%s\"",
1375                       node->class_desc);
1376   else if (node->level_group)
1377     DrawTextFCentered(40, FONT_TITLE_2, "enter group \"%s\"",
1378                       node->class_desc);
1379   else if (ti->type == TREE_TYPE_LEVEL_DIR)
1380     DrawTextFCentered(40, FONT_TITLE_2, "%3d levels (%s)",
1381                       node->levels, node->class_desc);
1382
1383   /* let BackToFront() redraw only what is needed */
1384   redraw_mask = last_redraw_mask | REDRAW_TILES;
1385   for (x = 0; x < SCR_FIELDX; x++)
1386     MarkTileDirty(x, 1);
1387 }
1388
1389 static void HandleChooseTree(int mx, int my, int dx, int dy, int button,
1390                              TreeInfo **ti_ptr)
1391 {
1392   TreeInfo *ti = *ti_ptr;
1393   int x = 0;
1394   int y = ti->cl_cursor;
1395   int step = (button == 1 ? 1 : button == 2 ? 5 : 10);
1396   int num_entries = numTreeInfoInGroup(ti);
1397   int num_page_entries;
1398   int last_game_status = game_status;   /* save current game status */
1399
1400   /* force LEVELS draw offset on choose level and artwork setup screen */
1401   game_status = GAME_MODE_LEVELS;
1402
1403   if (num_entries <= NUM_MENU_ENTRIES_ON_SCREEN)
1404     num_page_entries = num_entries;
1405   else
1406     num_page_entries = NUM_MENU_ENTRIES_ON_SCREEN;
1407
1408   game_status = last_game_status;       /* restore current game status */
1409
1410   if (button == MB_MENU_INITIALIZE)
1411   {
1412     int num_entries = numTreeInfoInGroup(ti);
1413     int entry_pos = posTreeInfo(ti);
1414
1415     if (ti->cl_first == -1)
1416     {
1417       /* only on initialization */
1418       ti->cl_first = MAX(0, entry_pos - num_page_entries + 1);
1419       ti->cl_cursor = entry_pos - ti->cl_first;
1420     }
1421     else if (ti->cl_cursor >= num_page_entries ||
1422              (num_entries > num_page_entries &&
1423               num_entries - ti->cl_first < num_page_entries))
1424     {
1425       /* only after change of list size (by custom graphic configuration) */
1426       ti->cl_first = MAX(0, entry_pos - num_page_entries + 1);
1427       ti->cl_cursor = entry_pos - ti->cl_first;
1428     }
1429
1430     if (dx == 999)      /* first entry is set by scrollbar position */
1431       ti->cl_first = dy;
1432     else
1433       AdjustChooseTreeScrollbar(SCREEN_CTRL_ID_SCROLL_VERTICAL,
1434                                 ti->cl_first, ti);
1435
1436     drawChooseTreeList(ti->cl_first, num_page_entries, ti);
1437     drawChooseTreeInfo(ti->cl_first + ti->cl_cursor, ti);
1438     drawChooseTreeCursor(ti->cl_cursor, FC_RED);
1439
1440     return;
1441   }
1442   else if (button == MB_MENU_LEAVE)
1443   {
1444     if (ti->node_parent)
1445     {
1446       *ti_ptr = ti->node_parent;
1447       DrawChooseTree(ti_ptr);
1448     }
1449     else if (game_status == GAME_MODE_SETUP)
1450     {
1451       execSetupArtwork();
1452     }
1453     else
1454     {
1455       game_status = GAME_MODE_MAIN;
1456       DrawMainMenu();
1457     }
1458
1459     return;
1460   }
1461
1462   if (mx || my)         /* mouse input */
1463   {
1464     int last_game_status = game_status; /* save current game status */
1465
1466     /* force LEVELS draw offset on artwork setup screen */
1467     game_status = GAME_MODE_LEVELS;
1468
1469     x = (mx - mSX) / 32;
1470     y = (my - mSY) / 32 - MENU_SCREEN_START_YPOS;
1471
1472     game_status = last_game_status;     /* restore current game status */
1473   }
1474   else if (dx || dy)    /* keyboard or scrollbar/scrollbutton input */
1475   {
1476     /* move cursor instead of scrolling when already at start/end of list */
1477     if (dy == -1 * SCROLL_LINE && ti->cl_first == 0)
1478       dy = -1;
1479     else if (dy == +1 * SCROLL_LINE &&
1480              ti->cl_first + num_page_entries == num_entries)
1481       dy = 1;
1482
1483     /* handle scrolling screen one line or page */
1484     if (ti->cl_cursor + dy < 0 ||
1485         ti->cl_cursor + dy > num_page_entries - 1)
1486     {
1487       if (ABS(dy) == SCROLL_PAGE)
1488         step = num_page_entries - 1;
1489
1490       if (dy < 0 && ti->cl_first > 0)
1491       {
1492         /* scroll page/line up */
1493
1494         ti->cl_first -= step;
1495         if (ti->cl_first < 0)
1496           ti->cl_first = 0;
1497
1498         drawChooseTreeList(ti->cl_first, num_page_entries, ti);
1499         drawChooseTreeInfo(ti->cl_first + ti->cl_cursor, ti);
1500         drawChooseTreeCursor(ti->cl_cursor, FC_RED);
1501         AdjustChooseTreeScrollbar(SCREEN_CTRL_ID_SCROLL_VERTICAL,
1502                                   ti->cl_first, ti);
1503       }
1504       else if (dy > 0 && ti->cl_first + num_page_entries < num_entries)
1505       {
1506         /* scroll page/line down */
1507
1508         ti->cl_first += step;
1509         if (ti->cl_first + num_page_entries > num_entries)
1510           ti->cl_first = MAX(0, num_entries - num_page_entries);
1511
1512         drawChooseTreeList(ti->cl_first, num_page_entries, ti);
1513         drawChooseTreeInfo(ti->cl_first + ti->cl_cursor, ti);
1514         drawChooseTreeCursor(ti->cl_cursor, FC_RED);
1515         AdjustChooseTreeScrollbar(SCREEN_CTRL_ID_SCROLL_VERTICAL,
1516                                   ti->cl_first, ti);
1517       }
1518
1519       return;
1520     }
1521
1522     /* handle moving cursor one line */
1523     y = ti->cl_cursor + dy;
1524   }
1525
1526   if (dx == 1)
1527   {
1528     TreeInfo *node_first, *node_cursor;
1529     int entry_pos = ti->cl_first + y;
1530
1531     node_first = getTreeInfoFirstGroupEntry(ti);
1532     node_cursor = getTreeInfoFromPos(node_first, entry_pos);
1533
1534     if (node_cursor->node_group)
1535     {
1536       node_cursor->cl_first = ti->cl_first;
1537       node_cursor->cl_cursor = ti->cl_cursor;
1538       *ti_ptr = node_cursor->node_group;
1539       DrawChooseTree(ti_ptr);
1540
1541       return;
1542     }
1543   }
1544   else if (dx == -1 && ti->node_parent)
1545   {
1546     *ti_ptr = ti->node_parent;
1547     DrawChooseTree(ti_ptr);
1548
1549     return;
1550   }
1551
1552   if (IN_GFX_SCREEN(mx, my) &&
1553       mx < screen_gadget[SCREEN_CTRL_ID_SCROLL_VERTICAL]->x &&
1554       y >= 0 && y < num_page_entries)
1555   {
1556     if (button)
1557     {
1558       if (y != ti->cl_cursor)
1559       {
1560         drawChooseTreeCursor(y, FC_RED);
1561         drawChooseTreeCursor(ti->cl_cursor, FC_BLUE);
1562         drawChooseTreeInfo(ti->cl_first + y, ti);
1563         ti->cl_cursor = y;
1564       }
1565     }
1566     else
1567     {
1568       TreeInfo *node_first, *node_cursor;
1569       int entry_pos = ti->cl_first + y;
1570
1571       node_first = getTreeInfoFirstGroupEntry(ti);
1572       node_cursor = getTreeInfoFromPos(node_first, entry_pos);
1573
1574       if (node_cursor->node_group)
1575       {
1576         node_cursor->cl_first = ti->cl_first;
1577         node_cursor->cl_cursor = ti->cl_cursor;
1578         *ti_ptr = node_cursor->node_group;
1579         DrawChooseTree(ti_ptr);
1580       }
1581       else if (node_cursor->parent_link)
1582       {
1583         *ti_ptr = node_cursor->node_parent;
1584         DrawChooseTree(ti_ptr);
1585       }
1586       else
1587       {
1588         node_cursor->cl_first = ti->cl_first;
1589         node_cursor->cl_cursor = ti->cl_cursor;
1590         *ti_ptr = node_cursor;
1591
1592         if (ti->type == TREE_TYPE_LEVEL_DIR)
1593         {
1594           LoadLevelSetup_SeriesInfo();
1595
1596           SaveLevelSetup_LastSeries();
1597           SaveLevelSetup_SeriesInfo();
1598           TapeErase();
1599         }
1600
1601         if (game_status == GAME_MODE_SETUP)
1602         {
1603           execSetupArtwork();
1604         }
1605         else
1606         {
1607           game_status = GAME_MODE_MAIN;
1608           DrawMainMenu();
1609         }
1610       }
1611     }
1612   }
1613 }
1614
1615 void DrawChooseLevel()
1616 {
1617   SetMainBackgroundImage(IMG_BACKGROUND_LEVELS);
1618
1619   DrawChooseTree(&leveldir_current);
1620
1621   PlayMenuSound();
1622   PlayMenuMusic();
1623 }
1624
1625 void HandleChooseLevel(int mx, int my, int dx, int dy, int button)
1626 {
1627   HandleChooseTree(mx, my, dx, dy, button, &leveldir_current);
1628
1629   DoAnimation();
1630 }
1631
1632 void DrawHallOfFame(int highlight_position)
1633 {
1634   UnmapAllGadgets();
1635   FadeSoundsAndMusic();
1636   CloseDoor(DOOR_CLOSE_2);
1637
1638   if (highlight_position < 0) 
1639     LoadScore(level_nr);
1640
1641   FadeToFront();
1642   InitAnimation();
1643
1644   PlayMenuSound();
1645   PlayMenuMusic();
1646
1647   HandleHallOfFame(highlight_position, 0, 0, 0, MB_MENU_INITIALIZE);
1648 }
1649
1650 static void drawHallOfFameList(int first_entry, int highlight_position)
1651 {
1652   int i;
1653
1654   SetMainBackgroundImage(IMG_BACKGROUND_SCORES);
1655   ClearWindow();
1656
1657   DrawText(mSX + 80, mSY + 8, "Hall Of Fame", FONT_TITLE_1);
1658   DrawTextFCentered(46, FONT_TITLE_2, "HighScores of Level %d", level_nr);
1659
1660   for (i = 0; i < NUM_MENU_ENTRIES_ON_SCREEN; i++)
1661   {
1662     int entry = first_entry + i;
1663     boolean active = (entry == highlight_position);
1664     int font_nr1 = (active ? FONT_TEXT_1_ACTIVE : FONT_TEXT_1);
1665     int font_nr2 = (active ? FONT_TEXT_2_ACTIVE : FONT_TEXT_2);
1666     int font_nr3 = (active ? FONT_TEXT_3_ACTIVE : FONT_TEXT_3);
1667     int font_nr4 = (active ? FONT_TEXT_4_ACTIVE : FONT_TEXT_4);
1668     int dx1 = 3 * getFontWidth(font_nr1);
1669     int dx2 = dx1 + getFontWidth(font_nr1);
1670     int dx3 = dx2 + 25 * getFontWidth(font_nr3);
1671     int sy = mSY + 64 + i * 32;
1672
1673     DrawText(mSX, sy, int2str(entry + 1, 3), font_nr1);
1674     DrawText(mSX + dx1, sy, ".", font_nr1);
1675     DrawText(mSX + dx2, sy, ".........................", font_nr3);
1676     if (strcmp(highscore[entry].Name, EMPTY_PLAYER_NAME) != 0)
1677       DrawText(mSX + dx2, sy, highscore[entry].Name, font_nr2);
1678     DrawText(mSX + dx3, sy, int2str(highscore[entry].Score, 5), font_nr4);
1679   }
1680
1681   redraw_mask |= REDRAW_FIELD;
1682 }
1683
1684 void HandleHallOfFame(int mx, int my, int dx, int dy, int button)
1685 {
1686   static int first_entry = 0;
1687   static int highlight_position = 0;
1688   int step = (button == 1 ? 1 : button == 2 ? 5 : 10);
1689   int button_released = !button;
1690
1691   if (button == MB_MENU_INITIALIZE)
1692   {
1693     first_entry = 0;
1694     highlight_position = mx;
1695     drawHallOfFameList(first_entry, highlight_position);
1696
1697     return;
1698   }
1699
1700   if (ABS(dy) == SCROLL_PAGE)           /* handle scrolling one page */
1701     step = NUM_MENU_ENTRIES_ON_SCREEN - 1;
1702
1703   if (dy < 0)
1704   {
1705     if (first_entry > 0)
1706     {
1707       first_entry -= step;
1708       if (first_entry < 0)
1709         first_entry = 0;
1710
1711       drawHallOfFameList(first_entry, highlight_position);
1712     }
1713   }
1714   else if (dy > 0)
1715   {
1716     if (first_entry + NUM_MENU_ENTRIES_ON_SCREEN < MAX_SCORE_ENTRIES)
1717     {
1718       first_entry += step;
1719       if (first_entry + NUM_MENU_ENTRIES_ON_SCREEN > MAX_SCORE_ENTRIES)
1720         first_entry = MAX(0, MAX_SCORE_ENTRIES - NUM_MENU_ENTRIES_ON_SCREEN);
1721
1722       drawHallOfFameList(first_entry, highlight_position);
1723     }
1724   }
1725   else if (button_released)
1726   {
1727     FadeSound(SND_BACKGROUND_SCORES);
1728     game_status = GAME_MODE_MAIN;
1729     DrawMainMenu();
1730   }
1731
1732   if (game_status == GAME_MODE_SCORES)
1733     PlayMenuSoundIfLoop();
1734
1735   DoAnimation();
1736 }
1737
1738
1739 /* ========================================================================= */
1740 /* setup screen functions                                                    */
1741 /* ========================================================================= */
1742
1743 static struct TokenInfo *setup_info;
1744 static int num_setup_info;
1745
1746 static char *graphics_set_name;
1747 static char *sounds_set_name;
1748 static char *music_set_name;
1749
1750 static void execSetupMain()
1751 {
1752   setup_mode = SETUP_MODE_MAIN;
1753   DrawSetupScreen();
1754 }
1755
1756 static void execSetupGame()
1757 {
1758   setup_mode = SETUP_MODE_GAME;
1759   DrawSetupScreen();
1760 }
1761
1762 static void execSetupEditor()
1763 {
1764   setup_mode = SETUP_MODE_EDITOR;
1765   DrawSetupScreen();
1766 }
1767
1768 static void execSetupGraphics()
1769 {
1770   setup_mode = SETUP_MODE_GRAPHICS;
1771   DrawSetupScreen();
1772 }
1773
1774 static void execSetupSound()
1775 {
1776   setup_mode = SETUP_MODE_SOUND;
1777   DrawSetupScreen();
1778 }
1779
1780 static void execSetupArtwork()
1781 {
1782   setup.graphics_set = artwork.gfx_current->identifier;
1783   setup.sounds_set = artwork.snd_current->identifier;
1784   setup.music_set = artwork.mus_current->identifier;
1785
1786   /* needed if last screen (setup choice) changed graphics, sounds or music */
1787   ReloadCustomArtwork();
1788
1789   /* needed for displaying artwork name instead of artwork identifier */
1790   graphics_set_name = artwork.gfx_current->name;
1791   sounds_set_name = artwork.snd_current->name;
1792   music_set_name = artwork.mus_current->name;
1793
1794   setup_mode = SETUP_MODE_ARTWORK;
1795   DrawSetupScreen();
1796 }
1797
1798 static void execSetupChooseGraphics()
1799 {
1800   setup_mode = SETUP_MODE_CHOOSE_GRAPHICS;
1801   DrawSetupScreen();
1802 }
1803
1804 static void execSetupChooseSounds()
1805 {
1806   setup_mode = SETUP_MODE_CHOOSE_SOUNDS;
1807   DrawSetupScreen();
1808 }
1809
1810 static void execSetupChooseMusic()
1811 {
1812   setup_mode = SETUP_MODE_CHOOSE_MUSIC;
1813   DrawSetupScreen();
1814 }
1815
1816 static void execSetupInput()
1817 {
1818   setup_mode = SETUP_MODE_INPUT;
1819   DrawSetupScreen();
1820 }
1821
1822 static void execSetupShortcut()
1823 {
1824   setup_mode = SETUP_MODE_SHORTCUT;
1825   DrawSetupScreen();
1826 }
1827
1828 static void execExitSetup()
1829 {
1830   game_status = GAME_MODE_MAIN;
1831   DrawMainMenu();
1832 }
1833
1834 static void execSaveAndExitSetup()
1835 {
1836   SaveSetup();
1837   execExitSetup();
1838 }
1839
1840 static struct TokenInfo setup_info_main[] =
1841 {
1842   { TYPE_ENTER_MENU,    execSetupGame,          "Game Settings"         },
1843   { TYPE_ENTER_MENU,    execSetupEditor,        "Editor Settings"       },
1844   { TYPE_ENTER_MENU,    execSetupGraphics,      "Graphics"              },
1845   { TYPE_ENTER_MENU,    execSetupSound,         "Sound & Music"         },
1846   { TYPE_ENTER_MENU,    execSetupArtwork,       "Custom Artwork"        },
1847   { TYPE_ENTER_MENU,    execSetupInput,         "Input Devices"         },
1848   { TYPE_ENTER_MENU,    execSetupShortcut,      "Key Shortcuts"         },
1849   { TYPE_EMPTY,         NULL,                   ""                      },
1850   { TYPE_LEAVE_MENU,    execExitSetup,          "Exit"                  },
1851   { TYPE_LEAVE_MENU,    execSaveAndExitSetup,   "Save and Exit"         },
1852
1853   { 0,                  NULL,                   NULL                    }
1854 };
1855
1856 static struct TokenInfo setup_info_game[] =
1857 {
1858   { TYPE_SWITCH,        &setup.team_mode,       "Team-Mode:"            },
1859   { TYPE_SWITCH,        &setup.handicap,        "Handicap:"             },
1860   { TYPE_SWITCH,        &setup.time_limit,      "Timelimit:"            },
1861   { TYPE_SWITCH,        &setup.autorecord,      "Auto-Record:"          },
1862   { TYPE_EMPTY,         NULL,                   ""                      },
1863   { TYPE_LEAVE_MENU,    execSetupMain,          "Back"                  },
1864
1865   { 0,                  NULL,                   NULL                    }
1866 };
1867
1868 static struct TokenInfo setup_info_editor[] =
1869 {
1870   { TYPE_STRING,        NULL,                   "Offer Special Elements:"},
1871   { TYPE_SWITCH,        &setup.editor.el_boulderdash,   "BoulderDash:"  },
1872   { TYPE_SWITCH,        &setup.editor.el_emerald_mine,  "Emerald Mine:" },
1873   { TYPE_SWITCH,        &setup.editor.el_more,          "More:"         },
1874   { TYPE_SWITCH,        &setup.editor.el_sokoban,       "Sokoban:"      },
1875   { TYPE_SWITCH,        &setup.editor.el_supaplex,      "Supaplex:"     },
1876   { TYPE_SWITCH,        &setup.editor.el_diamond_caves, "Diamd. Caves:" },
1877   { TYPE_SWITCH,        &setup.editor.el_dx_boulderdash,"DX Boulderd.:" },
1878   { TYPE_SWITCH,        &setup.editor.el_chars,         "Characters:"   },
1879   { TYPE_SWITCH,        &setup.editor.el_custom,        "Custom:"       },
1880   { TYPE_SWITCH,        &setup.editor.el_custom_more,   "More Custom:"  },
1881   { TYPE_SWITCH,        &setup.editor.el_headlines,     "Headlines:"    },
1882   { TYPE_SWITCH,        &setup.editor.el_user_defined,  "User defined:" },
1883   { TYPE_EMPTY,         NULL,                   ""                      },
1884   { TYPE_LEAVE_MENU,    execSetupMain,          "Back"                  },
1885
1886   { 0,                  NULL,                   NULL                    }
1887 };
1888
1889 static struct TokenInfo setup_info_graphics[] =
1890 {
1891   { TYPE_SWITCH,        &setup.fullscreen,      "Fullscreen:"           },
1892   { TYPE_SWITCH,        &setup.scroll_delay,    "Scroll Delay:"         },
1893   { TYPE_SWITCH,        &setup.soft_scrolling,  "Soft Scroll.:"         },
1894 #if 0
1895   { TYPE_SWITCH,        &setup.double_buffering,"Buffered gfx:"         },
1896   { TYPE_SWITCH,        &setup.fading,          "Fading:"               },
1897 #endif
1898   { TYPE_SWITCH,        &setup.quick_doors,     "Quick Doors:"          },
1899   { TYPE_SWITCH,        &setup.toons,           "Toons:"                },
1900   { TYPE_EMPTY,         NULL,                   ""                      },
1901   { TYPE_LEAVE_MENU,    execSetupMain,          "Back"                  },
1902
1903   { 0,                  NULL,                   NULL                    }
1904 };
1905
1906 static struct TokenInfo setup_info_sound[] =
1907 {
1908   { TYPE_SWITCH,        &setup.sound_simple,    "Simple Sound:"         },
1909   { TYPE_SWITCH,        &setup.sound_loops,     "Sound Loops:"          },
1910   { TYPE_SWITCH,        &setup.sound_music,     "Game Music:"           },
1911   { TYPE_EMPTY,         NULL,                   ""                      },
1912   { TYPE_LEAVE_MENU,    execSetupMain,          "Back"                  },
1913
1914   { 0,                  NULL,                   NULL                    }
1915 };
1916
1917 static struct TokenInfo setup_info_artwork[] =
1918 {
1919   { TYPE_ENTER_MENU,    execSetupChooseGraphics,"Custom Graphics"       },
1920   { TYPE_STRING,        &graphics_set_name,     ""                      },
1921   { TYPE_ENTER_MENU,    execSetupChooseSounds,  "Custom Sounds"         },
1922   { TYPE_STRING,        &sounds_set_name,       ""                      },
1923   { TYPE_ENTER_MENU,    execSetupChooseMusic,   "Custom Music"          },
1924   { TYPE_STRING,        &music_set_name,        ""                      },
1925   { TYPE_EMPTY,         NULL,                   ""                      },
1926   { TYPE_STRING,        NULL,                   "Override Level Artwork:"},
1927   { TYPE_YES_NO,        &setup.override_level_graphics, "Graphics:"     },
1928   { TYPE_YES_NO,        &setup.override_level_sounds,   "Sounds:"       },
1929   { TYPE_YES_NO,        &setup.override_level_music,    "Music:"        },
1930   { TYPE_EMPTY,         NULL,                   ""                      },
1931   { TYPE_LEAVE_MENU,    execSetupMain,          "Back"                  },
1932
1933   { 0,                  NULL,                   NULL                    }
1934 };
1935
1936 static struct TokenInfo setup_info_shortcut[] =
1937 {
1938   { TYPE_KEYTEXT,       NULL,                   "Quick Save Game:",     },
1939   { TYPE_KEY,           &setup.shortcut.save_game,      ""              },
1940   { TYPE_KEYTEXT,       NULL,                   "Quick Load Game:",     },
1941   { TYPE_KEY,           &setup.shortcut.load_game,      ""              },
1942   { TYPE_KEYTEXT,       NULL,                   "Toggle Pause:",        },
1943   { TYPE_KEY,           &setup.shortcut.toggle_pause,   ""              },
1944   { TYPE_EMPTY,         NULL,                   ""                      },
1945   { TYPE_YES_NO,        &setup.ask_on_escape,   "Ask on Esc:"           },
1946   { TYPE_EMPTY,         NULL,                   ""                      },
1947   { TYPE_LEAVE_MENU,    execSetupMain,          "Back"                  },
1948
1949   { 0,                  NULL,                   NULL                    }
1950 };
1951
1952 static Key getSetupKey()
1953 {
1954   Key key = KSYM_UNDEFINED;
1955   boolean got_key_event = FALSE;
1956
1957   while (!got_key_event)
1958   {
1959     if (PendingEvent())         /* got event */
1960     {
1961       Event event;
1962
1963       NextEvent(&event);
1964
1965       switch(event.type)
1966       {
1967         case EVENT_KEYPRESS:
1968           {
1969             key = GetEventKey((KeyEvent *)&event, TRUE);
1970
1971             /* press 'Escape' or 'Enter' to keep the existing key binding */
1972             if (key == KSYM_Escape || key == KSYM_Return)
1973               key = KSYM_UNDEFINED;     /* keep old value */
1974
1975             got_key_event = TRUE;
1976           }
1977           break;
1978
1979         case EVENT_KEYRELEASE:
1980           key_joystick_mapping = 0;
1981           break;
1982
1983         default:
1984           HandleOtherEvents(&event);
1985           break;
1986       }
1987     }
1988
1989     DoAnimation();
1990     BackToFront();
1991
1992     /* don't eat all CPU time */
1993     Delay(10);
1994   }
1995
1996   return key;
1997 }
1998
1999 static void drawSetupValue(int pos)
2000 {
2001   int xpos = MENU_SCREEN_VALUE_XPOS;
2002   int ypos = MENU_SCREEN_START_YPOS + pos;
2003   int font_nr = FONT_VALUE_1;
2004   int type = setup_info[pos].type;
2005   void *value = setup_info[pos].value;
2006   char *value_string = (!(type & TYPE_GHOSTED) ? getSetupValue(type, value) :
2007                         "n/a");
2008
2009   if (value_string == NULL)
2010     return;
2011
2012   if (type & TYPE_KEY)
2013   {
2014     xpos = 3;
2015
2016     if (type & TYPE_QUERY)
2017     {
2018       value_string = "<press key>";
2019       font_nr = FONT_INPUT_1_ACTIVE;
2020     }
2021   }
2022   else if (type & TYPE_STRING)
2023   {
2024     int max_value_len = (SCR_FIELDX - 2) * 2;
2025
2026     xpos = 1;
2027     font_nr = FONT_VALUE_2;
2028
2029     if (strlen(value_string) > max_value_len)
2030       value_string[max_value_len] = '\0';
2031   }
2032   else if (type & TYPE_BOOLEAN_STYLE)
2033   {
2034     font_nr = (*(boolean *)value ? FONT_OPTION_ON : FONT_OPTION_OFF);
2035   }
2036
2037   DrawText(mSX + xpos * 32, mSY + ypos * 32,
2038            (xpos == 3 ? "              " : "   "), font_nr);
2039   DrawText(mSX + xpos * 32, mSY + ypos * 32, value_string, font_nr);
2040 }
2041
2042 static void changeSetupValue(int pos)
2043 {
2044   if (setup_info[pos].type & TYPE_BOOLEAN_STYLE)
2045   {
2046     *(boolean *)setup_info[pos].value ^= TRUE;
2047   }
2048   else if (setup_info[pos].type & TYPE_KEY)
2049   {
2050     Key key;
2051
2052     setup_info[pos].type |= TYPE_QUERY;
2053     drawSetupValue(pos);
2054     setup_info[pos].type &= ~TYPE_QUERY;
2055
2056     key = getSetupKey();
2057     if (key != KSYM_UNDEFINED)
2058       *(Key *)setup_info[pos].value = key;
2059   }
2060
2061   drawSetupValue(pos);
2062 }
2063
2064 static void DrawSetupScreen_Generic()
2065 {
2066   char *title_string = NULL;
2067   int i;
2068
2069   UnmapAllGadgets();
2070   CloseDoor(DOOR_CLOSE_2);
2071
2072   ClearWindow();
2073
2074   if (setup_mode == SETUP_MODE_MAIN)
2075   {
2076     setup_info = setup_info_main;
2077     title_string = "Setup";
2078   }
2079   else if (setup_mode == SETUP_MODE_GAME)
2080   {
2081     setup_info = setup_info_game;
2082     title_string = "Setup Game";
2083   }
2084   else if (setup_mode == SETUP_MODE_EDITOR)
2085   {
2086     setup_info = setup_info_editor;
2087     title_string = "Setup Editor";
2088   }
2089   else if (setup_mode == SETUP_MODE_GRAPHICS)
2090   {
2091     setup_info = setup_info_graphics;
2092     title_string = "Setup Graphics";
2093   }
2094   else if (setup_mode == SETUP_MODE_SOUND)
2095   {
2096     setup_info = setup_info_sound;
2097     title_string = "Setup Sound";
2098   }
2099   else if (setup_mode == SETUP_MODE_ARTWORK)
2100   {
2101     setup_info = setup_info_artwork;
2102     title_string = "Custom Artwork";
2103   }
2104   else if (setup_mode == SETUP_MODE_SHORTCUT)
2105   {
2106     setup_info = setup_info_shortcut;
2107     title_string = "Setup Shortcuts";
2108   }
2109
2110   DrawText(mSX + 16, mSY + 16, title_string, FONT_TITLE_1);
2111
2112   num_setup_info = 0;
2113   for (i = 0; setup_info[i].type != 0 && i < NUM_MENU_ENTRIES_ON_SCREEN; i++)
2114   {
2115     void *value_ptr = setup_info[i].value;
2116     int ypos = MENU_SCREEN_START_YPOS + i;
2117     int font_nr = FONT_MENU_1;
2118
2119     /* set some entries to "unchangeable" according to other variables */
2120     if ((value_ptr == &setup.sound_simple && !audio.sound_available) ||
2121         (value_ptr == &setup.sound_loops  && !audio.loops_available) ||
2122         (value_ptr == &setup.sound_music  && !audio.music_available) ||
2123         (value_ptr == &setup.fullscreen   && !video.fullscreen_available))
2124       setup_info[i].type |= TYPE_GHOSTED;
2125
2126     if (setup_info[i].type & TYPE_STRING)
2127       font_nr = FONT_MENU_2;
2128
2129     DrawText(mSX + 32, mSY + ypos * 32, setup_info[i].text, font_nr);
2130
2131     if (setup_info[i].type & TYPE_ENTER_MENU)
2132       initCursor(i, IMG_MENU_BUTTON_RIGHT);
2133     else if (setup_info[i].type & TYPE_LEAVE_MENU)
2134       initCursor(i, IMG_MENU_BUTTON_LEFT);
2135     else if (setup_info[i].type & ~TYPE_SKIP_ENTRY)
2136       initCursor(i, IMG_MENU_BUTTON);
2137
2138     if (setup_info[i].type & TYPE_VALUE)
2139       drawSetupValue(i);
2140
2141     num_setup_info++;
2142   }
2143
2144   FadeToFront();
2145   InitAnimation();
2146   HandleSetupScreen_Generic(0, 0, 0, 0, MB_MENU_INITIALIZE);
2147 }
2148
2149 void HandleSetupScreen_Generic(int mx, int my, int dx, int dy, int button)
2150 {
2151   static int choice_store[MAX_SETUP_MODES];
2152   int choice = choice_store[setup_mode];        /* always starts with 0 */
2153   int x = 0;
2154   int y = choice;
2155
2156   if (button == MB_MENU_INITIALIZE)
2157   {
2158     /* advance to first valid menu entry */
2159     while (choice < num_setup_info &&
2160            setup_info[choice].type & TYPE_SKIP_ENTRY)
2161       choice++;
2162     choice_store[setup_mode] = choice;
2163
2164     drawCursor(choice, FC_RED);
2165     return;
2166   }
2167   else if (button == MB_MENU_LEAVE)
2168   {
2169     for (y = 0; y < num_setup_info; y++)
2170     {
2171       if (setup_info[y].type & TYPE_LEAVE_MENU)
2172       {
2173         void (*menu_callback_function)(void) = setup_info[y].value;
2174
2175         menu_callback_function();
2176         break;  /* absolutely needed because function changes 'setup_info'! */
2177       }
2178     }
2179
2180     return;
2181   }
2182
2183   if (mx || my)         /* mouse input */
2184   {
2185     x = (mx - mSX) / 32;
2186     y = (my - mSY) / 32 - MENU_SCREEN_START_YPOS;
2187   }
2188   else if (dx || dy)    /* keyboard input */
2189   {
2190     if (dx)
2191     {
2192       int menu_navigation_type = (dx < 0 ? TYPE_LEAVE_MENU : TYPE_ENTER_MENU);
2193
2194       if (setup_info[choice].type & menu_navigation_type ||
2195           setup_info[choice].type & TYPE_BOOLEAN_STYLE)
2196         button = MB_MENU_CHOICE;
2197     }
2198     else if (dy)
2199       y = choice + dy;
2200
2201     /* jump to next non-empty menu entry (up or down) */
2202     while (y > 0 && y < num_setup_info - 1 &&
2203            setup_info[y].type & TYPE_SKIP_ENTRY)
2204       y += dy;
2205   }
2206
2207   if (IN_GFX_SCREEN(mx, my) &&
2208       y >= 0 && y < num_setup_info && setup_info[y].type & ~TYPE_SKIP_ENTRY)
2209   {
2210     if (button)
2211     {
2212       if (y != choice)
2213       {
2214         drawCursor(y, FC_RED);
2215         drawCursor(choice, FC_BLUE);
2216         choice = choice_store[setup_mode] = y;
2217       }
2218     }
2219     else if (!(setup_info[y].type & TYPE_GHOSTED))
2220     {
2221       if (setup_info[y].type & TYPE_ENTER_OR_LEAVE_MENU)
2222       {
2223         void (*menu_callback_function)(void) = setup_info[choice].value;
2224
2225         menu_callback_function();
2226       }
2227       else
2228       {
2229         if (setup_info[y].type & TYPE_KEYTEXT &&
2230             setup_info[y + 1].type & TYPE_KEY)
2231           y++;
2232
2233         if (setup_info[y].type & TYPE_VALUE)
2234           changeSetupValue(y);
2235       }
2236     }
2237   }
2238 }
2239
2240 void DrawSetupScreen_Input()
2241 {
2242   ClearWindow();
2243
2244   DrawText(mSX+16, mSY+16, "Setup Input", FONT_TITLE_1);
2245
2246   initCursor(0, IMG_MENU_BUTTON);
2247   initCursor(1, IMG_MENU_BUTTON);
2248   initCursor(2, IMG_MENU_BUTTON_RIGHT);
2249   initCursor(13, IMG_MENU_BUTTON_LEFT);
2250
2251   drawCursorXY(10, 0, IMG_MENU_BUTTON_LEFT);
2252   drawCursorXY(12, 0, IMG_MENU_BUTTON_RIGHT);
2253
2254   DrawText(mSX+32, mSY+2*32, "Player:", FONT_MENU_1);
2255   DrawText(mSX+32, mSY+3*32, "Device:", FONT_MENU_1);
2256   DrawText(mSX+32, mSY+15*32, "Back",   FONT_MENU_1);
2257
2258 #if 0
2259   DeactivateJoystickForCalibration();
2260   DrawTextSCentered(SYSIZE - 20, FONT_TEXT_4,
2261                     "Joysticks deactivated on this screen");
2262 #endif
2263
2264   HandleSetupScreen_Input(0, 0, 0, 0, MB_MENU_INITIALIZE);
2265   FadeToFront();
2266   InitAnimation();
2267 }
2268
2269 static void setJoystickDeviceToNr(char *device_name, int device_nr)
2270 {
2271   if (device_name == NULL)
2272     return;
2273
2274   if (device_nr < 0 || device_nr >= MAX_PLAYERS)
2275     device_nr = 0;
2276
2277   if (strlen(device_name) > 1)
2278   {
2279     char c1 = device_name[strlen(device_name) - 1];
2280     char c2 = device_name[strlen(device_name) - 2];
2281
2282     if (c1 >= '0' && c1 <= '9' && !(c2 >= '0' && c2 <= '9'))
2283       device_name[strlen(device_name) - 1] = '0' + (char)(device_nr % 10);
2284   }
2285   else
2286     strncpy(device_name, getDeviceNameFromJoystickNr(device_nr),
2287             strlen(device_name));
2288 }
2289
2290 static void drawPlayerSetupInputInfo(int player_nr)
2291 {
2292   int i;
2293   static struct SetupKeyboardInfo custom_key;
2294   static struct
2295   {
2296     Key *key;
2297     char *text;
2298   } custom[] =
2299   {
2300     { &custom_key.left,  "Joystick Left"  },
2301     { &custom_key.right, "Joystick Right" },
2302     { &custom_key.up,    "Joystick Up"    },
2303     { &custom_key.down,  "Joystick Down"  },
2304     { &custom_key.snap,  "Button 1"       },
2305     { &custom_key.bomb,  "Button 2"       }
2306   };
2307   static char *joystick_name[MAX_PLAYERS] =
2308   {
2309     "Joystick1",
2310     "Joystick2",
2311     "Joystick3",
2312     "Joystick4"
2313   };
2314
2315   custom_key = setup.input[player_nr].key;
2316
2317   DrawText(mSX+11*32, mSY+2*32, int2str(player_nr +1, 1), FONT_INPUT_1_ACTIVE);
2318 #if 1
2319   DrawGraphicThruMaskExt(drawto, mSX + 8 * TILEX, mSY + 2 * TILEY,
2320                          PLAYER_NR_GFX(IMG_PLAYER_1, player_nr), 0);
2321 #else
2322   DrawGraphicThruMask(8, 2, PLAYER_NR_GFX(IMG_PLAYER_1, player_nr), 0);
2323 #endif
2324
2325   if (setup.input[player_nr].use_joystick)
2326   {
2327     char *device_name = setup.input[player_nr].joy.device_name;
2328
2329     DrawText(mSX+8*32, mSY+3*32,
2330              joystick_name[getJoystickNrFromDeviceName(device_name)],
2331              FONT_VALUE_1);
2332     DrawText(mSX+32, mSY+4*32, "Calibrate", FONT_MENU_1);
2333   }
2334   else
2335   {
2336     DrawText(mSX+8*32, mSY+3*32, "Keyboard ", FONT_VALUE_1);
2337     DrawText(mSX+32,   mSY+4*32, "Customize", FONT_MENU_1);
2338   }
2339
2340   DrawText(mSX+32, mSY+5*32, "Actual Settings:", FONT_MENU_1);
2341   drawCursorXY(1, 4, IMG_MENU_BUTTON_LEFT);
2342   drawCursorXY(1, 5, IMG_MENU_BUTTON_RIGHT);
2343   drawCursorXY(1, 6, IMG_MENU_BUTTON_UP);
2344   drawCursorXY(1, 7, IMG_MENU_BUTTON_DOWN);
2345   DrawText(mSX+2*32, mSY+6*32, ":", FONT_VALUE_OLD);
2346   DrawText(mSX+2*32, mSY+7*32, ":", FONT_VALUE_OLD);
2347   DrawText(mSX+2*32, mSY+8*32, ":", FONT_VALUE_OLD);
2348   DrawText(mSX+2*32, mSY+9*32, ":", FONT_VALUE_OLD);
2349   DrawText(mSX+32, mSY+10*32, "Snap Field:", FONT_VALUE_OLD);
2350   DrawText(mSX+32, mSY+12*32, "Place Bomb:", FONT_VALUE_OLD);
2351
2352   for (i = 0; i < 6; i++)
2353   {
2354     int ypos = 6 + i + (i > 3 ? i-3 : 0);
2355
2356     DrawText(mSX + 3*32, mSY + ypos*32,
2357              "              ", FONT_VALUE_1);
2358     DrawText(mSX + 3*32, mSY + ypos*32,
2359              (setup.input[player_nr].use_joystick ?
2360               custom[i].text :
2361               getKeyNameFromKey(*custom[i].key)), FONT_VALUE_1);
2362   }
2363 }
2364
2365 void HandleSetupScreen_Input(int mx, int my, int dx, int dy, int button)
2366 {
2367   static int choice = 0;
2368   static int player_nr = 0;
2369   int x = 0;
2370   int y = choice;
2371   int pos_start  = SETUPINPUT_SCREEN_POS_START;
2372   int pos_empty1 = SETUPINPUT_SCREEN_POS_EMPTY1;
2373   int pos_empty2 = SETUPINPUT_SCREEN_POS_EMPTY2;
2374   int pos_end    = SETUPINPUT_SCREEN_POS_END;
2375
2376   if (button == MB_MENU_INITIALIZE)
2377   {
2378     drawPlayerSetupInputInfo(player_nr);
2379     drawCursor(choice, FC_RED);
2380
2381     return;
2382   }
2383   else if (button == MB_MENU_LEAVE)
2384   {
2385     setup_mode = SETUP_MODE_MAIN;
2386     DrawSetupScreen();
2387     InitJoysticks();
2388
2389     return;
2390   }
2391
2392   if (mx || my)         /* mouse input */
2393   {
2394     x = (mx - mSX) / 32;
2395     y = (my - mSY) / 32 - MENU_SCREEN_START_YPOS;
2396   }
2397   else if (dx || dy)    /* keyboard input */
2398   {
2399     if (dx && choice == 0)
2400       x = (dx < 0 ? 10 : 12);
2401     else if ((dx && choice == 1) ||
2402              (dx == +1 && choice == 2) ||
2403              (dx == -1 && choice == pos_end))
2404       button = MB_MENU_CHOICE;
2405     else if (dy)
2406       y = choice + dy;
2407
2408     if (y >= pos_empty1 && y <= pos_empty2)
2409       y = (dy > 0 ? pos_empty2 + 1 : pos_empty1 - 1);
2410   }
2411
2412   if (IN_GFX_SCREEN(mx, my) &&
2413       y == 0 && ((x < 10 && !button) || ((x == 10 || x == 12) && button)))
2414   {
2415     static unsigned long delay = 0;
2416
2417     if (!DelayReached(&delay, GADGET_FRAME_DELAY))
2418       return;
2419
2420     player_nr = (player_nr + (x == 10 ? -1 : +1) + MAX_PLAYERS) % MAX_PLAYERS;
2421
2422     drawPlayerSetupInputInfo(player_nr);
2423   }
2424   else if (IN_GFX_SCREEN(mx, my) &&
2425            y >= pos_start && y <= pos_end &&
2426            !(y >= pos_empty1 && y <= pos_empty2))
2427   {
2428     if (button)
2429     {
2430       if (y != choice)
2431       {
2432         drawCursor(y, FC_RED);
2433         drawCursor(choice, FC_BLUE);
2434         choice = y;
2435       }
2436     }
2437     else
2438     {
2439       if (y == 1)
2440       {
2441         char *device_name = setup.input[player_nr].joy.device_name;
2442
2443         if (!setup.input[player_nr].use_joystick)
2444         {
2445           int new_device_nr = (dx >= 0 ? 0 : MAX_PLAYERS - 1);
2446
2447           setJoystickDeviceToNr(device_name, new_device_nr);
2448           setup.input[player_nr].use_joystick = TRUE;
2449         }
2450         else
2451         {
2452           int device_nr = getJoystickNrFromDeviceName(device_name);
2453           int new_device_nr = device_nr + (dx >= 0 ? +1 : -1);
2454
2455           if (new_device_nr < 0 || new_device_nr >= MAX_PLAYERS)
2456             setup.input[player_nr].use_joystick = FALSE;
2457           else
2458             setJoystickDeviceToNr(device_name, new_device_nr);
2459         }
2460
2461         drawPlayerSetupInputInfo(player_nr);
2462       }
2463       else if (y == 2)
2464       {
2465         if (setup.input[player_nr].use_joystick)
2466         {
2467           InitJoysticks();
2468           CalibrateJoystick(player_nr);
2469         }
2470         else
2471           CustomizeKeyboard(player_nr);
2472       }
2473       else if (y == pos_end)
2474       {
2475         InitJoysticks();
2476
2477         setup_mode = SETUP_MODE_MAIN;
2478         DrawSetupScreen();
2479       }
2480     }
2481   }
2482 }
2483
2484 void CustomizeKeyboard(int player_nr)
2485 {
2486   int i;
2487   int step_nr;
2488   boolean finished = FALSE;
2489   static struct SetupKeyboardInfo custom_key;
2490   static struct
2491   {
2492     Key *key;
2493     char *text;
2494   } customize_step[] =
2495   {
2496     { &custom_key.left,  "Move Left"  },
2497     { &custom_key.right, "Move Right" },
2498     { &custom_key.up,    "Move Up"    },
2499     { &custom_key.down,  "Move Down"  },
2500     { &custom_key.snap,  "Snap Field" },
2501     { &custom_key.bomb,  "Place Bomb" }
2502   };
2503
2504   /* read existing key bindings from player setup */
2505   custom_key = setup.input[player_nr].key;
2506
2507   ClearWindow();
2508   DrawText(mSX + 16, mSY + 16, "Keyboard Input", FONT_TITLE_1);
2509
2510   BackToFront();
2511   InitAnimation();
2512
2513   step_nr = 0;
2514   DrawText(mSX, mSY + (2+2*step_nr)*32,
2515            customize_step[step_nr].text, FONT_INPUT_1_ACTIVE);
2516   DrawText(mSX, mSY + (2+2*step_nr+1)*32,
2517            "Key:", FONT_INPUT_1_ACTIVE);
2518   DrawText(mSX + 4*32, mSY + (2+2*step_nr+1)*32,
2519            getKeyNameFromKey(*customize_step[step_nr].key), FONT_VALUE_OLD);
2520
2521   while (!finished)
2522   {
2523     if (PendingEvent())         /* got event */
2524     {
2525       Event event;
2526
2527       NextEvent(&event);
2528
2529       switch(event.type)
2530       {
2531         case EVENT_KEYPRESS:
2532           {
2533             Key key = GetEventKey((KeyEvent *)&event, FALSE);
2534
2535             if (key == KSYM_Escape || (key == KSYM_Return && step_nr == 6))
2536             {
2537               finished = TRUE;
2538               break;
2539             }
2540
2541             /* all keys configured -- wait for "Escape" or "Return" key */
2542             if (step_nr == 6)
2543               break;
2544
2545             /* press 'Enter' to keep the existing key binding */
2546             if (key == KSYM_Return)
2547               key = *customize_step[step_nr].key;
2548
2549             /* check if key already used */
2550             for (i = 0; i < step_nr; i++)
2551               if (*customize_step[i].key == key)
2552                 break;
2553             if (i < step_nr)
2554               break;
2555
2556             /* got new key binding */
2557             *customize_step[step_nr].key = key;
2558             DrawText(mSX + 4*32, mSY + (2+2*step_nr+1)*32,
2559                      "             ", FONT_VALUE_1);
2560             DrawText(mSX + 4*32, mSY + (2+2*step_nr+1)*32,
2561                      getKeyNameFromKey(key), FONT_VALUE_1);
2562             step_nr++;
2563
2564             /* un-highlight last query */
2565             DrawText(mSX, mSY+(2+2*(step_nr-1))*32,
2566                      customize_step[step_nr-1].text, FONT_MENU_1);
2567             DrawText(mSX, mSY+(2+2*(step_nr-1)+1)*32,
2568                      "Key:", FONT_MENU_1);
2569
2570             /* press 'Enter' to leave */
2571             if (step_nr == 6)
2572             {
2573               DrawText(mSX + 16, mSY + 15*32+16,
2574                        "Press Enter", FONT_TITLE_1);
2575               break;
2576             }
2577
2578             /* query next key binding */
2579             DrawText(mSX, mSY+(2+2*step_nr)*32,
2580                      customize_step[step_nr].text, FONT_INPUT_1_ACTIVE);
2581             DrawText(mSX, mSY+(2+2*step_nr+1)*32,
2582                      "Key:", FONT_INPUT_1_ACTIVE);
2583             DrawText(mSX + 4*32, mSY+(2+2*step_nr+1)*32,
2584                      getKeyNameFromKey(*customize_step[step_nr].key),
2585                      FONT_VALUE_OLD);
2586           }
2587           break;
2588
2589         case EVENT_KEYRELEASE:
2590           key_joystick_mapping = 0;
2591           break;
2592
2593         default:
2594           HandleOtherEvents(&event);
2595           break;
2596       }
2597     }
2598
2599     DoAnimation();
2600     BackToFront();
2601
2602     /* don't eat all CPU time */
2603     Delay(10);
2604   }
2605
2606   /* write new key bindings back to player setup */
2607   setup.input[player_nr].key = custom_key;
2608
2609   StopAnimation();
2610   DrawSetupScreen_Input();
2611 }
2612
2613 static boolean CalibrateJoystickMain(int player_nr)
2614 {
2615   int new_joystick_xleft = JOYSTICK_XMIDDLE;
2616   int new_joystick_xright = JOYSTICK_XMIDDLE;
2617   int new_joystick_yupper = JOYSTICK_YMIDDLE;
2618   int new_joystick_ylower = JOYSTICK_YMIDDLE;
2619   int new_joystick_xmiddle, new_joystick_ymiddle;
2620
2621   int joystick_fd = joystick.fd[player_nr];
2622   int x, y, last_x, last_y, xpos = 8, ypos = 3;
2623   boolean check[3][3];
2624   int check_remaining = 3 * 3;
2625   int joy_x, joy_y;
2626   int joy_value;
2627   int result = -1;
2628
2629   if (joystick.status == JOYSTICK_NOT_AVAILABLE)
2630     return FALSE;
2631
2632   if (joystick_fd < 0 || !setup.input[player_nr].use_joystick)
2633     return FALSE;
2634
2635   ClearWindow();
2636
2637   for (y = 0; y < 3; y++)
2638   {
2639     for (x = 0; x < 3; x++)
2640     {
2641       DrawGraphic(xpos + x - 1, ypos + y - 1, IMG_MENU_CALIBRATE_BLUE, 0);
2642       check[x][y] = FALSE;
2643     }
2644   }
2645
2646   DrawText(mSX,      mSY +  6 * 32, " ROTATE JOYSTICK ", FONT_TITLE_1);
2647   DrawText(mSX,      mSY +  7 * 32, "IN ALL DIRECTIONS", FONT_TITLE_1);
2648   DrawText(mSX + 16, mSY +  9 * 32, "  IF ALL BALLS  ",  FONT_TITLE_1);
2649   DrawText(mSX,      mSY + 10 * 32, "   ARE YELLOW,   ", FONT_TITLE_1);
2650   DrawText(mSX,      mSY + 11 * 32, " CENTER JOYSTICK ", FONT_TITLE_1);
2651   DrawText(mSX,      mSY + 12 * 32, "       AND       ", FONT_TITLE_1);
2652   DrawText(mSX,      mSY + 13 * 32, "PRESS ANY BUTTON!", FONT_TITLE_1);
2653
2654   joy_value = Joystick(player_nr);
2655   last_x = (joy_value & JOY_LEFT ? -1 : joy_value & JOY_RIGHT ? +1 : 0);
2656   last_y = (joy_value & JOY_UP   ? -1 : joy_value & JOY_DOWN  ? +1 : 0);
2657
2658   /* eventually uncalibrated center position (joystick could be uncentered) */
2659   if (!ReadJoystick(joystick_fd, &joy_x, &joy_y, NULL, NULL))
2660     return FALSE;
2661
2662   new_joystick_xmiddle = joy_x;
2663   new_joystick_ymiddle = joy_y;
2664
2665   DrawGraphic(xpos + last_x, ypos + last_y, IMG_MENU_CALIBRATE_RED, 0);
2666   BackToFront();
2667
2668   while (Joystick(player_nr) & JOY_BUTTON);     /* wait for released button */
2669   InitAnimation();
2670
2671   while (result < 0)
2672   {
2673     if (PendingEvent())         /* got event */
2674     {
2675       Event event;
2676
2677       NextEvent(&event);
2678
2679       switch(event.type)
2680       {
2681         case EVENT_KEYPRESS:
2682           switch(GetEventKey((KeyEvent *)&event, TRUE))
2683           {
2684             case KSYM_Return:
2685               if (check_remaining == 0)
2686                 result = 1;
2687               break;
2688
2689             case KSYM_Escape:
2690               result = 0;
2691               break;
2692
2693             default:
2694               break;
2695           }
2696           break;
2697
2698         case EVENT_KEYRELEASE:
2699           key_joystick_mapping = 0;
2700           break;
2701
2702         default:
2703           HandleOtherEvents(&event);
2704           break;
2705       }
2706     }
2707
2708     if (!ReadJoystick(joystick_fd, &joy_x, &joy_y, NULL, NULL))
2709       return FALSE;
2710
2711     new_joystick_xleft  = MIN(new_joystick_xleft,  joy_x);
2712     new_joystick_xright = MAX(new_joystick_xright, joy_x);
2713     new_joystick_yupper = MIN(new_joystick_yupper, joy_y);
2714     new_joystick_ylower = MAX(new_joystick_ylower, joy_y);
2715
2716     setup.input[player_nr].joy.xleft = new_joystick_xleft;
2717     setup.input[player_nr].joy.yupper = new_joystick_yupper;
2718     setup.input[player_nr].joy.xright = new_joystick_xright;
2719     setup.input[player_nr].joy.ylower = new_joystick_ylower;
2720     setup.input[player_nr].joy.xmiddle = new_joystick_xmiddle;
2721     setup.input[player_nr].joy.ymiddle = new_joystick_ymiddle;
2722
2723     CheckJoystickData();
2724
2725     joy_value = Joystick(player_nr);
2726
2727     if (joy_value & JOY_BUTTON && check_remaining == 0)
2728       result = 1;
2729
2730     x = (joy_value & JOY_LEFT ? -1 : joy_value & JOY_RIGHT ? +1 : 0);
2731     y = (joy_value & JOY_UP   ? -1 : joy_value & JOY_DOWN  ? +1 : 0);
2732
2733     if (x != last_x || y != last_y)
2734     {
2735       DrawGraphic(xpos + last_x, ypos + last_y, IMG_MENU_CALIBRATE_YELLOW, 0);
2736       DrawGraphic(xpos + x,      ypos + y,      IMG_MENU_CALIBRATE_RED,    0);
2737
2738       last_x = x;
2739       last_y = y;
2740
2741       if (check_remaining > 0 && !check[x+1][y+1])
2742       {
2743         check[x+1][y+1] = TRUE;
2744         check_remaining--;
2745       }
2746
2747 #if 0
2748 #ifdef DEBUG
2749       printf("LEFT / MIDDLE / RIGHT == %d / %d / %d\n",
2750              setup.input[player_nr].joy.xleft,
2751              setup.input[player_nr].joy.xmiddle,
2752              setup.input[player_nr].joy.xright);
2753       printf("UP / MIDDLE / DOWN == %d / %d / %d\n",
2754              setup.input[player_nr].joy.yupper,
2755              setup.input[player_nr].joy.ymiddle,
2756              setup.input[player_nr].joy.ylower);
2757 #endif
2758 #endif
2759
2760     }
2761
2762     DoAnimation();
2763     BackToFront();
2764
2765     /* don't eat all CPU time */
2766     Delay(10);
2767   }
2768
2769   /* calibrated center position (joystick should now be centered) */
2770   if (!ReadJoystick(joystick_fd, &joy_x, &joy_y, NULL, NULL))
2771     return FALSE;
2772
2773   new_joystick_xmiddle = joy_x;
2774   new_joystick_ymiddle = joy_y;
2775
2776   StopAnimation();
2777
2778   DrawSetupScreen_Input();
2779
2780   /* wait until the last pressed button was released */
2781   while (Joystick(player_nr) & JOY_BUTTON)
2782   {
2783     if (PendingEvent())         /* got event */
2784     {
2785       Event event;
2786
2787       NextEvent(&event);
2788       HandleOtherEvents(&event);
2789
2790       Delay(10);
2791     }
2792   }
2793
2794   return TRUE;
2795 }
2796
2797 void CalibrateJoystick(int player_nr)
2798 {
2799   if (!CalibrateJoystickMain(player_nr))
2800   {
2801     ClearWindow();
2802
2803     DrawText(mSX + 16, mSY + 6 * 32, "  JOYSTICK NOT  ",  FONT_TITLE_1);
2804     DrawText(mSX,      mSY + 7 * 32, "    AVAILABLE    ", FONT_TITLE_1);
2805     BackToFront();
2806     Delay(2000);        /* show error message for two seconds */
2807   }
2808 }
2809
2810 void DrawSetupScreen()
2811 {
2812   DeactivateJoystick();
2813
2814   SetMainBackgroundImage(IMG_BACKGROUND_SETUP);
2815
2816   if (setup_mode == SETUP_MODE_INPUT)
2817     DrawSetupScreen_Input();
2818   else if (setup_mode == SETUP_MODE_CHOOSE_GRAPHICS)
2819     DrawChooseTree(&artwork.gfx_current);
2820   else if (setup_mode == SETUP_MODE_CHOOSE_SOUNDS)
2821     DrawChooseTree(&artwork.snd_current);
2822   else if (setup_mode == SETUP_MODE_CHOOSE_MUSIC)
2823     DrawChooseTree(&artwork.mus_current);
2824   else
2825     DrawSetupScreen_Generic();
2826
2827   PlayMenuSound();
2828   PlayMenuMusic();
2829 }
2830
2831 void HandleSetupScreen(int mx, int my, int dx, int dy, int button)
2832 {
2833   if (setup_mode == SETUP_MODE_INPUT)
2834     HandleSetupScreen_Input(mx, my, dx, dy, button);
2835   else if (setup_mode == SETUP_MODE_CHOOSE_GRAPHICS)
2836     HandleChooseTree(mx, my, dx, dy, button, &artwork.gfx_current);
2837   else if (setup_mode == SETUP_MODE_CHOOSE_SOUNDS)
2838     HandleChooseTree(mx, my, dx, dy, button, &artwork.snd_current);
2839   else if (setup_mode == SETUP_MODE_CHOOSE_MUSIC)
2840     HandleChooseTree(mx, my, dx, dy, button, &artwork.mus_current);
2841   else
2842     HandleSetupScreen_Generic(mx, my, dx, dy, button);
2843
2844   DoAnimation();
2845 }
2846
2847 void HandleGameActions()
2848 {
2849   if (game_status != GAME_MODE_PLAYING)
2850     return;
2851
2852   if (local_player->LevelSolved)
2853     GameWon();
2854
2855   if (AllPlayersGone && !TAPE_IS_STOPPED(tape))
2856     TapeStop();
2857
2858   GameActions();
2859   BackToFront();
2860
2861   if (tape.auto_play && !tape.playing)
2862     AutoPlayTape();     /* continue automatically playing next tape */
2863 }
2864
2865 /* ---------- new screen button stuff -------------------------------------- */
2866
2867 /* graphic position and size values for buttons and scrollbars */
2868 #define SC_SCROLLBUTTON_XSIZE           TILEX
2869 #define SC_SCROLLBUTTON_YSIZE           TILEY
2870
2871 #define SC_SCROLL_VERTICAL_XSIZE        SC_SCROLLBUTTON_XSIZE
2872 #define SC_SCROLL_VERTICAL_YSIZE        ((MAX_MENU_ENTRIES_ON_SCREEN - 2) * \
2873                                          SC_SCROLLBUTTON_YSIZE)
2874 #define SC_SCROLL_UP_XPOS               (SXSIZE - SC_SCROLLBUTTON_XSIZE)
2875 #define SC_SCROLL_UP_YPOS               (2 * SC_SCROLLBUTTON_YSIZE)
2876 #define SC_SCROLL_VERTICAL_XPOS         SC_SCROLL_UP_XPOS
2877 #define SC_SCROLL_VERTICAL_YPOS         (SC_SCROLL_UP_YPOS + \
2878                                          SC_SCROLLBUTTON_YSIZE)
2879 #define SC_SCROLL_DOWN_XPOS             SC_SCROLL_UP_XPOS
2880 #define SC_SCROLL_DOWN_YPOS             (SC_SCROLL_VERTICAL_YPOS + \
2881                                          SC_SCROLL_VERTICAL_YSIZE)
2882
2883 #define SC_BORDER_SIZE                  14
2884
2885 static struct
2886 {
2887   int gfx_unpressed, gfx_pressed;
2888   int x, y;
2889   int gadget_id;
2890   char *infotext;
2891 } scrollbutton_info[NUM_SCREEN_SCROLLBUTTONS] =
2892 {
2893   {
2894     IMG_MENU_BUTTON_UP, IMG_MENU_BUTTON_UP_ACTIVE,
2895     SC_SCROLL_UP_XPOS, SC_SCROLL_UP_YPOS,
2896     SCREEN_CTRL_ID_SCROLL_UP,
2897     "scroll up"
2898   },
2899   {
2900     IMG_MENU_BUTTON_DOWN, IMG_MENU_BUTTON_DOWN_ACTIVE,
2901     SC_SCROLL_DOWN_XPOS, SC_SCROLL_DOWN_YPOS,
2902     SCREEN_CTRL_ID_SCROLL_DOWN,
2903     "scroll down"
2904   }
2905 };
2906
2907 static struct
2908 {
2909 #if defined(TARGET_X11_NATIVE_PERFORMANCE_WORKAROUND)
2910   Bitmap **gfx_unpressed, **gfx_pressed;
2911 #else
2912   int gfx_unpressed, gfx_pressed;
2913 #endif
2914   int x, y;
2915   int width, height;
2916   int type;
2917   int gadget_id;
2918   char *infotext;
2919 } scrollbar_info[NUM_SCREEN_SCROLLBARS] =
2920 {
2921   {
2922 #if defined(TARGET_X11_NATIVE_PERFORMANCE_WORKAROUND)
2923     &scrollbar_bitmap[0], &scrollbar_bitmap[1],
2924 #else
2925     IMG_MENU_SCROLLBAR, IMG_MENU_SCROLLBAR_ACTIVE,
2926 #endif
2927     SC_SCROLL_VERTICAL_XPOS, SC_SCROLL_VERTICAL_YPOS,
2928     SC_SCROLL_VERTICAL_XSIZE, SC_SCROLL_VERTICAL_YSIZE,
2929     GD_TYPE_SCROLLBAR_VERTICAL,
2930     SCREEN_CTRL_ID_SCROLL_VERTICAL,
2931     "scroll level series vertically"
2932   }
2933 };
2934
2935 static void CreateScreenScrollbuttons()
2936 {
2937   struct GadgetInfo *gi;
2938   unsigned long event_mask;
2939   int i;
2940
2941   for (i = 0; i < NUM_SCREEN_SCROLLBUTTONS; i++)
2942   {
2943     Bitmap *gd_bitmap_unpressed, *gd_bitmap_pressed;
2944     int gfx_unpressed, gfx_pressed;
2945     int x, y, width, height;
2946     int gd_x1, gd_x2, gd_y1, gd_y2;
2947     int id = scrollbutton_info[i].gadget_id;
2948
2949     event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
2950
2951     x = mSX + scrollbutton_info[i].x + menu.scrollbar_xoffset;
2952     y = mSY + scrollbutton_info[i].y;
2953     width = SC_SCROLLBUTTON_XSIZE;
2954     height = SC_SCROLLBUTTON_YSIZE;
2955
2956     if (id == SCREEN_CTRL_ID_SCROLL_DOWN)
2957       y = mSY + (SC_SCROLL_VERTICAL_YPOS +
2958                  (NUM_MENU_ENTRIES_ON_SCREEN - 2) * SC_SCROLLBUTTON_YSIZE);
2959
2960     gfx_unpressed = scrollbutton_info[i].gfx_unpressed;
2961     gfx_pressed   = scrollbutton_info[i].gfx_pressed;
2962     gd_bitmap_unpressed = graphic_info[gfx_unpressed].bitmap;
2963     gd_bitmap_pressed   = graphic_info[gfx_pressed].bitmap;
2964     gd_x1 = graphic_info[gfx_unpressed].src_x;
2965     gd_y1 = graphic_info[gfx_unpressed].src_y;
2966     gd_x2 = graphic_info[gfx_pressed].src_x;
2967     gd_y2 = graphic_info[gfx_pressed].src_y;
2968
2969     gi = CreateGadget(GDI_CUSTOM_ID, id,
2970                       GDI_CUSTOM_TYPE_ID, i,
2971                       GDI_INFO_TEXT, scrollbutton_info[i].infotext,
2972                       GDI_X, x,
2973                       GDI_Y, y,
2974                       GDI_WIDTH, width,
2975                       GDI_HEIGHT, height,
2976                       GDI_TYPE, GD_TYPE_NORMAL_BUTTON,
2977                       GDI_STATE, GD_BUTTON_UNPRESSED,
2978                       GDI_DESIGN_UNPRESSED, gd_bitmap_unpressed, gd_x1, gd_y1,
2979                       GDI_DESIGN_PRESSED, gd_bitmap_pressed, gd_x2, gd_y2,
2980                       GDI_DIRECT_DRAW, FALSE,
2981                       GDI_EVENT_MASK, event_mask,
2982                       GDI_CALLBACK_ACTION, HandleScreenGadgets,
2983                       GDI_END);
2984
2985     if (gi == NULL)
2986       Error(ERR_EXIT, "cannot create gadget");
2987
2988     screen_gadget[id] = gi;
2989   }
2990 }
2991
2992 static void CreateScreenScrollbars()
2993 {
2994   int i;
2995
2996   for (i = 0; i < NUM_SCREEN_SCROLLBARS; i++)
2997   {
2998     Bitmap *gd_bitmap_unpressed, *gd_bitmap_pressed;
2999 #if !defined(TARGET_X11_NATIVE_PERFORMANCE_WORKAROUND)
3000     int gfx_unpressed, gfx_pressed;
3001 #endif
3002     int x, y, width, height;
3003     int gd_x1, gd_x2, gd_y1, gd_y2;
3004     struct GadgetInfo *gi;
3005     int items_max, items_visible, item_position;
3006     unsigned long event_mask;
3007     int num_page_entries = NUM_MENU_ENTRIES_ON_SCREEN;
3008     int id = scrollbar_info[i].gadget_id;
3009
3010     event_mask = GD_EVENT_MOVING | GD_EVENT_OFF_BORDERS;
3011
3012     x = mSX + scrollbar_info[i].x + menu.scrollbar_xoffset;
3013     y = mSY + scrollbar_info[i].y;
3014     width  = scrollbar_info[i].width;
3015     height = scrollbar_info[i].height;
3016
3017     if (id == SCREEN_CTRL_ID_SCROLL_VERTICAL)
3018       height = (NUM_MENU_ENTRIES_ON_SCREEN - 2) * SC_SCROLLBUTTON_YSIZE;
3019
3020     items_max = num_page_entries;
3021     items_visible = num_page_entries;
3022     item_position = 0;
3023
3024 #if defined(TARGET_X11_NATIVE_PERFORMANCE_WORKAROUND)
3025     gd_bitmap_unpressed = *scrollbar_info[i].gfx_unpressed;
3026     gd_bitmap_pressed   = *scrollbar_info[i].gfx_pressed;
3027     gd_x1 = 0;
3028     gd_y1 = 0;
3029     gd_x2 = 0;
3030     gd_y2 = 0;
3031 #else
3032     gfx_unpressed = scrollbar_info[i].gfx_unpressed;
3033     gfx_pressed   = scrollbar_info[i].gfx_pressed;
3034     gd_bitmap_unpressed = graphic_info[gfx_unpressed].bitmap;
3035     gd_bitmap_pressed   = graphic_info[gfx_pressed].bitmap;
3036     gd_x1 = graphic_info[gfx_unpressed].src_x;
3037     gd_y1 = graphic_info[gfx_unpressed].src_y;
3038     gd_x2 = graphic_info[gfx_pressed].src_x;
3039     gd_y2 = graphic_info[gfx_pressed].src_y;
3040 #endif
3041
3042     gi = CreateGadget(GDI_CUSTOM_ID, id,
3043                       GDI_CUSTOM_TYPE_ID, i,
3044                       GDI_INFO_TEXT, scrollbar_info[i].infotext,
3045                       GDI_X, x,
3046                       GDI_Y, y,
3047                       GDI_WIDTH, width,
3048                       GDI_HEIGHT, height,
3049                       GDI_TYPE, scrollbar_info[i].type,
3050                       GDI_SCROLLBAR_ITEMS_MAX, items_max,
3051                       GDI_SCROLLBAR_ITEMS_VISIBLE, items_visible,
3052                       GDI_SCROLLBAR_ITEM_POSITION, item_position,
3053                       GDI_STATE, GD_BUTTON_UNPRESSED,
3054                       GDI_DESIGN_UNPRESSED, gd_bitmap_unpressed, gd_x1, gd_y1,
3055                       GDI_DESIGN_PRESSED, gd_bitmap_pressed, gd_x2, gd_y2,
3056                       GDI_BORDER_SIZE, SC_BORDER_SIZE, SC_BORDER_SIZE,
3057                       GDI_DIRECT_DRAW, FALSE,
3058                       GDI_EVENT_MASK, event_mask,
3059                       GDI_CALLBACK_ACTION, HandleScreenGadgets,
3060                       GDI_END);
3061
3062     if (gi == NULL)
3063       Error(ERR_EXIT, "cannot create gadget");
3064
3065     screen_gadget[id] = gi;
3066   }
3067 }
3068
3069 void CreateScreenGadgets()
3070 {
3071   int last_game_status = game_status;   /* save current game status */
3072
3073 #if defined(TARGET_X11_NATIVE_PERFORMANCE_WORKAROUND)
3074   int i;
3075
3076   for (i = 0; i < NUM_SCROLLBAR_BITMAPS; i++)
3077   {
3078     scrollbar_bitmap[i] = CreateBitmap(TILEX, TILEY, DEFAULT_DEPTH);
3079
3080     /* copy pointers to clip mask and GC */
3081     scrollbar_bitmap[i]->clip_mask =
3082       graphic_info[IMG_MENU_SCROLLBAR + i].clip_mask;
3083     scrollbar_bitmap[i]->stored_clip_gc =
3084       graphic_info[IMG_MENU_SCROLLBAR + i].clip_gc;
3085
3086     BlitBitmap(graphic_info[IMG_MENU_SCROLLBAR + i].bitmap,
3087                scrollbar_bitmap[i],
3088                graphic_info[IMG_MENU_SCROLLBAR + i].src_x,
3089                graphic_info[IMG_MENU_SCROLLBAR + i].src_y,
3090                TILEX, TILEY, 0, 0);
3091   }
3092 #endif
3093
3094   /* force LEVELS draw offset for scrollbar / scrollbutton gadgets */
3095   game_status = GAME_MODE_LEVELS;
3096
3097   CreateScreenScrollbuttons();
3098   CreateScreenScrollbars();
3099
3100   game_status = last_game_status;       /* restore current game status */
3101 }
3102
3103 void FreeScreenGadgets()
3104 {
3105   int i;
3106
3107 #if defined(TARGET_X11_NATIVE_PERFORMANCE_WORKAROUND)
3108   for (i = 0; i < NUM_SCROLLBAR_BITMAPS; i++)
3109   {
3110     /* prevent freeing clip mask and GC twice */
3111     scrollbar_bitmap[i]->clip_mask = None;
3112     scrollbar_bitmap[i]->stored_clip_gc = None;
3113
3114     FreeBitmap(scrollbar_bitmap[i]);
3115   }
3116 #endif
3117
3118   for (i = 0; i < NUM_SCREEN_GADGETS; i++)
3119     FreeGadget(screen_gadget[i]);
3120 }
3121
3122 void MapChooseTreeGadgets(TreeInfo *ti)
3123 {
3124   int num_entries = numTreeInfoInGroup(ti);
3125   int i;
3126
3127   if (num_entries <= NUM_MENU_ENTRIES_ON_SCREEN)
3128     return;
3129
3130   for (i = 0; i < NUM_SCREEN_GADGETS; i++)
3131     MapGadget(screen_gadget[i]);
3132 }
3133
3134 #if 0
3135 void UnmapChooseTreeGadgets()
3136 {
3137   int i;
3138
3139   for (i = 0; i < NUM_SCREEN_GADGETS; i++)
3140     UnmapGadget(screen_gadget[i]);
3141 }
3142 #endif
3143
3144 static void HandleScreenGadgets(struct GadgetInfo *gi)
3145 {
3146   int id = gi->custom_id;
3147
3148   if (game_status != GAME_MODE_LEVELS && game_status != GAME_MODE_SETUP)
3149     return;
3150
3151   switch (id)
3152   {
3153     case SCREEN_CTRL_ID_SCROLL_UP:
3154       if (game_status == GAME_MODE_LEVELS)
3155         HandleChooseLevel(0,0, 0, -1 * SCROLL_LINE, MB_MENU_MARK);
3156       else if (game_status == GAME_MODE_SETUP)
3157         HandleSetupScreen(0,0, 0, -1 * SCROLL_LINE, MB_MENU_MARK);
3158       break;
3159
3160     case SCREEN_CTRL_ID_SCROLL_DOWN:
3161       if (game_status == GAME_MODE_LEVELS)
3162         HandleChooseLevel(0,0, 0, +1 * SCROLL_LINE, MB_MENU_MARK);
3163       else if (game_status == GAME_MODE_SETUP)
3164         HandleSetupScreen(0,0, 0, +1 * SCROLL_LINE, MB_MENU_MARK);
3165       break;
3166
3167     case SCREEN_CTRL_ID_SCROLL_VERTICAL:
3168       if (game_status == GAME_MODE_LEVELS)
3169         HandleChooseLevel(0,0, 999,gi->event.item_position,MB_MENU_INITIALIZE);
3170       else if (game_status == GAME_MODE_SETUP)
3171         HandleSetupScreen(0,0, 999,gi->event.item_position,MB_MENU_INITIALIZE);
3172       break;
3173
3174     default:
3175       break;
3176   }
3177 }