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