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