added using setup option for displaying overlay touch buttons
[rocksndiamonds.git] / src / screens.c
index 49f390f973f643616266c0e1a78cfce6d6a432ce..b45e99119d3e7c5e163fd23cfff625fd5807db70 100644 (file)
@@ -22,6 +22,7 @@
 #include "network.h"
 #include "init.h"
 #include "config.h"
+#include "api.h"
 
 
 #define DEBUG_JOYSTICKS                0
 #define MENU_CHOOSE_TREE_FONT(x)       (FONT_TEXT_1 + (x))
 #define MENU_CHOOSE_TREE_COLOR(ti, a)  TREE_COLOR(ti, a)
 
+#define TEXT_NEXT_PAGE                 "Press any key or button for next page"
+#define TEXT_INFO_MENU                 "Press any key or button for info menu"
+
 // for input setup functions
 #define SETUPINPUT_SCREEN_POS_START    0
 #define SETUPINPUT_SCREEN_POS_EMPTY1   3
 // screen gadget identifiers
 #define SCREEN_CTRL_ID_PREV_LEVEL      0
 #define SCREEN_CTRL_ID_NEXT_LEVEL      1
-#define SCREEN_CTRL_ID_FIRST_LEVEL     2
-#define SCREEN_CTRL_ID_LAST_LEVEL      3
-#define SCREEN_CTRL_ID_LEVEL_NUMBER    4
-#define SCREEN_CTRL_ID_PREV_PLAYER     5
-#define SCREEN_CTRL_ID_NEXT_PLAYER     6
-#define SCREEN_CTRL_ID_INSERT_SOLUTION 7
-#define SCREEN_CTRL_ID_PLAY_SOLUTION   8
-#define SCREEN_CTRL_ID_SWITCH_ECS_AGA  9
-#define SCREEN_CTRL_ID_TOUCH_PREV_PAGE 10
-#define SCREEN_CTRL_ID_TOUCH_NEXT_PAGE 11
-#define SCREEN_CTRL_ID_TOUCH_PREV_PAGE2        12
-#define SCREEN_CTRL_ID_TOUCH_NEXT_PAGE2        13
-#define SCREEN_CTRL_ID_SCROLL_UP       14
-#define SCREEN_CTRL_ID_SCROLL_DOWN     15
-#define SCREEN_CTRL_ID_SCROLL_VERTICAL 16
-#define SCREEN_CTRL_ID_NETWORK_SERVER  17
-
-#define NUM_SCREEN_GADGETS             18
-
-#define NUM_SCREEN_MENUBUTTONS         14
+#define SCREEN_CTRL_ID_PREV_LEVEL2     2
+#define SCREEN_CTRL_ID_NEXT_LEVEL2     3
+#define SCREEN_CTRL_ID_PREV_SCORE      4
+#define SCREEN_CTRL_ID_NEXT_SCORE      5
+#define SCREEN_CTRL_ID_PLAY_TAPE       6
+#define SCREEN_CTRL_ID_FIRST_LEVEL     7
+#define SCREEN_CTRL_ID_LAST_LEVEL      8
+#define SCREEN_CTRL_ID_LEVEL_NUMBER    9
+#define SCREEN_CTRL_ID_PREV_PLAYER     10
+#define SCREEN_CTRL_ID_NEXT_PLAYER     11
+#define SCREEN_CTRL_ID_INSERT_SOLUTION 12
+#define SCREEN_CTRL_ID_PLAY_SOLUTION   13
+#define SCREEN_CTRL_ID_SWITCH_ECS_AGA  14
+#define SCREEN_CTRL_ID_TOUCH_PREV_PAGE 15
+#define SCREEN_CTRL_ID_TOUCH_NEXT_PAGE 16
+#define SCREEN_CTRL_ID_TOUCH_PREV_PAGE2        17
+#define SCREEN_CTRL_ID_TOUCH_NEXT_PAGE2        18
+
+#define NUM_SCREEN_MENUBUTTONS         19
+
+#define SCREEN_CTRL_ID_SCROLL_UP       19
+#define SCREEN_CTRL_ID_SCROLL_DOWN     20
+#define SCREEN_CTRL_ID_SCROLL_VERTICAL 21
+#define SCREEN_CTRL_ID_NETWORK_SERVER  22
+
+#define NUM_SCREEN_GADGETS             23
+
 #define NUM_SCREEN_SCROLLBUTTONS       2
 #define NUM_SCREEN_SCROLLBARS          1
 #define NUM_SCREEN_TEXTINPUT           1
 #define SCREEN_MASK_INPUT              (1 << 2)
 #define SCREEN_MASK_TOUCH              (1 << 3)
 #define SCREEN_MASK_TOUCH2             (1 << 4)
+#define SCREEN_MASK_SCORES             (1 << 5)
+#define SCREEN_MASK_SCORES_INFO                (1 << 6)
 
 // graphic position and size values for buttons and scrollbars
 #define SC_MENUBUTTON_XSIZE            TILEX
@@ -259,6 +272,8 @@ static void HandleChooseTree(int, int, int, int, int, TreeInfo **);
 static void DrawChoosePlayerName(void);
 static void DrawChooseLevelSet(void);
 static void DrawChooseLevelNr(void);
+static void DrawScoreInfo(int);
+static void DrawScoreInfo_Content(int);
 static void DrawInfoScreen(void);
 static void DrawSetupScreen(void);
 static void DrawTypeName(void);
@@ -267,26 +282,38 @@ static void DrawInfoScreen_NotAvailable(char *, char *);
 static void DrawInfoScreen_HelpAnim(int, int, boolean);
 static void DrawInfoScreen_HelpText(int, int, int, int);
 static void HandleInfoScreen_Main(int, int, int, int, int);
-static void HandleInfoScreen_TitleScreen(int);
-static void HandleInfoScreen_Elements(int);
-static void HandleInfoScreen_Music(int);
-static void HandleInfoScreen_Credits(int);
-static void HandleInfoScreen_Program(int);
+static void HandleInfoScreen_TitleScreen(int, int, int);
+static void HandleInfoScreen_Elements(int, int, int);
+static void HandleInfoScreen_Music(int, int, int);
+static void HandleInfoScreen_Credits(int, int, int);
+static void HandleInfoScreen_Program(int, int, int);
 static void HandleInfoScreen_Version(int);
 
 static void ModifyGameSpeedIfNeeded(void);
 static void DisableVsyncIfNeeded(void);
 
+static void RedrawScreenMenuGadgets(int);
 static void MapScreenMenuGadgets(int);
 static void UnmapScreenMenuGadgets(int);
 static void MapScreenGadgets(int);
+static void UnmapScreenGadgets(void);
 static void MapScreenTreeGadgets(TreeInfo *);
+static void UnmapScreenTreeGadgets(void);
 
 static void UpdateScreenMenuGadgets(int, boolean);
+static void AdjustScoreInfoButtons_SelectScore(int, int, int);
+static void AdjustScoreInfoButtons_PlayTape(int, int, boolean);
 
 static boolean OfferUploadTapes(void);
 static void execOfferUploadTapes(void);
 
+static void DrawHallOfFame_setScoreEntries(void);
+static void HandleHallOfFame_SelectLevel(int, int);
+static char *getHallOfFameRankText(int, int);
+static char *getHallOfFameScoreText(int, int);
+
+static struct TokenInfo *getSetupInfoFinal(struct TokenInfo *);
+
 static struct GadgetInfo *screen_gadget[NUM_SCREEN_GADGETS];
 
 static int info_mode = INFO_MODE_MAIN;
@@ -348,6 +375,9 @@ static TreeInfo *player_name_current = NULL;
 static TreeInfo *level_number = NULL;
 static TreeInfo *level_number_current = NULL;
 
+static TreeInfo *score_entries = NULL;
+static TreeInfo *score_entry_current = NULL;
+
 static struct ValueTextInfo window_sizes_list[] =
 {
   {    50,     "50 %"                          },
@@ -604,6 +634,10 @@ static int align_yoffset = 0;
                                 menu.extra_spacing[GAME_MODE_SETUP] :  \
                                 menu.extra_spacing_setup[DRAW_MODE_SETUP(i)])
 
+#define EXTRA_SPACING_SCORES(i)        (EXTRA_SPACING_INFO(i))
+
+#define EXTRA_SPACING_SCOREINFO(i) (menu.extra_spacing[GAME_MODE_SCOREINFO])
+
 #define DRAW_XOFFSET(s)                ((s) == GAME_MODE_INFO ?                \
                                 DRAW_XOFFSET_INFO(info_mode) :         \
                                 (s) == GAME_MODE_SETUP ?               \
@@ -618,6 +652,8 @@ static int align_yoffset = 0;
                                 EXTRA_SPACING_INFO(info_mode) :        \
                                 (s) == GAME_MODE_SETUP ?               \
                                 EXTRA_SPACING_SETUP(setup_mode) :      \
+                                (s) == GAME_MODE_SCORES ?              \
+                                EXTRA_SPACING_SCORES(info_mode) :      \
                                 menu.extra_spacing[DRAW_MODE(s)])
 
 #define mSX                    (SX + DRAW_XOFFSET(game_status))
@@ -655,6 +691,18 @@ struct TitleControlInfo
 
 struct TitleControlInfo title_controls[MAX_NUM_TITLE_SCREENS];
 
+
+// credits screens definitions
+
+static int num_credits_screens = 0;
+static boolean use_global_credits_screens = FALSE;
+
+
+// program info screens definitions
+
+static int num_program_info_screens = 0;
+
+
 // main menu display and control definitions
 
 #define MAIN_CONTROL_NAME                      0
@@ -1393,10 +1441,10 @@ static void AdjustScrollbar(int id, int items_max, int items_visible,
               GDI_SCROLLBAR_ITEM_POSITION, item_position, GDI_END);
 }
 
-static void AdjustChooseTreeScrollbar(int id, int first_entry, TreeInfo *ti)
+static void AdjustChooseTreeScrollbar(TreeInfo *ti, int id)
 {
   AdjustScrollbar(id, numTreeInfoInGroup(ti), NUM_MENU_ENTRIES_ON_SCREEN,
-                 first_entry);
+                 ti->cl_first);
 }
 
 static void clearMenuListArea(void)
@@ -1410,6 +1458,11 @@ static void clearMenuListArea(void)
   // clear menu list area, but not title or scrollbar
   DrawBackground(mSX, mSY + MENU_SCREEN_START_YPOS * 32,
                  scrollbar_xpos - mSX, NUM_MENU_ENTRIES_ON_SCREEN * 32);
+
+  // special compatibility handling for "Snake Bite" graphics set
+  if (strPrefix(leveldir_current->identifier, "snake_bite"))
+    ClearRectangle(drawto, mSX, mSY + MENU_SCREEN_START_YPOS * 32,
+                  scrollbar_xpos - mSX, NUM_MENU_ENTRIES_ON_SCREEN * 32);
 }
 
 static void drawCursorExt(int xpos, int ypos, boolean active, int graphic)
@@ -1478,6 +1531,15 @@ static int getChooseTreeEditYPos(int ypos_raw)
   return sy;
 }
 
+static int getChooseTreeEditXPosReal(int pos)
+{
+  int xpos = getChooseTreeEditXPos(pos);
+  int font_nr = getChooseTreeEditFont(FALSE);
+  int font_xoffset = getFontDrawOffsetX(font_nr);
+
+  return xpos + font_xoffset;
+}
+
 static void drawChooseTreeEdit(int ypos_raw, boolean active)
 {
   int sx = getChooseTreeEditXPos(POS_LEFT);
@@ -1647,6 +1709,21 @@ void DrawMainMenu(void)
     return;
   }
 
+  // needed if last screen was the playing screen, invoked from hall of fame
+  if (score_info_tape_play)
+  {
+    CloseDoor(DOOR_CLOSE_ALL);
+
+    SetGameStatus(GAME_MODE_SCOREINFO);
+
+    DrawScoreInfo(scores.last_entry_nr);
+
+    return;
+  }
+
+  // reset flag to continue playing next level from hall of fame
+  scores.continue_playing = FALSE;
+
   // leveldir_current may be invalid (level group, parent link, node copy)
   leveldir_current = getValidLevelSeries(leveldir_current, leveldir_last_valid);
 
@@ -1660,8 +1737,6 @@ void DrawMainMenu(void)
   // store valid level series information
   leveldir_last_valid = leveldir_current;
 
-  init_last = init;                    // switch to new busy animation
-
   // needed if last screen (level choice) changed graphics, sounds or music
   ReloadCustomArtwork(0);
 
@@ -1892,7 +1967,7 @@ void HandleTitleScreen(int mx, int my, int dx, int dy, int button)
   {
     return_to_main_menu = TRUE;
   }
-  else if (button == MB_MENU_CHOICE)
+  else if (button == MB_MENU_CHOICE || dx)
   {
     if (game_status_last_screen == GAME_MODE_INFO && num_title_screens == 0)
     {
@@ -1905,9 +1980,10 @@ void HandleTitleScreen(int mx, int my, int dx, int dy, int button)
       return;
     }
 
-    title_screen_nr++;
+    title_screen_nr +=
+      (game_status_last_screen == GAME_MODE_INFO && dx < 0 ? -1 : +1);
 
-    if (title_screen_nr < num_title_screens)
+    if (title_screen_nr >= 0 && title_screen_nr < num_title_screens)
     {
       tci = &title_controls[title_screen_nr];
 
@@ -2007,7 +2083,7 @@ static void HandleMainMenu_SelectLevel(int step, int direction,
   {
     // skipping levels is only allowed when trying to skip single level
     if (setup.skip_levels && new_level_nr == old_level_nr + 1 &&
-       Request("Level still unsolved! Skip despite handicap?", REQ_ASK))
+       Request("Level still unsolved! Skip it anyway?", REQ_ASK))
     {
       leveldir_current->handicap_level++;
       SaveLevelSetup_SeriesInfo();
@@ -2500,6 +2576,9 @@ static void DrawInfoScreen_Main(void)
 
   info_info = info_info_main;
 
+  // use modified info screen info without info screen entries marked as hidden
+  info_info = getSetupInfoFinal(info_info);
+
   // determine maximal number of info entries that can be displayed on screen
   num_info_info = 0;
   for (i = 0; info_info[i].type != 0 && i < NUM_MENU_ENTRIES_ON_SCREEN; i++)
@@ -2883,8 +2962,7 @@ void DrawInfoScreen_NotAvailable(char *text_title, char *text_error)
   DrawTextSCentered(ystart1, font_title, text_title);
   DrawTextSCentered(ystart2, font_error, text_error);
 
-  DrawTextSCentered(ybottom, font_foot,
-                   "Press any key or button for info menu");
+  DrawTextSCentered(ybottom, font_foot, TEXT_INFO_MENU);
 
   FadeIn(REDRAW_FIELD);
 }
@@ -2915,9 +2993,7 @@ void DrawInfoScreen_HelpAnim(int start, int max_anims, boolean init)
     DrawHeadline();
 
     DrawTextSCentered(ystart1, font_title, "The Game Elements:");
-
-    DrawTextSCentered(ybottom, font_foot,
-                     "Press any key or button for next page");
+    DrawTextSCentered(ybottom, font_foot, TEXT_NEXT_PAGE);
 
     FrameCounter = 0;
   }
@@ -3064,9 +3140,9 @@ static void DrawInfoScreen_TitleScreen(void)
   DrawTitleScreen();
 }
 
-void HandleInfoScreen_TitleScreen(int button)
+void HandleInfoScreen_TitleScreen(int dx, int dy, int button)
 {
-  HandleTitleScreen(0, 0, 0, 0, button);
+  HandleTitleScreen(0, 0, dx, dy, button);
 }
 
 static void DrawInfoScreen_Elements(void)
@@ -3078,12 +3154,12 @@ static void DrawInfoScreen_Elements(void)
   LoadHelpAnimInfo();
   LoadHelpTextInfo();
 
-  HandleInfoScreen_Elements(MB_MENU_INITIALIZE);
+  HandleInfoScreen_Elements(0, 0, MB_MENU_INITIALIZE);
 
   FadeIn(REDRAW_FIELD);
 }
 
