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