rnd-20060228-1-src
[rocksndiamonds.git] / src / screens.c
1 /***********************************************************
2 * Rocks'n'Diamonds -- McDuffin Strikes Back!               *
3 *----------------------------------------------------------*
4 * (c) 1995-2002 Artsoft Entertainment                      *
5 *               Holger Schemel                             *
6 *               Detmolder Strasse 189                      *
7 *               33604 Bielefeld                            *
8 *               Germany                                    *
9 *               e-mail: info@artsoft.org                   *
10 *----------------------------------------------------------*
11 * screens.c                                                *
12 ***********************************************************/
13
14 #include "libgame/libgame.h"
15
16 #include "screens.h"
17 #include "events.h"
18 #include "game.h"
19 #include "tools.h"
20 #include "editor.h"
21 #include "files.h"
22 #include "tape.h"
23 #include "cartoons.h"
24 #include "network.h"
25 #include "init.h"
26
27 /* screens in the setup menu */
28 #define SETUP_MODE_MAIN                 0
29 #define SETUP_MODE_GAME                 1
30 #define SETUP_MODE_EDITOR               2
31 #define SETUP_MODE_INPUT                3
32 #define SETUP_MODE_SHORTCUT_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_SCREEN_MAX_XPOS            (SCR_FIELDX - 1)
65 #define MENU_TITLE1_YPOS                8
66 #define MENU_TITLE2_YPOS                46
67
68 /* buttons and scrollbars identifiers */
69 #define SCREEN_CTRL_ID_SCROLL_UP        0
70 #define SCREEN_CTRL_ID_SCROLL_DOWN      1
71 #define SCREEN_CTRL_ID_SCROLL_VERTICAL  2
72
73 #define NUM_SCREEN_SCROLLBUTTONS        2
74 #define NUM_SCREEN_SCROLLBARS           1
75 #define NUM_SCREEN_GADGETS              3
76
77 /* forward declarations of internal functions */
78 static void HandleScreenGadgets(struct GadgetInfo *);
79 static void HandleSetupScreen_Generic(int, int, int, int, int);
80 static void HandleSetupScreen_Input(int, int, int, int, int);
81 static void CustomizeKeyboard(int);
82 static void CalibrateJoystick(int);
83 static void execSetupArtwork(void);
84 static void HandleChooseTree(int, int, int, int, int, TreeInfo **);
85
86 static void DrawChooseLevel(void);
87 static void DrawInfoScreen(void);
88 static void DrawSetupScreen(void);
89
90 static void DrawInfoScreen_HelpAnim(int, int, boolean);
91 static void DrawInfoScreen_HelpText(int, int, int, int);
92 static void HandleInfoScreen_Main(int, int, int, int, int);
93 static void HandleInfoScreen_Elements(int);
94 static void HandleInfoScreen_Music(int);
95 static void HandleInfoScreen_Credits(int);
96 static void HandleInfoScreen_Program(int);
97
98 static void MapChooseTreeGadgets(TreeInfo *);
99
100 static struct GadgetInfo *screen_gadget[NUM_SCREEN_GADGETS];
101 static int setup_mode = SETUP_MODE_MAIN;
102 static int info_mode = INFO_MODE_MAIN;
103
104 #define DRAW_OFFSET_MODE(x)     (x >= GAME_MODE_MAIN &&                 \
105                                  x <= GAME_MODE_SETUP ? x :             \
106                                  x == GAME_MODE_PSEUDO_TYPENAME ?       \
107                                  GAME_MODE_MAIN : GAME_MODE_DEFAULT)
108
109 #define mSX (SX + menu.draw_xoffset[DRAW_OFFSET_MODE(game_status)])
110 #define mSY (SY + menu.draw_yoffset[DRAW_OFFSET_MODE(game_status)])
111
112 #define NUM_MENU_ENTRIES_ON_SCREEN (menu.list_size[game_status] > 2 ?   \
113                                     menu.list_size[game_status] :       \
114                                     MAX_MENU_ENTRIES_ON_SCREEN)
115
116 #if defined(TARGET_X11_NATIVE_PERFORMANCE_WORKAROUND)
117 #define NUM_SCROLLBAR_BITMAPS           2
118 static Bitmap *scrollbar_bitmap[NUM_SCROLLBAR_BITMAPS];
119 #endif
120
121
122 static void drawCursorExt(int xpos, int ypos, int color, int g)
123 {
124   static int cursor_array[SCR_FIELDY];
125
126   if (xpos == 0)
127   {
128     if (g != 0)
129       cursor_array[ypos] = g;
130     else
131       g = cursor_array[ypos];
132   }
133
134   if (color == FC_RED)
135     g = (g == IMG_MENU_BUTTON_LEFT  ? IMG_MENU_BUTTON_LEFT_ACTIVE  :
136          g == IMG_MENU_BUTTON_RIGHT ? IMG_MENU_BUTTON_RIGHT_ACTIVE :
137          g == IMG_MENU_BUTTON_LEAVE_MENU ? IMG_MENU_BUTTON_LEAVE_MENU_ACTIVE :
138          g == IMG_MENU_BUTTON_ENTER_MENU ? IMG_MENU_BUTTON_ENTER_MENU_ACTIVE :
139          IMG_MENU_BUTTON_ACTIVE);
140
141   ypos += MENU_SCREEN_START_YPOS;
142
143   DrawBackground(mSX + xpos * TILEX, mSY + ypos * TILEY, TILEX, TILEY);
144   DrawGraphicThruMaskExt(drawto, mSX + xpos * TILEX, mSY + ypos * TILEY, g, 0);
145 }
146
147 static void initCursor(int ypos, int graphic)
148 {
149   drawCursorExt(0, ypos, FC_BLUE, graphic);
150 }
151
152 static void drawCursor(int ypos, int color)
153 {
154   drawCursorExt(0, ypos, color, 0);
155 }
156
157 static void drawCursorXY(int xpos, int ypos, int graphic)
158 {
159   drawCursorExt(xpos, ypos, -1, graphic);
160 }
161
162 static void drawChooseTreeCursor(int ypos, int color)
163 {
164   int last_game_status = game_status;   /* save current game status */
165
166   /* force LEVELS draw offset on artwork setup screen */
167   game_status = GAME_MODE_LEVELS;
168
169   drawCursorExt(0, ypos, color, 0);
170
171   game_status = last_game_status;       /* restore current game status */
172 }
173
174 static void PlayMenuSound()
175 {
176   int sound = menu.sound[game_status];
177
178   if (sound == SND_UNDEFINED)
179     return;
180
181   if (sound_info[sound].loop)
182     PlaySoundLoop(sound);
183   else
184     PlaySound(sound);
185 }
186
187 static void PlayMenuSoundIfLoop()
188 {
189   int sound = menu.sound[game_status];
190
191   if (sound == SND_UNDEFINED)
192     return;
193
194   if (sound_info[sound].loop)
195     PlaySoundLoop(sound);
196 }
197
198 static void PlayMenuMusic()
199 {
200   int music = menu.music[game_status];
201
202   if (music == MUS_UNDEFINED)
203     return;
204
205   PlayMusic(music);
206 }
207
208 void DrawHeadline()
209 {
210   DrawTextSCentered(MENU_TITLE1_YPOS, FONT_TITLE_1, PROGRAM_TITLE_STRING);
211   DrawTextSCentered(MENU_TITLE2_YPOS, FONT_TITLE_2, PROGRAM_COPYRIGHT_STRING);
212 }
213
214 static void ToggleFullscreenIfNeeded()
215 {
216   if (setup.fullscreen != video.fullscreen_enabled)
217   {
218     /* save old door content */
219     BlitBitmap(backbuffer, bitmap_db_door,
220                DX, DY, DXSIZE, DYSIZE, DOOR_GFX_PAGEX1, DOOR_GFX_PAGEY1);
221
222     /* toggle fullscreen */
223     ChangeVideoModeIfNeeded(setup.fullscreen);
224     setup.fullscreen = video.fullscreen_enabled;
225
226     /* redraw background to newly created backbuffer */
227     BlitBitmap(graphic_info[IMG_GLOBAL_BORDER].bitmap, backbuffer,
228                0,0, WIN_XSIZE,WIN_YSIZE, 0,0);
229
230     /* restore old door content */
231     BlitBitmap(bitmap_db_door, backbuffer,
232                DOOR_GFX_PAGEX1, DOOR_GFX_PAGEY1, DXSIZE, DYSIZE, DX, DY);
233
234     redraw_mask = REDRAW_ALL;
235   }
236 }
237
238 void DrawMainMenu()
239 {
240   static LevelDirTree *leveldir_last_valid = NULL;
241   char *name_text = (!options.network && setup.team_mode ? "Team:" : "Name:");
242 #if 1
243   char *level_text = "Levelset";
244 #else
245   char *level_text = "Level:";
246 #endif
247   int name_width, level_width;
248   int i;
249
250   UnmapAllGadgets();
251   FadeSoundsAndMusic();
252
253   KeyboardAutoRepeatOn();
254   ActivateJoystick();
255
256   SetDrawDeactivationMask(REDRAW_NONE);
257   SetDrawBackgroundMask(REDRAW_FIELD);
258
259   audio.sound_deactivated = FALSE;
260
261   /* needed if last screen was the playing screen, invoked from level editor */
262   if (level_editor_test_game)
263   {
264     game_status = GAME_MODE_EDITOR;
265     DrawLevelEd();
266
267     return;
268   }
269
270   /* needed if last screen was the editor screen */
271   UndrawSpecialEditorDoor();
272
273   /* needed if last screen was the setup screen and fullscreen state changed */
274   ToggleFullscreenIfNeeded();
275
276   /* leveldir_current may be invalid (level group, parent link) */
277   if (!validLevelSeries(leveldir_current))
278     leveldir_current = getFirstValidTreeInfoEntry(leveldir_last_valid);
279
280   /* store valid level series information */
281   leveldir_last_valid = leveldir_current;
282
283   /* needed if last screen (level choice) changed graphics, sounds or music */
284   ReloadCustomArtwork(0);
285
286 #ifdef TARGET_SDL
287   SetDrawtoField(DRAW_BACKBUFFER);
288 #endif
289
290 #if 0
291   /* map gadgets for main menu screen */
292   MapTapeButtons();
293 #endif
294
295   /* level_nr may have been set to value over handicap with level editor */
296   if (setup.handicap && level_nr > leveldir_current->handicap_level)
297     level_nr = leveldir_current->handicap_level;
298
299   GetPlayerConfig();
300   LoadLevel(level_nr);
301
302   SetMainBackgroundImage(IMG_BACKGROUND_MAIN);
303   ClearWindow();
304
305   DrawHeadline();
306
307   DrawText(mSX + 32, mSY + 2 * 32, name_text,       FONT_MENU_1);
308   DrawText(mSX + 32, mSY + 3 * 32, level_text,      FONT_MENU_1);
309   DrawText(mSX + 32, mSY + 4 * 32, "Hall Of Fame",  FONT_MENU_1);
310   DrawText(mSX + 32, mSY + 5 * 32, "Level Creator", FONT_MENU_1);
311   DrawText(mSX + 32, mSY + 6 * 32, "Info Screen",   FONT_MENU_1);
312   DrawText(mSX + 32, mSY + 7 * 32, "Start Game",    FONT_MENU_1);
313   DrawText(mSX + 32, mSY + 8 * 32, "Setup",         FONT_MENU_1);
314   DrawText(mSX + 32, mSY + 9 * 32, "Quit",          FONT_MENU_1);
315
316   /* calculated after (possible) reload of custom artwork */
317   name_width  = getTextWidth(name_text,  FONT_MENU_1);
318 #if 1
319   level_width = 9 * getFontWidth(FONT_MENU_1);
320 #else
321   level_width = getTextWidth(level_text, FONT_MENU_1);
322 #endif
323
324   DrawText(mSX + 32 + name_width, mSY + 2 * 32, setup.player_name,
325            FONT_INPUT_1);
326 #if 1
327   DrawText(mSX + level_width + 2 * 32, mSY + 3 * 32, int2str(level_nr, 3),
328            FONT_VALUE_1);
329 #else
330   DrawText(mSX + level_width + 5 * 32, mSY + 3 * 32, int2str(level_nr, 3),
331            FONT_VALUE_1);
332 #endif
333
334   DrawMicroLevel(MICROLEVEL_XPOS, MICROLEVEL_YPOS, TRUE);
335
336 #if 1
337
338 #if 1
339   {
340     int text_height = getFontHeight(FONT_TEXT_3);
341     int ypos2 = -SY + 3 * 32 + 16;
342     int ypos1 = ypos2 - text_height;
343
344     DrawTextF(mSX + level_width + 6 * 32, mSY + ypos1, FONT_TEXT_3,
345               "%03d", leveldir_current->first_level);
346     DrawTextF(mSX + level_width + 6 * 32, mSY + ypos2, FONT_TEXT_3,
347               "%03d", leveldir_current->last_level);
348   }
349 #else
350   DrawTextF(mSX + level_width + 6 * 32, mSY + 3 * 32 + 1, FONT_TEXT_3,
351             "%d", leveldir_current->levels);
352 #endif
353
354 #else
355   DrawTextF(mSX + 32 + level_width - 2, mSY + 3 * 32 + 1, FONT_TEXT_3, "%d-%d",
356             leveldir_current->first_level, leveldir_current->last_level);
357 #endif
358
359 #if 0
360   if (leveldir_current->readonly)
361   {
362     DrawTextS(mSX + level_width + 9 * 32 - 2,
363               mSY + 3 * 32 + 1 - 7, FONT_TEXT_3, "READ");
364     DrawTextS(mSX + level_width + 9 * 32 - 2,
365               mSY + 3 * 32 + 1 + 7, FONT_TEXT_3, "ONLY");
366   }
367 #endif
368
369   for (i = 0; i < 8; i++)
370     initCursor(i, (i == 1 || i == 4 || i == 6 ? IMG_MENU_BUTTON_ENTER_MENU :
371                    IMG_MENU_BUTTON));
372
373 #if 1
374   drawCursorXY(level_width / 32 + 1, 1, IMG_MENU_BUTTON_LEFT);
375   drawCursorXY(level_width / 32 + 5, 1, IMG_MENU_BUTTON_RIGHT);
376 #else
377   drawCursorXY(level_width / 32 + 4, 1, IMG_MENU_BUTTON_LEFT);
378   drawCursorXY(level_width / 32 + 8, 1, IMG_MENU_BUTTON_RIGHT);
379 #endif
380
381   DrawTextSCentered(326, FONT_TITLE_2, "A Game by Artsoft Entertainment");
382
383   FadeToFront();
384   InitAnimation();
385
386   HandleMainMenu(0, 0, 0, 0, MB_MENU_INITIALIZE);
387
388   TapeStop();
389   if (TAPE_IS_EMPTY(tape))
390     LoadTape(level_nr);
391   DrawCompleteVideoDisplay();
392
393   PlayMenuSound();
394   PlayMenuMusic();
395
396   OpenDoor(DOOR_CLOSE_1 | DOOR_OPEN_2);
397
398 #if 1
399   /* map gadgets for main menu screen */
400   MapTapeButtons();
401 #endif
402 }
403
404 #if 0
405 static void gotoTopLevelDir()
406 {
407   /* move upwards to top level directory */
408   while (leveldir_current->node_parent)
409   {
410     /* write a "path" into level tree for easy navigation to last level */
411     if (leveldir_current->node_parent->node_group->cl_first == -1)
412     {
413       int num_leveldirs = numTreeInfoInGroup(leveldir_current);
414       int leveldir_pos = posTreeInfo(leveldir_current);
415       int num_page_entries;
416       int cl_first, cl_cursor;
417
418       if (num_leveldirs <= NUM_MENU_ENTRIES_ON_SCREEN)
419         num_page_entries = num_leveldirs;
420       else
421         num_page_entries = NUM_MENU_ENTRIES_ON_SCREEN;
422
423       cl_first = MAX(0, leveldir_pos - num_page_entries + 1);
424       cl_cursor = leveldir_pos - cl_first;
425
426       leveldir_current->node_parent->node_group->cl_first = cl_first;
427       leveldir_current->node_parent->node_group->cl_cursor = cl_cursor;
428     }
429
430     leveldir_current = leveldir_current->node_parent;
431   }
432 }
433 #endif
434
435 void HandleMainMenu(int mx, int my, int dx, int dy, int button)
436 {
437   static unsigned long level_delay = 0;
438   static int choice = 5;
439   int x = 0;
440   int y = choice;
441
442   if (button == MB_MENU_INITIALIZE)
443   {
444     drawCursor(choice, FC_RED);
445     return;
446   }
447
448   if (mx || my)         /* mouse input */
449   {
450     x = (mx - mSX) / 32;
451     y = (my - mSY) / 32 - MENU_SCREEN_START_YPOS;
452   }
453   else if (dx || dy)    /* keyboard input */
454   {
455     if (dx && choice == 1)
456       x = (dx < 0 ? 10 : 14);
457     else if (dx > 0)
458     {
459       if (choice == 4 || choice == 6)
460         button = MB_MENU_CHOICE;
461     }
462     else if (dy)
463       y = choice + dy;
464   }
465
466   if (y == 1 && ((x == 10 && level_nr > leveldir_current->first_level) ||
467                  (x == 14 && level_nr < leveldir_current->last_level)) &&
468       button && DelayReached(&level_delay, GADGET_FRAME_DELAY))
469   {
470     int step = (button == 1 ? 1 : button == 2 ? 5 : 10);
471     int old_level_nr = level_nr;
472     int new_level_nr;
473
474     new_level_nr = level_nr + (x == 10 ? -step : +step);
475     if (new_level_nr < leveldir_current->first_level)
476       new_level_nr = leveldir_current->first_level;
477     if (new_level_nr > leveldir_current->last_level)
478       new_level_nr = leveldir_current->last_level;
479
480     if (setup.handicap && new_level_nr > leveldir_current->handicap_level)
481     {
482       /* skipping levels is only allowed when trying to skip single level */
483       if (setup.skip_levels && step == 1 &&
484           Request("Level still unsolved ! Skip despite handicap ?", REQ_ASK))
485       {
486         leveldir_current->handicap_level++;
487         SaveLevelSetup_SeriesInfo();
488       }
489
490       new_level_nr = leveldir_current->handicap_level;
491     }
492
493     if (new_level_nr != old_level_nr)
494     {
495       level_nr = new_level_nr;
496
497       DrawText(mSX + 11 * 32, mSY + 3 * 32, int2str(level_nr, 3),
498                FONT_VALUE_1);
499
500       LoadLevel(level_nr);
501       DrawMicroLevel(MICROLEVEL_XPOS, MICROLEVEL_YPOS, TRUE);
502
503       TapeErase();
504       LoadTape(level_nr);
505       DrawCompleteVideoDisplay();
506
507       /* needed because DrawMicroLevel() takes some time */
508       BackToFront();
509       SyncDisplay();
510       DelayReached(&level_delay, 0);    /* reset delay counter */
511     }
512   }
513   else if (IN_VIS_FIELD(x, y) &&
514            y >= 0 && y <= 7 && (y != 1 || x < 10))
515   {
516     if (button)
517     {
518       if (y != choice)
519       {
520         drawCursor(y, FC_RED);
521         drawCursor(choice, FC_BLUE);
522         choice = y;
523       }
524     }
525     else
526     {
527       if (y == 0)
528       {
529         game_status = GAME_MODE_PSEUDO_TYPENAME;
530         HandleTypeName(strlen(setup.player_name), 0);
531       }
532       else if (y == 1)
533       {
534         if (leveldir_first)
535         {
536           game_status = GAME_MODE_LEVELS;
537           SaveLevelSetup_LastSeries();
538           SaveLevelSetup_SeriesInfo();
539
540 #if 0
541           gotoTopLevelDir();
542 #endif
543
544           DrawChooseLevel();
545         }
546       }
547       else if (y == 2)
548       {
549         game_status = GAME_MODE_SCORES;
550         DrawHallOfFame(-1);
551       }
552       else if (y == 3)
553       {
554         if (leveldir_current->readonly &&
555             strcmp(setup.player_name, "Artsoft") != 0)
556           Request("This level is read only !", REQ_CONFIRM);
557         game_status = GAME_MODE_EDITOR;
558         DrawLevelEd();
559       }
560       else if (y == 4)
561       {
562         game_status = GAME_MODE_INFO;
563         info_mode = INFO_MODE_MAIN;
564         DrawInfoScreen();
565       }
566       else if (y == 5)
567       {
568         StartGameActions(options.network, setup.autorecord, NEW_RANDOMIZE);
569       }
570       else if (y == 6)
571       {
572         game_status = GAME_MODE_SETUP;
573         setup_mode = SETUP_MODE_MAIN;
574
575         DrawSetupScreen();
576       }
577       else if (y == 7)
578       {
579         SaveLevelSetup_LastSeries();
580         SaveLevelSetup_SeriesInfo();
581
582         if (Request("Do you really want to quit ?", REQ_ASK | REQ_STAY_CLOSED))
583           game_status = GAME_MODE_QUIT;
584       }
585     }
586   }
587
588   if (game_status == GAME_MODE_MAIN)
589   {
590     DrawMicroLevel(MICROLEVEL_XPOS, MICROLEVEL_YPOS, FALSE);
591     DoAnimation();
592   }
593 }
594
595
596 /* ========================================================================= */
597 /* info screen functions                                                     */
598 /* ========================================================================= */
599
600 static struct TokenInfo *info_info;
601 static int num_info_info;
602
603 static void execInfoElements()
604 {
605   info_mode = INFO_MODE_ELEMENTS;
606   DrawInfoScreen();
607 }
608
609 static void execInfoMusic()
610 {
611   info_mode = INFO_MODE_MUSIC;
612   DrawInfoScreen();
613 }
614
615 static void execInfoCredits()
616 {
617   info_mode = INFO_MODE_CREDITS;
618   DrawInfoScreen();
619 }
620
621 static void execInfoProgram()
622 {
623   info_mode = INFO_MODE_PROGRAM;
624   DrawInfoScreen();
625 }
626
627 static void execInfoLevelSet()
628 {
629   info_mode = INFO_MODE_LEVELSET;
630   DrawInfoScreen();
631 }
632
633 static void execExitInfo()
634 {
635   game_status = GAME_MODE_MAIN;
636   DrawMainMenu();
637 }
638
639 static struct TokenInfo info_info_main[] =
640 {
641   { TYPE_ENTER_SCREEN,  execInfoElements,       "Elements Info"         },
642   { TYPE_ENTER_SCREEN,  execInfoMusic,          "Music Info"            },
643   { TYPE_ENTER_SCREEN,  execInfoCredits,        "Credits"               },
644   { TYPE_ENTER_SCREEN,  execInfoProgram,        "Program Info"          },
645   { TYPE_ENTER_SCREEN,  execInfoLevelSet,       "Level Set Info"        },
646   { TYPE_EMPTY,         NULL,                   ""                      },
647   { TYPE_LEAVE_MENU,    execExitInfo,           "Exit"                  },
648
649   { 0,                  NULL,                   NULL                    }
650 };
651
652 static void DrawInfoScreen_Main()
653 {
654   int i;
655
656   UnmapAllGadgets();
657   CloseDoor(DOOR_CLOSE_2);
658
659   ClearWindow();
660
661 #if 1
662   DrawTextSCentered(mSY - SY + 16, FONT_TITLE_1, "Info Screen");
663 #else
664   DrawText(mSX + 16, mSY + 16, "Info Screen", FONT_TITLE_1);
665 #endif
666
667   info_info = info_info_main;
668   num_info_info = 0;
669
670   for (i = 0; info_info[i].type != 0 && i < NUM_MENU_ENTRIES_ON_SCREEN; i++)
671   {
672     int ypos = MENU_SCREEN_START_YPOS + i;
673     int font_nr = FONT_MENU_1;
674
675     DrawText(mSX + 32, mSY + ypos * 32, info_info[i].text, font_nr);
676
677     if (info_info[i].type & TYPE_ENTER_MENU)
678       initCursor(i, IMG_MENU_BUTTON_ENTER_MENU);
679     else if (info_info[i].type & TYPE_LEAVE_MENU)
680       initCursor(i, IMG_MENU_BUTTON_LEAVE_MENU);
681     else if (info_info[i].type & ~TYPE_SKIP_ENTRY)
682       initCursor(i, IMG_MENU_BUTTON);
683
684     num_info_info++;
685   }
686
687   FadeToFront();
688   InitAnimation();
689
690   PlayMenuSound();
691   PlayMenuMusic();
692
693   HandleInfoScreen_Main(0, 0, 0, 0, MB_MENU_INITIALIZE);
694 }
695
696 void HandleInfoScreen_Main(int mx, int my, int dx, int dy, int button)
697 {
698   static int choice_store[MAX_INFO_MODES];
699   int choice = choice_store[info_mode];         /* always starts with 0 */
700   int x = 0;
701   int y = choice;
702
703   if (button == MB_MENU_INITIALIZE)
704   {
705     /* advance to first valid menu entry */
706     while (choice < num_info_info &&
707            info_info[choice].type & TYPE_SKIP_ENTRY)
708       choice++;
709     choice_store[info_mode] = choice;
710
711     drawCursor(choice, FC_RED);
712     return;
713   }
714   else if (button == MB_MENU_LEAVE)
715   {
716     for (y = 0; y < num_info_info; y++)
717     {
718       if (info_info[y].type & TYPE_LEAVE_MENU)
719       {
720         void (*menu_callback_function)(void) = info_info[y].value;
721
722         menu_callback_function();
723         break;  /* absolutely needed because function changes 'info_info'! */
724       }
725     }
726
727     return;
728   }
729
730   if (mx || my)         /* mouse input */
731   {
732     x = (mx - mSX) / 32;
733     y = (my - mSY) / 32 - MENU_SCREEN_START_YPOS;
734   }
735   else if (dx || dy)    /* keyboard input */
736   {
737     if (dx)
738     {
739       int menu_navigation_type = (dx < 0 ? TYPE_LEAVE_MENU : TYPE_ENTER_MENU);
740
741       if (info_info[choice].type & menu_navigation_type ||
742           info_info[choice].type & TYPE_ENTER_SCREEN ||
743           info_info[choice].type & TYPE_BOOLEAN_STYLE)
744         button = MB_MENU_CHOICE;
745     }
746     else if (dy)
747       y = choice + dy;
748
749     /* jump to next non-empty menu entry (up or down) */
750     while (y > 0 && y < num_info_info - 1 &&
751            info_info[y].type & TYPE_SKIP_ENTRY)
752       y += dy;
753   }
754
755   if (IN_VIS_FIELD(x, y) &&
756       y >= 0 && y < num_info_info && info_info[y].type & ~TYPE_SKIP_ENTRY)
757   {
758     if (button)
759     {
760       if (y != choice)
761       {
762         drawCursor(y, FC_RED);
763         drawCursor(choice, FC_BLUE);
764         choice = choice_store[info_mode] = y;
765       }
766     }
767     else if (!(info_info[y].type & TYPE_GHOSTED))
768     {
769       if (info_info[y].type & TYPE_ENTER_OR_LEAVE_MENU)
770       {
771         void (*menu_callback_function)(void) = info_info[choice].value;
772
773         menu_callback_function();
774       }
775     }
776   }
777 }
778
779 void DrawInfoScreen_HelpAnim(int start, int max_anims, boolean init)
780 {
781   static int infoscreen_step[MAX_INFO_ELEMENTS_ON_SCREEN];
782   static int infoscreen_frame[MAX_INFO_ELEMENTS_ON_SCREEN];
783   int xstart = mSX + 16;
784   int ystart = mSY + 64 + 2 * 32;
785   int ystep = TILEY + 4;
786   int element, action, direction;
787   int graphic;
788   int delay;
789   int sync_frame;
790   int i, j;
791
792   if (init)
793   {
794     for (i = 0; i < MAX_INFO_ELEMENTS_ON_SCREEN; i++)
795       infoscreen_step[i] = infoscreen_frame[i] = 0;
796
797     ClearWindow();
798     DrawHeadline();
799
800     DrawTextSCentered(100, FONT_TEXT_1, "The Game Elements:");
801
802     DrawTextSCentered(SYSIZE - 20, FONT_TEXT_4,
803                       "Press any key or button for next page");
804
805     FrameCounter = 0;
806   }
807
808   i = j = 0;
809   while (helpanim_info[j].element != HELPANIM_LIST_END)
810   {
811     if (i >= start + MAX_INFO_ELEMENTS_ON_SCREEN ||
812         i >= max_anims)
813       break;
814     else if (i < start)
815     {
816       while (helpanim_info[j].element != HELPANIM_LIST_NEXT)
817         j++;
818
819       j++;
820       i++;
821
822       continue;
823     }
824
825     j += infoscreen_step[i - start];
826
827     element = helpanim_info[j].element;
828     action = helpanim_info[j].action;
829     direction = helpanim_info[j].direction;
830
831     if (element < 0)
832       element = EL_UNKNOWN;
833
834     if (action != -1 && direction != -1)
835       graphic = el_act_dir2img(element, action, direction);
836     else if (action != -1)
837       graphic = el_act2img(element, action);
838     else if (direction != -1)
839       graphic = el_dir2img(element, direction);
840     else
841       graphic = el2img(element);
842
843     delay = helpanim_info[j++].delay;
844
845     if (delay == -1)
846       delay = 1000000;
847
848     if (infoscreen_frame[i - start] == 0)
849     {
850       sync_frame = 0;
851       infoscreen_frame[i - start] = delay - 1;
852     }
853     else
854     {
855       sync_frame = delay - infoscreen_frame[i - start];
856       infoscreen_frame[i - start]--;
857     }
858
859     if (helpanim_info[j].element == HELPANIM_LIST_NEXT)
860     {
861       if (!infoscreen_frame[i - start])
862         infoscreen_step[i - start] = 0;
863     }
864     else
865     {
866       if (!infoscreen_frame[i - start])
867         infoscreen_step[i - start]++;
868       while (helpanim_info[j].element != HELPANIM_LIST_NEXT)
869         j++;
870     }
871
872     j++;
873
874     ClearRectangleOnBackground(drawto, xstart, ystart + (i - start) * ystep,
875                                TILEX, TILEY);
876     DrawGraphicAnimationExt(drawto, xstart, ystart + (i - start) * ystep,
877                             graphic, sync_frame, USE_MASKING);
878
879     if (init)
880       DrawInfoScreen_HelpText(element, action, direction, i - start);
881
882     i++;
883   }
884
885   redraw_mask |= REDRAW_FIELD;
886
887   FrameCounter++;
888 }
889
890 static char *getHelpText(int element, int action, int direction)
891 {
892   char token[MAX_LINE_LEN];
893
894   strcpy(token, element_info[element].token_name);
895
896   if (action != -1)
897     strcat(token, element_action_info[action].suffix);
898
899   if (direction != -1)
900     strcat(token, element_direction_info[MV_DIR_TO_BIT(direction)].suffix);
901
902   return getHashEntry(helptext_info, token);
903 }
904
905 void DrawInfoScreen_HelpText(int element, int action, int direction, int ypos)
906 {
907   int font_nr = FONT_LEVEL_NUMBER;
908   int font_width = getFontWidth(font_nr);
909   int sx = mSX + MINI_TILEX + TILEX + MINI_TILEX;
910   int sy = mSY + 65 + 2 * 32 + 1;
911   int ystep = TILEY + 4;
912   int pad_x = sx - SX;
913   int max_chars_per_line = (SXSIZE - pad_x - MINI_TILEX) / font_width;
914   int max_lines_per_text = 2;    
915   char *text = NULL;
916
917   if (action != -1 && direction != -1)          /* element.action.direction */
918     text = getHelpText(element, action, direction);
919
920   if (text == NULL && action != -1)             /* element.action */
921     text = getHelpText(element, action, -1);
922
923   if (text == NULL && direction != -1)          /* element.direction */
924     text = getHelpText(element, -1, direction);
925
926   if (text == NULL)                             /* base element */
927     text = getHelpText(element, -1, -1);
928
929   if (text == NULL)                             /* not found */
930     text = "No description available";
931
932   if (strlen(text) <= max_chars_per_line)       /* only one line of text */
933     sy += getFontHeight(font_nr) / 2;
934
935   DrawTextWrapped(sx, sy + ypos * ystep, text, font_nr,
936                   max_chars_per_line, max_lines_per_text);
937 }
938
939 void DrawInfoScreen_Elements()
940 {
941   SetMainBackgroundImageIfDefined(IMG_BACKGROUND_INFO_ELEMENTS);
942
943   LoadHelpAnimInfo();
944   LoadHelpTextInfo();
945
946   HandleInfoScreen_Elements(MB_MENU_INITIALIZE);
947
948   FadeToFront();
949   InitAnimation();
950 }
951
952 void HandleInfoScreen_Elements(int button)
953 {
954   static unsigned long info_delay = 0;
955   static int num_anims;
956   static int num_pages;
957   static int page;
958   int anims_per_page = MAX_INFO_ELEMENTS_ON_SCREEN;
959   int button_released = !button;
960   int i;
961
962   if (button == MB_MENU_INITIALIZE)
963   {
964     boolean new_element = TRUE;
965
966     num_anims = 0;
967     for (i = 0; helpanim_info[i].element != HELPANIM_LIST_END; i++)
968     {
969       if (helpanim_info[i].element == HELPANIM_LIST_NEXT)
970         new_element = TRUE;
971       else if (new_element)
972       {
973         num_anims++;
974         new_element = FALSE;
975       }
976     }
977
978     num_pages = (num_anims + anims_per_page - 1) / anims_per_page;
979     page = 0;
980   }
981   else if (button == MB_MENU_LEAVE)
982   {
983     info_mode = INFO_MODE_MAIN;
984     DrawInfoScreen();
985
986     return;
987   }
988
989   if (button_released || button == MB_MENU_INITIALIZE)
990   {
991     if (button != MB_MENU_INITIALIZE)
992       page++;
993
994     if (page >= num_pages)
995     {
996       FadeSoundsAndMusic();
997
998       info_mode = INFO_MODE_MAIN;
999       DrawInfoScreen();
1000
1001       return;
1002     }
1003
1004     DrawInfoScreen_HelpAnim(page * anims_per_page, num_anims, TRUE);
1005   }
1006   else
1007   {
1008     if (DelayReached(&info_delay, GameFrameDelay))
1009       if (page < num_pages)
1010         DrawInfoScreen_HelpAnim(page * anims_per_page, num_anims, FALSE);
1011
1012     PlayMenuSoundIfLoop();
1013   }
1014 }
1015
1016 void DrawInfoScreen_Music()
1017 {
1018   SetMainBackgroundImageIfDefined(IMG_BACKGROUND_INFO_MUSIC);
1019
1020   ClearWindow();
1021   DrawHeadline();
1022
1023   LoadMusicInfo();
1024
1025   HandleInfoScreen_Music(MB_MENU_INITIALIZE);
1026 }
1027
1028 void HandleInfoScreen_Music(int button)
1029 {
1030   static struct MusicFileInfo *list = NULL;
1031   int ystart = 150, dy = 30;
1032   int ybottom = SYSIZE - 20;
1033   int button_released = !button;
1034
1035   if (button == MB_MENU_INITIALIZE)
1036   {
1037     list = music_file_info;
1038
1039     if (list == NULL)
1040     {
1041       FadeSoundsAndMusic();
1042
1043       ClearWindow();
1044       DrawHeadline();
1045
1046       DrawTextSCentered(100, FONT_TEXT_1, "No music info for this level set.");
1047
1048       DrawTextSCentered(ybottom, FONT_TEXT_4,
1049                         "Press any key or button for info menu");
1050
1051       return;
1052     }
1053   }
1054   else if (button == MB_MENU_LEAVE)
1055   {
1056     info_mode = INFO_MODE_MAIN;
1057     DrawInfoScreen();
1058
1059     return;
1060   }
1061
1062   if (button_released || button == MB_MENU_INITIALIZE)
1063   {
1064     int y = 0;
1065
1066     if (button != MB_MENU_INITIALIZE)
1067       if (list != NULL)
1068         list = list->next;
1069
1070     if (list == NULL)
1071     {
1072       info_mode = INFO_MODE_MAIN;
1073       DrawInfoScreen();
1074
1075       return;
1076     }
1077
1078     FadeSoundsAndMusic();
1079
1080     ClearWindow();
1081     DrawHeadline();
1082
1083     if (list->is_sound)
1084     {
1085       int sound = list->music;
1086
1087       if (sound_info[sound].loop)
1088         PlaySoundLoop(sound);
1089       else
1090         PlaySound(sound);
1091
1092       DrawTextSCentered(100, FONT_TEXT_1, "The Game Background Sounds:");
1093     }
1094     else
1095     {
1096       PlayMusic(list->music);
1097
1098       DrawTextSCentered(100, FONT_TEXT_1, "The Game Background Music:");
1099     }
1100
1101     if (strcmp(list->title, UNKNOWN_NAME) != 0)
1102     {
1103       if (strcmp(list->title_header, UNKNOWN_NAME) != 0)
1104         DrawTextSCentered(ystart + y++ * dy, FONT_TEXT_2, list->title_header);
1105
1106       DrawTextFCentered(ystart + y++ * dy, FONT_TEXT_3, "\"%s\"", list->title);
1107     }
1108
1109     if (strcmp(list->artist, UNKNOWN_NAME) != 0)
1110     {
1111       if (strcmp(list->artist_header, UNKNOWN_NAME) != 0)
1112         DrawTextSCentered(ystart + y++ * dy, FONT_TEXT_2, list->artist_header);
1113       else
1114         DrawTextSCentered(ystart + y++ * dy, FONT_TEXT_2, "by");
1115
1116       DrawTextFCentered(ystart + y++ * dy, FONT_TEXT_3, "%s", list->artist);
1117     }
1118
1119     if (strcmp(list->album, UNKNOWN_NAME) != 0)
1120     {
1121       if (strcmp(list->album_header, UNKNOWN_NAME) != 0)
1122         DrawTextSCentered(ystart + y++ * dy, FONT_TEXT_2, list->album_header);
1123       else
1124         DrawTextSCentered(ystart + y++ * dy, FONT_TEXT_2, "from the album");
1125
1126       DrawTextFCentered(ystart + y++ * dy, FONT_TEXT_3, "\"%s\"", list->album);
1127     }
1128
1129     if (strcmp(list->year, UNKNOWN_NAME) != 0)
1130     {
1131       if (strcmp(list->year_header, UNKNOWN_NAME) != 0)
1132         DrawTextSCentered(ystart + y++ * dy, FONT_TEXT_2, list->year_header);
1133       else
1134         DrawTextSCentered(ystart + y++ * dy, FONT_TEXT_2, "from the year");
1135
1136       DrawTextFCentered(ystart + y++ * dy, FONT_TEXT_3, "%s", list->year);
1137     }
1138
1139     DrawTextSCentered(ybottom, FONT_TEXT_4,
1140                       "Press any key or button for next page");
1141   }
1142
1143   if (list != NULL && list->is_sound && sound_info[list->music].loop)
1144     PlaySoundLoop(list->music);
1145 }
1146
1147 void DrawInfoScreen_Credits()
1148 {
1149   int ystart = 150, ystep = 30;
1150   int ybottom = SYSIZE - 20;
1151
1152   SetMainBackgroundImageIfDefined(IMG_BACKGROUND_INFO_CREDITS);
1153
1154   FadeSoundsAndMusic();
1155
1156   ClearWindow();
1157   DrawHeadline();
1158
1159   DrawTextSCentered(100, FONT_TEXT_1, "Credits:");
1160   DrawTextSCentered(ystart + 0 * ystep, FONT_TEXT_2, "DOS port of the game:");
1161   DrawTextSCentered(ystart + 1 * ystep, FONT_TEXT_3, "Guido Schulz");
1162   DrawTextSCentered(ystart + 2 * ystep, FONT_TEXT_2, "Additional toons:");
1163   DrawTextSCentered(ystart + 3 * ystep, FONT_TEXT_3, "Karl Hörnell");
1164   DrawTextSCentered(ystart + 5 * ystep, FONT_TEXT_2,
1165                     "...and many thanks to all contributors");
1166   DrawTextSCentered(ystart + 6 * ystep, FONT_TEXT_2, "of new levels!");
1167
1168   DrawTextSCentered(ybottom, FONT_TEXT_4,
1169                     "Press any key or button for info menu");
1170 }
1171
1172 void HandleInfoScreen_Credits(int button)
1173 {
1174   int button_released = !button;
1175
1176   if (button == MB_MENU_LEAVE)
1177   {
1178     info_mode = INFO_MODE_MAIN;
1179     DrawInfoScreen();
1180
1181     return;
1182   }
1183
1184   if (button_released)
1185   {
1186     FadeSoundsAndMusic();
1187
1188     info_mode = INFO_MODE_MAIN;
1189     DrawInfoScreen();
1190   }
1191   else
1192   {
1193     PlayMenuSoundIfLoop();
1194   }
1195 }
1196
1197 void DrawInfoScreen_Program()
1198 {
1199   int ystart = 150, ystep = 30;
1200   int ybottom = SYSIZE - 20;
1201
1202   SetMainBackgroundImageIfDefined(IMG_BACKGROUND_INFO_PROGRAM);
1203
1204   ClearWindow();
1205   DrawHeadline();
1206
1207   DrawTextSCentered(100, FONT_TEXT_1, "Program Information:");
1208
1209   DrawTextSCentered(ystart + 0 * ystep, FONT_TEXT_2,
1210                     "This game is Freeware!");
1211   DrawTextSCentered(ystart + 1 * ystep, FONT_TEXT_2,
1212                     "If you like it, send e-mail to:");
1213   DrawTextSCentered(ystart + 2 * ystep, FONT_TEXT_3,
1214                     "info@artsoft.org");
1215   DrawTextSCentered(ystart + 3 * ystep, FONT_TEXT_2,
1216                     "or SnailMail to:");
1217   DrawTextSCentered(ystart + 4 * ystep + 0, FONT_TEXT_3,
1218                     "Holger Schemel");
1219   DrawTextSCentered(ystart + 4 * ystep + 20, FONT_TEXT_3,
1220                     "Detmolder Strasse 189");
1221   DrawTextSCentered(ystart + 4 * ystep + 40, FONT_TEXT_3,
1222                     "33604 Bielefeld");
1223   DrawTextSCentered(ystart + 4 * ystep + 60, FONT_TEXT_3,
1224                     "Germany");
1225
1226   DrawTextSCentered(ystart + 7 * ystep, FONT_TEXT_2,
1227                     "If you have created new levels,");
1228   DrawTextSCentered(ystart + 8 * ystep, FONT_TEXT_2,
1229                     "send them to me to include them!");
1230   DrawTextSCentered(ystart + 9 * ystep, FONT_TEXT_2,
1231                     ":-)");
1232
1233   DrawTextSCentered(ybottom, FONT_TEXT_4,
1234                     "Press any key or button for info menu");
1235 }
1236
1237 void HandleInfoScreen_Program(int button)
1238 {
1239   int button_released = !button;
1240
1241   if (button == MB_MENU_LEAVE)
1242   {
1243     info_mode = INFO_MODE_MAIN;
1244     DrawInfoScreen();
1245
1246     return;
1247   }
1248
1249   if (button_released)
1250   {
1251     FadeSoundsAndMusic();
1252
1253     info_mode = INFO_MODE_MAIN;
1254     DrawInfoScreen();
1255   }
1256   else
1257   {
1258     PlayMenuSoundIfLoop();
1259   }
1260 }
1261
1262 void DrawInfoScreen_LevelSet()
1263 {
1264   int ystart = 150;
1265   int ybottom = SYSIZE - 20;
1266   char *filename = getLevelSetInfoFilename();
1267   int font_nr = FONT_LEVEL_NUMBER;
1268   int font_width = getFontWidth(font_nr);
1269   int font_height = getFontHeight(font_nr);
1270   int pad_x = 32;
1271   int pad_y = ystart;
1272   int sx = SX + pad_x;
1273   int sy = SY + pad_y;
1274   int max_chars_per_line = (SXSIZE - 2 * pad_x) / font_width;
1275   int max_lines_per_screen = (SYSIZE - pad_y) / font_height - 1;
1276
1277   SetMainBackgroundImageIfDefined(IMG_BACKGROUND_INFO_LEVELSET);
1278
1279   ClearWindow();
1280   DrawHeadline();
1281
1282   DrawTextSCentered(100, FONT_TEXT_1, "Level Set Information:");
1283
1284   DrawTextSCentered(ybottom, FONT_TEXT_4,
1285                     "Press any key or button for info menu");
1286
1287   if (filename != NULL)
1288     DrawTextFromFile(sx, sy, filename, font_nr, max_chars_per_line,
1289                      max_lines_per_screen);
1290   else
1291     DrawTextSCentered(ystart, FONT_TEXT_2,
1292                       "No information for this level set.");
1293 }
1294
1295 void HandleInfoScreen_LevelSet(int button)
1296 {
1297   int button_released = !button;
1298
1299   if (button == MB_MENU_LEAVE)
1300   {
1301     info_mode = INFO_MODE_MAIN;
1302     DrawInfoScreen();
1303
1304     return;
1305   }
1306
1307   if (button_released)
1308   {
1309     FadeSoundsAndMusic();
1310
1311     info_mode = INFO_MODE_MAIN;
1312     DrawInfoScreen();
1313   }
1314   else
1315   {
1316     PlayMenuSoundIfLoop();
1317   }
1318 }
1319
1320 void DrawInfoScreen()
1321 {
1322   SetMainBackgroundImage(IMG_BACKGROUND_INFO);
1323
1324   if (info_mode == INFO_MODE_ELEMENTS)
1325     DrawInfoScreen_Elements();
1326   else if (info_mode == INFO_MODE_MUSIC)
1327     DrawInfoScreen_Music();
1328   else if (info_mode == INFO_MODE_CREDITS)
1329     DrawInfoScreen_Credits();
1330   else if (info_mode == INFO_MODE_PROGRAM)
1331     DrawInfoScreen_Program();
1332   else if (info_mode == INFO_MODE_LEVELSET)
1333     DrawInfoScreen_LevelSet();
1334   else
1335     DrawInfoScreen_Main();
1336
1337   if (info_mode != INFO_MODE_MUSIC)
1338   {
1339     PlayMenuSound();
1340     PlayMenuMusic();
1341   }
1342 }
1343
1344 void HandleInfoScreen(int mx, int my, int dx, int dy, int button)
1345 {
1346   if (info_mode == INFO_MODE_ELEMENTS)
1347     HandleInfoScreen_Elements(button);
1348   else if (info_mode == INFO_MODE_MUSIC)
1349     HandleInfoScreen_Music(button);
1350   else if (info_mode == INFO_MODE_CREDITS)
1351     HandleInfoScreen_Credits(button);
1352   else if (info_mode == INFO_MODE_PROGRAM)
1353     HandleInfoScreen_Program(button);
1354   else if (info_mode == INFO_MODE_LEVELSET)
1355     HandleInfoScreen_LevelSet(button);
1356   else
1357     HandleInfoScreen_Main(mx, my, dx, dy, button);
1358
1359   DoAnimation();
1360 }
1361
1362
1363 /* ========================================================================= */
1364 /* type name functions                                                       */
1365 /* ========================================================================= */
1366
1367 void HandleTypeName(int newxpos, Key key)
1368 {
1369   static int xpos = 0, ypos = 2;
1370   int font_width = getFontWidth(FONT_INPUT_1_ACTIVE);
1371   int name_width = getFontWidth(FONT_MENU_1) * strlen("Name:");
1372   int startx = mSX + 32 + name_width;
1373   int starty = mSY + ypos * 32;
1374
1375   if (newxpos)
1376   {
1377     xpos = newxpos;
1378
1379     DrawText(startx, starty, setup.player_name, FONT_INPUT_1_ACTIVE);
1380     DrawText(startx + xpos * font_width, starty, "_", FONT_INPUT_1_ACTIVE);
1381
1382     return;
1383   }
1384
1385   if (((key >= KSYM_A && key <= KSYM_Z) ||
1386        (key >= KSYM_a && key <= KSYM_z)) && 
1387       xpos < MAX_PLAYER_NAME_LEN)
1388   {
1389     char ascii;
1390
1391     if (key >= KSYM_A && key <= KSYM_Z)
1392       ascii = 'A' + (char)(key - KSYM_A);
1393     else
1394       ascii = 'a' + (char)(key - KSYM_a);
1395
1396     setup.player_name[xpos] = ascii;
1397     setup.player_name[xpos + 1] = 0;
1398     xpos++;
1399
1400     DrawText(startx, starty, setup.player_name, FONT_INPUT_1_ACTIVE);
1401     DrawText(startx + xpos * font_width, starty, "_", FONT_INPUT_1_ACTIVE);
1402   }
1403   else if ((key == KSYM_Delete || key == KSYM_BackSpace) && xpos > 0)
1404   {
1405     xpos--;
1406     setup.player_name[xpos] = 0;
1407
1408     DrawText(startx + xpos * font_width, starty, "_ ", FONT_INPUT_1_ACTIVE);
1409   }
1410   else if (key == KSYM_Return && xpos > 0)
1411   {
1412     DrawText(startx, starty, setup.player_name, FONT_INPUT_1);
1413     DrawText(startx + xpos * font_width, starty, " ", FONT_INPUT_1_ACTIVE);
1414
1415     SaveSetup();
1416     game_status = GAME_MODE_MAIN;
1417   }
1418 }
1419
1420
1421 /* ========================================================================= */
1422 /* tree menu functions                                                       */
1423 /* ========================================================================= */
1424
1425 static void DrawChooseTree(TreeInfo **ti_ptr)
1426 {
1427   UnmapAllGadgets();
1428
1429   FreeScreenGadgets();
1430   CreateScreenGadgets();
1431
1432   CloseDoor(DOOR_CLOSE_2);
1433
1434   ClearWindow();
1435
1436   HandleChooseTree(0, 0, 0, 0, MB_MENU_INITIALIZE, ti_ptr);
1437   MapChooseTreeGadgets(*ti_ptr);
1438
1439   FadeToFront();
1440   InitAnimation();
1441 }
1442
1443 static void AdjustChooseTreeScrollbar(int id, int first_entry, TreeInfo *ti)
1444 {
1445   struct GadgetInfo *gi = screen_gadget[id];
1446   int items_max, items_visible, item_position;
1447
1448   items_max = numTreeInfoInGroup(ti);
1449   items_visible = NUM_MENU_ENTRIES_ON_SCREEN;
1450   item_position = first_entry;
1451
1452   if (item_position > items_max - items_visible)
1453     item_position = items_max - items_visible;
1454
1455   ModifyGadget(gi, GDI_SCROLLBAR_ITEMS_MAX, items_max,
1456                GDI_SCROLLBAR_ITEMS_VISIBLE, items_visible,
1457                GDI_SCROLLBAR_ITEM_POSITION, item_position, GDI_END);
1458 }
1459
1460 static void drawChooseTreeList(int first_entry, int num_page_entries,
1461                                TreeInfo *ti)
1462 {
1463   int i;
1464   char buffer[SCR_FIELDX * 2];
1465   int max_buffer_len = (SCR_FIELDX - 2) * 2;
1466   char *title_string = NULL;
1467 #if 0
1468   int xoffset_sets = 16;
1469 #endif
1470   int yoffset_sets = MENU_TITLE1_YPOS;
1471 #if 0
1472   int xoffset_setup = 16;
1473 #endif
1474   int yoffset_setup = 16;
1475 #if 1
1476 #if 0
1477   int xoffset = (ti->type == TREE_TYPE_LEVEL_DIR ? xoffset_sets :
1478                  xoffset_setup);
1479 #endif
1480   int yoffset = (ti->type == TREE_TYPE_LEVEL_DIR ? yoffset_sets :
1481                  yoffset_setup);
1482 #else
1483   int xoffset = (ti->type == TREE_TYPE_LEVEL_DIR ? 0 : xoffset_setup);
1484   int yoffset = (ti->type == TREE_TYPE_LEVEL_DIR ? 0 : yoffset_setup);
1485 #endif
1486   int last_game_status = game_status;   /* save current game status */
1487
1488   title_string =
1489     (ti->type == TREE_TYPE_LEVEL_DIR ? "Level Sets" :
1490      ti->type == TREE_TYPE_GRAPHICS_DIR ? "Custom Graphics" :
1491      ti->type == TREE_TYPE_SOUNDS_DIR ? "Custom Sounds" :
1492      ti->type == TREE_TYPE_MUSIC_DIR ? "Custom Music" : "");
1493
1494 #if 1
1495   DrawTextSCentered(mSY - SY + yoffset, FONT_TITLE_1, title_string);
1496 #else
1497   DrawText(SX + xoffset, SY + yoffset, title_string, FONT_TITLE_1);
1498 #endif
1499
1500   /* force LEVELS font on artwork setup screen */
1501   game_status = GAME_MODE_LEVELS;
1502
1503   /* clear tree list area, but not title or scrollbar */
1504   DrawBackground(mSX, mSY + MENU_SCREEN_START_YPOS * 32,
1505                  SXSIZE - 32 + menu.scrollbar_xoffset,
1506                  MAX_MENU_ENTRIES_ON_SCREEN * 32);
1507
1508   for (i = 0; i < num_page_entries; i++)
1509   {
1510     TreeInfo *node, *node_first;
1511     int entry_pos = first_entry + i;
1512     int ypos = MENU_SCREEN_START_YPOS + i;
1513
1514     node_first = getTreeInfoFirstGroupEntry(ti);
1515     node = getTreeInfoFromPos(node_first, entry_pos);
1516
1517     strncpy(buffer, node->name, max_buffer_len);
1518     buffer[max_buffer_len] = '\0';
1519
1520     DrawText(mSX + 32, mSY + ypos * 32, buffer, FONT_TEXT_1 + node->color);
1521
1522     if (node->parent_link)
1523       initCursor(i, IMG_MENU_BUTTON_LEAVE_MENU);
1524     else if (node->level_group)
1525       initCursor(i, IMG_MENU_BUTTON_ENTER_MENU);
1526     else
1527       initCursor(i, IMG_MENU_BUTTON);
1528   }
1529
1530   game_status = last_game_status;       /* restore current game status */
1531
1532   redraw_mask |= REDRAW_FIELD;
1533 }
1534
1535 static void drawChooseTreeInfo(int entry_pos, TreeInfo *ti)
1536 {
1537   TreeInfo *node, *node_first;
1538   int x, last_redraw_mask = redraw_mask;
1539 #if 1
1540   int ypos = MENU_TITLE2_YPOS;
1541 #else
1542   int ypos = 40;
1543 #endif
1544
1545   if (ti->type != TREE_TYPE_LEVEL_DIR)
1546     return;
1547
1548   node_first = getTreeInfoFirstGroupEntry(ti);
1549   node = getTreeInfoFromPos(node_first, entry_pos);
1550
1551   DrawBackground(SX, SY + ypos, SXSIZE, getFontHeight(FONT_TITLE_2));
1552
1553   if (node->parent_link)
1554     DrawTextFCentered(ypos, FONT_TITLE_2, "leave group \"%s\"",
1555                       node->class_desc);
1556   else if (node->level_group)
1557     DrawTextFCentered(ypos, FONT_TITLE_2, "enter group \"%s\"",
1558                       node->class_desc);
1559   else if (ti->type == TREE_TYPE_LEVEL_DIR)
1560     DrawTextFCentered(ypos, FONT_TITLE_2, "%3d levels (%s)",
1561                       node->levels, node->class_desc);
1562
1563   /* let BackToFront() redraw only what is needed */
1564   redraw_mask = last_redraw_mask | REDRAW_TILES;
1565   for (x = 0; x < SCR_FIELDX; x++)
1566     MarkTileDirty(x, 1);
1567 }
1568
1569 static void HandleChooseTree(int mx, int my, int dx, int dy, int button,
1570                              TreeInfo **ti_ptr)
1571 {
1572   TreeInfo *ti = *ti_ptr;
1573   int x = 0;
1574   int y = ti->cl_cursor;
1575   int step = (button == 1 ? 1 : button == 2 ? 5 : 10);
1576   int num_entries = numTreeInfoInGroup(ti);
1577   int num_page_entries;
1578   int last_game_status = game_status;   /* save current game status */
1579   boolean position_set_by_scrollbar = (dx == 999);
1580
1581   /* force LEVELS draw offset on choose level and artwork setup screen */
1582   game_status = GAME_MODE_LEVELS;
1583
1584   if (num_entries <= NUM_MENU_ENTRIES_ON_SCREEN)
1585     num_page_entries = num_entries;
1586   else
1587     num_page_entries = NUM_MENU_ENTRIES_ON_SCREEN;
1588
1589   game_status = last_game_status;       /* restore current game status */
1590
1591   if (button == MB_MENU_INITIALIZE)
1592   {
1593     int num_entries = numTreeInfoInGroup(ti);
1594     int entry_pos = posTreeInfo(ti);
1595
1596     if (ti->cl_first == -1)
1597     {
1598       /* only on initialization */
1599       ti->cl_first = MAX(0, entry_pos - num_page_entries + 1);
1600       ti->cl_cursor = entry_pos - ti->cl_first;
1601     }
1602     else if (ti->cl_cursor >= num_page_entries ||
1603              (num_entries > num_page_entries &&
1604               num_entries - ti->cl_first < num_page_entries))
1605     {
1606       /* only after change of list size (by custom graphic configuration) */
1607       ti->cl_first = MAX(0, entry_pos - num_page_entries + 1);
1608       ti->cl_cursor = entry_pos - ti->cl_first;
1609     }
1610
1611     if (position_set_by_scrollbar)
1612       ti->cl_first = dy;
1613     else
1614       AdjustChooseTreeScrollbar(SCREEN_CTRL_ID_SCROLL_VERTICAL,
1615                                 ti->cl_first, ti);
1616
1617     drawChooseTreeList(ti->cl_first, num_page_entries, ti);
1618     drawChooseTreeInfo(ti->cl_first + ti->cl_cursor, ti);
1619     drawChooseTreeCursor(ti->cl_cursor, FC_RED);
1620
1621     return;
1622   }
1623   else if (button == MB_MENU_LEAVE)
1624   {
1625     if (ti->node_parent)
1626     {
1627       *ti_ptr = ti->node_parent;
1628       DrawChooseTree(ti_ptr);
1629     }
1630     else if (game_status == GAME_MODE_SETUP)
1631     {
1632       execSetupArtwork();
1633     }
1634     else
1635     {
1636       game_status = GAME_MODE_MAIN;
1637       DrawMainMenu();
1638     }
1639
1640     return;
1641   }
1642
1643   if (mx || my)         /* mouse input */
1644   {
1645     int last_game_status = game_status; /* save current game status */
1646
1647     /* force LEVELS draw offset on artwork setup screen */
1648     game_status = GAME_MODE_LEVELS;
1649
1650     x = (mx - mSX) / 32;
1651     y = (my - mSY) / 32 - MENU_SCREEN_START_YPOS;
1652
1653     game_status = last_game_status;     /* restore current game status */
1654   }
1655   else if (dx || dy)    /* keyboard or scrollbar/scrollbutton input */
1656   {
1657     /* move cursor instead of scrolling when already at start/end of list */
1658     if (dy == -1 * SCROLL_LINE && ti->cl_first == 0)
1659       dy = -1;
1660     else if (dy == +1 * SCROLL_LINE &&
1661              ti->cl_first + num_page_entries == num_entries)
1662       dy = 1;
1663
1664     /* handle scrolling screen one line or page */
1665     if (ti->cl_cursor + dy < 0 ||
1666         ti->cl_cursor + dy > num_page_entries - 1)
1667     {
1668       if (ABS(dy) == SCROLL_PAGE)
1669         step = num_page_entries - 1;
1670
1671       if (dy < 0 && ti->cl_first > 0)
1672       {
1673         /* scroll page/line up */
1674
1675         ti->cl_first -= step;
1676         if (ti->cl_first < 0)
1677           ti->cl_first = 0;
1678
1679         drawChooseTreeList(ti->cl_first, num_page_entries, ti);
1680         drawChooseTreeInfo(ti->cl_first + ti->cl_cursor, ti);
1681         drawChooseTreeCursor(ti->cl_cursor, FC_RED);
1682         AdjustChooseTreeScrollbar(SCREEN_CTRL_ID_SCROLL_VERTICAL,
1683                                   ti->cl_first, ti);
1684       }
1685       else if (dy > 0 && ti->cl_first + num_page_entries < num_entries)
1686       {
1687         /* scroll page/line down */
1688
1689         ti->cl_first += step;
1690         if (ti->cl_first + num_page_entries > num_entries)
1691           ti->cl_first = MAX(0, num_entries - num_page_entries);
1692
1693         drawChooseTreeList(ti->cl_first, num_page_entries, ti);
1694         drawChooseTreeInfo(ti->cl_first + ti->cl_cursor, ti);
1695         drawChooseTreeCursor(ti->cl_cursor, FC_RED);
1696         AdjustChooseTreeScrollbar(SCREEN_CTRL_ID_SCROLL_VERTICAL,
1697                                   ti->cl_first, ti);
1698       }
1699
1700       return;
1701     }
1702
1703     /* handle moving cursor one line */
1704     y = ti->cl_cursor + dy;
1705   }
1706
1707   if (dx == 1)
1708   {
1709     TreeInfo *node_first, *node_cursor;
1710     int entry_pos = ti->cl_first + y;
1711
1712     node_first = getTreeInfoFirstGroupEntry(ti);
1713     node_cursor = getTreeInfoFromPos(node_first, entry_pos);
1714
1715     if (node_cursor->node_group)
1716     {
1717       node_cursor->cl_first = ti->cl_first;
1718       node_cursor->cl_cursor = ti->cl_cursor;
1719       *ti_ptr = node_cursor->node_group;
1720       DrawChooseTree(ti_ptr);
1721
1722       return;
1723     }
1724   }
1725   else if (dx == -1 && ti->node_parent)
1726   {
1727     *ti_ptr = ti->node_parent;
1728     DrawChooseTree(ti_ptr);
1729
1730     return;
1731   }
1732
1733   if (!anyScrollbarGadgetActive() &&
1734       IN_VIS_FIELD(x, y) &&
1735       mx < screen_gadget[SCREEN_CTRL_ID_SCROLL_VERTICAL]->x &&
1736       y >= 0 && y < num_page_entries)
1737   {
1738     if (button)
1739     {
1740       if (y != ti->cl_cursor)
1741       {
1742         drawChooseTreeCursor(y, FC_RED);
1743         drawChooseTreeCursor(ti->cl_cursor, FC_BLUE);
1744         drawChooseTreeInfo(ti->cl_first + y, ti);
1745         ti->cl_cursor = y;
1746       }
1747     }
1748     else
1749     {
1750       TreeInfo *node_first, *node_cursor;
1751       int entry_pos = ti->cl_first + y;
1752
1753       node_first = getTreeInfoFirstGroupEntry(ti);
1754       node_cursor = getTreeInfoFromPos(node_first, entry_pos);
1755
1756       if (node_cursor->node_group)
1757       {
1758         node_cursor->cl_first = ti->cl_first;
1759         node_cursor->cl_cursor = ti->cl_cursor;
1760         *ti_ptr = node_cursor->node_group;
1761         DrawChooseTree(ti_ptr);
1762       }
1763       else if (node_cursor->parent_link)
1764       {
1765         *ti_ptr = node_cursor->node_parent;
1766         DrawChooseTree(ti_ptr);
1767       }
1768       else
1769       {
1770         node_cursor->cl_first = ti->cl_first;
1771         node_cursor->cl_cursor = ti->cl_cursor;
1772         *ti_ptr = node_cursor;
1773
1774         if (ti->type == TREE_TYPE_LEVEL_DIR)
1775         {
1776           LoadLevelSetup_SeriesInfo();
1777
1778           SaveLevelSetup_LastSeries();
1779           SaveLevelSetup_SeriesInfo();
1780           TapeErase();
1781         }
1782
1783         if (game_status == GAME_MODE_SETUP)
1784         {
1785           execSetupArtwork();
1786         }
1787         else
1788         {
1789           game_status = GAME_MODE_MAIN;
1790           DrawMainMenu();
1791         }
1792       }
1793     }
1794   }
1795 }
1796
1797 void DrawChooseLevel()
1798 {
1799   SetMainBackgroundImage(IMG_BACKGROUND_LEVELS);
1800
1801   DrawChooseTree(&leveldir_current);
1802
1803   PlayMenuSound();
1804   PlayMenuMusic();
1805 }
1806
1807 void HandleChooseLevel(int mx, int my, int dx, int dy, int button)
1808 {
1809   HandleChooseTree(mx, my, dx, dy, button, &leveldir_current);
1810
1811   DoAnimation();
1812 }
1813
1814 void DrawHallOfFame(int highlight_position)
1815 {
1816   UnmapAllGadgets();
1817   FadeSoundsAndMusic();
1818   CloseDoor(DOOR_CLOSE_2);
1819
1820   if (highlight_position < 0) 
1821     LoadScore(level_nr);
1822
1823   FadeToFront();
1824   InitAnimation();
1825
1826   PlayMenuSound();
1827   PlayMenuMusic();
1828
1829   HandleHallOfFame(highlight_position, 0, 0, 0, MB_MENU_INITIALIZE);
1830 }
1831
1832 static void drawHallOfFameList(int first_entry, int highlight_position)
1833 {
1834   int i;
1835
1836   SetMainBackgroundImage(IMG_BACKGROUND_SCORES);
1837   ClearWindow();
1838
1839 #if 1
1840   DrawTextSCentered(MENU_TITLE1_YPOS, FONT_TITLE_1, "Hall Of Fame");
1841   DrawTextFCentered(MENU_TITLE2_YPOS, FONT_TITLE_2,
1842                     "HighScores of Level %d", level_nr);
1843 #else
1844   DrawText(mSX + 80, mSY + MENU_TITLE1_YPOS, "Hall Of Fame", FONT_TITLE_1);
1845   DrawTextFCentered(MENU_TITLE2_YPOS, FONT_TITLE_2,
1846                     "HighScores of Level %d", level_nr);
1847 #endif
1848
1849   for (i = 0; i < NUM_MENU_ENTRIES_ON_SCREEN; i++)
1850   {
1851     int entry = first_entry + i;
1852     boolean active = (entry == highlight_position);
1853     int font_nr1 = (active ? FONT_TEXT_1_ACTIVE : FONT_TEXT_1);
1854     int font_nr2 = (active ? FONT_TEXT_2_ACTIVE : FONT_TEXT_2);
1855     int font_nr3 = (active ? FONT_TEXT_3_ACTIVE : FONT_TEXT_3);
1856     int font_nr4 = (active ? FONT_TEXT_4_ACTIVE : FONT_TEXT_4);
1857     int dx1 = 3 * getFontWidth(font_nr1);
1858     int dx2 = dx1 + getFontWidth(font_nr1);
1859     int dx3 = dx2 + 25 * getFontWidth(font_nr3);
1860     int sy = mSY + 64 + i * 32;
1861
1862     DrawText(mSX, sy, int2str(entry + 1, 3), font_nr1);
1863     DrawText(mSX + dx1, sy, ".", font_nr1);
1864     DrawText(mSX + dx2, sy, ".........................", font_nr3);
1865     if (strcmp(highscore[entry].Name, EMPTY_PLAYER_NAME) != 0)
1866       DrawText(mSX + dx2, sy, highscore[entry].Name, font_nr2);
1867     DrawText(mSX + dx3, sy, int2str(highscore[entry].Score, 5), font_nr4);
1868   }
1869
1870   redraw_mask |= REDRAW_FIELD;
1871 }
1872
1873 void HandleHallOfFame(int mx, int my, int dx, int dy, int button)
1874 {
1875   static int first_entry = 0;
1876   static int highlight_position = 0;
1877   int step = (button == 1 ? 1 : button == 2 ? 5 : 10);
1878   int button_released = !button;
1879
1880   if (button == MB_MENU_INITIALIZE)
1881   {
1882     first_entry = 0;
1883     highlight_position = mx;
1884     drawHallOfFameList(first_entry, highlight_position);
1885
1886     return;
1887   }
1888
1889   if (ABS(dy) == SCROLL_PAGE)           /* handle scrolling one page */
1890     step = NUM_MENU_ENTRIES_ON_SCREEN - 1;
1891
1892   if (dy < 0)
1893   {
1894     if (first_entry > 0)
1895     {
1896       first_entry -= step;
1897       if (first_entry < 0)
1898         first_entry = 0;
1899
1900       drawHallOfFameList(first_entry, highlight_position);
1901     }
1902   }
1903   else if (dy > 0)
1904   {
1905     if (first_entry + NUM_MENU_ENTRIES_ON_SCREEN < MAX_SCORE_ENTRIES)
1906     {
1907       first_entry += step;
1908       if (first_entry + NUM_MENU_ENTRIES_ON_SCREEN > MAX_SCORE_ENTRIES)
1909         first_entry = MAX(0, MAX_SCORE_ENTRIES - NUM_MENU_ENTRIES_ON_SCREEN);
1910
1911       drawHallOfFameList(first_entry, highlight_position);
1912     }
1913   }
1914   else if (button_released)
1915   {
1916     FadeSound(SND_BACKGROUND_SCORES);
1917     game_status = GAME_MODE_MAIN;
1918     DrawMainMenu();
1919   }
1920
1921   if (game_status == GAME_MODE_SCORES)
1922     PlayMenuSoundIfLoop();
1923
1924   DoAnimation();
1925 }
1926
1927
1928 /* ========================================================================= */
1929 /* setup screen functions                                                    */
1930 /* ========================================================================= */
1931
1932 static struct TokenInfo *setup_info;
1933 static int num_setup_info;
1934
1935 static char *graphics_set_name;
1936 static char *sounds_set_name;
1937 static char *music_set_name;
1938
1939 static void execSetupMain()
1940 {
1941   setup_mode = SETUP_MODE_MAIN;
1942   DrawSetupScreen();
1943 }
1944
1945 static void execSetupGame()
1946 {
1947   setup_mode = SETUP_MODE_GAME;
1948   DrawSetupScreen();
1949 }
1950
1951 static void execSetupEditor()
1952 {
1953   setup_mode = SETUP_MODE_EDITOR;
1954   DrawSetupScreen();
1955 }
1956
1957 static void execSetupGraphics()
1958 {
1959   setup_mode = SETUP_MODE_GRAPHICS;
1960   DrawSetupScreen();
1961 }
1962
1963 static void execSetupSound()
1964 {
1965   setup_mode = SETUP_MODE_SOUND;
1966   DrawSetupScreen();
1967 }
1968
1969 static void execSetupArtwork()
1970 {
1971   setup.graphics_set = artwork.gfx_current->identifier;
1972   setup.sounds_set = artwork.snd_current->identifier;
1973   setup.music_set = artwork.mus_current->identifier;
1974
1975   /* needed if last screen (setup choice) changed graphics, sounds or music */
1976   ReloadCustomArtwork(0);
1977
1978   /* needed for displaying artwork name instead of artwork identifier */
1979   graphics_set_name = artwork.gfx_current->name;
1980   sounds_set_name = artwork.snd_current->name;
1981   music_set_name = artwork.mus_current->name;
1982
1983   setup_mode = SETUP_MODE_ARTWORK;
1984   DrawSetupScreen();
1985 }
1986
1987 static void execSetupChooseGraphics()
1988 {
1989   setup_mode = SETUP_MODE_CHOOSE_GRAPHICS;
1990   DrawSetupScreen();
1991 }
1992
1993 static void execSetupChooseSounds()
1994 {
1995   setup_mode = SETUP_MODE_CHOOSE_SOUNDS;
1996   DrawSetupScreen();
1997 }
1998
1999 static void execSetupChooseMusic()
2000 {
2001   setup_mode = SETUP_MODE_CHOOSE_MUSIC;
2002   DrawSetupScreen();
2003 }
2004
2005 static void execSetupInput()
2006 {
2007   setup_mode = SETUP_MODE_INPUT;
2008   DrawSetupScreen();
2009 }
2010
2011 static void execSetupShortcut1()
2012 {
2013   setup_mode = SETUP_MODE_SHORTCUT_1;
2014   DrawSetupScreen();
2015 }
2016
2017 static void execSetupShortcut2()
2018 {
2019   setup_mode = SETUP_MODE_SHORTCUT_2;
2020   DrawSetupScreen();
2021 }
2022
2023 static void execExitSetup()
2024 {
2025   game_status = GAME_MODE_MAIN;
2026   DrawMainMenu();
2027 }
2028
2029 static void execSaveAndExitSetup()
2030 {
2031   SaveSetup();
2032   execExitSetup();
2033 }
2034
2035 static struct TokenInfo setup_info_main[] =
2036 {
2037   { TYPE_ENTER_MENU,    execSetupGame,          "Game & Menu"           },
2038   { TYPE_ENTER_MENU,    execSetupEditor,        "Editor"                },
2039   { TYPE_ENTER_MENU,    execSetupGraphics,      "Graphics"              },
2040   { TYPE_ENTER_MENU,    execSetupSound,         "Sound & Music"         },
2041   { TYPE_ENTER_MENU,    execSetupArtwork,       "Custom Artwork"        },
2042   { TYPE_ENTER_MENU,    execSetupInput,         "Input Devices"         },
2043   { TYPE_ENTER_MENU,    execSetupShortcut1,     "Key Shortcuts 1"       },
2044   { TYPE_ENTER_MENU,    execSetupShortcut2,     "Key Shortcuts 2"       },
2045   { TYPE_EMPTY,         NULL,                   ""                      },
2046   { TYPE_LEAVE_MENU,    execExitSetup,          "Exit"                  },
2047   { TYPE_LEAVE_MENU,    execSaveAndExitSetup,   "Save and Exit"         },
2048
2049   { 0,                  NULL,                   NULL                    }
2050 };
2051
2052 static struct TokenInfo setup_info_game[] =
2053 {
2054   { TYPE_SWITCH,        &setup.team_mode,       "Team-Mode (Multi-Player):" },
2055   { TYPE_SWITCH,        &setup.handicap,        "Handicap:"             },
2056   { TYPE_SWITCH,        &setup.skip_levels,     "Skip Unsolved Levels:" },
2057   { TYPE_SWITCH,        &setup.time_limit,      "Time Limit:"           },
2058   { TYPE_SWITCH,        &setup.autorecord,      "Auto-Record Tapes:"    },
2059   { TYPE_EMPTY,         NULL,                   ""                      },
2060   { TYPE_LEAVE_MENU,    execSetupMain,          "Back"                  },
2061
2062   { 0,                  NULL,                   NULL                    }
2063 };
2064
2065 static struct TokenInfo setup_info_editor[] =
2066 {
2067   { TYPE_SWITCH,        &setup.editor.el_boulderdash,   "Boulder Dash:" },
2068   { TYPE_SWITCH,        &setup.editor.el_emerald_mine,  "Emerald Mine:" },
2069   { TYPE_SWITCH, &setup.editor.el_emerald_mine_club,    "Emerald Mine Club:" },
2070   { TYPE_SWITCH,        &setup.editor.el_more,          "Rocks'n'Diamonds:" },
2071   { TYPE_SWITCH,        &setup.editor.el_sokoban,       "Sokoban:"      },
2072   { TYPE_SWITCH,        &setup.editor.el_supaplex,      "Supaplex:"     },
2073   { TYPE_SWITCH,        &setup.editor.el_diamond_caves, "Diamond Caves II:" },
2074   { TYPE_SWITCH,        &setup.editor.el_dx_boulderdash,"DX-Boulderdash:" },
2075   { TYPE_SWITCH,        &setup.editor.el_chars,         "Text Characters:" },
2076   { TYPE_SWITCH,        &setup.editor.el_custom,  "Custom & Group Elements:" },
2077   { TYPE_SWITCH,        &setup.editor.el_headlines,     "Headlines:"    },
2078   { TYPE_SWITCH, &setup.editor.el_user_defined, "User defined element list:" },
2079   { TYPE_SWITCH,        &setup.editor.el_dynamic,  "Dynamic level elements:" },
2080   { TYPE_EMPTY,         NULL,                   ""                      },
2081   { TYPE_LEAVE_MENU,    execSetupMain,          "Back"                  },
2082
2083   { 0,                  NULL,                   NULL                    }
2084 };
2085
2086 static struct TokenInfo setup_info_graphics[] =
2087 {
2088   { TYPE_SWITCH,        &setup.fullscreen,      "Fullscreen Mode:"      },
2089   { TYPE_SWITCH,        &setup.scroll_delay,    "Delayed Scrolling:"    },
2090   { TYPE_SWITCH,        &setup.soft_scrolling,  "Soft Scrolling:"       },
2091 #if 0
2092   { TYPE_SWITCH,        &setup.double_buffering,"Double-Buffering:"     },
2093   { TYPE_SWITCH,        &setup.fading,          "Fading:"               },
2094 #endif
2095   { TYPE_SWITCH,        &setup.quick_switch,    "Quick Player Focus Switch:" },
2096   { TYPE_SWITCH,        &setup.quick_doors,     "Quick Menu Doors:"     },
2097   { TYPE_SWITCH,        &setup.toons,           "Toons:"                },
2098   { TYPE_EMPTY,         NULL,                   ""                      },
2099   { TYPE_LEAVE_MENU,    execSetupMain,          "Back"                  },
2100
2101   { 0,                  NULL,                   NULL                    }
2102 };
2103
2104 static struct TokenInfo setup_info_sound[] =
2105 {
2106   { TYPE_SWITCH,        &setup.sound_simple,    "Sound Effects (Normal):"  },
2107   { TYPE_SWITCH,        &setup.sound_loops,     "Sound Effects (Looping):" },
2108   { TYPE_SWITCH,        &setup.sound_music,     "Music:"                },
2109   { TYPE_EMPTY,         NULL,                   ""                      },
2110   { TYPE_LEAVE_MENU,    execSetupMain,          "Back"                  },
2111
2112   { 0,                  NULL,                   NULL                    }
2113 };
2114
2115 static struct TokenInfo setup_info_artwork[] =
2116 {
2117   { TYPE_ENTER_MENU,    execSetupChooseGraphics,"Custom Graphics"       },
2118   { TYPE_STRING,        &graphics_set_name,     ""                      },
2119   { TYPE_ENTER_MENU,    execSetupChooseSounds,  "Custom Sounds"         },
2120   { TYPE_STRING,        &sounds_set_name,       ""                      },
2121   { TYPE_ENTER_MENU,    execSetupChooseMusic,   "Custom Music"          },
2122   { TYPE_STRING,        &music_set_name,        ""                      },
2123   { TYPE_EMPTY,         NULL,                   ""                      },
2124 #if 1
2125   { TYPE_YES_NO, &setup.override_level_graphics,"Override Level Graphics:" },
2126   { TYPE_YES_NO, &setup.override_level_sounds,  "Override Level Sounds:"   },
2127   { TYPE_YES_NO, &setup.override_level_music,   "Override Level Music:"    },
2128 #else
2129   { TYPE_STRING,        NULL,                   "Override Level Artwork:"},
2130   { TYPE_YES_NO,        &setup.override_level_graphics, "Graphics:"     },
2131   { TYPE_YES_NO,        &setup.override_level_sounds,   "Sounds:"       },
2132   { TYPE_YES_NO,        &setup.override_level_music,    "Music:"        },
2133 #endif
2134   { TYPE_EMPTY,         NULL,                   ""                      },
2135   { TYPE_LEAVE_MENU,    execSetupMain,          "Back"                  },
2136
2137   { 0,                  NULL,                   NULL                    }
2138 };
2139
2140 static struct TokenInfo setup_info_shortcut_1[] =
2141 {
2142   { TYPE_KEYTEXT,       NULL,           "Quick Save Game to Tape:",     },
2143   { TYPE_KEY,           &setup.shortcut.save_game, ""                   },
2144   { TYPE_KEYTEXT,       NULL,           "Quick Load Game from Tape:",   },
2145   { TYPE_KEY,           &setup.shortcut.load_game, ""                   },
2146   { TYPE_KEYTEXT,       NULL,           "Start Game & Toggle Pause:",   },
2147   { TYPE_KEY,           &setup.shortcut.toggle_pause, ""                },
2148   { TYPE_EMPTY,         NULL,                   ""                      },
2149   { TYPE_YES_NO,        &setup.ask_on_escape,   "Ask on 'Esc' Key:"     },
2150   { TYPE_YES_NO, &setup.ask_on_escape_editor,   "Ask on 'Esc' Key (Editor):" },
2151   { TYPE_EMPTY,         NULL,                   ""                      },
2152   { TYPE_LEAVE_MENU,    execSetupMain,          "Back"                  },
2153
2154   { 0,                  NULL,                   NULL                    }
2155 };
2156
2157 static struct TokenInfo setup_info_shortcut_2[] =
2158 {
2159   { TYPE_KEYTEXT,       NULL,           "Set Focus to Player 1:",       },
2160   { TYPE_KEY,           &setup.shortcut.focus_player[0], ""             },
2161   { TYPE_KEYTEXT,       NULL,           "Set Focus to Player 2:",       },
2162   { TYPE_KEY,           &setup.shortcut.focus_player[1], ""             },
2163   { TYPE_KEYTEXT,       NULL,           "Set Focus to Player 3:",       },
2164   { TYPE_KEY,           &setup.shortcut.focus_player[2], ""             },
2165   { TYPE_KEYTEXT,       NULL,           "Set Focus to Player 4:",       },
2166   { TYPE_KEY,           &setup.shortcut.focus_player[3], ""             },
2167   { TYPE_KEYTEXT,       NULL,           "Set Focus to All Players:",    },
2168   { TYPE_KEY,           &setup.shortcut.focus_player_all, ""            },
2169   { TYPE_EMPTY,         NULL,                   ""                      },
2170   { TYPE_LEAVE_MENU,    execSetupMain,          "Back"                  },
2171
2172   { 0,                  NULL,                   NULL                    }
2173 };
2174
2175 static Key getSetupKey()
2176 {
2177   Key key = KSYM_UNDEFINED;
2178   boolean got_key_event = FALSE;
2179
2180   while (!got_key_event)
2181   {
2182     if (PendingEvent())         /* got event */
2183     {
2184       Event event;
2185
2186       NextEvent(&event);
2187
2188       switch(event.type)
2189       {
2190         case EVENT_KEYPRESS:
2191           {
2192             key = GetEventKey((KeyEvent *)&event, TRUE);
2193
2194             /* press 'Escape' or 'Enter' to keep the existing key binding */
2195             if (key == KSYM_Escape || key == KSYM_Return)
2196               key = KSYM_UNDEFINED;     /* keep old value */
2197
2198             got_key_event = TRUE;
2199           }
2200           break;
2201
2202         case EVENT_KEYRELEASE:
2203           key_joystick_mapping = 0;
2204           break;
2205
2206         default:
2207           HandleOtherEvents(&event);
2208           break;
2209       }
2210     }
2211
2212     DoAnimation();
2213     BackToFront();
2214
2215     /* don't eat all CPU time */
2216     Delay(10);
2217   }
2218
2219   return key;
2220 }
2221
2222 static void drawSetupValue(int pos)
2223 {
2224   int xpos = MENU_SCREEN_VALUE_XPOS;
2225   int ypos = MENU_SCREEN_START_YPOS + pos;
2226   int font_nr = FONT_VALUE_1;
2227   int type = setup_info[pos].type;
2228   void *value = setup_info[pos].value;
2229   char *value_string = getSetupValue(type, value);
2230   int i;
2231
2232   if (value_string == NULL)
2233     return;
2234
2235   if (type & TYPE_KEY)
2236   {
2237 #if 1
2238     xpos = 1;
2239 #else
2240     xpos = 3;
2241 #endif
2242
2243     if (type & TYPE_QUERY)
2244     {
2245       value_string = "<press key>";
2246       font_nr = FONT_INPUT_1_ACTIVE;
2247     }
2248   }
2249   else if (type & TYPE_STRING)
2250   {
2251     int max_value_len = (SCR_FIELDX - 2) * 2;
2252
2253     xpos = 1;
2254     font_nr = FONT_VALUE_2;
2255
2256     if (strlen(value_string) > max_value_len)
2257       value_string[max_value_len] = '\0';
2258   }
2259   else if (type & TYPE_BOOLEAN_STYLE)
2260   {
2261     font_nr = (*(boolean *)value ? FONT_OPTION_ON : FONT_OPTION_OFF);
2262   }
2263
2264 #if 1
2265   for (i = xpos; i <= MENU_SCREEN_MAX_XPOS; i++)
2266     DrawText(mSX + i * 32, mSY + ypos * 32, " ", font_nr);
2267 #else
2268   DrawText(mSX + xpos * 32, mSY + ypos * 32,
2269            (xpos == 3 ? "              " : "   "), font_nr);
2270 #endif
2271
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 }