-void HandleInfoScreen_Elements(int button)
+void HandleInfoScreen_Elements(int dx, int dy, int button)
 {
   static unsigned int info_delay = 0;
   static int num_anims;
@@ -3122,16 +3198,16 @@ void HandleInfoScreen_Elements(int button)
 
     return;
   }
-  else if (button == MB_MENU_CHOICE || button == MB_MENU_INITIALIZE)
+  else if (button == MB_MENU_CHOICE || button == MB_MENU_INITIALIZE || dx)
   {
     if (button != MB_MENU_INITIALIZE)
     {
       PlaySound(SND_MENU_ITEM_SELECTING);
 
-      page++;
+      page += (dx < 0 ? -1 : +1);
     }
 
-    if (page >= num_pages)
+    if (page < 0 || page >= num_pages)
     {
       FadeMenuSoundsAndMusic();
 
@@ -3141,7 +3217,7 @@ void HandleInfoScreen_Elements(int button)
       return;
     }
 
-    if (page > 0)
+    if (button != MB_MENU_INITIALIZE)
       FadeSetNextScreen();
 
     if (button != MB_MENU_INITIALIZE)
@@ -3173,12 +3249,12 @@ static void DrawInfoScreen_Music(void)
 
   LoadMusicInfo();
 
-  HandleInfoScreen_Music(MB_MENU_INITIALIZE);
+  HandleInfoScreen_Music(0, 0, MB_MENU_INITIALIZE);
 
   FadeIn(REDRAW_FIELD);
 }
 
-void HandleInfoScreen_Music(int button)
+void HandleInfoScreen_Music(int dx, int dy, int button)
 {
   static struct MusicFileInfo *list = NULL;
   int font_title = MENU_INFO_FONT_TITLE;
@@ -3203,11 +3279,8 @@ void HandleInfoScreen_Music(int button)
       ClearField();
       DrawHeadline();
 
-      DrawTextSCentered(ystart, font_title,
-                       "No music info for this level set.");
-
-      DrawTextSCentered(ybottom, font_foot,
-                       "Press any key or button for info menu");
+      DrawTextSCentered(ystart, font_title, "No music info for this level set.");
+      DrawTextSCentered(ybottom, font_foot, TEXT_INFO_MENU);
 
       return;
     }
@@ -3224,14 +3297,14 @@ void HandleInfoScreen_Music(int button)
 
     return;
   }
-  else if (button == MB_MENU_CHOICE || button == MB_MENU_INITIALIZE)
+  else if (button == MB_MENU_CHOICE || button == MB_MENU_INITIALIZE || dx)
   {
     if (button != MB_MENU_INITIALIZE)
     {
       PlaySound(SND_MENU_ITEM_SELECTING);
 
       if (list != NULL)
-       list = list->next;
+       list = (dx < 0 ? list->prev : list->next);
     }
 
     if (list == NULL)
@@ -3331,8 +3404,7 @@ void HandleInfoScreen_Music(int button)
       ystart += ystep_head;
     }
 
-    DrawTextSCentered(ybottom, FONT_TEXT_4,
-                     "Press any key or button for next page");
+    DrawTextSCentered(ybottom, font_foot, TEXT_NEXT_PAGE);
 
     if (button != MB_MENU_INITIALIZE)
       FadeIn(REDRAW_FIELD);
@@ -3345,17 +3417,11 @@ void HandleInfoScreen_Music(int button)
 static void DrawInfoScreen_CreditsScreen(int screen_nr)
 {
   int font_title = MENU_INFO_FONT_TITLE;
-  int font_head  = MENU_INFO_FONT_HEAD;
   int font_text  = MENU_INFO_FONT_TEXT;
   int font_foot  = MENU_INFO_FONT_FOOT;
   int spacing_title = menu.headline1_spacing_info[info_mode];
-  int spacing_head  = menu.headline2_spacing_info[info_mode];
-  int spacing_para  = menu.paragraph_spacing_info[info_mode];
   int spacing_line  = menu.line_spacing_info[info_mode];
   int ystep_title = getMenuTextStep(spacing_title, font_title);
-  int ystep_head  = getMenuTextStep(spacing_head,  font_head);
-  int ystep_para  = getMenuTextStep(spacing_para,  font_text);
-  int ystep_line  = getMenuTextStep(spacing_line,  font_text);
   int ystart  = mSY - SY + MENU_SCREEN_INFO_YSTART1;
   int ybottom = mSY - SY + MENU_SCREEN_INFO_YBOTTOM;
 
@@ -3363,214 +3429,26 @@ static void DrawInfoScreen_CreditsScreen(int screen_nr)
   DrawHeadline();
 
   DrawTextSCentered(ystart, font_title, "Credits:");
-  ystart += ystep_title;
 
-  if (screen_nr == 0)
-  {
-    DrawTextSCentered(ystart, font_head,
-                     "Special thanks to");
-    ystart += ystep_head;
-    DrawTextSCentered(ystart, font_text,
-                     "Peter Liepa");
-    ystart += ystep_head;
-    DrawTextSCentered(ystart, font_head,
-                     "for creating");
-    ystart += ystep_head;
-    DrawTextSCentered(ystart, font_text,
-                     "\"Boulder Dash\"");
-    ystart += ystep_head;
-    DrawTextSCentered(ystart, font_head,
-                     "in the year");
-    ystart += ystep_head;
-    DrawTextSCentered(ystart, font_text,
-                     "1984");
-    ystart += ystep_head;
-    DrawTextSCentered(ystart, font_head,
-                     "published by");
-    ystart += ystep_head;
-    DrawTextSCentered(ystart, font_text,
-                     "First Star Software");
-  }
-  else if (screen_nr == 1)
-  {
-    DrawTextSCentered(ystart, font_head,
-                     "Special thanks to");
-    ystart += ystep_head;
-    DrawTextSCentered(ystart, font_text,
-                     "Klaus Heinz & Volker Wertich");
-    ystart += ystep_head;
-    DrawTextSCentered(ystart, font_head,
-                     "for creating");
-    ystart += ystep_head;
-    DrawTextSCentered(ystart, font_text,
-                     "\"Emerald Mine\"");
-    ystart += ystep_head;
-    DrawTextSCentered(ystart, font_head,
-                     "in the year");
-    ystart += ystep_head;
-    DrawTextSCentered(ystart, font_text,
-                     "1987");
-    ystart += ystep_head;
-    DrawTextSCentered(ystart, font_head,
-                     "published by");
-    ystart += ystep_head;
-    DrawTextSCentered(ystart, font_text,
-                     "Kingsoft");
-  }
-  else if (screen_nr == 2)
-  {
-    DrawTextSCentered(ystart, font_head,
-                     "Special thanks to");
-    ystart += ystep_head;
-    DrawTextSCentered(ystart, font_text,
-                     "Michael Stopp & Philip Jespersen");
-    ystart += ystep_head;
-    DrawTextSCentered(ystart, font_head,
-                     "for creating");
-    ystart += ystep_head;
-    DrawTextSCentered(ystart, font_text,
-                     "\"Supaplex\"");
-    ystart += ystep_head;
-    DrawTextSCentered(ystart, font_head,
-                     "in the year");
-    ystart += ystep_head;
-    DrawTextSCentered(ystart, font_text,
-                     "1991");
-    ystart += ystep_head;
-    DrawTextSCentered(ystart, font_head,
-                     "published by");
-    ystart += ystep_head;
-    DrawTextSCentered(ystart, font_text,
-                     "Digital Integration");
-  }
-  else if (screen_nr == 3)
-  {
-    DrawTextSCentered(ystart, font_head,
-                     "Special thanks to");
-    ystart += ystep_head;
-    DrawTextSCentered(ystart, font_text,
-                     "Hiroyuki Imabayashi");
-    ystart += ystep_head;
-    DrawTextSCentered(ystart, font_head,
-                     "for creating");
-    ystart += ystep_head;
-    DrawTextSCentered(ystart, font_text,
-                     "\"Sokoban\"");
-    ystart += ystep_head;
-    DrawTextSCentered(ystart, font_head,
-                     "in the year");
-    ystart += ystep_head;
-    DrawTextSCentered(ystart, font_text,
-                     "1982");
-    ystart += ystep_head;
-    DrawTextSCentered(ystart, font_head,
-                     "published by");
-    ystart += ystep_head;
-    DrawTextSCentered(ystart, font_text,
-                     "Thinking Rabbit");
-  }
-  else if (screen_nr == 4)
-  {
-    DrawTextSCentered(ystart, font_head,
-                     "Special thanks to");
-    ystart += ystep_head;
-    DrawTextSCentered(ystart, font_text,
-                     "Alan Bond");
-    ystart += ystep_head;
-    DrawTextSCentered(ystart, font_head,
-                     "and");
-    ystart += ystep_head;
-    DrawTextSCentered(ystart, font_text,
-                     "J\xfcrgen Bonhagen");
-    ystart += ystep_head;
-    DrawTextSCentered(ystart, font_head,
-                     "for the continuous creation");
-    ystart += ystep_line;
-    DrawTextSCentered(ystart, font_head,
-                     "of outstanding level sets");
-  }
-  else if (screen_nr == 5)
-  {
-    DrawTextSCentered(ystart, font_head,
-                     "Thanks to");
-    ystart += ystep_head;
-    DrawTextSCentered(ystart, font_text,
-                     "Peter Elzner");
-    ystart += ystep_head;
-    DrawTextSCentered(ystart, font_head,
-                     "for ideas and inspiration by");
-    ystart += ystep_head;
-    DrawTextSCentered(ystart, font_text,
-                     "Diamond Caves");
-    ystart += ystep_para;
-
-    DrawTextSCentered(ystart, font_head,
-                     "Thanks to");
-    ystart += ystep_head;
-    DrawTextSCentered(ystart, font_text,
-                     "Steffest");
-    ystart += ystep_head;
-    DrawTextSCentered(ystart, font_head,
-                     "for ideas and inspiration by");
-    ystart += ystep_head;
-    DrawTextSCentered(ystart, font_text,
-                     "DX-Boulderdash");
-  }
-  else if (screen_nr == 6)
-  {
-    DrawTextSCentered(ystart, font_head,
-                     "Thanks to");
-    ystart += ystep_head;
-    DrawTextSCentered(ystart, font_text,
-                     "David Tritscher");
-    ystart += ystep_head;
-    DrawTextSCentered(ystart, font_head,
-                     "for the code base used for the");
-    ystart += ystep_line;
-    DrawTextSCentered(ystart, font_head,
-                     "native Emerald Mine engine");
-  }
-  else if (screen_nr == 7)
-  {
-    DrawTextSCentered(ystart, font_head,
-                     "Thanks to");
-    ystart += ystep_head;
-    DrawTextSCentered(ystart, font_text,
-                     "Guido Schulz");
-    ystart += ystep_head;
-    DrawTextSCentered(ystart, font_head,
-                     "for the initial DOS port");
-    ystart += ystep_para;
-
-    DrawTextSCentered(ystart, font_head,
-                     "Thanks to");
-    ystart += ystep_head;
-    DrawTextSCentered(ystart, font_text,
-                     "Karl H\xf6rnell");
-    ystart += ystep_head;
-    DrawTextSCentered(ystart, font_head,
-                     "for some additional toons");
-  }
-  else if (screen_nr == 8)
-  {
-    DrawTextSCentered(ystart, font_head,
-                     "And not to forget:");
-    ystart += ystep_head;
-    DrawTextSCentered(ystart, font_head,
-                     "Many thanks to");
-    ystart += ystep_head;
-    DrawTextSCentered(ystart, font_text,
-                     "All those who contributed");
-    ystart += ystep_line;
-    DrawTextSCentered(ystart, font_text,
-                     "levels to this game");
-    ystart += ystep_line;
-    DrawTextSCentered(ystart, font_text,
-                     "since 1995");
-  }
+  char *filename = getCreditsFilename(screen_nr, use_global_credits_screens);
+  int width = SXSIZE;
+  int height = MENU_SCREEN_INFO_YBOTTOM - MENU_SCREEN_INFO_YSTART1;
+  int chars = width / getFontWidth(font_text);
+  int lines = height / getFontHeight(font_text);
+  int padx = (width - chars * getFontWidth(font_text)) / 2;
+  int line_spacing = getMenuTextSpacing(spacing_line, font_text);
+  boolean autowrap = FALSE;
+  boolean centered = TRUE;
+  boolean parse_comments = TRUE;
 
-  DrawTextSCentered(ybottom, font_foot,
-                   "Press any key or button for next page");
+  DrawTextFile(mSX + padx, mSY + MENU_SCREEN_INFO_YSTART1 + ystep_title,
+              filename, font_text, chars, -1, lines, line_spacing, -1,
+              autowrap, centered, parse_comments);
+
+  boolean last_screen = (screen_nr == num_credits_screens - 1);
+  char *text_foot = (last_screen ? TEXT_INFO_MENU : TEXT_NEXT_PAGE);
+
+  DrawTextSCentered(ybottom, font_foot, text_foot);
 }
 
 static void DrawInfoScreen_Credits(void)
@@ -3581,24 +3459,54 @@ static void DrawInfoScreen_Credits(void)
 
   FadeOut(REDRAW_FIELD);
 
-  HandleInfoScreen_Credits(MB_MENU_INITIALIZE);
+  HandleInfoScreen_Credits(0, 0, MB_MENU_INITIALIZE);
 
   FadeIn(REDRAW_FIELD);
 }
 
-void HandleInfoScreen_Credits(int button)
+void HandleInfoScreen_Credits(int dx, int dy, int button)
 {
   static int screen_nr = 0;
-  int num_screens = 9;
 
   if (button == MB_MENU_INITIALIZE)
   {
+    int i;
+
+    // determine number of (global or level set specific) credits screens
+    for (i = 0; i < 2; i++)
+    {
+      num_credits_screens = 0;
+      use_global_credits_screens = i;
+
+      while (getCreditsFilename(num_credits_screens,
+                               use_global_credits_screens) != NULL)
+       num_credits_screens++;
+
+      if (num_credits_screens > 0)
+       break;
+    }
+
+    if (num_credits_screens == 0)
+    {
+      int font_title = MENU_INFO_FONT_TITLE;
+      int font_foot  = MENU_INFO_FONT_FOOT;
+      int ystart  = mSY - SY + MENU_SCREEN_INFO_YSTART1;
+      int ybottom = mSY - SY + MENU_SCREEN_INFO_YBOTTOM;
+
+      ClearField();
+      DrawHeadline();
+
+      DrawTextSCentered(ystart, font_title, "No credits for this level set.");
+      DrawTextSCentered(ybottom, font_foot, TEXT_INFO_MENU);
+
+      return;
+    }
+
     screen_nr = 0;
 
-    // DrawInfoScreen_CreditsScreen(screen_nr);
+    DrawInfoScreen_CreditsScreen(screen_nr);
   }
-
-  if (button == MB_MENU_LEAVE)
+  else if (button == MB_MENU_LEAVE)
   {
     PlaySound(SND_MENU_ITEM_SELECTING);
 
@@ -3607,16 +3515,13 @@ void HandleInfoScreen_Credits(int button)
 
     return;
   }
-  else if (button == MB_MENU_CHOICE || button == MB_MENU_INITIALIZE)
+  else if (button == MB_MENU_CHOICE || dx)
   {
-    if (button != MB_MENU_INITIALIZE)
-    {
-      PlaySound(SND_MENU_ITEM_SELECTING);
+    PlaySound(SND_MENU_ITEM_SELECTING);
 
-      screen_nr++;
-    }
+    screen_nr += (dx < 0 ? -1 : +1);
 
-    if (screen_nr >= num_screens)
+    if (screen_nr < 0 || screen_nr >= num_credits_screens)
     {
       FadeMenuSoundsAndMusic();
 
@@ -3626,16 +3531,13 @@ void HandleInfoScreen_Credits(int button)
       return;
     }
 
-    if (screen_nr > 0)
-      FadeSetNextScreen();
+    FadeSetNextScreen();
 
-    if (button != MB_MENU_INITIALIZE)
-      FadeOut(REDRAW_FIELD);
+    FadeOut(REDRAW_FIELD);
 
     DrawInfoScreen_CreditsScreen(screen_nr);
 
-    if (button != MB_MENU_INITIALIZE)
-      FadeIn(REDRAW_FIELD);
+    FadeIn(REDRAW_FIELD);
   }
   else
   {
@@ -3643,68 +3545,89 @@ void HandleInfoScreen_Credits(int button)
   }
 }
 
