fixed background image on score info page after playing score tape
[rocksndiamonds.git] / src / screens.c
index 7a6bfe41b9fff219d2430bec383145f9f6d53a38..5ffd3409e36d5c2251ae130c99cbc5c7c7042aea 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
 #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_FIRST_LEVEL     6
-#define SCREEN_CTRL_ID_LAST_LEVEL      7
-#define SCREEN_CTRL_ID_LEVEL_NUMBER    8
-#define SCREEN_CTRL_ID_PREV_PLAYER     9
-#define SCREEN_CTRL_ID_NEXT_PLAYER     10
-#define SCREEN_CTRL_ID_INSERT_SOLUTION 11
-#define SCREEN_CTRL_ID_PLAY_SOLUTION   12
-#define SCREEN_CTRL_ID_SWITCH_ECS_AGA  13
-#define SCREEN_CTRL_ID_TOUCH_PREV_PAGE 14
-#define SCREEN_CTRL_ID_TOUCH_NEXT_PAGE 15
-#define SCREEN_CTRL_ID_TOUCH_PREV_PAGE2        16
-#define SCREEN_CTRL_ID_TOUCH_NEXT_PAGE2        17
-
-#define NUM_SCREEN_MENUBUTTONS         18
-
-#define SCREEN_CTRL_ID_SCROLL_UP       18
-#define SCREEN_CTRL_ID_SCROLL_DOWN     19
-#define SCREEN_CTRL_ID_SCROLL_VERTICAL 20
-#define SCREEN_CTRL_ID_NETWORK_SERVER  21
-
-#define NUM_SCREEN_GADGETS             22
+#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
@@ -281,7 +286,7 @@ 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);
+static void HandleInfoScreen_Program(int, int, int);
 static void HandleInfoScreen_Version(int);
 
 static void ModifyGameSpeedIfNeeded(void);
@@ -296,7 +301,8 @@ static void MapScreenTreeGadgets(TreeInfo *);
 static void UnmapScreenTreeGadgets(void);
 
 static void UpdateScreenMenuGadgets(int, boolean);
-static void AdjustScoreInfoButtons(int, int, int);
+static void AdjustScoreInfoButtons_SelectScore(int, int, int);
+static void AdjustScoreInfoButtons_PlayTape(int, int, boolean);
 
 static boolean OfferUploadTapes(void);
 static void execOfferUploadTapes(void);
@@ -306,6 +312,8 @@ 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;
@@ -690,6 +698,11 @@ 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
@@ -1682,6 +1695,18 @@ 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;
+  }
+
   // leveldir_current may be invalid (level group, parent link, node copy)
   leveldir_current = getValidLevelSeries(leveldir_current, leveldir_last_valid);
 
@@ -2534,6 +2559,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++)
@@ -2917,8 +2945,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);
 }
@@ -2949,9 +2976,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;
   }
@@ -3237,11 +3262,8 @@ void HandleInfoScreen_Music(int dx, int dy, 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;
     }
@@ -3365,8 +3387,7 @@ void HandleInfoScreen_Music(int dx, int dy, 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);
@@ -3407,8 +3428,10 @@ static void DrawInfoScreen_CreditsScreen(int screen_nr)
               filename, font_text, chars, -1, lines, line_spacing, -1,
               autowrap, centered, parse_comments);
 
-  DrawTextSCentered(ybottom, font_foot,
-                   "Press any key or button for next page");
+  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)
@@ -3456,11 +3479,8 @@ void HandleInfoScreen_Credits(int dx, int dy, int button)
       ClearField();
       DrawHeadline();
 
-      DrawTextSCentered(ystart, font_title,
-                       "No credits for this level set.");
-
-      DrawTextSCentered(ybottom, font_foot,
-                       "Press any key or button for info menu");
+      DrawTextSCentered(ystart, font_title, "No credits for this level set.");
+      DrawTextSCentered(ybottom, font_foot, TEXT_INFO_MENU);
 
       return;
     }
@@ -3508,68 +3528,89 @@ void HandleInfoScreen_Credits(int dx, int dy, 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);
 
@@ -3578,14 +3619,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
   {
@@ -3758,8 +3814,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);
 }
@@ -3795,6 +3850,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;
 
@@ -3846,8 +3902,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);
 }
@@ -3914,7 +3969,7 @@ void HandleInfoScreen(int mx, int my, int dx, int dy, int button)
   else if (info_mode == INFO_MODE_CREDITS)
     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)
@@ -3924,412 +3979,6 @@ void HandleInfoScreen(int mx, int my, int dx, int dy, int button)
 }
 
 
