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