-static void DrawInfoScreen_Program(void)
+static void DrawInfoScreen_ProgramScreen(int screen_nr)
 {
   int font_title = MENU_INFO_FONT_TITLE;
-  int font_head  = MENU_INFO_FONT_HEAD;
   int font_text  = MENU_INFO_FONT_TEXT;
   int font_foot  = MENU_INFO_FONT_FOOT;
   int spacing_title = menu.headline1_spacing_info[info_mode];
-  int spacing_head  = menu.headline2_spacing_info[info_mode];
-  int spacing_para  = menu.paragraph_spacing_info[info_mode];
   int spacing_line  = menu.line_spacing_info[info_mode];
   int ystep_title = getMenuTextStep(spacing_title, font_title);
-  int ystep_head  = getMenuTextStep(spacing_head,  font_head);
-  int ystep_para  = getMenuTextStep(spacing_para,  font_text);
-  int ystep_line  = getMenuTextStep(spacing_line,  font_text);
   int ystart  = mSY - SY + MENU_SCREEN_INFO_YSTART1;
   int ybottom = mSY - SY + MENU_SCREEN_INFO_YBOTTOM;
 
-  SetMainBackgroundImageIfDefined(IMG_BACKGROUND_INFO_PROGRAM);
-
-  FadeOut(REDRAW_FIELD);
-
   ClearField();
   DrawHeadline();
 
   DrawTextSCentered(ystart, font_title, "Program Information:");
-  ystart += ystep_title;
 
-  DrawTextSCentered(ystart, font_head,
-                   "This game is Freeware!");
-  ystart += ystep_head;
-  DrawTextSCentered(ystart, font_head,
-                   "If you like it, send e-mail to:");
-  ystart += ystep_head;
-  DrawTextSCentered(ystart, font_text,
-                   setup.internal.program_email);
-  ystart += ystep_para;
+  char *filename = getProgramInfoFilename(screen_nr);
+  int width = SXSIZE;
+  int height = MENU_SCREEN_INFO_YBOTTOM - MENU_SCREEN_INFO_YSTART1;
+  int chars = width / getFontWidth(font_text);
+  int lines = height / getFontHeight(font_text);
+  int padx = (width - chars * getFontWidth(font_text)) / 2;
+  int line_spacing = getMenuTextSpacing(spacing_line, font_text);
+  boolean autowrap = FALSE;
+  boolean centered = TRUE;
+  boolean parse_comments = TRUE;
 
-  DrawTextSCentered(ystart, font_head,
-                   "More information and levels:");
-  ystart += ystep_head;
-  DrawTextSCentered(ystart, font_text,
-                   setup.internal.program_website);
-  ystart += ystep_para;
+  DrawTextFile(mSX + padx, mSY + MENU_SCREEN_INFO_YSTART1 + ystep_title,
+              filename, font_text, chars, -1, lines, line_spacing, -1,
+              autowrap, centered, parse_comments);
 
-  DrawTextSCentered(ystart, font_head,
-                   "If you have created new levels,");
-  ystart += ystep_line;
-  DrawTextSCentered(ystart, font_head,
-                   "send them to me to include them!");
-  ystart += ystep_head;
-  DrawTextSCentered(ystart, font_head,
-                   ":-)");
+  boolean last_screen = (screen_nr == num_program_info_screens - 1);
+  char *text_foot = (last_screen ? TEXT_INFO_MENU : TEXT_NEXT_PAGE);
 
-  DrawTextSCentered(ybottom, font_foot,
-                   "Press any key or button for info menu");
+  DrawTextSCentered(ybottom, font_foot, text_foot);
+}
+
+static void DrawInfoScreen_Program(void)
+{
+  SetMainBackgroundImageIfDefined(IMG_BACKGROUND_INFO_PROGRAM);
+
+  FadeMenuSoundsAndMusic();
+
+  FadeOut(REDRAW_FIELD);
+
+  HandleInfoScreen_Program(0, 0, MB_MENU_INITIALIZE);
 
   FadeIn(REDRAW_FIELD);
 }
 
-void HandleInfoScreen_Program(int button)
+void HandleInfoScreen_Program(int dx, int dy, int button)
 {
-  if (button == MB_MENU_LEAVE)
+  static int screen_nr = 0;
+
+  if (button == MB_MENU_INITIALIZE)
+  {
+    // determine number of program info screens
+    num_program_info_screens = 0;
+
+    while (getProgramInfoFilename(num_program_info_screens) != NULL)
+      num_program_info_screens++;
+
+    if (num_program_info_screens == 0)
+    {
+      int font_title = MENU_INFO_FONT_TITLE;
+      int font_foot  = MENU_INFO_FONT_FOOT;
+      int ystart  = mSY - SY + MENU_SCREEN_INFO_YSTART1;
+      int ybottom = mSY - SY + MENU_SCREEN_INFO_YBOTTOM;
+
+      ClearField();
+      DrawHeadline();
+
+      DrawTextSCentered(ystart, font_title, "No program info available.");
+      DrawTextSCentered(ybottom, font_foot, TEXT_INFO_MENU);
+
+      return;
+    }
+
+    screen_nr = 0;
+
+    DrawInfoScreen_ProgramScreen(screen_nr);
+  }
+  else if (button == MB_MENU_LEAVE)
   {
     PlaySound(SND_MENU_ITEM_SELECTING);
 
@@ -3713,14 +3636,29 @@ void HandleInfoScreen_Program(int button)
 
     return;
   }
-  else if (button == MB_MENU_CHOICE)
+  else if (button == MB_MENU_CHOICE || dx)
   {
     PlaySound(SND_MENU_ITEM_SELECTING);
 
-    FadeMenuSoundsAndMusic();
+    screen_nr += (dx < 0 ? -1 : +1);
 
-    info_mode = INFO_MODE_MAIN;
-    DrawInfoScreen();
+    if (screen_nr < 0 || screen_nr >= num_program_info_screens)
+    {
+      FadeMenuSoundsAndMusic();
+
+      info_mode = INFO_MODE_MAIN;
+      DrawInfoScreen();
+
+      return;
+    }
+
+    FadeSetNextScreen();
+
+    FadeOut(REDRAW_FIELD);
+
+    DrawInfoScreen_ProgramScreen(screen_nr);
+
+    FadeIn(REDRAW_FIELD);
   }
   else
   {
@@ -3893,8 +3831,7 @@ static void DrawInfoScreen_Version(void)
   DrawTextF(xstart2, ystart, font_text, "%s", setup.system.sdl_audiodriver);
   DrawTextF(xstart3, ystart, font_text, "%s", driver_name);
 
-  DrawTextSCentered(ybottom, font_foot,
-                   "Press any key or button for info menu");
+  DrawTextSCentered(ybottom, font_foot, TEXT_INFO_MENU);
 
   FadeIn(REDRAW_FIELD);
 }
@@ -3930,6 +3867,7 @@ static void DrawInfoScreen_LevelSet(void)
   struct TitleMessageInfo *tmi = &readme;
   char *filename = getLevelSetInfoFilename();
   char *title = "Level Set Information:";
+  int font_foot = MENU_INFO_FONT_FOOT;
   int ystart  = mSY - SY + MENU_SCREEN_INFO_YSTART1;
   int ybottom = mSY - SY + MENU_SCREEN_INFO_YBOTTOM;
 
@@ -3981,8 +3919,7 @@ static void DrawInfoScreen_LevelSet(void)
               filename, tmi->font, tmi->chars, -1, tmi->lines, 0, -1,
               tmi->autowrap, tmi->centered, tmi->parse_comments);
 
-  DrawTextSCentered(ybottom, FONT_TEXT_4,
-                   "Press any key or button for info menu");
+  DrawTextSCentered(ybottom, font_foot, TEXT_INFO_MENU);
 
   FadeIn(REDRAW_FIELD);
 }
@@ -4041,15 +3978,15 @@ static void DrawInfoScreen(void)
 void HandleInfoScreen(int mx, int my, int dx, int dy, int button)
 {
   if (info_mode == INFO_MODE_TITLE)
-    HandleInfoScreen_TitleScreen(button);
+    HandleInfoScreen_TitleScreen(dx, dy, button);
   else if (info_mode == INFO_MODE_ELEMENTS)
-    HandleInfoScreen_Elements(button);
+    HandleInfoScreen_Elements(dx, dy, button);
   else if (info_mode == INFO_MODE_MUSIC)
-    HandleInfoScreen_Music(button);
+    HandleInfoScreen_Music(dx, dy, button);
   else if (info_mode == INFO_MODE_CREDITS)
-    HandleInfoScreen_Credits(button);
+    HandleInfoScreen_Credits(dx, dy, button);
   else if (info_mode == INFO_MODE_PROGRAM)
-    HandleInfoScreen_Program(button);
+    HandleInfoScreen_Program(dx, dy, button);
   else if (info_mode == INFO_MODE_VERSION)
     HandleInfoScreen_Version(button);
   else if (info_mode == INFO_MODE_LEVELSET)
@@ -4060,532 +3997,130 @@ void HandleInfoScreen(int mx, int my, int dx, int dy, int button)
 
 
 // ============================================================================
-// rename player API functions
+// type name functions
 // ============================================================================
 
-struct ApiRenamePlayerThreadData
-{
-  char *player_name;
-  char *player_uuid;
-};
-
-static void *CreateThreadData_ApiRenamePlayer(void)
-{
-  struct ApiRenamePlayerThreadData *data =
-    checked_malloc(sizeof(struct ApiRenamePlayerThreadData));
-
-  data->player_name = getStringCopy(setup.player_name);
-  data->player_uuid = getStringCopy(setup.player_uuid);
-
-  return data;
-}
+static TreeInfo *type_name_node = NULL;
+static char type_name_last[MAX_PLAYER_NAME_LEN + 1] = { 0 };
+static int type_name_nr = 0;
 
-static void FreeThreadData_ApiRenamePlayer(void *data_raw)
+static int getPlayerNameColor(char *name)
 {
-  struct ApiRenamePlayerThreadData *data = data_raw;
-
-  checked_free(data->player_name);
-  checked_free(data->player_uuid);
-  checked_free(data);
+  return (strEqual(name, EMPTY_PLAYER_NAME) ? FC_BLUE : FC_RED);
 }
 
-static boolean SetRequest_ApiRenamePlayer(struct HttpRequest *request,
-                                         void *data_raw)
+static void drawTypeNameText(char *name, struct TextPosInfo *pos,
+                            boolean active)
 {
-  struct ApiRenamePlayerThreadData *data = data_raw;
-  char *player_name_raw = data->player_name;
-  char *player_uuid_raw = data->player_uuid;
-
-  request->hostname = setup.api_server_hostname;
-  request->port     = API_SERVER_PORT;
-  request->method   = API_SERVER_METHOD;
-  request->uri      = API_SERVER_URI_RENAME;
-
-  char *player_name = getEscapedJSON(player_name_raw);
-  char *player_uuid = getEscapedJSON(player_uuid_raw);
+  char text[MAX_PLAYER_NAME_LEN + 2] = { 0 };
+  boolean multiple_users = (game_status == GAME_MODE_PSEUDO_TYPENAMES);
+  int sx = (multiple_users ? amSX + pos->x : mSX + ALIGNED_TEXT_XPOS(pos));
+  int sy = (multiple_users ? amSY + pos->y : mSY + ALIGNED_TEXT_YPOS(pos));
+  int font_nr = (active ? FONT_ACTIVE(pos->font) : pos->font);
+  int font_width = getFontWidth(font_nr);
+  int font_xoffset = getFontDrawOffsetX(font_nr);
+  int font_yoffset = getFontDrawOffsetY(font_nr);
+  int font_sx = sx + font_xoffset;
+  int font_sy = sy + font_yoffset;
 
-  snprintf(request->body, MAX_HTTP_BODY_SIZE,
-          "{\n"
-          "%s"
-          "  \"game_version\":         \"%s\",\n"
-          "  \"game_platform\":        \"%s\",\n"
-          "  \"name\":                 \"%s\",\n"
-          "  \"uuid\":                 \"%s\"\n"
-          "}\n",
-          getPasswordJSON(setup.api_server_password),
-          getProgramRealVersionString(),
-          getProgramPlatformString(),
-          player_name,
-          player_uuid);
+  DrawBackgroundForFont(font_sx, font_sy, pos->width, pos->height, font_nr);
 
-  checked_free(player_name);
-  checked_free(player_uuid);
+  sprintf(text, "%s%c", name, (active ? '_' : '\0'));
 
-  ConvertHttpRequestBodyToServerEncoding(request);
+  pos->width = strlen(text) * font_width;
+  sx = (multiple_users ? amSX + pos->x : mSX + ALIGNED_TEXT_XPOS(pos));
 
-  return TRUE;
+  DrawText(sx, sy, text, font_nr);
 }
 
-static void HandleResponse_ApiRenamePlayer(struct HttpResponse *response,
-                                          void *data_raw)
+static void getTypeNameValues(char *name, struct TextPosInfo *pos, int *xpos)
 {
-  // nothing to do here
-}
+  struct MainControlInfo *mci = getMainControlInfo(MAIN_CONTROL_NAME);
 
-#if defined(PLATFORM_EMSCRIPTEN)
-static void Emscripten_ApiRenamePlayer_Loaded(unsigned handle, void *data_raw,
-                                             void *buffer, unsigned int size)
-{
-  struct HttpResponse *response = GetHttpResponseFromBuffer(buffer, size);
+  *pos = *mci->pos_input;
 
-  if (response != NULL)
+  if (game_status == GAME_MODE_PSEUDO_TYPENAMES)
   {
-    HandleResponse_ApiRenamePlayer(response, data_raw);
+    TreeInfo *ti = player_name_current;
+    int first_entry = ti->cl_first;
+    int entry_pos = first_entry + ti->cl_cursor;
+    TreeInfo *node_first = getTreeInfoFirstGroupEntry(ti);
+    int xpos = MENU_SCREEN_START_XPOS;
+    int ypos = MENU_SCREEN_START_YPOS + ti->cl_cursor;
+
+    type_name_node = getTreeInfoFromPos(node_first, entry_pos);
+    type_name_nr = entry_pos;
+
+    strcpy(name, type_name_node->name);
 
-    checked_free(response);
+    pos->x = xpos * 32;
+    pos->y = ypos * 32;
+    pos->width = MAX_PLAYER_NAME_LEN * 32;
   }
   else
   {
-    Error("server response too large to handle (%d bytes)", size);
+    type_name_nr = user.nr;
+
+    strcpy(name, setup.player_name);
   }
 
-  FreeThreadData_ApiRenamePlayer(data_raw);
-}
+  strcpy(type_name_last, name);
 
-static void Emscripten_ApiRenamePlayer_Failed(unsigned handle, void *data_raw,
-                                             int code, const char *status)
-{
-  Error("server failed to handle request: %d %s", code, status);
+  if (strEqual(name, EMPTY_PLAYER_NAME))
+    strcpy(name, "");
 
-  FreeThreadData_ApiRenamePlayer(data_raw);
+  *xpos = strlen(name);
 }
 
-static void Emscripten_ApiRenamePlayer_Progress(unsigned handle, void *data_raw,
-                                               int bytes, int size)
+static void setTypeNameValues_Name(char *name, struct TextPosInfo *pos)
 {
-  // nothing to do here
-}
+  // change name of edited user in global list of user names
+  setString(&global.user_names[type_name_nr], name);
 
-static void Emscripten_ApiRenamePlayer_HttpRequest(struct HttpRequest *request,
-                                                  void *data_raw)
-{
-  if (!SetRequest_ApiRenamePlayer(request, data_raw))
+  if (game_status == GAME_MODE_PSEUDO_TYPENAMES)
   {
-    FreeThreadData_ApiRenamePlayer(data_raw);
+    TreeInfo *node = type_name_node;
 
-    return;
-  }
+    // change name of edited user in local menu tree structure
+    setString(&node->name, name);
+    setString(&node->name_sorting, name);
 
-  emscripten_async_wget2_data(request->uri,
-                             request->method,
-                             request->body,
-                             data_raw,
-                             TRUE,
-                             Emscripten_ApiRenamePlayer_Loaded,
-                             Emscripten_ApiRenamePlayer_Failed,
-                             Emscripten_ApiRenamePlayer_Progress);
+    node->color = getPlayerNameColor(name);
+    pos->font = MENU_CHOOSE_TREE_FONT(node->color);
+  }
 }
 
