ed937406044ecf2e7479b0f47ca1288335f53805
[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 #if 0
2068   { TYPE_STRING,        NULL,                   "Offer Special Elements:"},
2069 #endif
2070
2071 #if 0
2072 #else
2073   { TYPE_SWITCH,        &setup.editor.el_boulderdash,   "Boulder Dash:" },
2074   { TYPE_SWITCH,        &setup.editor.el_emerald_mine,  "Emerald Mine:" },
2075   { TYPE_SWITCH, &setup.editor.el_emerald_mine_club,    "Emerald Mine Club:" },
2076   { TYPE_SWITCH,        &setup.editor.el_more,          "Rocks'n'Diamonds:" },
2077   { TYPE_SWITCH,        &setup.editor.el_sokoban,       "Sokoban:"      },
2078   { TYPE_SWITCH,        &setup.editor.el_supaplex,      "Supaplex:"     },
2079   { TYPE_SWITCH,        &setup.editor.el_diamond_caves, "Diamond Caves II:" },
2080   { TYPE_SWITCH,        &setup.editor.el_dx_boulderdash,"DX-Boulderdash:" },
2081 #endif
2082   { TYPE_SWITCH,        &setup.editor.el_chars,         "Text Characters:" },
2083   { TYPE_SWITCH,        &setup.editor.el_custom,  "Custom & Group Elements:" },
2084   { TYPE_SWITCH,        &setup.editor.el_headlines,     "Headlines:"    },
2085   { TYPE_SWITCH, &setup.editor.el_user_defined, "User defined element list:" },
2086   { TYPE_SWITCH,        &setup.editor.el_dynamic,  "Dynamic level elements:" },
2087   { TYPE_EMPTY,         NULL,                   ""                      },
2088   { TYPE_LEAVE_MENU,    execSetupMain,          "Back"                  },
2089
2090   { 0,                  NULL,                   NULL                    }
2091 };
2092
2093 static struct TokenInfo setup_info_graphics[] =
2094 {
2095   { TYPE_SWITCH,        &setup.fullscreen,      "Fullscreen Mode:"      },
2096   { TYPE_SWITCH,        &setup.scroll_delay,    "Delayed Scrolling:"    },
2097   { TYPE_SWITCH,        &setup.soft_scrolling,  "Soft Scrolling:"       },
2098 #if 0
2099   { TYPE_SWITCH,        &setup.double_buffering,"Double-Buffering:"     },
2100   { TYPE_SWITCH,        &setup.fading,          "Fading:"               },
2101 #endif
2102   { TYPE_SWITCH,        &setup.quick_switch,    "Quick Player Focus Switch:" },
2103   { TYPE_SWITCH,        &setup.quick_doors,     "Quick Menu Doors:"     },
2104   { TYPE_SWITCH,        &setup.toons,           "Toons:"                },
2105   { TYPE_EMPTY,         NULL,                   ""                      },
2106   { TYPE_LEAVE_MENU,    execSetupMain,          "Back"                  },
2107
2108   { 0,                  NULL,                   NULL                    }
2109 };
2110
2111 static struct TokenInfo setup_info_sound[] =
2112 {
2113   { TYPE_SWITCH,        &setup.sound_simple,    "Sound Effects (Normal):"  },
2114   { TYPE_SWITCH,        &setup.sound_loops,     "Sound Effects (Looping):" },
2115   { TYPE_SWITCH,        &setup.sound_music,     "Music:"                },
2116   { TYPE_EMPTY,         NULL,                   ""                      },
2117   { TYPE_LEAVE_MENU,    execSetupMain,          "Back"                  },
2118
2119   { 0,                  NULL,                   NULL                    }
2120 };
2121
2122 static struct TokenInfo setup_info_artwork[] =
2123 {
2124   { TYPE_ENTER_MENU,    execSetupChooseGraphics,"Custom Graphics"       },
2125   { TYPE_STRING,        &graphics_set_name,     ""                      },
2126   { TYPE_ENTER_MENU,    execSetupChooseSounds,  "Custom Sounds"         },
2127   { TYPE_STRING,        &sounds_set_name,       ""                      },
2128   { TYPE_ENTER_MENU,    execSetupChooseMusic,   "Custom Music"          },
2129   { TYPE_STRING,        &music_set_name,        ""                      },
2130   { TYPE_EMPTY,         NULL,                   ""                      },
2131 #if 1
2132   { TYPE_YES_NO, &setup.override_level_graphics,"Override Level Graphics:" },
2133   { TYPE_YES_NO, &setup.override_level_sounds,  "Override Level Sounds:"   },
2134   { TYPE_YES_NO, &setup.override_level_music,   "Override Level Music:"    },
2135 #else
2136   { TYPE_STRING,        NULL,                   "Override Level Artwork:"},
2137   { TYPE_YES_NO,        &setup.override_level_graphics, "Graphics:"     },
2138   { TYPE_YES_NO,        &setup.override_level_sounds,   "Sounds:"       },
2139   { TYPE_YES_NO,        &setup.override_level_music,    "Music:"        },
2140 #endif
2141   { TYPE_EMPTY,         NULL,                   ""                      },
2142   { TYPE_LEAVE_MENU,    execSetupMain,          "Back"                  },
2143
2144   { 0,                  NULL,                   NULL                    }
2145 };
2146
2147 static struct TokenInfo setup_info_shortcut_1[] =
2148 {
2149   { TYPE_KEYTEXT,       NULL,           "Quick Save Game to Tape:",     },
2150   { TYPE_KEY,           &setup.shortcut.save_game, ""                   },
2151   { TYPE_KEYTEXT,       NULL,           "Quick Load Game from Tape:",   },
2152   { TYPE_KEY,           &setup.shortcut.load_game, ""                   },
2153   { TYPE_KEYTEXT,       NULL,           "Start Game & Toggle Pause:",   },
2154   { TYPE_KEY,           &setup.shortcut.toggle_pause, ""                },
2155   { TYPE_EMPTY,         NULL,                   ""                      },
2156   { TYPE_YES_NO,        &setup.ask_on_escape,   "Ask on 'Esc' Key:"     },
2157   { TYPE_YES_NO, &setup.ask_on_escape_editor,   "Ask on 'Esc' Key (Editor):" },
2158   { TYPE_EMPTY,         NULL,                   ""                      },
2159   { TYPE_LEAVE_MENU,    execSetupMain,          "Back"                  },
2160
2161   { 0,                  NULL,                   NULL                    }
2162 };
2163
2164 static struct TokenInfo setup_info_shortcut_2[] =
2165 {
2166   { TYPE_KEYTEXT,       NULL,           "Set Focus to Player 1:",       },
2167   { TYPE_KEY,           &setup.shortcut.focus_player[0], ""             },
2168   { TYPE_KEYTEXT,       NULL,           "Set Focus to Player 2:",       },
2169   { TYPE_KEY,           &setup.shortcut.focus_player[1], ""             },
2170   { TYPE_KEYTEXT,       NULL,           "Set Focus to Player 3:",       },
2171   { TYPE_KEY,           &setup.shortcut.focus_player[2], ""             },
2172   { TYPE_KEYTEXT,       NULL,           "Set Focus to Player 4:",       },
2173   { TYPE_KEY,           &setup.shortcut.focus_player[3], ""             },
2174   { TYPE_KEYTEXT,       NULL,           "Set Focus to All Players:",    },
2175   { TYPE_KEY,           &setup.shortcut.focus_player_all, ""            },
2176   { TYPE_EMPTY,         NULL,                   ""                      },
2177   { TYPE_LEAVE_MENU,    execSetupMain,          "Back"                  },
2178
2179   { 0,                  NULL,                   NULL                    }
2180 };
2181
2182 static Key getSetupKey()
2183 {
2184   Key key = KSYM_UNDEFINED;
2185   boolean got_key_event = FALSE;
2186
2187   while (!got_key_event)
2188   {
2189     if (PendingEvent())         /* got event */
2190     {
2191       Event event;
2192
2193       NextEvent(&event);
2194
2195       switch(event.type)
2196       {
2197         case EVENT_KEYPRESS:
2198           {
2199             key = GetEventKey((KeyEvent *)&event, TRUE);
2200
2201             /* press 'Escape' or 'Enter' to keep the existing key binding */
2202             if (key == KSYM_Escape || key == KSYM_Return)
2203               key = KSYM_UNDEFINED;     /* keep old value */
2204
2205             got_key_event = TRUE;
2206           }
2207           break;
2208
2209         case EVENT_KEYRELEASE:
2210           key_joystick_mapping = 0;
2211           break;
2212
2213         default:
2214           HandleOtherEvents(&event);
2215           break;
2216       }
2217     }
2218
2219     DoAnimation();
2220     BackToFront();
2221
2222     /* don't eat all CPU time */
2223     Delay(10);
2224   }
2225
2226   return key;
2227 }
2228
2229 static void drawSetupValue(int pos)
2230 {
2231   int xpos = MENU_SCREEN_VALUE_XPOS;
2232   int ypos = MENU_SCREEN_START_YPOS + pos;
2233   int font_nr = FONT_VALUE_1;
2234   int type = setup_info[pos].type;
2235   void *value = setup_info[pos].value;
2236   char *value_string = getSetupValue(type, value);
2237   int i;
2238
2239   if (value_string == NULL)
2240     return;
2241
2242   if (type & TYPE_KEY)
2243   {
2244 #if 1
2245     xpos = 1;
2246 #else
2247     xpos = 3;
2248 #endif
2249
2250     if (type & TYPE_QUERY)
2251     {
2252       value_string = "<press key>";
2253       font_nr = FONT_INPUT_1_ACTIVE;
2254     }
2255   }
2256   else if (type & TYPE_STRING)
2257   {
2258     int max_value_len = (SCR_FIELDX - 2) * 2;
2259
2260     xpos = 1;
2261     font_nr = FONT_VALUE_2;
2262
2263     if (strlen(value_string) > max_value_len)
2264       value_string[max_value_len] = '\0';
2265   }
2266   else if (type & TYPE_BOOLEAN_STYLE)
2267   {
2268     font_nr = (*(boolean *)value ? FONT_OPTION_ON : FONT_OPTION_OFF);
2269   }
2270
2271 #if 1
2272   for (i = xpos; i <= MENU_SCREEN_MAX_XPOS; i++)
2273     DrawText(mSX + i * 32, mSY + ypos * 32, " ", font_nr);
2274 #else
2275   DrawText(mSX + xpos * 32, mSY + ypos * 32,
2276            (xpos == 3 ? "              " : "   "), font_nr);
2277 #endif
2278
2279   DrawText(mSX + xpos * 32, mSY + ypos * 32, value_string, font_nr);
2280 }
2281
2282 static void changeSetupValue(int pos)
2283 {
2284   if (setup_info[pos].type & TYPE_BOOLEAN_STYLE)
2285   {
2286     *(boolean *)setup_info[pos].value ^= TRUE;
2287   }
2288   else if (setup_info[pos].type & TYPE_KEY)
2289   {
2290     Key key;
2291
2292     setup_info[pos].type |= TYPE_QUERY;
2293     drawSetupValue(pos);
2294     setup_info[pos].type &= ~TYPE_QUERY;
2295
2296     key = getSetupKey();
2297     if (key != KSYM_UNDEFINED)
2298       *(Key *)setup_info[pos].value = key;
2299   }
2300
2301   drawSetupValue(pos);
2302 }
2303
2304 static void DrawSetupScreen_Generic()
2305 {
2306   char *title_string = NULL;
2307   int i;
2308
2309   UnmapAllGadgets();
2310   CloseDoor(DOOR_CLOSE_2);
2311
2312   ClearWindow();
2313
2314   if (setup_mode == SETUP_MODE_MAIN)
2315   {
2316     setup_info = setup_info_main;
2317     title_string = "Setup";
2318   }
2319   else if (setup_mode == SETUP_MODE_GAME)
2320   {
2321     setup_info = setup_info_game;
2322     title_string = "Setup Game";
2323   }
2324   else if (setup_mode == SETUP_MODE_EDITOR)
2325   {
2326     setup_info = setup_info_editor;
2327     title_string = "Setup Editor";
2328   }
2329   else if (setup_mode == SETUP_MODE_GRAPHICS)
2330   {
2331     setup_info = setup_info_graphics;
2332     title_string = "Setup Graphics";
2333   }
2334   else if (setup_mode == SETUP_MODE_SOUND)
2335   {
2336     setup_info = setup_info_sound;
2337     title_string = "Setup Sound";
2338   }
2339   else if (setup_mode == SETUP_MODE_ARTWORK)
2340   {
2341     setup_info = setup_info_artwork;
2342     title_string = "Custom Artwork";
2343   }
2344   else if (setup_mode == SETUP_MODE_SHORTCUT_1)
2345   {
2346     setup_info = setup_info_shortcut_1;
2347     title_string = "Setup Shortcuts";
2348   }
2349   else if (setup_mode == SETUP_MODE_SHORTCUT_2)
2350   {
2351     setup_info = setup_info_shortcut_2;
2352     title_string = "Setup Shortcuts";
2353   }
2354
2355 #if 1
2356   DrawTextSCentered(mSY - SY + 16, FONT_TITLE_1, title_string);
2357 #else
2358   DrawText(mSX + 16, mSY + 16, title_string, FONT_TITLE_1);
2359 #endif
2360
2361   num_setup_info = 0;
2362   for (i = 0; setup_info[i].type != 0 && i < NUM_MENU_ENTRIES_ON_SCREEN; i++)
2363   {
2364     void *value_ptr = setup_info[i].value;
2365     int ypos = MENU_SCREEN_START_YPOS + i;
2366     int font_nr = FONT_MENU_1;
2367
2368     /* set some entries to "unchangeable" according to other variables */
2369     if ((value_ptr == &setup.sound_simple && !audio.sound_available) ||
2370         (value_ptr == &setup.sound_loops  && !audio.loops_available) ||
2371         (value_ptr == &setup.sound_music  && !audio.music_available) ||
2372         (value_ptr == &setup.fullscreen   && !video.fullscreen_available))
2373       setup_info[i].type |= TYPE_GHOSTED;
2374
2375 #if 1
2376     if (setup_info[i].type & (TYPE_SWITCH |
2377                               TYPE_YES_NO |
2378                               TYPE_STRING |
2379                               TYPE_KEYTEXT))
2380       font_nr = FONT_MENU_2;
2381 #else
2382     if (setup_info[i].type & TYPE_STRING)
2383       font_nr = FONT_MENU_2;
2384 #endif
2385
2386     DrawText(mSX + 32, mSY + ypos * 32, setup_info[i].text, font_nr);
2387
2388     if (setup_info[i].type & TYPE_ENTER_MENU)
2389       initCursor(i, IMG_MENU_BUTTON_ENTER_MENU);
2390     else if (setup_info[i].type & TYPE_LEAVE_MENU)
2391       initCursor(i, IMG_MENU_BUTTON_LEAVE_MENU);
2392     else if (setup_info[i].type & ~TYPE_SKIP_ENTRY)
2393       initCursor(i, IMG_MENU_BUTTON);
2394
2395     if (setup_info[i].type & TYPE_VALUE)
2396       drawSetupValue(i);
2397
2398     num_setup_info++;
2399   }
2400
2401 #if 0
2402   DrawTextSCentered(SYSIZE - 20, FONT_TEXT_4,
2403                     "Joysticks deactivated in setup menu");
2404 #endif
2405
2406   FadeToFront();
2407   InitAnimation();
2408   HandleSetupScreen_Generic(0, 0, 0, 0, MB_MENU_INITIALIZE);
2409 }
2410
2411 void HandleSetupScreen_Generic(int mx, int my, int dx, int dy, int button)
2412 {
2413   static int choice_store[MAX_SETUP_MODES];
2414   int choice = choice_store[setup_mode];        /* always starts with 0 */
2415   int x = 0;
2416   int y = choice;
2417
2418   if (button == MB_MENU_INITIALIZE)
2419   {
2420     /* advance to first valid menu entry */
2421     while (choice < num_setup_info &&
2422            setup_info[choice].type & TYPE_SKIP_ENTRY)
2423       choice++;
2424     choice_store[setup_mode] = choice;
2425
2426     drawCursor(choice, FC_RED);
2427     return;
2428   }
2429   else if (button == MB_MENU_LEAVE)
2430   {
2431     for (y = 0; y < num_setup_info; y++)
2432     {
2433       if (setup_info[y].type & TYPE_LEAVE_MENU)
2434       {
2435         void (*menu_callback_function)(void) = setup_info[y].value;
2436
2437         menu_callback_function();
2438         break;  /* absolutely needed because function changes 'setup_info'! */
2439       }
2440     }
2441
2442     return;
2443   }
2444
2445   if (mx || my)         /* mouse input */
2446   {
2447     x = (mx - mSX) / 32;
2448     y = (my - mSY) / 32 - MENU_SCREEN_START_YPOS;
2449   }
2450   else if (dx || dy)    /* keyboard input */
2451   {
2452     if (dx)
2453     {
2454       int menu_navigation_type = (dx < 0 ? TYPE_LEAVE_MENU : TYPE_ENTER_MENU);
2455
2456       if (setup_info[choice].type & menu_navigation_type ||
2457           setup_info[choice].type & TYPE_BOOLEAN_STYLE)
2458         button = MB_MENU_CHOICE;
2459     }
2460     else if (dy)
2461       y = choice + dy;
2462
2463     /* jump to next non-empty menu entry (up or down) */
2464     while (y > 0 && y < num_setup_info - 1 &&
2465            setup_info[y].type & TYPE_SKIP_ENTRY)
2466       y += dy;
2467   }
2468
2469   if (IN_VIS_FIELD(x, y) &&
2470       y >= 0 && y < num_setup_info && setup_info[y].type & ~TYPE_SKIP_ENTRY)
2471   {
2472     if (button)
2473     {
2474       if (y != choice)
2475       {
2476         drawCursor(y, FC_RED);
2477         drawCursor(choice, FC_BLUE);
2478         choice = choice_store[setup_mode] = y;
2479       }
2480     }
2481     else if (!(setup_info[y].type & TYPE_GHOSTED))
2482     {
2483       if (setup_info[y].type & TYPE_ENTER_OR_LEAVE_MENU)
2484       {
2485         void (*menu_callback_function)(void) = setup_info[choice].value;
2486
2487         menu_callback_function();
2488       }
2489       else
2490       {
2491         if (setup_info[y].type & TYPE_KEYTEXT &&
2492             setup_info[y + 1].type & TYPE_KEY)
2493           y++;
2494
2495         if (setup_info[y].type & TYPE_VALUE)
2496           changeSetupValue(y);
2497       }
2498     }
2499   }
2500 }
2501
2502 void DrawSetupScreen_Input()
2503 {
2504   ClearWindow();
2505
2506 #if 1
2507   DrawTextSCentered(mSY - SY + 16, FONT_TITLE_1, "Setup Input");
2508 #else
2509   DrawText(mSX + 16, mSY + 16, "Setup Input", FONT_TITLE_1);
2510 #endif
2511
2512   initCursor(0,  IMG_MENU_BUTTON);
2513   initCursor(1,  IMG_MENU_BUTTON);
2514   initCursor(2,  IMG_MENU_BUTTON_ENTER_MENU);
2515   initCursor(13, IMG_MENU_BUTTON_LEAVE_MENU);
2516
2517   drawCursorXY(10, 0, IMG_MENU_BUTTON_LEFT);
2518   drawCursorXY(12, 0, IMG_MENU_BUTTON_RIGHT);
2519
2520   DrawText(mSX + 32, mSY +  2 * 32, "Player:", FONT_MENU_1);
2521   DrawText(mSX + 32, mSY +  3 * 32, "Device:", FONT_MENU_1);
2522   DrawText(mSX + 32, mSY + 15 * 32, "Back",   FONT_MENU_1);
2523
2524 #if 0
2525   DeactivateJoystickForCalibration();
2526 #endif
2527 #if 1
2528   DrawTextSCentered(SYSIZE - 20, FONT_TEXT_4,
2529                     "Joysticks deactivated on this screen");
2530 #endif
2531
2532   HandleSetupScreen_Input(0, 0, 0, 0, MB_MENU_INITIALIZE);
2533   FadeToFront();
2534   InitAnimation();
2535 }
2536
2537 static void setJoystickDeviceToNr(char *device_name, int device_nr)
2538 {
2539   if (device_name == NULL)
2540     return;
2541
2542   if (device_nr < 0 || device_nr >= MAX_PLAYERS)
2543     device_nr = 0;
2544
2545   if (strlen(device_name) > 1)
2546   {
2547     char c1 = device_name[strlen(device_name) - 1];
2548     char c2 = device_name[strlen(device_name) - 2];
2549
2550     if (c1 >= '0' && c1 <= '9' && !(c2 >= '0' && c2 <= '9'))
2551       device_name[strlen(device_name) - 1] = '0' + (char)(device_nr % 10);
2552   }
2553   else
2554     strncpy(device_name, getDeviceNameFromJoystickNr(device_nr),
2555             strlen(device_name));
2556 }
2557
2558 static void drawPlayerSetupInputInfo(int player_nr)
2559 {
2560   int i;
2561   static struct SetupKeyboardInfo custom_key;
2562   static struct
2563   {
2564     Key *key;
2565     char *text;
2566   } custom[] =
2567   {
2568     { &custom_key.left,  "Joystick Left"  },
2569     { &custom_key.right, "Joystick Right" },
2570     { &custom_key.up,    "Joystick Up"    },
2571     { &custom_key.down,  "Joystick Down"  },
2572     { &custom_key.snap,  "Button 1"       },
2573     { &custom_key.drop,  "Button 2"       }
2574   };
2575   static char *joystick_name[MAX_PLAYERS] =
2576   {
2577     "Joystick1",
2578     "Joystick2",
2579     "Joystick3",
2580     "Joystick4"
2581   };
2582
2583   InitJoysticks();
2584
2585   custom_key = setup.input[player_nr].key;
2586
2587   DrawText(mSX + 11 * 32, mSY + 2 * 32, int2str(player_nr + 1, 1),
2588            FONT_INPUT_1_ACTIVE);
2589
2590   ClearRectangleOnBackground(drawto, mSX + 8 * TILEX, mSY + 2 * TILEY,
2591                              TILEX, TILEY);
2592   DrawGraphicThruMaskExt(drawto, mSX + 8 * TILEX, mSY + 2 * TILEY,
2593                          PLAYER_NR_GFX(IMG_PLAYER_1, player_nr), 0);
2594
2595   if (setup.input[player_nr].use_joystick)
2596   {
2597     char *device_name = setup.input[player_nr].joy.device_name;
2598     char *text = joystick_name[getJoystickNrFromDeviceName(device_name)];
2599     int font_nr = (joystick.fd[player_nr] < 0 ? FONT_VALUE_OLD : FONT_VALUE_1);
2600
2601     DrawText(mSX + 8 * 32, mSY + 3 * 32, text, font_nr);
2602     DrawText(mSX + 32, mSY + 4 * 32, "Calibrate", FONT_MENU_1);
2603   }
2604   else
2605   {
2606     DrawText(mSX + 8 * 32, mSY + 3 * 32, "Keyboard ", FONT_VALUE_1);
2607     DrawText(mSX + 1 * 32, mSY + 4 * 32, "Customize", FONT_MENU_1);
2608   }
2609
2610   DrawText(mSX + 32, mSY + 5 * 32, "Actual Settings:", FONT_MENU_1);
2611   drawCursorXY(1, 4, IMG_MENU_BUTTON_LEFT);
2612   drawCursorXY(1, 5, IMG_MENU_BUTTON_RIGHT);
2613   drawCursorXY(1, 6, IMG_MENU_BUTTON_UP);
2614   drawCursorXY(1, 7, IMG_MENU_BUTTON_DOWN);
2615   DrawText(mSX + 2 * 32, mSY +  6 * 32, ":", FONT_VALUE_OLD);
2616   DrawText(mSX + 2 * 32, mSY +  7 * 32, ":", FONT_VALUE_OLD);
2617   DrawText(mSX + 2 * 32, mSY +  8 * 32, ":", FONT_VALUE_OLD);
2618   DrawText(mSX + 2 * 32, mSY +  9 * 32, ":", FONT_VALUE_OLD);
2619   DrawText(mSX + 1 * 32, mSY + 10 * 32, "Snap Field:", FONT_VALUE_OLD);
2620   DrawText(mSX + 1 * 32, mSY + 12 * 32, "Drop Element:", FONT_VALUE_OLD);
2621
2622   for (i = 0; i < 6; i++)
2623   {
2624     int ypos = 6 + i + (i > 3 ? i-3 : 0);
2625
2626     DrawText(mSX + 3 * 32, mSY + ypos * 32,
2627              "              ", FONT_VALUE_1);
2628     DrawText(mSX + 3 * 32, mSY + ypos * 32,
2629              (setup.input[player_nr].use_joystick ?
2630               custom[i].text :
2631               getKeyNameFromKey(*custom[i].key)), FONT_VALUE_1);
2632   }
2633 }
2634
2635 void HandleSetupScreen_Input(int mx, int my, int dx, int dy, int button)
2636 {
2637   static int choice = 0;
2638   static int player_nr = 0;
2639   int x = 0;
2640   int y = choice;
2641   int pos_start  = SETUPINPUT_SCREEN_POS_START;
2642   int pos_empty1 = SETUPINPUT_SCREEN_POS_EMPTY1;
2643   int pos_empty2 = SETUPINPUT_SCREEN_POS_EMPTY2;
2644   int pos_end    = SETUPINPUT_SCREEN_POS_END;
2645
2646   if (button == MB_MENU_INITIALIZE)
2647   {
2648     drawPlayerSetupInputInfo(player_nr);
2649     drawCursor(choice, FC_RED);
2650
2651     return;
2652   }
2653   else if (button == MB_MENU_LEAVE)
2654   {
2655     setup_mode = SETUP_MODE_MAIN;
2656     DrawSetupScreen();
2657     InitJoysticks();
2658
2659     return;
2660   }
2661
2662   if (mx || my)         /* mouse input */
2663   {
2664     x = (mx - mSX) / 32;
2665     y = (my - mSY) / 32 - MENU_SCREEN_START_YPOS;
2666   }
2667   else if (dx || dy)    /* keyboard input */
2668   {
2669     if (dx && choice == 0)
2670       x = (dx < 0 ? 10 : 12);
2671     else if ((dx && choice == 1) ||
2672              (dx == +1 && choice == 2) ||
2673              (dx == -1 && choice == pos_end))
2674       button = MB_MENU_CHOICE;
2675     else if (dy)
2676       y = choice + dy;
2677
2678     if (y >= pos_empty1 && y <= pos_empty2)
2679       y = (dy > 0 ? pos_empty2 + 1 : pos_empty1 - 1);
2680   }
2681
2682   if (IN_VIS_FIELD(x, y) &&
2683       y == 0 && ((x < 10 && !button) || ((x == 10 || x == 12) && button)))
2684   {
2685     static unsigned long delay = 0;
2686
2687     if (!DelayReached(&delay, GADGET_FRAME_DELAY))
2688       return;
2689
2690     player_nr = (player_nr + (x == 10 ? -1 : +1) + MAX_PLAYERS) % MAX_PLAYERS;
2691
2692     drawPlayerSetupInputInfo(player_nr);
2693   }
2694   else if (IN_VIS_FIELD(x, y) &&
2695            y >= pos_start && y <= pos_end &&
2696            !(y >= pos_empty1 && y <= pos_empty2))
2697   {
2698     if (button)
2699     {
2700       if (y != choice)
2701       {
2702         drawCursor(y, FC_RED);
2703         drawCursor(choice, FC_BLUE);
2704         choice = y;
2705       }
2706     }
2707     else
2708     {
2709       if (y == 1)
2710       {
2711         char *device_name = setup.input[player_nr].joy.device_name;
2712
2713         if (!setup.input[player_nr].use_joystick)
2714         {
2715           int new_device_nr = (dx >= 0 ? 0 : MAX_PLAYERS - 1);
2716
2717           setJoystickDeviceToNr(device_name, new_device_nr);
2718           setup.input[player_nr].use_joystick = TRUE;
2719         }
2720         else
2721         {
2722           int device_nr = getJoystickNrFromDeviceName(device_name);
2723           int new_device_nr = device_nr + (dx >= 0 ? +1 : -1);
2724
2725           if (new_device_nr < 0 || new_device_nr >= MAX_PLAYERS)
2726             setup.input[player_nr].use_joystick = FALSE;
2727           else
2728             setJoystickDeviceToNr(device_name, new_device_nr);
2729         }
2730
2731         drawPlayerSetupInputInfo(player_nr);
2732       }
2733       else if (y == 2)
2734       {
2735         if (setup.input[player_nr].use_joystick)
2736         {
2737           InitJoysticks();
2738           CalibrateJoystick(player_nr);
2739         }
2740         else
2741           CustomizeKeyboard(player_nr);
2742       }
2743       else if (y == pos_end)
2744       {
2745         InitJoysticks();
2746
2747         setup_mode = SETUP_MODE_MAIN;
2748         DrawSetupScreen();
2749       }
2750     }
2751   }
2752 }
2753
2754 void CustomizeKeyboard(int player_nr)
2755 {
2756   int i;
2757   int step_nr;
2758   boolean finished = FALSE;
2759   static struct SetupKeyboardInfo custom_key;
2760   static struct
2761   {
2762     Key *key;
2763     char *text;
2764   } customize_step[] =
2765   {
2766     { &custom_key.left,  "Move Left"    },
2767     { &custom_key.right, "Move Right"   },
2768     { &custom_key.up,    "Move Up"      },
2769     { &custom_key.down,  "Move Down"    },
2770     { &custom_key.snap,  "Snap Field"   },
2771     { &custom_key.drop,  "Drop Element" }
2772   };
2773
2774   /* read existing key bindings from player setup */
2775   custom_key = setup.input[player_nr].key;
2776
2777   ClearWindow();
2778
2779 #if 1
2780   DrawTextSCentered(mSY - SY + 16, FONT_TITLE_1, "Keyboard Input");
2781 #else
2782   DrawText(mSX + 16, mSY + 16, "Keyboard Input", FONT_TITLE_1);
2783 #endif
2784
2785   BackToFront();
2786   InitAnimation();
2787
2788   step_nr = 0;
2789   DrawText(mSX, mSY + (2 + 2 * step_nr) * 32,
2790            customize_step[step_nr].text, FONT_INPUT_1_ACTIVE);
2791   DrawText(mSX, mSY + (2 + 2 * step_nr + 1) * 32,
2792            "Key:", FONT_INPUT_1_ACTIVE);
2793   DrawText(mSX + 4 * 32, mSY + (2 + 2 * step_nr + 1) * 32,
2794            getKeyNameFromKey(*customize_step[step_nr].key), FONT_VALUE_OLD);
2795
2796   while (!finished)
2797   {
2798     if (PendingEvent())         /* got event */
2799     {
2800       Event event;
2801
2802       NextEvent(&event);
2803
2804       switch(event.type)
2805       {
2806         case EVENT_KEYPRESS:
2807           {
2808             Key key = GetEventKey((KeyEvent *)&event, FALSE);
2809
2810             if (key == KSYM_Escape || (key == KSYM_Return && step_nr == 6))
2811             {
2812               finished = TRUE;
2813               break;
2814             }
2815
2816             /* all keys configured -- wait for "Escape" or "Return" key */
2817             if (step_nr == 6)
2818               break;
2819
2820             /* press 'Enter' to keep the existing key binding */
2821             if (key == KSYM_Return)
2822               key = *customize_step[step_nr].key;
2823
2824             /* check if key already used */
2825             for (i = 0; i < step_nr; i++)
2826               if (*customize_step[i].key == key)
2827                 break;
2828             if (i < step_nr)
2829               break;
2830
2831             /* got new key binding */
2832             *customize_step[step_nr].key = key;
2833             DrawText(mSX + 4 * 32, mSY + (2 + 2 * step_nr + 1) * 32,
2834                      "             ", FONT_VALUE_1);
2835             DrawText(mSX + 4 * 32, mSY + (2 + 2 * step_nr + 1) * 32,
2836                      getKeyNameFromKey(key), FONT_VALUE_1);
2837             step_nr++;
2838
2839             /* un-highlight last query */
2840             DrawText(mSX, mSY + (2 + 2 * (step_nr - 1)) * 32,
2841                      customize_step[step_nr - 1].text, FONT_MENU_1);
2842             DrawText(mSX, mSY + (2 + 2 * (step_nr - 1) + 1) * 32,
2843                      "Key:", FONT_MENU_1);
2844
2845             /* press 'Enter' to leave */
2846             if (step_nr == 6)
2847             {
2848               DrawText(mSX + 16, mSY + 15 * 32 + 16,
2849                        "Press Enter", FONT_TITLE_1);
2850               break;
2851             }
2852
2853             /* query next key binding */
2854             DrawText(mSX, mSY + (2 + 2 * step_nr) * 32,
2855                      customize_step[step_nr].text, FONT_INPUT_1_ACTIVE);
2856             DrawText(mSX, mSY + (2 + 2 * step_nr + 1) * 32,
2857                      "Key:", FONT_INPUT_1_ACTIVE);
2858             DrawText(mSX + 4 * 32, mSY + (2 + 2 * step_nr + 1) * 32,
2859                      getKeyNameFromKey(*customize_step[step_nr].key),
2860                      FONT_VALUE_OLD);
2861           }
2862           break;
2863
2864         case EVENT_KEYRELEASE:
2865           key_joystick_mapping = 0;
2866           break;
2867
2868         default:
2869           HandleOtherEvents(&event);
2870           break;
2871       }
2872     }
2873
2874     DoAnimation();
2875     BackToFront();
2876
2877     /* don't eat all CPU time */
2878     Delay(10);
2879   }
2880
2881   /* write new key bindings back to player setup */
2882   setup.input[player_nr].key = custom_key;
2883
2884   StopAnimation();
2885   DrawSetupScreen_Input();
2886 }
2887
2888 static boolean CalibrateJoystickMain(int player_nr)
2889 {
2890   int new_joystick_xleft = JOYSTICK_XMIDDLE;
2891   int new_joystick_xright = JOYSTICK_XMIDDLE;
2892   int new_joystick_yupper = JOYSTICK_YMIDDLE;
2893   int new_joystick_ylower = JOYSTICK_YMIDDLE;
2894   int new_joystick_xmiddle, new_joystick_ymiddle;
2895
2896   int joystick_fd = joystick.fd[player_nr];
2897   int x, y, last_x, last_y, xpos = 8, ypos = 3;
2898   boolean check[3][3];
2899   int check_remaining = 3 * 3;
2900   int joy_x, joy_y;
2901   int joy_value;
2902   int result = -1;
2903
2904   if (joystick.status == JOYSTICK_NOT_AVAILABLE)
2905     return FALSE;
2906
2907   if (joystick_fd < 0 || !setup.input[player_nr].use_joystick)
2908     return FALSE;
2909
2910   ClearWindow();
2911
2912   for (y = 0; y < 3; y++)
2913   {
2914     for (x = 0; x < 3; x++)
2915     {
2916       DrawGraphic(xpos + x - 1, ypos + y - 1, IMG_MENU_CALIBRATE_BLUE, 0);
2917       check[x][y] = FALSE;
2918     }
2919   }
2920
2921   DrawTextSCentered(mSY - SY +  6 * 32, FONT_TITLE_1, "Rotate joystick");
2922   DrawTextSCentered(mSY - SY +  7 * 32, FONT_TITLE_1, "in all directions");
2923   DrawTextSCentered(mSY - SY +  9 * 32, FONT_TITLE_1, "if all balls");
2924   DrawTextSCentered(mSY - SY + 10 * 32, FONT_TITLE_1, "are marked,");
2925   DrawTextSCentered(mSY - SY + 11 * 32, FONT_TITLE_1, "center joystick");
2926   DrawTextSCentered(mSY - SY + 12 * 32, FONT_TITLE_1, "and");
2927   DrawTextSCentered(mSY - SY + 13 * 32, FONT_TITLE_1, "press any button!");
2928
2929   joy_value = Joystick(player_nr);
2930   last_x = (joy_value & JOY_LEFT ? -1 : joy_value & JOY_RIGHT ? +1 : 0);
2931   last_y = (joy_value & JOY_UP   ? -1 : joy_value & JOY_DOWN  ? +1 : 0);
2932
2933   /* eventually uncalibrated center position (joystick could be uncentered) */
2934   if (!ReadJoystick(joystick_fd, &joy_x, &joy_y, NULL, NULL))
2935     return FALSE;
2936
2937   new_joystick_xmiddle = joy_x;
2938   new_joystick_ymiddle = joy_y;
2939
2940   DrawGraphic(xpos + last_x, ypos + last_y, IMG_MENU_CALIBRATE_RED, 0);
2941   BackToFront();
2942
2943   while (Joystick(player_nr) & JOY_BUTTON);     /* wait for released button */
2944   InitAnimation();
2945
2946   while (result < 0)
2947   {
2948     if (PendingEvent())         /* got event */
2949     {
2950       Event event;
2951
2952       NextEvent(&event);
2953
2954       switch(event.type)
2955       {
2956         case EVENT_KEYPRESS:
2957           switch(GetEventKey((KeyEvent *)&event, TRUE))
2958           {
2959             case KSYM_Return:
2960               if (check_remaining == 0)
2961                 result = 1;
2962               break;
2963
2964             case KSYM_Escape:
2965               result = 0;
2966               break;
2967
2968             default:
2969               break;
2970           }
2971           break;
2972
2973         case EVENT_KEYRELEASE:
2974           key_joystick_mapping = 0;
2975           break;
2976
2977         default:
2978           HandleOtherEvents(&event);
2979           break;
2980       }
2981     }
2982
2983     if (!ReadJoystick(joystick_fd, &joy_x, &joy_y, NULL, NULL))
2984       return FALSE;
2985
2986     new_joystick_xleft  = MIN(new_joystick_xleft,  joy_x);
2987     new_joystick_xright = MAX(new_joystick_xright, joy_x);
2988     new_joystick_yupper = MIN(new_joystick_yupper, joy_y);
2989     new_joystick_ylower = MAX(new_joystick_ylower, joy_y);
2990
2991     setup.input[player_nr].joy.xleft = new_joystick_xleft;
2992     setup.input[player_nr].joy.yupper = new_joystick_yupper;
2993     setup.input[player_nr].joy.xright = new_joystick_xright;
2994     setup.input[player_nr].joy.ylower = new_joystick_ylower;
2995     setup.input[player_nr].joy.xmiddle = new_joystick_xmiddle;
2996     setup.input[player_nr].joy.ymiddle = new_joystick_ymiddle;
2997
2998     CheckJoystickData();
2999
3000     joy_value = Joystick(player_nr);
3001
3002     if (joy_value & JOY_BUTTON && check_remaining == 0)
3003       result = 1;
3004
3005     x = (joy_value & JOY_LEFT ? -1 : joy_value & JOY_RIGHT ? +1 : 0);
3006     y = (joy_value & JOY_UP   ? -1 : joy_value & JOY_DOWN  ? +1 : 0);
3007
3008     if (x != last_x || y != last_y)
3009     {
3010       DrawGraphic(xpos + last_x, ypos + last_y, IMG_MENU_CALIBRATE_YELLOW, 0);
3011       DrawGraphic(xpos + x,      ypos + y,      IMG_MENU_CALIBRATE_RED,    0);
3012
3013       last_x = x;
3014       last_y = y;
3015
3016       if (check_remaining > 0 && !check[x+1][y+1])
3017       {
3018         check[x+1][y+1] = TRUE;
3019         check_remaining--;
3020       }
3021
3022 #if 0
3023 #ifdef DEBUG
3024       printf("LEFT / MIDDLE / RIGHT == %d / %d / %d\n",
3025              setup.input[player_nr].joy.xleft,
3026              setup.input[player_nr].joy.xmiddle,
3027              setup.input[player_nr].joy.xright);
3028       printf("UP / MIDDLE / DOWN == %d / %d / %d\n",
3029              setup.input[player_nr].joy.yupper,
3030              setup.input[player_nr].joy.ymiddle,
3031              setup.input[player_nr].joy.ylower);
3032 #endif
3033 #endif
3034
3035     }
3036
3037     DoAnimation();
3038     BackToFront();
3039
3040     /* don't eat all CPU time */
3041     Delay(10);
3042   }
3043
3044   /* calibrated center position (joystick should now be centered) */
3045   if (!ReadJoystick(joystick_fd, &joy_x, &joy_y, NULL, NULL))
3046     return FALSE;
3047
3048   new_joystick_xmiddle = joy_x;
3049   new_joystick_ymiddle = joy_y;
3050
3051   StopAnimation();
3052
3053 #if 0
3054   DrawSetupScreen_Input();
3055 #endif
3056
3057   /* wait until the last pressed button was released */
3058   while (Joystick(player_nr) & JOY_BUTTON)
3059   {
3060     if (PendingEvent())         /* got event */
3061     {
3062       Event event;
3063
3064       NextEvent(&event);
3065       HandleOtherEvents(&event);
3066
3067       Delay(10);
3068     }
3069   }
3070
3071   return TRUE;
3072 }
3073
3074 void CalibrateJoystick(int player_nr)
3075 {
3076   if (!CalibrateJoystickMain(player_nr))
3077   {
3078     char *device_name = setup.input[player_nr].joy.device_name;
3079     int nr = getJoystickNrFromDeviceName(device_name) + 1;
3080     int xpos = mSX - SX;
3081     int ypos = mSY - SY;
3082
3083     ClearWindow();
3084
3085     DrawTextF(xpos + 16, ypos + 6 * 32, FONT_TITLE_1, "   JOYSTICK %d   ", nr);
3086     DrawTextF(xpos + 16, ypos + 7 * 32, FONT_TITLE_1, " NOT AVAILABLE! ");
3087     BackToFront();
3088
3089     Delay(2000);                /* show error message for a short time */
3090
3091     ClearEventQueue();
3092   }
3093
3094 #if 1
3095   DrawSetupScreen_Input();
3096 #endif
3097 }
3098
3099 void DrawSetupScreen()
3100 {
3101   DeactivateJoystick();
3102
3103   SetMainBackgroundImage(IMG_BACKGROUND_SETUP);
3104
3105   if (setup_mode == SETUP_MODE_INPUT)
3106     DrawSetupScreen_Input();
3107   else if (setup_mode == SETUP_MODE_CHOOSE_GRAPHICS)
3108     DrawChooseTree(&artwork.gfx_current);
3109   else if (setup_mode == SETUP_MODE_CHOOSE_SOUNDS)
3110     DrawChooseTree(&artwork.snd_current);
3111   else if (setup_mode == SETUP_MODE_CHOOSE_MUSIC)
3112     DrawChooseTree(&artwork.mus_current);
3113   else
3114     DrawSetupScreen_Generic();
3115
3116   PlayMenuSound();
3117   PlayMenuMusic();
3118 }
3119
3120 void HandleSetupScreen(int mx, int my, int dx, int dy, int button)
3121 {
3122   if (setup_mode == SETUP_MODE_INPUT)
3123     HandleSetupScreen_Input(mx, my, dx, dy, button);
3124   else if (setup_mode == SETUP_MODE_CHOOSE_GRAPHICS)
3125     HandleChooseTree(mx, my, dx, dy, button, &artwork.gfx_current);
3126   else if (setup_mode == SETUP_MODE_CHOOSE_SOUNDS)
3127     HandleChooseTree(mx, my, dx, dy, button, &artwork.snd_current);
3128   else if (setup_mode == SETUP_MODE_CHOOSE_MUSIC)
3129     HandleChooseTree(mx, my, dx, dy, button, &artwork.mus_current);
3130   else
3131     HandleSetupScreen_Generic(mx, my, dx, dy, button);
3132
3133   DoAnimation();
3134 }
3135
3136 void HandleGameActions()
3137 {
3138   if (game_status != GAME_MODE_PLAYING)
3139     return;
3140
3141   /* !!! FIX THIS (START) !!! */
3142   if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
3143   {
3144     byte *recorded_player_action;
3145     byte summarized_player_action = 0;
3146     byte tape_action[MAX_PLAYERS];
3147     int i;
3148
3149 #if 1
3150     if (level.native_em_level->lev->home == 0)  /* all players at home */
3151     {
3152       local_player->LevelSolved = TRUE;
3153       AllPlayersGone = TRUE;
3154
3155       level.native_em_level->lev->home = -1;
3156     }
3157
3158     if (local_player->LevelSolved)
3159       GameWon();
3160
3161     if (AllPlayersGone && !TAPE_IS_STOPPED(tape))
3162       TapeStop();
3163
3164     if (game_status != GAME_MODE_PLAYING)
3165       return;
3166 #else
3167     if (level.native_em_level->lev->home == 0)  /* all players at home */
3168     {
3169       if (local_player->LevelSolved)
3170         GameWon();
3171
3172       if (AllPlayersGone && !TAPE_IS_STOPPED(tape))
3173         TapeStop();
3174
3175       if (game_status != GAME_MODE_PLAYING)
3176         return;
3177     }
3178 #endif
3179
3180     if (level.native_em_level->ply[0]->alive == 0 &&
3181         level.native_em_level->ply[1]->alive == 0 &&
3182         level.native_em_level->ply[2]->alive == 0 &&
3183         level.native_em_level->ply[3]->alive == 0)      /* all dead */
3184       AllPlayersGone = TRUE;
3185
3186     if (AllPlayersGone && !TAPE_IS_STOPPED(tape))
3187       TapeStop();
3188
3189     /* --- game actions --- */
3190
3191     if (tape.pausing)
3192     {
3193       /* don't use 100% CPU while in pause mode -- this should better be solved
3194          like in the R'n'D game engine! */
3195
3196       Delay(10);
3197
3198       return;
3199     }
3200
3201     recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
3202
3203 #if 1
3204     /* !!! CHECK THIS (tape.pausing is always FALSE here!) !!! */
3205     if (recorded_player_action == NULL && tape.pausing)
3206       return;
3207 #endif
3208
3209     for (i = 0; i < MAX_PLAYERS; i++)
3210     {
3211       summarized_player_action |= stored_player[i].action;
3212
3213       if (!network_playing)
3214         stored_player[i].effective_action = stored_player[i].action;
3215     }
3216
3217     if (!options.network && !setup.team_mode)
3218       local_player->effective_action = summarized_player_action;
3219
3220     if (recorded_player_action != NULL)
3221       for (i = 0; i < MAX_PLAYERS; i++)
3222         stored_player[i].effective_action = recorded_player_action[i];
3223
3224     for (i = 0; i < MAX_PLAYERS; i++)
3225     {
3226       tape_action[i] = stored_player[i].effective_action;
3227
3228       /* !!! (this does not happen in the EM engine) !!! */
3229       if (tape.recording && tape_action[i] && !tape.player_participates[i])
3230         tape.player_participates[i] = TRUE;  /* player just appeared from CE */
3231     }
3232
3233     /* only save actions from input devices, but not programmed actions */
3234     if (tape.recording)
3235       TapeRecordAction(tape_action);
3236
3237 #if 1
3238     {
3239       byte effective_action[MAX_PLAYERS];
3240
3241       for (i = 0; i < MAX_PLAYERS; i++)
3242         effective_action[i] = stored_player[i].effective_action;
3243
3244
3245 #if 0
3246       printf("::: %s: ",
3247              tape.playing ? "PLAYING" :
3248              tape.recording ? "RECORDING" :
3249              "STOPPED");
3250
3251       for (i = 1; i < MAX_PLAYERS; i++)
3252         if ((recorded_player_action && recorded_player_action[i] != 0) ||
3253             tape_action[i] != 0 ||
3254             effective_action[i] != 0)
3255           printf("::: -----------------> WARNING!\n");
3256
3257       printf("::: %08d: %08x [%08x] [%08x]\n",
3258              FrameCounter,
3259              (recorded_player_action ? recorded_player_action[0] : -1),
3260              tape_action[0],
3261              effective_action[0]);
3262 #endif
3263
3264
3265       GameActions_EM(effective_action);
3266     }
3267 #else
3268     GameActions_EM(local_player->effective_action);
3269 #endif
3270
3271     if (TimeFrames >= FRAMES_PER_SECOND)
3272     {
3273       TimeFrames = 0;
3274       TapeTime++;
3275
3276       if (!level.use_step_counter)
3277       {
3278         TimePlayed++;
3279
3280         if (TimeLeft > 0)
3281         {
3282           TimeLeft--;
3283
3284           if (TimeLeft <= 10 && setup.time_limit)
3285             PlaySoundStereo(SND_GAME_RUNNING_OUT_OF_TIME, SOUND_MIDDLE);
3286
3287           DrawGameValue_Time(TimeLeft);
3288
3289           if (!TimeLeft && setup.time_limit)
3290             level.native_em_level->lev->killed_out_of_time = TRUE;
3291         }
3292         else if (level.time == 0 && level.native_em_level->lev->home > 0)
3293           DrawGameValue_Time(TimePlayed);
3294
3295         level.native_em_level->lev->time =
3296           (level.time == 0 ? TimePlayed : TimeLeft);
3297       }
3298
3299       if (tape.recording || tape.playing)
3300         DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
3301     }
3302
3303     FrameCounter++;
3304     TimeFrames++;
3305
3306     BackToFront();
3307   }
3308   else
3309   {
3310     if (game.restart_level)
3311       StartGameActions(options.network, setup.autorecord, NEW_RANDOMIZE);
3312
3313     if (local_player->LevelSolved)
3314       GameWon();
3315
3316     if (AllPlayersGone && !TAPE_IS_STOPPED(tape))
3317       TapeStop();
3318
3319     GameActions();
3320     BackToFront();
3321
3322     if (tape.auto_play && !tape.playing)
3323       AutoPlayTape();   /* continue automatically playing next tape */
3324   }
3325 }
3326
3327 void StartGameActions(boolean init_network_game, boolean record_tape,
3328                       long random_seed)
3329 {
3330 #if 1
3331   unsigned long new_random_seed = InitRND(random_seed);
3332
3333   if (record_tape)
3334     TapeStartRecording(new_random_seed);
3335 #else
3336   if (record_tape)
3337     TapeStartRecording(random_seed);
3338 #endif
3339
3340 #if defined(NETWORK_AVALIABLE)
3341   if (init_network_game)
3342   {
3343     SendToServer_StartPlaying();
3344
3345     return;
3346   }
3347 #endif
3348
3349   StopAnimation();
3350
3351   game_status = GAME_MODE_PLAYING;
3352
3353 #if 0
3354   InitRND(random_seed);
3355 #endif
3356
3357   InitGame();
3358 }
3359
3360 /* ---------- new screen button stuff -------------------------------------- */
3361
3362 /* graphic position and size values for buttons and scrollbars */
3363 #define SC_SCROLLBUTTON_XSIZE           TILEX
3364 #define SC_SCROLLBUTTON_YSIZE           TILEY
3365
3366 #define SC_SCROLL_VERTICAL_XSIZE        SC_SCROLLBUTTON_XSIZE
3367 #define SC_SCROLL_VERTICAL_YSIZE        ((MAX_MENU_ENTRIES_ON_SCREEN - 2) * \
3368                                          SC_SCROLLBUTTON_YSIZE)
3369 #define SC_SCROLL_UP_XPOS               (SXSIZE - SC_SCROLLBUTTON_XSIZE)
3370 #define SC_SCROLL_UP_YPOS               (2 * SC_SCROLLBUTTON_YSIZE)
3371 #define SC_SCROLL_VERTICAL_XPOS         SC_SCROLL_UP_XPOS
3372 #define SC_SCROLL_VERTICAL_YPOS         (SC_SCROLL_UP_YPOS + \
3373                                          SC_SCROLLBUTTON_YSIZE)
3374 #define SC_SCROLL_DOWN_XPOS             SC_SCROLL_UP_XPOS
3375 #define SC_SCROLL_DOWN_YPOS             (SC_SCROLL_VERTICAL_YPOS + \
3376                                          SC_SCROLL_VERTICAL_YSIZE)
3377
3378 #define SC_BORDER_SIZE                  14
3379
3380 static struct
3381 {
3382   int gfx_unpressed, gfx_pressed;
3383   int x, y;
3384   int gadget_id;
3385   char *infotext;
3386 } scrollbutton_info[NUM_SCREEN_SCROLLBUTTONS] =
3387 {
3388   {
3389     IMG_MENU_BUTTON_UP, IMG_MENU_BUTTON_UP_ACTIVE,
3390     SC_SCROLL_UP_XPOS, SC_SCROLL_UP_YPOS,
3391     SCREEN_CTRL_ID_SCROLL_UP,
3392     "scroll up"
3393   },
3394   {
3395     IMG_MENU_BUTTON_DOWN, IMG_MENU_BUTTON_DOWN_ACTIVE,
3396     SC_SCROLL_DOWN_XPOS, SC_SCROLL_DOWN_YPOS,
3397     SCREEN_CTRL_ID_SCROLL_DOWN,
3398     "scroll down"
3399   }
3400 };
3401
3402 static struct
3403 {
3404 #if defined(TARGET_X11_NATIVE_PERFORMANCE_WORKAROUND)
3405   Bitmap **gfx_unpressed, **gfx_pressed;
3406 #else
3407   int gfx_unpressed, gfx_pressed;
3408 #endif
3409   int x, y;
3410   int width, height;
3411   int type;
3412   int gadget_id;
3413   char *infotext;
3414 } scrollbar_info[NUM_SCREEN_SCROLLBARS] =
3415 {
3416   {
3417 #if defined(TARGET_X11_NATIVE_PERFORMANCE_WORKAROUND)
3418     &scrollbar_bitmap[0], &scrollbar_bitmap[1],
3419 #else
3420     IMG_MENU_SCROLLBAR, IMG_MENU_SCROLLBAR_ACTIVE,
3421 #endif
3422     SC_SCROLL_VERTICAL_XPOS, SC_SCROLL_VERTICAL_YPOS,
3423     SC_SCROLL_VERTICAL_XSIZE, SC_SCROLL_VERTICAL_YSIZE,
3424     GD_TYPE_SCROLLBAR_VERTICAL,
3425     SCREEN_CTRL_ID_SCROLL_VERTICAL,
3426     "scroll level series vertically"
3427   }
3428 };
3429
3430 static void CreateScreenScrollbuttons()
3431 {
3432   struct GadgetInfo *gi;
3433   unsigned long event_mask;
3434   int i;
3435
3436   for (i = 0; i < NUM_SCREEN_SCROLLBUTTONS; i++)
3437   {
3438     Bitmap *gd_bitmap_unpressed, *gd_bitmap_pressed;
3439     int gfx_unpressed, gfx_pressed;
3440     int x, y, width, height;
3441     int gd_x1, gd_x2, gd_y1, gd_y2;
3442     int id = scrollbutton_info[i].gadget_id;
3443
3444     event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
3445
3446     x = mSX + scrollbutton_info[i].x + menu.scrollbar_xoffset;
3447     y = mSY + scrollbutton_info[i].y;
3448     width = SC_SCROLLBUTTON_XSIZE;
3449     height = SC_SCROLLBUTTON_YSIZE;
3450
3451     if (id == SCREEN_CTRL_ID_SCROLL_DOWN)
3452       y = mSY + (SC_SCROLL_VERTICAL_YPOS +
3453                  (NUM_MENU_ENTRIES_ON_SCREEN - 2) * SC_SCROLLBUTTON_YSIZE);
3454
3455     gfx_unpressed = scrollbutton_info[i].gfx_unpressed;
3456     gfx_pressed   = scrollbutton_info[i].gfx_pressed;
3457     gd_bitmap_unpressed = graphic_info[gfx_unpressed].bitmap;
3458     gd_bitmap_pressed   = graphic_info[gfx_pressed].bitmap;
3459     gd_x1 = graphic_info[gfx_unpressed].src_x;
3460     gd_y1 = graphic_info[gfx_unpressed].src_y;
3461     gd_x2 = graphic_info[gfx_pressed].src_x;
3462     gd_y2 = graphic_info[gfx_pressed].src_y;
3463
3464     gi = CreateGadget(GDI_CUSTOM_ID, id,
3465                       GDI_CUSTOM_TYPE_ID, i,
3466                       GDI_INFO_TEXT, scrollbutton_info[i].infotext,
3467                       GDI_X, x,
3468                       GDI_Y, y,
3469                       GDI_WIDTH, width,
3470                       GDI_HEIGHT, height,
3471                       GDI_TYPE, GD_TYPE_NORMAL_BUTTON,
3472                       GDI_STATE, GD_BUTTON_UNPRESSED,
3473                       GDI_DESIGN_UNPRESSED, gd_bitmap_unpressed, gd_x1, gd_y1,
3474                       GDI_DESIGN_PRESSED, gd_bitmap_pressed, gd_x2, gd_y2,
3475                       GDI_DIRECT_DRAW, FALSE,
3476                       GDI_EVENT_MASK, event_mask,
3477                       GDI_CALLBACK_ACTION, HandleScreenGadgets,
3478                       GDI_END);
3479
3480     if (gi == NULL)
3481       Error(ERR_EXIT, "cannot create gadget");
3482
3483     screen_gadget[id] = gi;
3484   }
3485 }
3486
3487 static void CreateScreenScrollbars()
3488 {
3489   int i;
3490
3491   for (i = 0; i < NUM_SCREEN_SCROLLBARS; i++)
3492   {
3493     Bitmap *gd_bitmap_unpressed, *gd_bitmap_pressed;
3494 #if !defined(TARGET_X11_NATIVE_PERFORMANCE_WORKAROUND)
3495     int gfx_unpressed, gfx_pressed;
3496 #endif
3497     int x, y, width, height;
3498     int gd_x1, gd_x2, gd_y1, gd_y2;
3499     struct GadgetInfo *gi;
3500     int items_max, items_visible, item_position;
3501     unsigned long event_mask;
3502     int num_page_entries = NUM_MENU_ENTRIES_ON_SCREEN;
3503     int id = scrollbar_info[i].gadget_id;
3504
3505     event_mask = GD_EVENT_MOVING | GD_EVENT_OFF_BORDERS;
3506
3507     x = mSX + scrollbar_info[i].x + menu.scrollbar_xoffset;
3508     y = mSY + scrollbar_info[i].y;
3509     width  = scrollbar_info[i].width;
3510     height = scrollbar_info[i].height;
3511
3512     if (id == SCREEN_CTRL_ID_SCROLL_VERTICAL)
3513       height = (NUM_MENU_ENTRIES_ON_SCREEN - 2) * SC_SCROLLBUTTON_YSIZE;
3514
3515     items_max = num_page_entries;
3516     items_visible = num_page_entries;
3517     item_position = 0;
3518
3519 #if defined(TARGET_X11_NATIVE_PERFORMANCE_WORKAROUND)
3520     gd_bitmap_unpressed = *scrollbar_info[i].gfx_unpressed;
3521     gd_bitmap_pressed   = *scrollbar_info[i].gfx_pressed;
3522     gd_x1 = 0;
3523     gd_y1 = 0;
3524     gd_x2 = 0;
3525     gd_y2 = 0;
3526 #else
3527     gfx_unpressed = scrollbar_info[i].gfx_unpressed;
3528     gfx_pressed   = scrollbar_info[i].gfx_pressed;
3529     gd_bitmap_unpressed = graphic_info[gfx_unpressed].bitmap;
3530     gd_bitmap_pressed   = graphic_info[gfx_pressed].bitmap;
3531     gd_x1 = graphic_info[gfx_unpressed].src_x;
3532     gd_y1 = graphic_info[gfx_unpressed].src_y;
3533     gd_x2 = graphic_info[gfx_pressed].src_x;
3534     gd_y2 = graphic_info[gfx_pressed].src_y;
3535 #endif
3536
3537     gi = CreateGadget(GDI_CUSTOM_ID, id,
3538                       GDI_CUSTOM_TYPE_ID, i,
3539                       GDI_INFO_TEXT, scrollbar_info[i].infotext,
3540                       GDI_X, x,
3541                       GDI_Y, y,
3542                       GDI_WIDTH, width,
3543                       GDI_HEIGHT, height,
3544                       GDI_TYPE, scrollbar_info[i].type,
3545                       GDI_SCROLLBAR_ITEMS_MAX, items_max,
3546                       GDI_SCROLLBAR_ITEMS_VISIBLE, items_visible,
3547                       GDI_SCROLLBAR_ITEM_POSITION, item_position,
3548                       GDI_STATE, GD_BUTTON_UNPRESSED,
3549                       GDI_DESIGN_UNPRESSED, gd_bitmap_unpressed, gd_x1, gd_y1,
3550                       GDI_DESIGN_PRESSED, gd_bitmap_pressed, gd_x2, gd_y2,
3551                       GDI_BORDER_SIZE, SC_BORDER_SIZE, SC_BORDER_SIZE,
3552                       GDI_DIRECT_DRAW, FALSE,
3553                       GDI_EVENT_MASK, event_mask,
3554                       GDI_CALLBACK_ACTION, HandleScreenGadgets,
3555                       GDI_END);
3556
3557     if (gi == NULL)
3558       Error(ERR_EXIT, "cannot create gadget");
3559
3560     screen_gadget[id] = gi;
3561   }
3562 }
3563
3564 void CreateScreenGadgets()
3565 {
3566   int last_game_status = game_status;   /* save current game status */
3567
3568 #if defined(TARGET_X11_NATIVE_PERFORMANCE_WORKAROUND)
3569   int i;
3570
3571   for (i = 0; i < NUM_SCROLLBAR_BITMAPS; i++)
3572   {
3573     scrollbar_bitmap[i] = CreateBitmap(TILEX, TILEY, DEFAULT_DEPTH);
3574
3575     /* copy pointers to clip mask and GC */
3576     scrollbar_bitmap[i]->clip_mask =
3577       graphic_info[IMG_MENU_SCROLLBAR + i].clip_mask;
3578     scrollbar_bitmap[i]->stored_clip_gc =
3579       graphic_info[IMG_MENU_SCROLLBAR + i].clip_gc;
3580
3581     BlitBitmap(graphic_info[IMG_MENU_SCROLLBAR + i].bitmap,
3582                scrollbar_bitmap[i],
3583                graphic_info[IMG_MENU_SCROLLBAR + i].src_x,
3584                graphic_info[IMG_MENU_SCROLLBAR + i].src_y,
3585                TILEX, TILEY, 0, 0);
3586   }
3587 #endif
3588
3589   /* force LEVELS draw offset for scrollbar / scrollbutton gadgets */
3590   game_status = GAME_MODE_LEVELS;
3591
3592   CreateScreenScrollbuttons();
3593   CreateScreenScrollbars();
3594
3595   game_status = last_game_status;       /* restore current game status */
3596 }
3597
3598 void FreeScreenGadgets()
3599 {
3600   int i;
3601
3602 #if defined(TARGET_X11_NATIVE_PERFORMANCE_WORKAROUND)
3603   for (i = 0; i < NUM_SCROLLBAR_BITMAPS; i++)
3604   {
3605     /* prevent freeing clip mask and GC twice */
3606     scrollbar_bitmap[i]->clip_mask = None;
3607     scrollbar_bitmap[i]->stored_clip_gc = None;
3608
3609     FreeBitmap(scrollbar_bitmap[i]);
3610   }
3611 #endif
3612
3613   for (i = 0; i < NUM_SCREEN_GADGETS; i++)
3614     FreeGadget(screen_gadget[i]);
3615 }
3616
3617 void MapChooseTreeGadgets(TreeInfo *ti)
3618 {
3619   int num_entries = numTreeInfoInGroup(ti);
3620   int i;
3621
3622   if (num_entries <= NUM_MENU_ENTRIES_ON_SCREEN)
3623     return;
3624
3625   for (i = 0; i < NUM_SCREEN_GADGETS; i++)
3626     MapGadget(screen_gadget[i]);
3627 }
3628
3629 #if 0
3630 void UnmapChooseTreeGadgets()
3631 {
3632   int i;
3633
3634   for (i = 0; i < NUM_SCREEN_GADGETS; i++)
3635     UnmapGadget(screen_gadget[i]);
3636 }
3637 #endif
3638
3639 static void HandleScreenGadgets(struct GadgetInfo *gi)
3640 {
3641   int id = gi->custom_id;
3642
3643   if (game_status != GAME_MODE_LEVELS && game_status != GAME_MODE_SETUP)
3644     return;
3645
3646   switch (id)
3647   {
3648     case SCREEN_CTRL_ID_SCROLL_UP:
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_DOWN:
3656       if (game_status == GAME_MODE_LEVELS)
3657         HandleChooseLevel(0,0, 0, +1 * SCROLL_LINE, MB_MENU_MARK);
3658       else if (game_status == GAME_MODE_SETUP)
3659         HandleSetupScreen(0,0, 0, +1 * SCROLL_LINE, MB_MENU_MARK);
3660       break;
3661
3662     case SCREEN_CTRL_ID_SCROLL_VERTICAL:
3663       if (game_status == GAME_MODE_LEVELS)
3664         HandleChooseLevel(0,0, 999,gi->event.item_position,MB_MENU_INITIALIZE);
3665       else if (game_status == GAME_MODE_SETUP)
3666         HandleSetupScreen(0,0, 999,gi->event.item_position,MB_MENU_INITIALIZE);
3667       break;
3668
3669     default:
3670       break;
3671   }
3672 }