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