-#else
-
-static void ApiRenamePlayer_HttpRequestExt(struct HttpRequest *request,
-                                          struct HttpResponse *response,
-                                          void *data_raw)
+static void setTypeNameValues(char *name, struct TextPosInfo *pos,
+                             boolean changed)
 {
-  if (!SetRequest_ApiRenamePlayer(request, data_raw))
-    return;
+  boolean reset_setup = strEqual(name, "");
+  boolean remove_user = strEqual(name, EMPTY_PLAYER_NAME);
+  boolean create_user = strEqual(type_name_last, EMPTY_PLAYER_NAME);
 
-  if (!DoHttpRequest(request, response))
-  {
-    Error("HTTP request failed: %s", GetHttpError());
+  if (!changed)
+    strcpy(name, type_name_last);
 
-    return;
-  }
+  if (strEqual(name, ""))
+    strcpy(name, EMPTY_PLAYER_NAME);
 
-  if (!HTTP_SUCCESS(response->status_code))
-  {
-    Error("server failed to handle request: %d %s",
-         response->status_code,
-         response->status_text);
+  setTypeNameValues_Name(name, pos);
 
+  // if player name not changed, no further action required
+  if (strEqual(name, type_name_last))
     return;
-  }
 
-  HandleResponse_ApiRenamePlayer(response, data_raw);
-}
+  // redraw player name before (possibly) opening request dialogs
+  drawTypeNameText(name, pos, FALSE);
 