-// ============================================================================
-// rename player API 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 void FreeThreadData_ApiRenamePlayer(void *data_raw)
-{
-  struct ApiRenamePlayerThreadData *data = data_raw;
-
-  checked_free(data->player_name);
-  checked_free(data->player_uuid);
-  checked_free(data);
-}
-
-static boolean SetRequest_ApiRenamePlayer(struct HttpRequest *request,
-                                         void *data_raw)
-{
-  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);
-
-  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);
-
-  checked_free(player_name);
-  checked_free(player_uuid);
-
-  ConvertHttpRequestBodyToServerEncoding(request);
-
-  return TRUE;
-}
-
-static void HandleResponse_ApiRenamePlayer(struct HttpResponse *response,
-                                          void *data_raw)
-{
-  // nothing to do here
-}
-
-#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);
-
-  if (response != NULL)
-  {
-    HandleResponse_ApiRenamePlayer(response, data_raw);
-
-    checked_free(response);
-  }
-  else
-  {
-    Error("server response too large to handle (%d bytes)", size);
-  }
-
-  FreeThreadData_ApiRenamePlayer(data_raw);
-}
-
-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);
-
-  FreeThreadData_ApiRenamePlayer(data_raw);
-}
-
-static void Emscripten_ApiRenamePlayer_Progress(unsigned handle, void *data_raw,
-                                               int bytes, int size)
-{
-  // nothing to do here
-}
-
-static void Emscripten_ApiRenamePlayer_HttpRequest(struct HttpRequest *request,
-                                                  void *data_raw)
-{
-  if (!SetRequest_ApiRenamePlayer(request, data_raw))
-  {
-    FreeThreadData_ApiRenamePlayer(data_raw);
-
-    return;
-  }
-
-  emscripten_async_wget2_data(request->uri,
-                             request->method,
-                             request->body,
-                             data_raw,
-                             TRUE,
-                             Emscripten_ApiRenamePlayer_Loaded,
-                             Emscripten_ApiRenamePlayer_Failed,
-                             Emscripten_ApiRenamePlayer_Progress);
-}
-
-#else
-
-static void ApiRenamePlayer_HttpRequestExt(struct HttpRequest *request,
-                                          struct HttpResponse *response,
-                                          void *data_raw)
-{
-  if (!SetRequest_ApiRenamePlayer(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_ApiRenamePlayer(response, data_raw);
-}
-
-static void ApiRenamePlayer_HttpRequest(struct HttpRequest *request,
-                                   struct HttpResponse *response,
-                                   void *data_raw)
-{
-  ApiRenamePlayer_HttpRequestExt(request, response, data_raw);
-
-  FreeThreadData_ApiRenamePlayer(data_raw);
-}
-#endif
-
-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
 // ============================================================================
@@ -5129,6 +4778,8 @@ static void HandleChooseTree(int mx, int my, int dx, int dy, int button,
 
   if (mx || my)                // mouse input
   {
+    scores.was_just_playing = FALSE;
+
     x = (mx - amSX) / 32;
     y = (my - amSY) / 32 - MENU_SCREEN_START_YPOS;
 
@@ -5137,6 +4788,8 @@ static void HandleChooseTree(int mx, int my, int dx, int dy, int button,
   }
   else if (dx || dy)   // keyboard or scrollbar/scrollbutton input
   {
+    scores.was_just_playing = FALSE;
+
     // move cursor instead of scrolling when already at start/end of list
     if (dy == -1 * SCROLL_LINE && ti->cl_first == 0)
       dy = -1;
@@ -5405,16 +5058,16 @@ static void HandleChooseTree(int mx, int my, int dx, int dy, int button,
          }
          else if (game_status == GAME_MODE_SCORES)
          {
-           if (game_status_last_screen == GAME_MODE_PLAYING &&
-               setup.auto_play_next_level && setup.increment_levels &&
+           if (setup.auto_play_next_level && setup.increment_levels &&
                scores.last_level_nr < leveldir_current->last_level &&
+               scores.was_just_playing &&
                !network_playing)
            {
              StartGameActions(network.enabled, setup.autorecord,
                               level.random_seed);
              return;
            }
-           else
+           else if (!scores.was_just_playing)
            {
              SetGameStatus(GAME_MODE_SCOREINFO);
 
@@ -5614,6 +5267,7 @@ static void DrawHallOfFame_setScoreEntries(void)
 void DrawHallOfFame(int level_nr)
 {
   scores.last_level_nr = level_nr;
+  scores.was_just_playing = (game_status_last_screen == GAME_MODE_PLAYING);
 
   // (this is needed when called from GameEnd() after winning a game)
   KeyboardAutoRepeatOn();
@@ -5667,6 +5321,28 @@ static char *getHallOfFameScoreText(int nr, int size)
     return getHallOfFameTimeText(nr);                  // show playing time
 }
 
+static char *getHallOfFameTapeDateText(struct ScoreEntry *entry)
+{
+  static char tape_date[MAX_ISO_DATE_LEN + 1];
+  int i, j;
+
+  if (!strEqual(entry->tape_date, UNKNOWN_NAME) ||
+      strEqual(entry->tape_basename, UNDEFINED_FILENAME))
+    return entry->tape_date;
+
+  for (i = 0, j = 0; i < 8; i++, j++)
+  {
+    tape_date[j] = entry->tape_basename[i];
+
+    if (i == 3 || i == 5)
+      tape_date[++j] = '-';
+  }
+
+  tape_date[MAX_ISO_DATE_LEN] = '\0';
+
+  return tape_date;
+}
+
 static void HandleHallOfFame_SelectLevel(int step, int direction)
 {
   int old_level_nr = scores.last_level_nr;
@@ -5723,6 +5399,7 @@ 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;
@@ -5738,10 +5415,14 @@ static void DrawScoreInfo_Content(int entry_nr)
   int ybottom = mSY - SY + SYSIZE - menu.bottom_spacing[GAME_MODE_SCOREINFO];
   int xstart1 = mSX - SX + 2 * xstep;
   int xstart2 = mSX - SX + 13 * xstep;
-  int button_x = SX + xstart1;
-  int button_y1, button_y2;
+  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_SCREEN_INFO_SPACE_RIGHT;
   int max_chars_per_line = (SXSIZE - pad_left - pad_right) / font_width;
@@ -5778,7 +5459,7 @@ static void DrawScoreInfo_Content(int entry_nr)
                          TRUE, FALSE, FALSE);
   ystart += ystep_para + (lines > 0 ? lines - 1 : 0) * font_height;
 
-  button_y1 = SY + ystart;
+  select_y1 = SY + ystart;
   ystart += graphic_info[IMG_MENU_BUTTON_PREV_SCORE].height;
 
   DrawTextF(xstart1, ystart, font_head, "Rank");
@@ -5811,8 +5492,11 @@ static void DrawScoreInfo_Content(int entry_nr)
 
   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, entry->tape_date);
+  DrawTextF(xstart2, ystart, font_text, tape_date);
   ystart += ystep_line;
 
   DrawTextF(xstart1, ystart, font_head, "Platform");
@@ -5829,23 +5513,34 @@ static void DrawScoreInfo_Content(int entry_nr)
                          TRUE, FALSE, FALSE);
   ystart += ystep_line;
 
-  button_y2 = SY + ystart;
+  select_y2 = SY + ystart;
 
   DrawTextSCentered(ybottom, font_foot, "Press any key or button to go back");
 
-  AdjustScoreInfoButtons(button_x, button_y1, button_y2);
+  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;
-
-  SetMainBackgroundImageIfDefined(IMG_BACKGROUND_SCOREINFO);
+  score_info_tape_play = FALSE;
 
   UnmapAllGadgets();
 
   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);
+
   DrawScoreInfo_Content(entry_nr);
 
   // map gadgets for score info screen
@@ -5874,6 +5569,16 @@ static void HandleScoreInfo_SelectScore(int step, int direction)
   }
 }
 
+static void HandleScoreInfo_PlayTape(void)
+{
+  if (!PlayScoreTape(scores.last_entry_nr))
+  {
+    DrawScoreInfo_Content(scores.last_entry_nr);
+
+    FadeIn(REDRAW_FIELD);
+  }
+}
+
 void HandleScoreInfo(int mx, int my, int dx, int dy, int button)
 {
   boolean button_action = (button == MB_MENU_LEAVE || button == MB_MENU_CHOICE);
@@ -5898,7 +5603,7 @@ void HandleScoreInfo(int mx, int my, int dx, int dy, int button)
 
     SetGameStatus(GAME_MODE_SCORES);
 
-    DrawHallOfFame(level_nr);
+    DrawHallOfFame(scores.last_level_nr);
   }
   else if (dx)
   {
@@ -7490,6 +7195,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                            }
 };
 
@@ -9773,6 +9487,14 @@ static struct
     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,
@@ -9946,6 +9668,8 @@ static void CreateScreenMenubuttons(void)
     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;
@@ -9976,7 +9700,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;
@@ -10330,7 +10054,7 @@ static void UnmapScreenTreeGadgets(void)
   UnmapScreenGadgets();
 }
 
-static void AdjustScoreInfoButtons(int x, int y1, int y2)
+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];
@@ -10344,6 +10068,19 @@ static void AdjustScoreInfoButtons(int x, int y1, int y2)
     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;
@@ -10378,6 +10115,10 @@ static void HandleScreenGadgets(struct GadgetInfo *gi)
       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;