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