-static void ApiRenamePlayer_HttpRequest(struct HttpRequest *request,
-                                   struct HttpResponse *response,
-                                   void *data_raw)
-{
-  ApiRenamePlayer_HttpRequestExt(request, response, data_raw);
+  int last_user_nr = user.nr;
 
-  FreeThreadData_ApiRenamePlayer(data_raw);
-}
-#endif
+  if (game_status == GAME_MODE_PSEUDO_TYPENAMES)
+  {
+    // save setup of currently active user (may differ from edited user)
+    SaveSetup();
 
-static int ApiRenamePlayerThread(void *data_raw)
-{
-  struct HttpRequest *request = checked_calloc(sizeof(struct HttpRequest));
-  struct HttpResponse *response = checked_calloc(sizeof(struct HttpResponse));
-
-  program.api_thread_count++;
-
-#if defined(PLATFORM_EMSCRIPTEN)
-  Emscripten_ApiRenamePlayer_HttpRequest(request, data_raw);
-#else
-  ApiRenamePlayer_HttpRequest(request, response, data_raw);
-#endif
-
-  program.api_thread_count--;
-
-  checked_free(request);
-  checked_free(response);
-
-  return 0;
-}
-
-static void ApiRenamePlayerAsThread(void)
-{
-  struct ApiRenamePlayerThreadData *data = CreateThreadData_ApiRenamePlayer();
-
-  ExecuteAsThread(ApiRenamePlayerThread,
-                 "ApiRenamePlayer", data,
-                 "rename player on server");
-}
-
-
-// ============================================================================
-// reset player UUID API functions
-// ============================================================================
-
-struct ApiResetUUIDThreadData
-{
-  char *player_name;
-  char *player_uuid_old;
-  char *player_uuid_new;
-};
-
-static void *CreateThreadData_ApiResetUUID(char *uuid_new)
-{
-  struct ApiResetUUIDThreadData *data =
-    checked_malloc(sizeof(struct ApiResetUUIDThreadData));
-
-  data->player_name     = getStringCopy(setup.player_name);
-  data->player_uuid_old = getStringCopy(setup.player_uuid);
-  data->player_uuid_new = getStringCopy(uuid_new);
-
-  return data;
-}
-
-static void FreeThreadData_ApiResetUUID(void *data_raw)
-{
-  struct ApiResetUUIDThreadData *data = data_raw;
-
-  checked_free(data->player_name);
-  checked_free(data->player_uuid_old);
-  checked_free(data->player_uuid_new);
-  checked_free(data);
-}
-
-static boolean SetRequest_ApiResetUUID(struct HttpRequest *request,
-                                      void *data_raw)
-{
-  struct ApiResetUUIDThreadData *data = data_raw;
-  char *player_name_raw = data->player_name;
-  char *player_uuid_old_raw = data->player_uuid_old;
-  char *player_uuid_new_raw = data->player_uuid_new;
-
-  request->hostname = setup.api_server_hostname;
-  request->port     = API_SERVER_PORT;
-  request->method   = API_SERVER_METHOD;
-  request->uri      = API_SERVER_URI_RESETUUID;
-
-  char *player_name = getEscapedJSON(player_name_raw);
-  char *player_uuid_old = getEscapedJSON(player_uuid_old_raw);
-  char *player_uuid_new = getEscapedJSON(player_uuid_new_raw);
-
-  snprintf(request->body, MAX_HTTP_BODY_SIZE,
-          "{\n"
-          "%s"
-          "  \"game_version\":         \"%s\",\n"
-          "  \"game_platform\":        \"%s\",\n"
-          "  \"name\":                 \"%s\",\n"
-          "  \"uuid_old\":             \"%s\",\n"
-          "  \"uuid_new\":             \"%s\"\n"
-          "}\n",
-          getPasswordJSON(setup.api_server_password),
-          getProgramRealVersionString(),
-          getProgramPlatformString(),
-          player_name,
-          player_uuid_old,
-          player_uuid_new);
-
-  checked_free(player_name);
-  checked_free(player_uuid_old);
-  checked_free(player_uuid_new);
-
-  ConvertHttpRequestBodyToServerEncoding(request);
-
-  return TRUE;
-}
-
-static void HandleResponse_ApiResetUUID(struct HttpResponse *response,
-                                       void *data_raw)
-{
-  struct ApiResetUUIDThreadData *data = data_raw;
-
-  // upgrade player UUID in server setup file
-  setup.player_uuid = getStringCopy(data->player_uuid_new);
-  setup.player_version = 2;
-
-  SaveSetup_ServerSetup();
-}
-
-#if defined(PLATFORM_EMSCRIPTEN)
-static void Emscripten_ApiResetUUID_Loaded(unsigned handle, void *data_raw,
-                                          void *buffer, unsigned int size)
-{
-  struct HttpResponse *response = GetHttpResponseFromBuffer(buffer, size);
-
-  if (response != NULL)
-  {
-    HandleResponse_ApiResetUUID(response, data_raw);
-
-    checked_free(response);
-  }
-  else
-  {
-    Error("server response too large to handle (%d bytes)", size);
-  }
-
-  FreeThreadData_ApiResetUUID(data_raw);
-}
-
-static void Emscripten_ApiResetUUID_Failed(unsigned handle, void *data_raw,
-                                          int code, const char *status)
-{
-  Error("server failed to handle request: %d %s", code, status);
-
-  FreeThreadData_ApiResetUUID(data_raw);
-}
-
-static void Emscripten_ApiResetUUID_Progress(unsigned handle, void *data_raw,
-                                            int bytes, int size)
-{
-  // nothing to do here
-}
-
-static void Emscripten_ApiResetUUID_HttpRequest(struct HttpRequest *request,
-                                               void *data_raw)
-{
-  if (!SetRequest_ApiResetUUID(request, data_raw))
-  {
-    FreeThreadData_ApiResetUUID(data_raw);
-
-    return;
-  }
-
-  emscripten_async_wget2_data(request->uri,
-                             request->method,
-                             request->body,
-                             data_raw,
-                             TRUE,
-                             Emscripten_ApiResetUUID_Loaded,
-                             Emscripten_ApiResetUUID_Failed,
-                             Emscripten_ApiResetUUID_Progress);
-}
-
-#else
-
-static void ApiResetUUID_HttpRequestExt(struct HttpRequest *request,
-                                       struct HttpResponse *response,
-                                       void *data_raw)
-{
-  if (!SetRequest_ApiResetUUID(request, data_raw))
-    return;
-
-  if (!DoHttpRequest(request, response))
-  {
-    Error("HTTP request failed: %s", GetHttpError());
-
-    return;
-  }
-
-  if (!HTTP_SUCCESS(response->status_code))
-  {
-    Error("server failed to handle request: %d %s",
-         response->status_code,
-         response->status_text);
-
-    return;
-  }
-
-  HandleResponse_ApiResetUUID(response, data_raw);
-}
-
-static void ApiResetUUID_HttpRequest(struct HttpRequest *request,
-                                    struct HttpResponse *response,
-                                    void *data_raw)
-{
-  ApiResetUUID_HttpRequestExt(request, response, data_raw);
-
-  FreeThreadData_ApiResetUUID(data_raw);
-}
-#endif
-
-static int ApiResetUUIDThread(void *data_raw)
-{
-  struct HttpRequest *request = checked_calloc(sizeof(struct HttpRequest));
-  struct HttpResponse *response = checked_calloc(sizeof(struct HttpResponse));
-
-  program.api_thread_count++;
-
-#if defined(PLATFORM_EMSCRIPTEN)
-  Emscripten_ApiResetUUID_HttpRequest(request, data_raw);
-#else
-  ApiResetUUID_HttpRequest(request, response, data_raw);
-#endif
-
-  program.api_thread_count--;
-
-  checked_free(request);
-  checked_free(response);
-
-  return 0;
-}
-
-static void ApiResetUUIDAsThread(char *uuid_new)
-{
-  struct ApiResetUUIDThreadData *data = CreateThreadData_ApiResetUUID(uuid_new);
-
-  ExecuteAsThread(ApiResetUUIDThread,
-                 "ApiResetUUID", data,
-                 "reset UUID on server");
-}
-
-
-// ============================================================================
-// type name functions
-// ============================================================================
-
-static TreeInfo *type_name_node = NULL;
-static char type_name_last[MAX_PLAYER_NAME_LEN + 1] = { 0 };
-static int type_name_nr = 0;
-
-static int getPlayerNameColor(char *name)
-{
-  return (strEqual(name, EMPTY_PLAYER_NAME) ? FC_BLUE : FC_RED);
-}
-
-static void drawTypeNameText(char *name, struct TextPosInfo *pos,
-                             boolean active)
-{
-  char text[MAX_PLAYER_NAME_LEN + 2] = { 0 };
-  boolean multiple_users = (game_status == GAME_MODE_PSEUDO_TYPENAMES);
-  int sx = (multiple_users ? amSX + pos->x : mSX + ALIGNED_TEXT_XPOS(pos));
-  int sy = (multiple_users ? amSY + pos->y : mSY + ALIGNED_TEXT_YPOS(pos));
-  int font_nr = (active ? FONT_ACTIVE(pos->font) : pos->font);
-  int font_width = getFontWidth(font_nr);
-
-  DrawBackgroundForFont(sx, sy, pos->width, pos->height, font_nr);
-
-  sprintf(text, "%s%c", name, (active ? '_' : '\0'));
-
-  pos->width = strlen(text) * font_width;
-  sx = (multiple_users ? amSX + pos->x : mSX + ALIGNED_TEXT_XPOS(pos));
-
-  DrawText(sx, sy, text, font_nr);
-}
-
-static void getTypeNameValues(char *name, struct TextPosInfo *pos, int *xpos)
-{
-  struct MainControlInfo *mci = getMainControlInfo(MAIN_CONTROL_NAME);
-
-  *pos = *mci->pos_input;
-
-  if (game_status == GAME_MODE_PSEUDO_TYPENAMES)
-  {
-    TreeInfo *ti = player_name_current;
-    int first_entry = ti->cl_first;
-    int entry_pos = first_entry + ti->cl_cursor;
-    TreeInfo *node_first = getTreeInfoFirstGroupEntry(ti);
-    int xpos = MENU_SCREEN_START_XPOS;
-    int ypos = MENU_SCREEN_START_YPOS + ti->cl_cursor;
-
-    type_name_node = getTreeInfoFromPos(node_first, entry_pos);
-    type_name_nr = entry_pos;
-
-    strcpy(name, type_name_node->name);
-
-    pos->x = xpos * 32;
-    pos->y = ypos * 32;
-    pos->width = MAX_PLAYER_NAME_LEN * 32;
-  }
-  else
-  {
-    type_name_nr = user.nr;
-
-    strcpy(name, setup.player_name);
-  }
-
-  strcpy(type_name_last, name);
-
-  if (strEqual(name, EMPTY_PLAYER_NAME))
-    strcpy(name, "");
-
-  *xpos = strlen(name);
-}
-
-static void setTypeNameValues_Name(char *name, struct TextPosInfo *pos)
-{
-  // change name of edited user in global list of user names
-  setString(&global.user_names[type_name_nr], name);
-
-  if (game_status == GAME_MODE_PSEUDO_TYPENAMES)
-  {
-    TreeInfo *node = type_name_node;
-
-    // change name of edited user in local menu tree structure
-    setString(&node->name, name);
-    setString(&node->name_sorting, name);
-
-    node->color = getPlayerNameColor(name);
-    pos->font = MENU_CHOOSE_TREE_FONT(node->color);
-  }
-}
-
-static void setTypeNameValues(char *name, struct TextPosInfo *pos,
-                             boolean changed)
-{
-  boolean reset_setup = strEqual(name, "");
-  boolean remove_user = strEqual(name, EMPTY_PLAYER_NAME);
-  boolean create_user = strEqual(type_name_last, EMPTY_PLAYER_NAME);
-
-  if (!changed)
-    strcpy(name, type_name_last);
-
-  if (strEqual(name, ""))
-    strcpy(name, EMPTY_PLAYER_NAME);
-
-  setTypeNameValues_Name(name, pos);
-
-  // if player name not changed, no further action required
-  if (strEqual(name, type_name_last))
-    return;
-
-  // redraw player name before (possibly) opening request dialogs
-  drawTypeNameText(name, pos, FALSE);
-
-  int last_user_nr = user.nr;
-
-  if (game_status == GAME_MODE_PSEUDO_TYPENAMES)
-  {
-    // save setup of currently active user (may differ from edited user)
-    SaveSetup();
-
-    // temporarily change active user to edited user
-    user.nr = type_name_nr;
+    // temporarily change active user to edited user
+    user.nr = type_name_nr;
 
     if (create_user &&
         Request("Use current setup values for the new player?", REQ_ASK))
@@ -4804,20 +4339,37 @@ static int getAlignYOffsetFromTreeInfo(TreeInfo *ti)
   return align_yoffset;
 }
 
+static void StartPlayingFromHallOfFame(void)
+{
+  level_nr = scores.next_level_nr;
+  LoadLevel(level_nr);
+
+  StartGameActions(network.enabled, setup.autorecord, level.random_seed);
+}
+
 static void DrawChooseTree(TreeInfo **ti_ptr)
 {
   int fade_mask = REDRAW_FIELD;
+  boolean restart_music = (game_status != game_status_last_screen &&
+                          game_status_last_screen != GAME_MODE_SCOREINFO);
+
+  scores.continue_on_return = (game_status == GAME_MODE_SCORES &&
+                              game_status_last_screen == GAME_MODE_PLAYING);
 
   if (CheckFadeAll())
     fade_mask = REDRAW_ALL;
 
-  if (strEqual((*ti_ptr)->subdir, STRING_TOP_DIRECTORY))
+  if (*ti_ptr != NULL && strEqual((*ti_ptr)->subdir, STRING_TOP_DIRECTORY))
   {
     if (game_status == GAME_MODE_SETUP)
     {
       execSetupArtwork();
     }
-    else       // GAME_MODE_LEVELS
+    else if (game_status == GAME_MODE_SCORES && scores.continue_playing)
+    {
+      StartPlayingFromHallOfFame();
+    }
+    else
     {
       SetGameStatus(GAME_MODE_MAIN);
 
@@ -4832,12 +4384,12 @@ static void DrawChooseTree(TreeInfo **ti_ptr)
   FreeScreenGadgets();
   CreateScreenGadgets();
 
-  if (game_status != game_status_last_screen)
+  if (restart_music)
     FadeMenuSoundsAndMusic();
 
   FadeOut(fade_mask);
 
-  // needed if different viewport properties defined for choosing level (set)
+  // needed if different viewport properties defined for this screen
   ChangeViewportPropertiesIfNeeded();
 
   if (game_status == GAME_MODE_NAMES)
@@ -4846,17 +4398,24 @@ static void DrawChooseTree(TreeInfo **ti_ptr)
     SetMainBackgroundImage(IMG_BACKGROUND_LEVELNR);
   else if (game_status == GAME_MODE_LEVELS)
     SetMainBackgroundImage(IMG_BACKGROUND_LEVELS);
+  else if (game_status == GAME_MODE_SCORES)
+    SetMainBackgroundImage(IMG_BACKGROUND_SCORES);
 
   ClearField();
 
   OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
 
+  // map gadgets for high score screen
+  if (game_status == GAME_MODE_SCORES)
+    MapScreenMenuGadgets(SCREEN_MASK_SCORES);
+
   MapScreenTreeGadgets(*ti_ptr);
+
   HandleChooseTree(0, 0, 0, 0, MB_MENU_INITIALIZE, ti_ptr);
 
   DrawMaskedBorder(fade_mask);
 
-  if (game_status != game_status_last_screen)
+  if (restart_music)
     PlayMenuSoundsAndMusic();
 
   FadeIn(fade_mask);
@@ -4864,13 +4423,13 @@ static void DrawChooseTree(TreeInfo **ti_ptr)
 
 static int getChooseTreeFont(TreeInfo *node, boolean active)
 {
-  int font_color = MENU_CHOOSE_TREE_COLOR(node, active);
-  int font_nr = MENU_CHOOSE_TREE_FONT(font_color);
-
-  return font_nr;
+  if (game_status == GAME_MODE_SCORES)
+    return (active ? FONT_TEXT_1_ACTIVE : FONT_TEXT_1);
+  else
+    return MENU_CHOOSE_TREE_FONT(MENU_CHOOSE_TREE_COLOR(node, active));
 }
 
-static void drawChooseTreeText(int y, boolean active, TreeInfo *ti)
+static void drawChooseTreeText(TreeInfo *ti, int y, boolean active)
 {
   int num_entries = numTreeInfoInGroup(ti);
   boolean scrollbar_needed = (num_entries > NUM_MENU_ENTRIES_ON_SCREEN);
@@ -4881,36 +4440,83 @@ static void drawChooseTreeText(int y, boolean active, TreeInfo *ti)
   TreeInfo *node_first = getTreeInfoFirstGroupEntry(ti);
   TreeInfo *node = getTreeInfoFromPos(node_first, entry_pos);
   int font_nr = getChooseTreeFont(node, active);
-  int font_xoffset = getFontBitmapInfo(font_nr)->draw_xoffset;
+  int font_xoffset = getFontDrawOffsetX(font_nr);
   int xpos = MENU_SCREEN_START_XPOS;
   int ypos = MENU_SCREEN_START_YPOS + y;
-  int startx = amSX + xpos * 32;
-  int starty = amSY + ypos * 32;
+  int startdx = xpos * 32;
+  int startdy = ypos * 32;
+  int startx = amSX + startdx;
+  int starty = amSY + startdy;
   int startx_text = startx + font_xoffset;
   int endx_text = amSX + screen_width;
   int max_text_size = endx_text - startx_text;
   int max_buffer_len = max_text_size / getFontWidth(font_nr);
   char buffer[max_buffer_len + 1];
 
-  strncpy(buffer, node->name, max_buffer_len);
-  buffer[max_buffer_len] = '\0';
+  if (game_status == GAME_MODE_SCORES && !node->parent_link)
+  {
+    int font_nr1 = (active ? FONT_TEXT_1_ACTIVE : FONT_TEXT_1);
+    int font_nr2 = (active ? FONT_TEXT_2_ACTIVE : FONT_TEXT_2);
+    int font_nr3 = (active ? FONT_TEXT_3_ACTIVE : FONT_TEXT_3);
+    int font_nr4 = (active ? FONT_TEXT_4_ACTIVE : FONT_TEXT_4);
+    int font_size_1 = getFontWidth(font_nr1);
+    int font_size_3 = getFontWidth(font_nr3);
+    int font_size_4 = getFontWidth(font_nr4);
+    int text_size_1 = 4 * font_size_1;
+    int text_size_4 = 5 * font_size_4;
+    int border = amSX - SX + getFontDrawOffsetX(font_nr1);
+    int dx1 = 0;
+    int dx3 = text_size_1;
+    int dx4 = SXSIZE - 2 * startdx - 2 * border - text_size_4;
+    int num_dots = (dx4 - dx3) / font_size_3;
+    int startx1 = startx + dx1;
+    int startx3 = startx + dx3;
+    int startx4 = startx + dx4;
+    int pos = node->pos;
+    char *pos_text = getHallOfFameRankText(pos, 3);
+    int i;
+
+    DrawText(startx1, starty, pos_text, font_nr1);
+
+    for (i = 0; i < num_dots; i++)
+      DrawText(startx3 + i * font_size_3, starty, ".", font_nr3);
+
+    if (!strEqual(scores.entry[pos].name, EMPTY_PLAYER_NAME))
+      DrawText(startx3, starty, scores.entry[pos].name, font_nr2);
+
+    DrawText(startx4, starty, getHallOfFameScoreText(pos, 5), font_nr4);
+  }
+  else
+  {
+    strncpy(buffer, node->name, max_buffer_len);
+    buffer[max_buffer_len] = '\0';
 
-  DrawText(startx, starty, buffer, font_nr);
+    DrawText(startx, starty, buffer, font_nr);
+  }
 }
 
-static void drawChooseTreeList(int first_entry, int num_page_entries,
-                              TreeInfo *ti)
+static void drawChooseTreeHeadExt(int type, char *title_string)
 {
-  int i;
-  char *title_string = NULL;
   int yoffset_sets = MENU_TITLE1_YPOS;
   int yoffset_setup = 16;
-  int yoffset = (ti->type == TREE_TYPE_LEVEL_DIR ||
-                ti->type == TREE_TYPE_LEVEL_NR ? yoffset_sets : yoffset_setup);
-
-  title_string = ti->infotext;
+  int yoffset = (type == TREE_TYPE_SCORE_ENTRY ||
+                type == TREE_TYPE_LEVEL_DIR ||
+                type == TREE_TYPE_LEVEL_NR ? yoffset_sets : yoffset_setup);
 
   DrawTextSCentered(mSY - SY + yoffset, FONT_TITLE_1, title_string);
+}
+
+static void drawChooseTreeHead(TreeInfo *ti)
+{
+  drawChooseTreeHeadExt(ti->type, ti->infotext);
+}
+
+static void drawChooseTreeList(TreeInfo *ti)
+{
+  int first_entry = ti->cl_first;
+  int num_entries = numTreeInfoInGroup(ti);
+  int num_page_entries = MIN(num_entries, NUM_MENU_ENTRIES_ON_SCREEN);
+  int i;
 
   clearMenuListArea();
 
@@ -4922,7 +4528,7 @@ static void drawChooseTreeList(int first_entry, int num_page_entries,
     node_first = getTreeInfoFirstGroupEntry(ti);
     node = getTreeInfoFromPos(node_first, entry_pos);
 
-    drawChooseTreeText(i, FALSE, ti);
+    drawChooseTreeText(ti, i, FALSE);
 
     if (node->parent_link)
       initCursor(i, IMG_MENU_BUTTON_LEAVE_MENU);
@@ -4931,6 +4537,9 @@ static void drawChooseTreeList(int first_entry, int num_page_entries,
     else
       initCursor(i, IMG_MENU_BUTTON);
 
+    if (game_status == GAME_MODE_SCORES && node->pos == scores.last_added)
+      initCursor(i, IMG_MENU_BUTTON_ENTER_MENU);
+
     if (game_status == GAME_MODE_NAMES)
       drawChooseTreeEdit(i, FALSE);
   }
@@ -4938,21 +4547,26 @@ static void drawChooseTreeList(int first_entry, int num_page_entries,
   redraw_mask |= REDRAW_FIELD;
 }
 
-static void drawChooseTreeInfo(int entry_pos, TreeInfo *ti)
+static void drawChooseTreeInfo(TreeInfo *ti)
 {
-  TreeInfo *node, *node_first;
-  int x, last_redraw_mask = redraw_mask;
+  int entry_pos = ti->cl_first + ti->cl_cursor;
+  int last_redraw_mask = redraw_mask;
   int ypos = MENU_TITLE2_YPOS;
   int font_nr = FONT_TITLE_2;
+  int x;
 
   if (ti->type == TREE_TYPE_LEVEL_NR)
     DrawTextFCentered(ypos, font_nr, leveldir_current->name);
 
+  if (ti->type == TREE_TYPE_SCORE_ENTRY)
+    DrawTextFCentered(ypos, font_nr, "HighScores of Level %d",
+                     scores.last_level_nr);
+
   if (ti->type != TREE_TYPE_LEVEL_DIR)
     return;
 
-  node_first = getTreeInfoFirstGroupEntry(ti);
-  node = getTreeInfoFromPos(node_first, entry_pos);
+  TreeInfo *node_first = getTreeInfoFirstGroupEntry(ti);
+  TreeInfo *node = getTreeInfoFromPos(node_first, entry_pos);
 
   DrawBackgroundForFont(SX, SY + ypos, SXSIZE, getFontHeight(font_nr), font_nr);
 
@@ -4973,22 +4587,79 @@ static void drawChooseTreeInfo(int entry_pos, TreeInfo *ti)
     MarkTileDirty(x, 1);
 }
 
-static void drawChooseTreeCursorAndText(int y, boolean active, TreeInfo *ti)
+static void drawChooseTreeCursorAndText(TreeInfo *ti, boolean active)
 {
-  drawChooseTreeCursor(y, active);
-  drawChooseTreeText(y, active, ti);
+  drawChooseTreeCursor(ti->cl_cursor, active);
+  drawChooseTreeText(ti, ti->cl_cursor, active);
 }
 
 static void drawChooseTreeScreen(TreeInfo *ti)
 {
-  int num_entries = numTreeInfoInGroup(ti);
+  drawChooseTreeHead(ti);
+  drawChooseTreeList(ti);
+  drawChooseTreeInfo(ti);
+  drawChooseTreeCursorAndText(ti, TRUE);
+
+  AdjustChooseTreeScrollbar(ti, SCREEN_CTRL_ID_SCROLL_VERTICAL);
+
+  // scroll bar and buttons may just have been added after reloading scores
+  if (game_status == GAME_MODE_SCORES)
+    MapScreenTreeGadgets(ti);
+}
+
+static void drawChooseTreeScreen_Scores_NotAvailable(void)
+{
+  // dirty workaround to use spacing definitions from info screen
+  info_mode = INFO_MODE_TITLE;
+
+  char *text_info = "HighScores of Level %d";
+  char *text_title = "Score information:";
+  char *text_error = "No scores for this level.";
+  char *text_foot = "Press any key or button for main menu";
+  int font_info = FONT_TITLE_2;
+  int font_title = FONT_INITIAL_3;
+  int font_error = FONT_INITIAL_4;
+  int font_foot  = FONT_INITIAL_2;
+  int spacing_title = menu.headline1_spacing_info[INFO_MODE_TITLE];
+  int ystep_title = getMenuTextStep(spacing_title, font_title);
+  int ystart1 = mSY - SY + MENU_SCREEN_INFO_YSTART1;
+  int ystart2 = ystart1 + ystep_title;
+  int ybottom = mSY - SY + MENU_SCREEN_INFO_YBOTTOM;
+  int ystart0 = MENU_TITLE2_YPOS;
+
+  drawChooseTreeHeadExt(TREE_TYPE_SCORE_ENTRY, INFOTEXT_SCORE_ENTRY);
+  DrawTextFCentered(ystart0, font_info, text_info, scores.last_level_nr);
+
+  DrawTextSCentered(ystart1, font_title, text_title);
+  DrawTextSCentered(ystart2, font_error, text_error);
+
+  DrawTextSCentered(ybottom, font_foot, text_foot);
+}
+
+static TreeInfo *setHallOfFameActiveEntry(TreeInfo **ti_ptr)
+{
+  int score_pos = scores.last_added;
+
+  if (game_status_last_screen == GAME_MODE_SCOREINFO)
+    score_pos = scores.last_entry_nr;
+
+  // set current tree entry to last added score entry
+  *ti_ptr = getTreeInfoFromIdentifier(score_entries, i_to_a(score_pos));
+
+  // if that fails, set current tree entry to first entry (back link)
+  if (*ti_ptr == NULL)
+    *ti_ptr = score_entries->node_group;
+
+  int num_entries = numTreeInfoInGroup(*ti_ptr);
   int num_page_entries = MIN(num_entries, NUM_MENU_ENTRIES_ON_SCREEN);
+  int pos_score = getPosFromTreeInfo(*ti_ptr);
+  int pos_first_raw = pos_score - (num_page_entries + 1) / 2 + 1;
+  int pos_first = MIN(MAX(0, pos_first_raw), num_entries - num_page_entries);
 
-  drawChooseTreeList(ti->cl_first, num_page_entries, ti);
-  drawChooseTreeInfo(ti->cl_first + ti->cl_cursor, ti);
-  drawChooseTreeCursorAndText(ti->cl_cursor, TRUE, ti);
+  (*ti_ptr)->cl_first = pos_first;
+  (*ti_ptr)->cl_cursor = pos_score - pos_first;
 
-  AdjustChooseTreeScrollbar(SCREEN_CTRL_ID_SCROLL_VERTICAL, ti->cl_first, ti);
+  return *ti_ptr;
 }
 
 static void HandleChooseTree(int mx, int my, int dx, int dy, int button,
@@ -4998,14 +4669,56 @@ static void HandleChooseTree(int mx, int my, int dx, int dy, int button,
   boolean has_scrollbar = screen_gadget[SCREEN_CTRL_ID_SCROLL_VERTICAL]->mapped;
   int mx_scrollbar = screen_gadget[SCREEN_CTRL_ID_SCROLL_VERTICAL]->x;
   int mx_right_border = (has_scrollbar ? mx_scrollbar : SX + SXSIZE);
-  int sx1_edit_name = getChooseTreeEditXPos(POS_LEFT);
-  int sx2_edit_name = getChooseTreeEditXPos(POS_RIGHT);
+  int sx1_edit_name = getChooseTreeEditXPosReal(POS_LEFT);
+  int sx2_edit_name = getChooseTreeEditXPosReal(POS_RIGHT);
   int x = 0;
-  int y = ti->cl_cursor;
+  int y = (ti != NULL ? ti->cl_cursor : 0);
   int step = (button == 1 ? 1 : button == 2 ? 5 : 10);
   int num_entries = numTreeInfoInGroup(ti);
   int num_page_entries = MIN(num_entries, NUM_MENU_ENTRIES_ON_SCREEN);
   boolean position_set_by_scrollbar = (dx == 999);
+  boolean button_action = (button == MB_MENU_LEAVE || button == MB_MENU_CHOICE);
+  boolean button_is_valid = (mx >= 0 && my >= 0);
+  boolean button_screen_clicked = (button_action && button_is_valid);
+
+  if (game_status == GAME_MODE_SCORES)
+  {
+    if (server_scores.updated)
+    {
+      // reload scores, using updated server score cache file
+      LoadLocalAndServerScore(scores.last_level_nr, FALSE);
+
+      server_scores.updated = FALSE;
+
+      DrawHallOfFame_setScoreEntries();
+
+      if (score_entries != NULL)
+      {
+       ti = setHallOfFameActiveEntry(ti_ptr);
+
+       if (button != MB_MENU_INITIALIZE)
+         drawChooseTreeScreen(ti);
+      }
+    }
+
+    if (score_entries == NULL)
+    {
+      if (button == MB_MENU_INITIALIZE)
+      {
+       drawChooseTreeScreen_Scores_NotAvailable();
+      }
+      else if (button_screen_clicked)
+      {
+       PlaySound(SND_MENU_ITEM_SELECTING);
+
+       SetGameStatus(GAME_MODE_MAIN);
+
+       DrawMainMenu();
+      }
+
+      return;
+    }
+  }
 
   if (button == MB_MENU_INITIALIZE)
   {
@@ -5015,11 +4728,16 @@ static void HandleChooseTree(int mx, int my, int dx, int dy, int button,
     align_xoffset = getAlignXOffsetFromTreeInfo(ti);
     align_yoffset = getAlignYOffsetFromTreeInfo(ti);
 
-    if (ti->cl_first == -1)
+    if (game_status == GAME_MODE_SCORES)
+    {
+      ti = setHallOfFameActiveEntry(ti_ptr);
+    }
+    else if (ti->cl_first == -1)
     {
       // only on initialization
       ti->cl_first = MAX(0, entry_pos - num_page_entries + 1);
       ti->cl_cursor = entry_pos - ti->cl_first;
+
     }
     else if (ti->cl_cursor >= num_page_entries ||
             (num_entries > num_page_entries &&
@@ -5039,7 +4757,8 @@ static void HandleChooseTree(int mx, int my, int dx, int dy, int button,
   }
   else if (button == MB_MENU_LEAVE)
   {
-    FadeSetLeaveMenu();
+    if (game_status != GAME_MODE_SCORES)
+      FadeSetLeaveMenu();
 
     PlaySound(SND_MENU_ITEM_SELECTING);
 
@@ -5093,6 +4812,23 @@ static void HandleChooseTree(int mx, int my, int dx, int dy, int button,
     return;
   }
 
+#if defined(PLATFORM_ANDROID)
+  // directly continue when touching the screen after playing
+  if ((mx || my) && scores.continue_on_return)
+  {
+    // ignore touch events until released
+    mx = my = 0;
+  }
+#endif
+
+  // any mouse click or cursor key stops leaving scores by "Return" key
+  if ((mx || my || dx || dy) && scores.continue_on_return)
+  {
+    scores.continue_on_return = FALSE;
+    level_nr = scores.last_level_nr;
+    LoadLevel(level_nr);
+  }
+
   if (mx || my)                // mouse input
   {
     x = (mx - amSX) / 32;
@@ -5150,7 +4886,13 @@ static void HandleChooseTree(int mx, int my, int dx, int dy, int button,
     y = ti->cl_cursor + dy;
   }
 
-  if (dx == 1)
+  if (game_status == GAME_MODE_SCORES && ABS(dx) == 1)
+  {
+    HandleHallOfFame_SelectLevel(1, dx);
+
+    return;
+  }
+  else if (dx == 1)
   {
     TreeInfo *node_first, *node_cursor;
     int entry_pos = ti->cl_first + y;
@@ -5166,15 +4908,17 @@ static void HandleChooseTree(int mx, int my, int dx, int dy, int button,
 
       node_cursor->cl_first = ti->cl_first;
       node_cursor->cl_cursor = ti->cl_cursor;
+
       *ti_ptr = node_cursor->node_group;
       DrawChooseTree(ti_ptr);
 
       return;
     }
   }
-  else if (dx == -1 && ti->node_parent)
+  else if ((dx == -1 || button == MB_MENU_CONTINUE) && ti->node_parent)
   {
-    FadeSetLeaveMenu();
+    if (game_status != GAME_MODE_SCORES)
+      FadeSetLeaveMenu();
 
     PlaySound(SND_MENU_ITEM_SELECTING);
 
@@ -5201,11 +4945,13 @@ static void HandleChooseTree(int mx, int my, int dx, int dy, int button,
       {
        PlaySound(SND_MENU_ITEM_ACTIVATING);
 
-       drawChooseTreeCursorAndText(ti->cl_cursor, FALSE, ti);
-       drawChooseTreeCursorAndText(y, TRUE, ti);
-       drawChooseTreeInfo(ti->cl_first + y, ti);
+       drawChooseTreeCursorAndText(ti, FALSE);
 
        ti->cl_cursor = y;
+
+       drawChooseTreeCursorAndText(ti, TRUE);
+
+       drawChooseTreeInfo(ti);
       }
       else if (dx < 0)
       {
@@ -5255,22 +5001,26 @@ static void HandleChooseTree(int mx, int my, int dx, int dy, int button,
 
        node_cursor->cl_first = ti->cl_first;
        node_cursor->cl_cursor = ti->cl_cursor;
+
        *ti_ptr = node_cursor->node_group;
        DrawChooseTree(ti_ptr);
       }
       else if (node_cursor->parent_link)
       {
-       FadeSetLeaveMenu();
+       if (game_status != GAME_MODE_SCORES)
+         FadeSetLeaveMenu();
 
        *ti_ptr = node_cursor->node_parent;
        DrawChooseTree(ti_ptr);
       }
       else
       {
-       FadeSetEnterMenu();
+       if (game_status != GAME_MODE_SCORES)
+         FadeSetEnterMenu();
 
        node_cursor->cl_first = ti->cl_first;
        node_cursor->cl_cursor = ti->cl_cursor;
+
        *ti_ptr = node_cursor;
 
        if (ti->type == TREE_TYPE_LEVEL_DIR)
@@ -5359,6 +5109,23 @@ static void HandleChooseTree(int mx, int my, int dx, int dy, int button,
            ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS);
            ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC);
          }
+         else if (game_status == GAME_MODE_SCORES)
+         {
+           if (scores.continue_playing && scores.continue_on_return)
+           {
+             StartPlayingFromHallOfFame();
+
+             return;
+           }
+           else if (!scores.continue_on_return)
+           {
+             SetGameStatus(GAME_MODE_SCOREINFO);
+
+             DrawScoreInfo(node_cursor->pos);
+
+             return;
+           }
+         }
 
          SetGameStatus(GAME_MODE_MAIN);
 
@@ -5367,6 +5134,9 @@ static void HandleChooseTree(int mx, int my, int dx, int dy, int button,
       }
     }
   }
+
+  if (game_status == GAME_MODE_SCORES)
+    PlayMenuSoundIfLoop();
 }
 
 void DrawChoosePlayerName(void)
@@ -5411,6 +5181,9 @@ void DrawChoosePlayerName(void)
   if (player_name_current == NULL)
     player_name_current = player_name;
 
+  // set text size for main name input (also used on name selection screen)
+  InitializeMainControls();
+
   DrawChooseTree(&player_name_current);
 }
 
@@ -5466,34 +5239,90 @@ void DrawChooseLevelNr(void)
     pushTreeInfo(&level_number, ti);
   }
 
-  // sort level number values to start with lowest level number
-  sortTreeInfo(&level_number);
+  // sort level number values to start with lowest level number
+  sortTreeInfo(&level_number);
+
+  // set current level number to current level number
+  level_number_current =
+    getTreeInfoFromIdentifier(level_number, i_to_a(level_nr));
+
+  // if that also fails, set current level number to first available level
+  if (level_number_current == NULL)
+    level_number_current = level_number;
+
+  DrawChooseTree(&level_number_current);
+}
+
+void HandleChooseLevelNr(int mx, int my, int dx, int dy, int button)
+{
+  HandleChooseTree(mx, my, dx, dy, button, &level_number_current);
+}
+
+static void DrawHallOfFame_setScoreEntries(void)
+{
+  int max_empty_entries = 10;  // at least show "top ten" list, if empty
+  int max_visible_entries = NUM_MENU_ENTRIES_ON_SCREEN - 1;   // w/o back link
+  int min_score_entries = MIN(max_empty_entries, max_visible_entries);
+  int score_pos = (scores.last_added >= 0 ? scores.last_added : 0);
+  int i;
+
+  if (score_entries != NULL)
+  {
+    freeTreeInfo(score_entries);
+
+    score_entries = NULL;
+  }
+
+  for (i = 0; i < MAX_SCORE_ENTRIES; i++)
+  {
+    // do not add empty score entries if off-screen
+    if (scores.entry[i].score == 0 &&
+       scores.entry[i].time == 0 &&
+       i >= min_score_entries)
+      break;
+
+    TreeInfo *ti = newTreeInfo_setDefaults(TREE_TYPE_SCORE_ENTRY);
+    char identifier[32], name[64];
+    int value = i;
+
+    ti->node_top = &score_entries;
+    ti->sort_priority = 10000 + value;
+    ti->color = FC_YELLOW;
+    ti->pos = i;
+
+    snprintf(identifier, sizeof(identifier), "%d", value);
+    snprintf(name, sizeof(name), "%03d.", value + 1);
+
+    setString(&ti->identifier, identifier);
+    setString(&ti->name, name);
+    setString(&ti->name_sorting, name);
+
+    pushTreeInfo(&score_entries, ti);
+  }
+
+  // sort score entries to start with highest score entry
+  sortTreeInfo(&score_entries);
+
+  // add top tree node to create back link to main menu
+  score_entries = addTopTreeInfoNode(score_entries);
 
-  // set current level number to current level number
-  level_number_current =
-    getTreeInfoFromIdentifier(level_number, i_to_a(level_nr));
+  // set current score entry to last added or highest score entry
+  score_entry_current =
+    getTreeInfoFromIdentifier(score_entries, i_to_a(score_pos));
 
-  // if that also fails, set current level number to first available level
-  if (level_number_current == NULL)
-    level_number_current = level_number;
+  // if that fails, set current score entry to first valid score entry
+  if (score_entry_current == NULL)
+    score_entry_current = getFirstValidTreeInfoEntry(score_entries);
 
-  DrawChooseTree(&level_number_current);
-}
+  if (score_entries != NULL && scores.continue_playing)
+    setString(&score_entries->node_group->name, BACKLINK_TEXT_NEXT);
 
-void HandleChooseLevelNr(int mx, int my, int dx, int dy, int button)
-{
-  HandleChooseTree(mx, my, dx, dy, button, &level_number_current);
+  // ("score_entries" and "score_entry_current" may be NULL here)
 }
 
-void DrawHallOfFame(int level_nr)
+void DrawHallOfFame(int nr)
 {
-  int fade_mask = REDRAW_FIELD;
-
-  if (CheckFadeAll())
-    fade_mask = REDRAW_ALL;
-
-  UnmapAllGadgets();
-  FadeMenuSoundsAndMusic();
+  scores.last_level_nr = nr;
 
   // (this is needed when called from GameEnd() after winning a game)
   KeyboardAutoRepeatOn();
@@ -5502,52 +5331,31 @@ void DrawHallOfFame(int level_nr)
   SetDrawDeactivationMask(REDRAW_NONE);
   SetDrawBackgroundMask(REDRAW_FIELD);
 
-  LoadLocalAndServerScore(level_nr, TRUE);
+  LoadLocalAndServerScore(scores.last_level_nr, TRUE);
+
+  DrawHallOfFame_setScoreEntries();
 
   if (scores.last_added >= 0)
     SetAnimStatus(GAME_MODE_PSEUDO_SCORESNEW);
 
   FadeSetEnterScreen();
 
-  FadeOut(fade_mask);
-
-  // needed if different viewport properties defined for scores
-  ChangeViewportPropertiesIfNeeded();
-
-  PlayMenuSoundsAndMusic();
-
-  OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
-
-  HandleHallOfFame(level_nr, 0, 0, 0, MB_MENU_INITIALIZE);
-
-  DrawMaskedBorder(fade_mask);
-
-  FadeIn(fade_mask);
+  DrawChooseTree(&score_entry_current);
 }
 
-static int getHallOfFameFirstEntry(int first_entry, int step)
+static char *getHallOfFameRankText(int nr, int size)
 {
-  if (step == 0)
-    first_entry = scores.last_added - (NUM_MENU_ENTRIES_ON_SCREEN + 1) / 2 + 1;
-  else
-    first_entry += step;
+  static char rank_text[10];
+  boolean forced = (scores.force_last_added && nr == scores.last_added);
+  char *rank_text_raw = (forced ? "???" : int2str(nr + 1, size));
 
-  if (first_entry < 0)
-    first_entry = 0;
-  else if (first_entry > MAX_SCORE_ENTRIES - NUM_MENU_ENTRIES_ON_SCREEN)
-    first_entry = MAX(0, MAX_SCORE_ENTRIES - NUM_MENU_ENTRIES_ON_SCREEN);
+  sprintf(rank_text, "%s%s", rank_text_raw, (size > 0 || !forced ? "." : ""));
 
-  return first_entry;
+  return rank_text;
 }
 
-static char *getHallOfFameScoreText(int nr)
+static char *getHallOfFameTimeText(int nr)
 {
-  if (!level.rate_time_over_score)
-    return int2str(scores.entry[nr].score, 5); // show normal score
-
-  if (level.use_step_counter)
-    return int2str(scores.entry[nr].time, 5);  // show number of steps
-
   static char score_text[10];
   int time_seconds = scores.entry[nr].time / FRAMES_PER_SECOND;
   int mm = (time_seconds / 60) % 60;
@@ -5558,124 +5366,317 @@ static char *getHallOfFameScoreText(int nr)
   return score_text;
 }
 
-static void drawHallOfFameList(int level_nr, int first_entry)
+static char *getHallOfFameScoreText(int nr, int size)
 {
-  int i, j;
+  if (!level.rate_time_over_score)
+    return int2str(scores.entry[nr].score, size);      // show normal score
+  else if (level.use_step_counter)
+    return int2str(scores.entry[nr].time, size);       // show number of steps
+  else
+    return getHallOfFameTimeText(nr);                  // show playing time
+}
 
-  SetMainBackgroundImage(IMG_BACKGROUND_SCORES);
-  ClearField();
+static char *getHallOfFameTapeDateText(struct ScoreEntry *entry)
+{
+  static char tape_date[MAX_ISO_DATE_LEN + 1];
+  int i, j;
 
-  DrawTextSCentered(MENU_TITLE1_YPOS, FONT_TITLE_1, "Hall Of Fame");
-  DrawTextFCentered(MENU_TITLE2_YPOS, FONT_TITLE_2,
-                   "HighScores of Level %d", level_nr);
+  if (!strEqual(entry->tape_date, UNKNOWN_NAME) ||
+      strEqual(entry->tape_basename, UNDEFINED_FILENAME))
+    return entry->tape_date;
 
-  for (i = 0; i < NUM_MENU_ENTRIES_ON_SCREEN; i++)
+  for (i = 0, j = 0; i < 8; i++, j++)
   {
-    int entry = first_entry + i;
-    boolean active = (entry == scores.last_added);
-    boolean forced = (scores.force_last_added && active);
-    int font_nr1 = (active ? FONT_TEXT_1_ACTIVE : FONT_TEXT_1);
-    int font_nr2 = (active ? FONT_TEXT_2_ACTIVE : FONT_TEXT_2);
-    int font_nr3 = (active ? FONT_TEXT_3_ACTIVE : FONT_TEXT_3);
-    int font_nr4 = (active ? FONT_TEXT_4_ACTIVE : FONT_TEXT_4);
-    int dxoff = getFontDrawOffsetX(font_nr1);
-    int dx1 = 3 * getFontWidth(font_nr1);
-    int dx2 = dx1 + getFontWidth(font_nr1);
-    int dx3 = SXSIZE - 2 * (mSX - SX + dxoff) - 5 * getFontWidth(font_nr4);
-    int num_dots = (dx3 - dx2) / getFontWidth(font_nr3);
-    int sy = mSY + 64 + i * 32;
-    char *pos_text = (forced ? "???" : int2str(entry + 1, 3));
-
-    DrawText(mSX, sy, pos_text, font_nr1);
-    DrawText(mSX + dx1, sy, ".", font_nr1);
+    tape_date[j] = entry->tape_basename[i];
 
-    for (j = 0; j < num_dots; j++)
-      DrawText(mSX + dx2 + j * getFontWidth(font_nr3), sy, ".", font_nr3);
-
-    if (!strEqual(scores.entry[entry].name, EMPTY_PLAYER_NAME))
-      DrawText(mSX + dx2, sy, scores.entry[entry].name, font_nr2);
-
-    DrawText(mSX + dx3, sy, getHallOfFameScoreText(entry), font_nr4);
+    if (i == 3 || i == 5)
+      tape_date[++j] = '-';
   }
 
-  redraw_mask |= REDRAW_FIELD;
+  tape_date[MAX_ISO_DATE_LEN] = '\0';
+
+  return tape_date;
 }
 
-void HandleHallOfFame(int mx, int my, int dx, int dy, int button)
+static void HandleHallOfFame_SelectLevel(int step, int direction)
 {
-  static int level_nr = 0;
-  static int first_entry = 0;
-  int step = (button == 1 ? 1 : button == 2 ? 5 : 10);
+  int old_level_nr = scores.last_level_nr;
+  int new_level_nr = old_level_nr + step * direction;
 
-  if (button == MB_MENU_INITIALIZE)
+  if (new_level_nr < leveldir_current->first_level)
+    new_level_nr = leveldir_current->first_level;
+  if (new_level_nr > leveldir_current->last_level)
+    new_level_nr = leveldir_current->last_level;
+
+  if (setup.handicap && new_level_nr > leveldir_current->handicap_level)
+    new_level_nr = leveldir_current->handicap_level;
+
+  if (new_level_nr != old_level_nr)
   {
-    level_nr = mx;
+    PlaySound(SND_MENU_ITEM_SELECTING);
 
-    if (server_scores.updated)
+    scores.last_level_nr = level_nr = new_level_nr;
+    scores.last_entry_nr = 0;
+
+    LoadLevel(level_nr);
+    LoadLocalAndServerScore(level_nr, TRUE);
+
+    DrawHallOfFame_setScoreEntries();
+
+    if (game_status == GAME_MODE_SCORES)
     {
-      // reload scores, using updated server score cache file
-      LoadLocalAndServerScore(level_nr, FALSE);
+      // force remapping optional gadgets (especially scroll bar)
+      UnmapScreenTreeGadgets();
 
-      server_scores.updated = FALSE;
+      // redraw complete high score screen, as sub-title has changed
+      ClearField();
+
+      // redraw level selection buttons (which have just been erased)
+      RedrawScreenMenuGadgets(SCREEN_MASK_SCORES);
+
+      HandleChooseTree(0, 0, 0, 0, MB_MENU_INITIALIZE, &score_entry_current);
+    }
+    else
+    {
+      DrawScoreInfo_Content(scores.last_entry_nr);
     }
 
-    first_entry = getHallOfFameFirstEntry(0, 0);
+    SaveLevelSetup_SeriesInfo();
+  }
+}
+
+void HandleHallOfFame(int mx, int my, int dx, int dy, int button)
+{
+  HandleChooseTree(mx, my, dx, dy, button, &score_entry_current);
+}
+
+static void DrawScoreInfo_Content(int entry_nr)
+{
+  struct ScoreEntry *entry = &scores.entry[entry_nr];
+  char *pos_text = getHallOfFameRankText(entry_nr, 0);
+  char *tape_date = getHallOfFameTapeDateText(entry);
+  int font_title = MENU_INFO_FONT_TITLE;
+  int font_head  = MENU_INFO_FONT_HEAD;
+  int font_text  = MENU_INFO_FONT_TEXT;
+  int font_foot  = MENU_INFO_FONT_FOOT;
+  int spacing_title = menu.headline1_spacing[GAME_MODE_SCOREINFO];
+  int spacing_para  = menu.paragraph_spacing[GAME_MODE_SCOREINFO];
+  int spacing_line  = menu.line_spacing[GAME_MODE_SCOREINFO];
+  int xstep = getFontWidth(font_text);
+  int ystep_title = getMenuTextStep(spacing_title, font_title);
+  int ystep_para  = getMenuTextStep(spacing_para,  font_text);
+  int ystep_line  = getMenuTextStep(spacing_line,  font_text);
+  int xstart  = mSX - SX + menu.left_spacing[GAME_MODE_SCOREINFO];
+  int ystart  = mSY - SY + menu.top_spacing[GAME_MODE_SCOREINFO];
+  int ybottom = mSY - SY + SYSIZE - menu.bottom_spacing[GAME_MODE_SCOREINFO];
+  int xstart1 = xstart + xstep;
+  int xstart2 = xstart + xstep * 12;
+  int select_x = SX + xstart1;
+  int select_y1, select_y2;
+  int play_x, play_y;
+  int play_height = screen_gadget[SCREEN_CTRL_ID_PLAY_TAPE]->height;
+  boolean play_visible = !strEqual(tape_date, UNKNOWN_NAME);
+  int font_width = getFontWidth(font_text);
+  int font_height = getFontHeight(font_text);
+  int tape_date_width  = getTextWidth(tape_date, font_text);
+  int pad_left = xstart2;
+  int pad_right = menu.right_spacing[GAME_MODE_SCOREINFO];
+  int max_chars_per_line = (SXSIZE - pad_left - pad_right) / font_width;
+  int max_lines_per_text = 5;
+  int lines;
+
+  ClearField();
+
+  // redraw level selection buttons (which have just been erased)
+  RedrawScreenMenuGadgets(SCREEN_MASK_SCORES);
 
-    drawHallOfFameList(level_nr, first_entry);
+  if (score_entries == NULL)
+  {
+    drawChooseTreeScreen_Scores_NotAvailable();
 
     return;
   }
 
-  if (ABS(dy) == SCROLL_PAGE)          // handle scrolling one page
-    step = NUM_MENU_ENTRIES_ON_SCREEN - 1;
+  drawChooseTreeHead(score_entries);
+  drawChooseTreeInfo(score_entries);
 
-  if (dy < 0)
-  {
-    first_entry = getHallOfFameFirstEntry(first_entry, -step);
+  DrawTextSCentered(ystart, font_title, "Score Information:");
+  ystart += ystep_title;
+
+  DrawTextF(xstart1, ystart, font_head, "Level Set");
+  lines = DrawTextBufferS(xstart2, ystart, leveldir_current->name, font_text,
+                         max_chars_per_line, -1, max_lines_per_text, 0, -1,
+                         TRUE, FALSE, FALSE);
+  ystart += ystep_line + (lines > 0 ? lines - 1 : 0) * font_height;
+
+  DrawTextF(xstart1, ystart, font_head, "Level");
+  lines = DrawTextBufferS(xstart2, ystart, level.name, font_text,
+                         max_chars_per_line, -1, max_lines_per_text, 0, -1,
+                         TRUE, FALSE, FALSE);
+  ystart += ystep_para + (lines > 0 ? lines - 1 : 0) * font_height;
+
+  select_y1 = SY + ystart;
+  ystart += graphic_info[IMG_MENU_BUTTON_PREV_SCORE].height;
+
+  DrawTextF(xstart1, ystart, font_head, "Rank");
+  DrawTextF(xstart2, ystart, font_text, pos_text);
+  ystart += ystep_line;
+
+  DrawTextF(xstart1, ystart, font_head, "Player");
+  DrawTextF(xstart2, ystart, font_text, entry->name);
+  ystart += ystep_line;
 
-    drawHallOfFameList(level_nr, first_entry);
+  if (level.use_step_counter)
+  {
+    DrawTextF(xstart1, ystart, font_head, "Steps");
+    DrawTextF(xstart2, ystart, font_text, int2str(entry->time, 5));
+    ystart += ystep_line;
   }
-  else if (dy > 0)
+  else
   {
-    first_entry = getHallOfFameFirstEntry(first_entry, step);
+    DrawTextF(xstart1, ystart, font_head, "Time");
+    DrawTextF(xstart2, ystart, font_text, getHallOfFameTimeText(entry_nr));
+    ystart += ystep_line;
+  }
 
-    drawHallOfFameList(level_nr, first_entry);
+  if (!level.rate_time_over_score || entry->score > 0)
+  {
+    DrawTextF(xstart1, ystart, font_head, "Score");
+    DrawTextF(xstart2, ystart, font_text, int2str(entry->score, 5));
+    ystart += ystep_line;
   }
-  else if (button == MB_MENU_LEAVE || button == MB_MENU_CHOICE)
+
+  ystart += ystep_line;
+
+  play_x = SX + xstart2 + tape_date_width + font_width;
+  play_y = SY + ystart + (font_height - play_height) / 2;
+
+  DrawTextF(xstart1, ystart, font_head, "Tape Date");
+  DrawTextF(xstart2, ystart, font_text, tape_date);
+  ystart += ystep_line;
+
+  DrawTextF(xstart1, ystart, font_head, "Platform");
+  DrawTextF(xstart2, ystart, font_text, entry->platform);
+  ystart += ystep_line;
+
+  DrawTextF(xstart1, ystart, font_head, "Version");
+  DrawTextF(xstart2, ystart, font_text, entry->version);
+  ystart += ystep_line;
+
+  DrawTextF(xstart1, ystart, font_head, "Country");
+  lines = DrawTextBufferS(xstart2, ystart, entry->country_name, font_text,
+                         max_chars_per_line, -1, max_lines_per_text, 0, -1,
+                         TRUE, FALSE, FALSE);
+  ystart += ystep_line;
+
+  select_y2 = SY + ystart;
+
+  DrawTextSCentered(ybottom, font_foot, "Press any key or button to go back");
+
+  AdjustScoreInfoButtons_SelectScore(select_x, select_y1, select_y2);
+  AdjustScoreInfoButtons_PlayTape(play_x, play_y, play_visible);
+}
+
+static void DrawScoreInfo(int entry_nr)
+{
+  scores.last_entry_nr = entry_nr;
+  score_info_tape_play = FALSE;
+
+  UnmapAllGadgets();
+
+  FreeScreenGadgets();
+  CreateScreenGadgets();
+
+  FadeOut(REDRAW_FIELD);
+
+  // needed if different viewport properties defined after playing score tape
+  ChangeViewportPropertiesIfNeeded();
+
+  // set this after "ChangeViewportPropertiesIfNeeded()" (which may reset it)
+  SetDrawDeactivationMask(REDRAW_NONE);
+  SetDrawBackgroundMask(REDRAW_FIELD);
+
+  // needed if different background image defined after playing score tape
+  SetMainBackgroundImage(IMG_BACKGROUND_SCORES);
+  SetMainBackgroundImageIfDefined(IMG_BACKGROUND_SCOREINFO);
+
+  // special compatibility handling for "Snake Bite" graphics set
+  if (strPrefix(leveldir_current->identifier, "snake_bite"))
+    ClearRectangle(gfx.background_bitmap, gfx.real_sx, gfx.real_sy + 64,
+                  gfx.full_sxsize, gfx.full_sysize - 64);
+
+  DrawScoreInfo_Content(entry_nr);
+
+  // map gadgets for score info screen
+  MapScreenMenuGadgets(SCREEN_MASK_SCORES_INFO);
+
+  FadeIn(REDRAW_FIELD);
+}
+
+static void HandleScoreInfo_SelectScore(int step, int direction)
+{
+  int old_entry_nr = scores.last_entry_nr;
+  int new_entry_nr = old_entry_nr + step * direction;
+  int num_nodes = numTreeInfoInGroup(score_entry_current);
+  int num_entries = num_nodes - 1;     // score nodes only, without back link
+
+  if (new_entry_nr < 0)
+    new_entry_nr = 0;
+  if (new_entry_nr > num_entries - 1)
+    new_entry_nr = num_entries - 1;
+
+  if (new_entry_nr != old_entry_nr)
   {
-    PlaySound(SND_MENU_ITEM_SELECTING);
+    scores.last_entry_nr = new_entry_nr;
 
-    FadeSound(SND_BACKGROUND_SCORES);
+    DrawScoreInfo_Content(new_entry_nr);
+  }
+}
 
-    if (button == MB_MENU_CHOICE &&
-       game_status_last_screen == GAME_MODE_PLAYING &&
-       setup.auto_play_next_level && setup.increment_levels &&
-       level_nr < leveldir_current->last_level &&
-       !network_playing)
-    {
-      StartGameActions(network.enabled, setup.autorecord, level.random_seed);
-    }
-    else
-    {
-      SetGameStatus(GAME_MODE_MAIN);
+static void HandleScoreInfo_PlayTape(void)
+{
+  if (!PlayScoreTape(scores.last_entry_nr))
+  {
+    DrawScoreInfo_Content(scores.last_entry_nr);
 
-      DrawMainMenu();
-    }
+    FadeIn(REDRAW_FIELD);
   }
-  else if (server_scores.updated)
+}
+
+void HandleScoreInfo(int mx, int my, int dx, int dy, int button)
+{
+  boolean button_action = (button == MB_MENU_LEAVE || button == MB_MENU_CHOICE);
+  boolean button_is_valid = (mx >= 0 && my >= 0);
+  boolean button_screen_clicked = (button_action && button_is_valid);
+
+  if (server_scores.updated)
   {
     // reload scores, using updated server score cache file
-    LoadLocalAndServerScore(level_nr, FALSE);
+    LoadLocalAndServerScore(scores.last_level_nr, FALSE);
 
     server_scores.updated = FALSE;
 
-    first_entry = getHallOfFameFirstEntry(0, 0);
+    DrawHallOfFame_setScoreEntries();
 
-    drawHallOfFameList(level_nr, first_entry);
+    DrawScoreInfo_Content(scores.last_entry_nr);
   }
 
-  if (game_status == GAME_MODE_SCORES)
-    PlayMenuSoundIfLoop();
+  if (button_screen_clicked)
+  {
+    PlaySound(SND_MENU_ITEM_SELECTING);
+
+    SetGameStatus(GAME_MODE_SCORES);
+
+    DrawHallOfFame(scores.last_level_nr);
+  }
+  else if (dx)
+  {
+    HandleHallOfFame_SelectLevel(1, SIGN(dx) * (ABS(dx) > 1 ? 10 : 1));
+  }
+  else if (dy)
+  {
+    HandleScoreInfo_SelectScore(1, SIGN(dy) * (ABS(dy) > 1 ? 10 : 1));
+  }
 }
 
 
@@ -7258,6 +7259,15 @@ static struct
   { &setup.internal.menu_exit,         execExitSetup                   },
   { &setup.internal.menu_save_and_exit,        execSaveAndExitSetup            },
 
+  { &setup.internal.info_title,                execInfoTitleScreen             },
+  { &setup.internal.info_elements,     execInfoElements                },
+  { &setup.internal.info_music,                execInfoMusic                   },
+  { &setup.internal.info_credits,      execInfoCredits                 },
+  { &setup.internal.info_program,      execInfoProgram                 },
+  { &setup.internal.info_version,      execInfoVersion                 },
+  { &setup.internal.info_levelset,     execInfoLevelSet                },
+  { &setup.internal.info_exit,         execExitInfo                    },
+
   { NULL,                              NULL                            }
 };
 
@@ -7302,8 +7312,8 @@ static struct TokenInfo setup_info_game[] =
   { TYPE_SWITCH,       &setup.multiple_users,  "Multiple Users/Teams:" },
   { TYPE_YES_NO,       &setup.input_on_focus,  "Only Move Focussed Player:" },
   { TYPE_SWITCH,       &setup.time_limit,      "Time Limit:"           },
-  { TYPE_SWITCH,       &setup.handicap,        "Handicap:"             },
-  { TYPE_SWITCH,       &setup.skip_levels,     "Skip Unsolved Levels:" },
+  { TYPE_SWITCH,       &setup.handicap,        "Force Solving Levels:" },
+  { TYPE_SWITCH,       &setup.skip_levels,     "Allow Skipping Levels:" },
   { TYPE_SWITCH,       &setup.increment_levels,"Increment Solved Levels:" },
   { TYPE_SWITCH,       &setup.auto_play_next_level,"Auto-play Next Level:" },
   { TYPE_SWITCH,       &setup.count_score_after_game,"Count Score After Game:" },
@@ -7384,7 +7394,7 @@ static struct TokenInfo setup_info_editor[] =
 
 static struct TokenInfo setup_info_graphics[] =
 {
-#if !defined(PLATFORM_ANDROID)
+#if !defined(PLATFORM_ANDROID) && !defined(PLATFORM_EMSCRIPTEN)
   { TYPE_SWITCH,       &setup.fullscreen,      "Fullscreen:"           },
   { TYPE_ENTER_LIST,   execSetupChooseWindowSize, "Window Scaling:"    },
   { TYPE_STRING,       &window_size_text,      ""                      },
@@ -7397,8 +7407,10 @@ static struct TokenInfo setup_info_graphics[] =
   { TYPE_ENTER_LIST,   execSetupChooseScrollDelay, "Scroll Delay:"     },
   { TYPE_STRING,       &scroll_delay_text,     ""                      },
 #endif
+#if !defined(PLATFORM_EMSCRIPTEN)
   { TYPE_ENTER_LIST,   execSetupChooseVsyncMode, "Vertical Sync (VSync):" },
   { TYPE_STRING,       &vsync_mode_text,       ""                      },
+#endif
   { TYPE_SWITCH,       &setup.fade_screens,    "Fade Screens:"         },
   { TYPE_SWITCH,       &setup.quick_switch,    "Quick Player Focus Switch:" },
   { TYPE_SWITCH,       &setup.quick_doors,     "Quick Menu Doors:"     },
@@ -7772,10 +7784,10 @@ static void drawSetupValue(int screen_pos, int setup_info_pos_raw)
   if (scrollbar_needed && xpos > MENU_SCREEN_START_XPOS)
   {
     int max_menu_text_length = 26;     // maximum text length for classic menu
-    int font_xoffset = getFontBitmapInfo(font_nr)->draw_xoffset;
+    int font_xoffset = getFontDrawOffsetX(font_nr);
     int text_startx = mSX + MENU_SCREEN_START_XPOS * 32;
     int text_font_nr = getMenuTextFont(FONT_MENU_2);
-    int text_font_xoffset = getFontBitmapInfo(text_font_nr)->draw_xoffset;
+    int text_font_xoffset = getFontDrawOffsetX(text_font_nr);
     int text_width = max_menu_text_length * getFontWidth(text_font_nr);
 
     if (startx + font_xoffset < text_startx + text_width + text_font_xoffset)
@@ -7798,11 +7810,11 @@ static void drawSetupValue(int screen_pos, int setup_info_pos_raw)
                                    MENU_SCREEN_START_XPOS);
     int max_menu_text_length_medium = max_menu_text_length_big * 2;
     int check_font_nr = FONT_OPTION_ON; // known font that needs correction
-    int font1_xoffset = getFontBitmapInfo(font_nr)->draw_xoffset;
-    int font2_xoffset = getFontBitmapInfo(check_font_nr)->draw_xoffset;
+    int font1_xoffset = getFontDrawOffsetX(font_nr);
+    int font2_xoffset = getFontDrawOffsetX(check_font_nr);
     int text_startx = mSX + MENU_SCREEN_START_XPOS * 32;
     int text_font_nr = getMenuTextFont(FONT_MENU_2);
-    int text_font_xoffset = getFontBitmapInfo(text_font_nr)->draw_xoffset;
+    int text_font_xoffset = getFontDrawOffsetX(text_font_nr);
     int text_width = max_menu_text_length_medium * getFontWidth(text_font_nr);
     boolean correct_font_draw_xoffset = FALSE;
 
@@ -7818,7 +7830,7 @@ static void drawSetupValue(int screen_pos, int setup_info_pos_raw)
     // (this can happen for extreme/wrong values for font draw offset)
     if (correct_font_draw_xoffset)
     {
-      font_draw_xoffset_old = getFontBitmapInfo(font_nr)->draw_xoffset;
+      font_draw_xoffset_old = getFontDrawOffsetX(font_nr);
       font_draw_xoffset_modified = TRUE;
 
       if (type & TYPE_KEY)
@@ -9509,6 +9521,46 @@ static struct
     GD_EVENT_PRESSED | GD_EVENT_REPEATED,
     FALSE, "next level"
   },
+  {
+    IMG_MENU_BUTTON_PREV_LEVEL2, IMG_MENU_BUTTON_PREV_LEVEL2_ACTIVE,
+    &menu.scores.button.prev_level, NULL,
+    SCREEN_CTRL_ID_PREV_LEVEL2,
+    SCREEN_MASK_SCORES | SCREEN_MASK_SCORES_INFO,
+    GD_EVENT_PRESSED | GD_EVENT_REPEATED,
+    FALSE, "previous level"
+  },
+  {
+    IMG_MENU_BUTTON_NEXT_LEVEL2, IMG_MENU_BUTTON_NEXT_LEVEL2_ACTIVE,
+    &menu.scores.button.next_level, NULL,
+    SCREEN_CTRL_ID_NEXT_LEVEL2,
+    SCREEN_MASK_SCORES | SCREEN_MASK_SCORES_INFO,
+    GD_EVENT_PRESSED | GD_EVENT_REPEATED,
+    FALSE, "next level"
+  },
+  {
+    IMG_MENU_BUTTON_PREV_SCORE, IMG_MENU_BUTTON_PREV_SCORE_ACTIVE,
+    &menu.scores.button.prev_score, NULL,
+    SCREEN_CTRL_ID_PREV_SCORE,
+    SCREEN_MASK_SCORES_INFO,
+    GD_EVENT_PRESSED | GD_EVENT_REPEATED,
+    FALSE, "previous score"
+  },
+  {
+    IMG_MENU_BUTTON_NEXT_SCORE, IMG_MENU_BUTTON_NEXT_SCORE_ACTIVE,
+    &menu.scores.button.next_score, NULL,
+    SCREEN_CTRL_ID_NEXT_SCORE,
+    SCREEN_MASK_SCORES_INFO,
+    GD_EVENT_PRESSED | GD_EVENT_REPEATED,
+    FALSE, "next score"
+  },
+  {
+    IMG_MENU_BUTTON_PLAY_TAPE, IMG_MENU_BUTTON_PLAY_TAPE,
+    &menu.scores.button.play_tape, NULL,
+    SCREEN_CTRL_ID_PLAY_TAPE,
+    SCREEN_MASK_SCORES_INFO,
+    GD_EVENT_RELEASED,
+    FALSE, "play tape"
+  },
   {
     IMG_MENU_BUTTON_FIRST_LEVEL, IMG_MENU_BUTTON_FIRST_LEVEL_ACTIVE,
     &menu.main.button.first_level, NULL,
@@ -9678,8 +9730,12 @@ static void CreateScreenMenubuttons(void)
   for (i = 0; i < NUM_SCREEN_MENUBUTTONS; i++)
   {
     struct MenuPosInfo *pos = menubutton_info[i].pos;
+    int screen_mask = menubutton_info[i].screen_mask;
     boolean is_touch_button = menubutton_info[i].is_touch_button;
     boolean is_check_button = menubutton_info[i].check_value != NULL;
+    boolean is_score_button = (screen_mask & SCREEN_MASK_SCORES_INFO);
+    boolean has_gfx_pressed = (menubutton_info[i].gfx_pressed ==
+                               menubutton_info[i].gfx_unpressed);
     Bitmap *gd_bitmap_unpressed, *gd_bitmap_pressed;
     int gfx_unpressed, gfx_pressed;
     int x, y, width, height;
@@ -9689,6 +9745,10 @@ static void CreateScreenMenubuttons(void)
     int type = GD_TYPE_NORMAL_BUTTON;
     boolean checked = FALSE;
 
+    // do not use touch buttons if overlay touch buttons are disabled
+    if (is_touch_button && !setup.touch.overlay_buttons)
+      continue;
+
     event_mask = menubutton_info[i].event_mask;
 
     x = (is_touch_button ? pos->x : mSX + GDI_ACTIVE_POS(pos->x));
@@ -9710,7 +9770,7 @@ static void CreateScreenMenubuttons(void)
     gd_x2a = gd_x2;
     gd_y2a = gd_y2;
 
-    if (is_touch_button)
+    if (has_gfx_pressed)
     {
       gd_x2 += graphic_info[gfx_pressed].pressed_xoffset;
       gd_y2 += graphic_info[gfx_pressed].pressed_yoffset;
@@ -9727,6 +9787,28 @@ static void CreateScreenMenubuttons(void)
       checked = *menubutton_info[i].check_value;
     }
 
+    if (is_score_button)
+    {
+      // if x/y set to -1, dynamically place buttons next to title text
+      int title_width = getTextWidth(INFOTEXT_SCORE_ENTRY, FONT_TITLE_1);
+
+      // special compatibility handling for "Snake Bite" graphics set
+      if (strPrefix(leveldir_current->identifier, "snake_bite"))
+       title_width = strlen(INFOTEXT_SCORE_ENTRY) * 32;
+
+      // use "SX" here to center buttons (ignore horizontal draw offset)
+      if (pos->x == -1)
+       x = (id == SCREEN_CTRL_ID_PREV_LEVEL2 ?
+            SX + (SXSIZE - title_width) / 2 - width * 3 / 2 :
+            id == SCREEN_CTRL_ID_NEXT_LEVEL2 ?
+            SX + (SXSIZE + title_width) / 2 + width / 2 : 0);
+
+      // use "mSY" here to place buttons (respect vertical draw offset)
+      if (pos->y == -1)
+       y = (id == SCREEN_CTRL_ID_PREV_LEVEL2 ||
+            id == SCREEN_CTRL_ID_NEXT_LEVEL2 ? mSY + MENU_TITLE1_YPOS : 0);
+    }
+
     gi = CreateGadget(GDI_CUSTOM_ID, id,
                      GDI_CUSTOM_TYPE_ID, i,
                      GDI_IMAGE_ID, gfx_unpressed,
@@ -9968,6 +10050,15 @@ void FreeScreenGadgets(void)
     FreeGadget(screen_gadget[i]);
 }
 
+static void RedrawScreenMenuGadgets(int screen_mask)
+{
+  int i;
+
+  for (i = 0; i < NUM_SCREEN_MENUBUTTONS; i++)
+    if (screen_mask & menubutton_info[i].screen_mask)
+      RedrawGadget(screen_gadget[menubutton_info[i].gadget_id]);
+}
+
 static void MapScreenMenuGadgets(int screen_mask)
 {
   int i;
@@ -10018,11 +10109,54 @@ static void MapScreenGadgets(int num_entries)
     MapGadget(screen_gadget[scrollbar_info[i].gadget_id]);
 }
 
+static void UnmapScreenGadgets()
+{
+  int i;
+
+  for (i = 0; i < NUM_SCREEN_SCROLLBUTTONS; i++)
+    UnmapGadget(screen_gadget[scrollbutton_info[i].gadget_id]);
+
+  for (i = 0; i < NUM_SCREEN_SCROLLBARS; i++)
+    UnmapGadget(screen_gadget[scrollbar_info[i].gadget_id]);
+}
+
 static void MapScreenTreeGadgets(TreeInfo *ti)
 {
   MapScreenGadgets(numTreeInfoInGroup(ti));
 }
 
+static void UnmapScreenTreeGadgets(void)
+{
+  UnmapScreenGadgets();
+}
+
+static void AdjustScoreInfoButtons_SelectScore(int x, int y1, int y2)
+{
+  struct GadgetInfo *gi_1 = screen_gadget[SCREEN_CTRL_ID_PREV_SCORE];
+  struct GadgetInfo *gi_2 = screen_gadget[SCREEN_CTRL_ID_NEXT_SCORE];
+  struct MenuPosInfo *pos_1 = menubutton_info[SCREEN_CTRL_ID_PREV_SCORE].pos;
+  struct MenuPosInfo *pos_2 = menubutton_info[SCREEN_CTRL_ID_NEXT_SCORE].pos;
+
+  if (pos_1->x == -1 && pos_1->y == -1)
+    ModifyGadget(gi_1, GDI_X, x, GDI_Y, y1, GDI_END);
+
+  if (pos_2->x == -1 && pos_2->y == -1)
+    ModifyGadget(gi_2, GDI_X, x, GDI_Y, y2, GDI_END);
+}
+
+static void AdjustScoreInfoButtons_PlayTape(int x, int y, boolean visible)
+{
+  struct GadgetInfo *gi = screen_gadget[SCREEN_CTRL_ID_PLAY_TAPE];
+  struct MenuPosInfo *pos = menubutton_info[SCREEN_CTRL_ID_PLAY_TAPE].pos;
+
+  // set gadget position dynamically, pre-defined or off-screen
+  int xx = (visible ? (pos->x == -1 ? x : pos->x) : POS_OFFSCREEN);
+  int yy = (visible ? (pos->y == -1 ? y : pos->y) : POS_OFFSCREEN);
+
+  ModifyGadget(gi, GDI_X, xx, GDI_Y, yy, GDI_END);
+  MapGadget(gi);       // (needed if deactivated on last score page)
+}
+
 static void HandleScreenGadgets(struct GadgetInfo *gi)
 {
   int id = gi->custom_id;
@@ -10041,6 +10175,26 @@ static void HandleScreenGadgets(struct GadgetInfo *gi)
       HandleMainMenu_SelectLevel(step, +1, NO_DIRECT_LEVEL_SELECT);
       break;
 
+    case SCREEN_CTRL_ID_PREV_LEVEL2:
+      HandleHallOfFame_SelectLevel(step, -1);
+      break;
+
+    case SCREEN_CTRL_ID_NEXT_LEVEL2:
+      HandleHallOfFame_SelectLevel(step, +1);
+      break;
+
+    case SCREEN_CTRL_ID_PREV_SCORE:
+      HandleScoreInfo_SelectScore(step, -1);
+      break;
+
+    case SCREEN_CTRL_ID_NEXT_SCORE:
+      HandleScoreInfo_SelectScore(step, +1);
+      break;
+
+    case SCREEN_CTRL_ID_PLAY_TAPE:
+      HandleScoreInfo_PlayTape();
+      break;
+
     case SCREEN_CTRL_ID_FIRST_LEVEL:
       HandleMainMenu_SelectLevel(MAX_LEVELS, -1, NO_DIRECT_LEVEL_SELECT);
       break;
@@ -10094,6 +10248,8 @@ static void HandleScreenGadgets(struct GadgetInfo *gi)
        HandleSetupScreen(0,0, 0, -1 * SCROLL_LINE, MB_MENU_MARK);
       else if (game_status == GAME_MODE_INFO)
        HandleInfoScreen(0,0, 0, -1 * SCROLL_LINE, MB_MENU_MARK);
+      else if (game_status == GAME_MODE_SCORES)
+       HandleHallOfFame(0,0, 0, -1 * SCROLL_LINE, MB_MENU_MARK);
       break;
 
     case SCREEN_CTRL_ID_SCROLL_DOWN:
@@ -10107,6 +10263,8 @@ static void HandleScreenGadgets(struct GadgetInfo *gi)
        HandleSetupScreen(0,0, 0, +1 * SCROLL_LINE, MB_MENU_MARK);
       else if (game_status == GAME_MODE_INFO)
        HandleInfoScreen(0,0, 0, +1 * SCROLL_LINE, MB_MENU_MARK);
+      else if (game_status == GAME_MODE_SCORES)
+       HandleHallOfFame(0,0, 0, +1 * SCROLL_LINE, MB_MENU_MARK);
       break;
 
     case SCREEN_CTRL_ID_SCROLL_VERTICAL:
@@ -10120,6 +10278,8 @@ static void HandleScreenGadgets(struct GadgetInfo *gi)
        HandleSetupScreen(0,0, 999,gi->event.item_position,MB_MENU_INITIALIZE);
       else if (game_status == GAME_MODE_INFO)
        HandleInfoScreen(0,0, 999,gi->event.item_position,MB_MENU_INITIALIZE);
+      else if (game_status == GAME_MODE_SCORES)
+       HandleHallOfFame(0,0, 999,gi->event.item_position,MB_MENU_INITIALIZE);
       break;
 
     case SCREEN_CTRL_ID_NETWORK_SERVER:
@@ -10152,6 +10312,12 @@ static void HandleScreenGadgets(struct GadgetInfo *gi)
   }
 }
 
+void HandleScreenGadgetKeys(Key key)
+{
+  if (key == setup.shortcut.tape_play)
+    HandleScreenGadgets(screen_gadget[SCREEN_CTRL_ID_PLAY_TAPE]);
+}
+
 void DumpScreenIdentifiers(void)
 {
   int i;