moved code for HTTP request handling to separate functions
[rocksndiamonds.git] / src / screens.c
index 84b890f6e9bb6c0d05e2d519a36ba4ed89e91f15..55aa0c22f855bd6abad91e51fcfd88a8353108d5 100644 (file)
 // other screen text constants
 #define STR_CHOOSE_TREE_EDIT           "Edit"
 #define MENU_CHOOSE_TREE_FONT(x)       (FONT_TEXT_1 + (x))
+#define MENU_CHOOSE_TREE_COLOR(ti, a)  TREE_COLOR(ti, a)
 
 // for input setup functions
 #define SETUPINPUT_SCREEN_POS_START    0
@@ -281,6 +282,9 @@ static void MapScreenTreeGadgets(TreeInfo *);
 
 static void UpdateScreenMenuGadgets(int, boolean);
 
+static boolean OfferUploadTapes(void);
+static void execOfferUploadTapes(void);
+
 static struct GadgetInfo *screen_gadget[NUM_SCREEN_GADGETS];
 
 static int info_mode = INFO_MODE_MAIN;
@@ -1629,12 +1633,15 @@ void DrawMainMenu(void)
     return;
   }
 
-  // leveldir_current may be invalid (level group, parent link)
-  if (!validLevelSeries(leveldir_current))
-    leveldir_current = getFirstValidTreeInfoEntry(leveldir_last_valid);
+  // leveldir_current may be invalid (level group, parent link, node copy)
+  leveldir_current = getValidLevelSeries(leveldir_current, leveldir_last_valid);
 
   if (leveldir_current != leveldir_last_valid)
+  {
+    UpdateLastPlayedLevels_TreeInfo();
+
     levelset_has_changed = TRUE;
+  }
 
   // store valid level series information
   leveldir_last_valid = leveldir_current;
@@ -1734,6 +1741,8 @@ void DrawMainMenu(void)
   SetMouseCursor(CURSOR_DEFAULT);
 
   OpenDoor(DOOR_CLOSE_1 | DOOR_OPEN_2);
+
+  SyncEmscriptenFilesystem();
 }
 
 static void gotoTopLevelDir(void)
@@ -1951,6 +1960,16 @@ void HandleTitleScreen(int mx, int my, int dx, int dy, int button)
   }
 }
 
+static void HandleMainMenu_ToggleTeamMode(void)
+{
+  setup.team_mode = !setup.team_mode;
+
+  InitializeMainControls();
+  DrawCursorAndText_Main(MAIN_CONTROL_NAME, TRUE, FALSE);
+
+  DrawPreviewPlayers();
+}
+
 static void HandleMainMenu_SelectLevel(int step, int direction,
                                       int selected_level_nr)
 {
@@ -2100,9 +2119,16 @@ void HandleMainMenu(int mx, int my, int dx, int dy, int button)
       }
       else if (dx != 0)
       {
-       if (choice != MAIN_CONTROL_INFO &&
-           choice != MAIN_CONTROL_SETUP)
+       if (choice == MAIN_CONTROL_NAME)
+       {
+         // special case: cursor left or right pressed -- toggle team mode
+         HandleMainMenu_ToggleTeamMode();
+       }
+       else if (choice != MAIN_CONTROL_INFO &&
+                choice != MAIN_CONTROL_SETUP)
+       {
          HandleMainMenu_SelectLevel(1, dx, NO_DIRECT_LEVEL_SELECT);
+       }
       }
     }
     else
@@ -2115,12 +2141,7 @@ void HandleMainMenu(int mx, int my, int dx, int dy, int button)
            insideTextPosRect(main_controls[i].pos_text, mx - mSX, my - mSY))
        {
          // special case: menu text "name/team" clicked -- toggle team mode
-         setup.team_mode = !setup.team_mode;
-
-         InitializeMainControls();
-         DrawCursorAndText_Main(choice, TRUE, FALSE);
-
-         DrawPreviewPlayers();
+         HandleMainMenu_ToggleTeamMode();
        }
        else
        {
@@ -2151,6 +2172,9 @@ void HandleMainMenu(int mx, int my, int dx, int dy, int button)
          SaveLevelSetup_LastSeries();
          SaveLevelSetup_SeriesInfo();
 
+         // restore level set if chosen from "last played level set" menu
+         RestoreLastPlayedLevels(&leveldir_current);
+
          if (setup.internal.choose_from_top_leveldir)
            gotoTopLevelDir();
 
@@ -2163,7 +2187,7 @@ void HandleMainMenu(int mx, int my, int dx, int dy, int button)
 
        SetGameStatus(GAME_MODE_SCORES);
 
-       DrawHallOfFame(level_nr, -1);
+       DrawHallOfFame(level_nr);
       }
       else if (pos == MAIN_CONTROL_EDITOR)
       {
@@ -2208,7 +2232,8 @@ void HandleMainMenu(int mx, int my, int dx, int dy, int button)
        SaveLevelSetup_LastSeries();
        SaveLevelSetup_SeriesInfo();
 
-        if (Request("Do you really want to quit?", REQ_ASK | REQ_STAY_CLOSED))
+       if (!setup.ask_on_quit_program ||
+           Request("Do you really want to quit?", REQ_ASK | REQ_STAY_CLOSED))
          SetGameStatus(GAME_MODE_QUIT);
       }
     }
@@ -4013,6 +4038,138 @@ void HandleInfoScreen(int mx, int my, int dx, int dy, int button)
 }
 
 
+// ============================================================================
+// change 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 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
+}
+
+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);
+}
+
+static int ApiRenamePlayerThread(void *data_raw)
+{
+  struct HttpRequest *request = checked_calloc(sizeof(struct HttpRequest));
+  struct HttpResponse *response = checked_calloc(sizeof(struct HttpResponse));
+
+  ApiRenamePlayer_HttpRequest(request, response, data_raw);
+
+  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");
+}
+
+
 // ============================================================================
 // type name functions
 // ============================================================================
@@ -4150,6 +4307,9 @@ static void setTypeNameValues(char *name, struct TextPosInfo *pos,
   // save setup of edited user
   SaveSetup();
 
+  // change name of edited user on score server
+  ApiRenamePlayerAsThread();
+
   if (game_status == GAME_MODE_PSEUDO_TYPENAMES || reset_setup)
   {
     if (reset_setup)
@@ -4351,9 +4511,16 @@ static void DrawChooseTree(TreeInfo **ti_ptr)
 
   if (strEqual((*ti_ptr)->subdir, STRING_TOP_DIRECTORY))
   {
-    SetGameStatus(GAME_MODE_MAIN);
+    if (game_status == GAME_MODE_SETUP)
+    {
+      execSetupArtwork();
+    }
+    else       // GAME_MODE_LEVELS
+    {
+      SetGameStatus(GAME_MODE_MAIN);
 
-    DrawMainMenu();
+      DrawMainMenu();
+    }
 
     return;
   }
@@ -4387,13 +4554,38 @@ static void DrawChooseTree(TreeInfo **ti_ptr)
   FadeIn(fade_mask);
 }
 
-static void drawChooseTreeList(int first_entry, int num_page_entries,
-                              TreeInfo *ti)
+static void drawChooseTreeText(int y, boolean active, TreeInfo *ti)
 {
   int num_entries = numTreeInfoInGroup(ti);
   boolean scrollbar_needed = (num_entries > NUM_MENU_ENTRIES_ON_SCREEN);
   int scrollbar_xpos = SC_SCROLLBAR_XPOS + menu.scrollbar_xoffset;
   int screen_width = (scrollbar_needed ? scrollbar_xpos : SXSIZE);
+  int first_entry = ti->cl_first;
+  int entry_pos = first_entry + y;
+  TreeInfo *node_first = getTreeInfoFirstGroupEntry(ti);
+  TreeInfo *node = getTreeInfoFromPos(node_first, entry_pos);
+  int font_color = MENU_CHOOSE_TREE_COLOR(node, active);
+  int font_nr = MENU_CHOOSE_TREE_FONT(font_color);
+  int font_xoffset = getFontBitmapInfo(font_nr)->draw_xoffset;
+  int xpos = MENU_SCREEN_START_XPOS;
+  int ypos = MENU_SCREEN_START_YPOS + y;
+  int startx = amSX + xpos * 32;
+  int starty = amSY + ypos * 32;
+  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';
+
+  DrawText(startx, starty, buffer, font_nr);
+}
+
+static void drawChooseTreeList(int first_entry, int num_page_entries,
+                              TreeInfo *ti)
+{
   int i;
   char *title_string = NULL;
   int yoffset_sets = MENU_TITLE1_YPOS;
@@ -4415,22 +4607,7 @@ static void drawChooseTreeList(int first_entry, int num_page_entries,
     node_first = getTreeInfoFirstGroupEntry(ti);
     node = getTreeInfoFromPos(node_first, entry_pos);
 
-    int font_nr = MENU_CHOOSE_TREE_FONT(node->color);
-    int font_xoffset = getFontBitmapInfo(font_nr)->draw_xoffset;
-    int xpos = MENU_SCREEN_START_XPOS;
-    int ypos = MENU_SCREEN_START_YPOS + i;
-    int startx = amSX + xpos * 32;
-    int starty = amSY + ypos * 32;
-    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';
-
-    DrawText(startx, starty, buffer, font_nr);
+    drawChooseTreeText(i, FALSE, ti);
 
     if (node->parent_link)
       initCursor(i, IMG_MENU_BUTTON_LEAVE_MENU);
@@ -4481,6 +4658,12 @@ static void drawChooseTreeInfo(int entry_pos, TreeInfo *ti)
     MarkTileDirty(x, 1);
 }
 
+static void drawChooseTreeCursorAndText(int y, boolean active, TreeInfo *ti)
+{
+  drawChooseTreeCursor(y, active);
+  drawChooseTreeText(y, active, ti);
+}
+
 static void HandleChooseTree(int mx, int my, int dx, int dy, int button,
                             TreeInfo **ti_ptr)
 {
@@ -4528,7 +4711,7 @@ static void HandleChooseTree(int mx, int my, int dx, int dy, int button,
 
     drawChooseTreeList(ti->cl_first, num_page_entries, ti);
     drawChooseTreeInfo(ti->cl_first + ti->cl_cursor, ti);
-    drawChooseTreeCursor(ti->cl_cursor, TRUE);
+    drawChooseTreeCursorAndText(ti->cl_cursor, TRUE, ti);
 
     return;
   }
@@ -4638,7 +4821,7 @@ static void HandleChooseTree(int mx, int my, int dx, int dy, int button,
       {
        drawChooseTreeList(ti->cl_first, num_page_entries, ti);
        drawChooseTreeInfo(ti->cl_first + ti->cl_cursor, ti);
-       drawChooseTreeCursor(ti->cl_cursor, TRUE);
+       drawChooseTreeCursorAndText(ti->cl_cursor, TRUE, ti);
 
        AdjustChooseTreeScrollbar(SCREEN_CTRL_ID_SCROLL_VERTICAL,
                                  ti->cl_first, ti);
@@ -4702,8 +4885,8 @@ static void HandleChooseTree(int mx, int my, int dx, int dy, int button,
       {
        PlaySound(SND_MENU_ITEM_ACTIVATING);
 
-       drawChooseTreeCursor(ti->cl_cursor, FALSE);
-       drawChooseTreeCursor(y, TRUE);
+       drawChooseTreeCursorAndText(ti->cl_cursor, FALSE, ti);
+       drawChooseTreeCursorAndText(y, TRUE, ti);
        drawChooseTreeInfo(ti->cl_first + y, ti);
 
        ti->cl_cursor = y;
@@ -4817,6 +5000,11 @@ static void HandleChooseTree(int mx, int my, int dx, int dy, int button,
 
            HandleMainMenu_SelectLevel(0, 0, new_level_nr);
          }
+         else if (game_status == GAME_MODE_LEVELS)
+         {
+           // store level set if chosen from "last played level set" menu
+           StoreLastPlayedLevels(leveldir_current);
+         }
          else if (game_status == GAME_MODE_NAMES)
          {
            if (mx >= sx1_edit_name && mx <= sx2_edit_name)
@@ -4841,6 +5029,9 @@ static void HandleChooseTree(int mx, int my, int dx, int dy, int button,
            LoadLevelSetup_LastSeries();
            LoadLevelSetup_SeriesInfo();
 
+           // update list of last played level sets
+           UpdateLastPlayedLevels_TreeInfo();
+
            TapeErase();
 
            ToggleFullscreenIfNeeded();
@@ -4988,7 +5179,7 @@ void HandleChooseLevelNr(int mx, int my, int dx, int dy, int button)
   HandleChooseTree(mx, my, dx, dy, button, &level_number_current);
 }
 
-void DrawHallOfFame(int level_nr, int highlight_position)
+void DrawHallOfFame(int level_nr)
 {
   int fade_mask = REDRAW_FIELD;
 
@@ -5005,9 +5196,9 @@ void DrawHallOfFame(int level_nr, int highlight_position)
   SetDrawDeactivationMask(REDRAW_NONE);
   SetDrawBackgroundMask(REDRAW_FIELD);
 
-  if (highlight_position < 0) 
-    LoadScore(level_nr);
-  else
+  LoadLocalAndServerScore(level_nr, TRUE);
+
+  if (scores.last_added >= 0)
     SetAnimStatus(GAME_MODE_PSEUDO_SCORESNEW);
 
   FadeSetEnterScreen();
@@ -5021,15 +5212,47 @@ void DrawHallOfFame(int level_nr, int highlight_position)
 
   OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
 
-  HandleHallOfFame(level_nr, highlight_position, 0, 0, MB_MENU_INITIALIZE);
+  HandleHallOfFame(level_nr, 0, 0, 0, MB_MENU_INITIALIZE);
 
   DrawMaskedBorder(fade_mask);
 
   FadeIn(fade_mask);
 }
 
-static void drawHallOfFameList(int level_nr, int first_entry,
-                              int highlight_position)
+static int getHallOfFameFirstEntry(int first_entry, int step)
+{
+  if (step == 0)
+    first_entry = scores.last_added - (NUM_MENU_ENTRIES_ON_SCREEN + 1) / 2 + 1;
+  else
+    first_entry += step;
+
+  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);
+
+  return first_entry;
+}
+
+static char *getHallOfFameScoreText(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;
+  int ss = (time_seconds % 60);
+
+  sprintf(score_text, "%02d:%02d", mm, ss);    // show playing time
+
+  return score_text;
+}
+
+static void drawHallOfFameList(int level_nr, int first_entry)
 {
   int i, j;
 
@@ -5043,7 +5266,8 @@ static void drawHallOfFameList(int level_nr, int first_entry,
   for (i = 0; i < NUM_MENU_ENTRIES_ON_SCREEN; i++)
   {
     int entry = first_entry + i;
-    boolean active = (entry == highlight_position);
+    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);
@@ -5054,17 +5278,18 @@ static void drawHallOfFameList(int level_nr, int first_entry,
     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, int2str(entry + 1, 3), font_nr1);
+    DrawText(mSX, sy, pos_text, font_nr1);
     DrawText(mSX + dx1, sy, ".", font_nr1);
 
     for (j = 0; j < num_dots; j++)
       DrawText(mSX + dx2 + j * getFontWidth(font_nr3), sy, ".", font_nr3);
 
-    if (!strEqual(highscore[entry].Name, EMPTY_PLAYER_NAME))
-      DrawText(mSX + dx2, sy, highscore[entry].Name, font_nr2);
+    if (!strEqual(scores.entry[entry].name, EMPTY_PLAYER_NAME))
+      DrawText(mSX + dx2, sy, scores.entry[entry].name, font_nr2);
 
-    DrawText(mSX + dx3, sy, int2str(highscore[entry].Score, 5), font_nr4);
+    DrawText(mSX + dx3, sy, getHallOfFameScoreText(entry), font_nr4);
   }
 
   redraw_mask |= REDRAW_FIELD;
@@ -5074,22 +5299,23 @@ void HandleHallOfFame(int mx, int my, int dx, int dy, int button)
 {
   static int level_nr = 0;
   static int first_entry = 0;
-  static int highlight_position = 0;
   int step = (button == 1 ? 1 : button == 2 ? 5 : 10);
 
   if (button == MB_MENU_INITIALIZE)
   {
     level_nr = mx;
-    highlight_position = my;
 
-    first_entry = highlight_position - (NUM_MENU_ENTRIES_ON_SCREEN + 1) / 2 + 1;
+    if (server_scores.updated)
+    {
+      // reload scores, using updated server score cache file
+      LoadLocalAndServerScore(level_nr, FALSE);
+
+      server_scores.updated = FALSE;
+    }
 
-    if (first_entry < 0)
-      first_entry = 0;
-    else if (first_entry + NUM_MENU_ENTRIES_ON_SCREEN > MAX_SCORE_ENTRIES)
-      first_entry = MAX(0, MAX_SCORE_ENTRIES - NUM_MENU_ENTRIES_ON_SCREEN);
+    first_entry = getHallOfFameFirstEntry(0, 0);
 
-    drawHallOfFameList(level_nr, first_entry, highlight_position);
+    drawHallOfFameList(level_nr, first_entry);
 
     return;
   }
@@ -5099,25 +5325,15 @@ void HandleHallOfFame(int mx, int my, int dx, int dy, int button)
 
   if (dy < 0)
   {
-    if (first_entry > 0)
-    {
-      first_entry -= step;
-      if (first_entry < 0)
-       first_entry = 0;
+    first_entry = getHallOfFameFirstEntry(first_entry, -step);
 
-      drawHallOfFameList(level_nr, first_entry, highlight_position);
-    }
+    drawHallOfFameList(level_nr, first_entry);
   }
   else if (dy > 0)
   {
-    if (first_entry + NUM_MENU_ENTRIES_ON_SCREEN < MAX_SCORE_ENTRIES)
-    {
-      first_entry += step;
-      if (first_entry + NUM_MENU_ENTRIES_ON_SCREEN > MAX_SCORE_ENTRIES)
-       first_entry = MAX(0, MAX_SCORE_ENTRIES - NUM_MENU_ENTRIES_ON_SCREEN);
+    first_entry = getHallOfFameFirstEntry(first_entry, step);
 
-      drawHallOfFameList(level_nr, first_entry, highlight_position);
-    }
+    drawHallOfFameList(level_nr, first_entry);
   }
   else if (button == MB_MENU_LEAVE || button == MB_MENU_CHOICE)
   {
@@ -5140,6 +5356,17 @@ void HandleHallOfFame(int mx, int my, int dx, int dy, int button)
       DrawMainMenu();
     }
   }
+  else if (server_scores.updated)
+  {
+    // reload scores, using updated server score cache file
+    LoadLocalAndServerScore(level_nr, FALSE);
+
+    server_scores.updated = FALSE;
+
+    first_entry = getHallOfFameFirstEntry(0, 0);
+
+    drawHallOfFameList(level_nr, first_entry);
+  }
 
   if (game_status == GAME_MODE_SCORES)
     PlayMenuSoundIfLoop();
@@ -5379,6 +5606,9 @@ static void execSetupGame(void)
 
   execSetupGame_setNetworkServerText();
 
+  if (!setup.provide_uploading_tapes)
+    setHideSetupEntry(execOfferUploadTapes);
+
   setup_mode = SETUP_MODE_GAME;
 
   DrawSetupScreen();
@@ -6325,6 +6555,23 @@ static void execSetupTouch(void)
 
 static void execSetupArtwork(void)
 {
+  static ArtworkDirTree *gfx_last_valid = NULL;
+  static ArtworkDirTree *snd_last_valid = NULL;
+  static ArtworkDirTree *mus_last_valid = NULL;
+
+  // current artwork directory may be invalid (level group, parent link)
+  if (!validLevelSeries(artwork.gfx_current))
+    artwork.gfx_current = getFirstValidTreeInfoEntry(gfx_last_valid);
+  if (!validLevelSeries(artwork.snd_current))
+    artwork.snd_current = getFirstValidTreeInfoEntry(snd_last_valid);
+  if (!validLevelSeries(artwork.mus_current))
+    artwork.mus_current = getFirstValidTreeInfoEntry(mus_last_valid);
+
+  // store valid artwork directory information
+  gfx_last_valid = artwork.gfx_current;
+  snd_last_valid = artwork.snd_current;
+  mus_last_valid = artwork.mus_current;
+
 #if 0
   Debug("screens:execSetupArtwork", "'%s', '%s', '%s'",
        artwork.gfx_current->subdir,
@@ -6447,6 +6694,11 @@ static void execGadgetNetworkServer(void)
   ClickOnGadget(gi, MB_LEFTBUTTON);
 }
 
+static void execOfferUploadTapes(void)
+{
+  OfferUploadTapes();
+}
+
 static void ToggleNetworkModeIfNeeded(void)
 {
   int font_title = FONT_TITLE_1;
@@ -6664,6 +6916,9 @@ static struct TokenInfo setup_info_game[] =
   { TYPE_PLAYER,       &setup.network_player_nr,"Preferred Network Player:" },
   { TYPE_TEXT_INPUT,   execGadgetNetworkServer, "Network Server Hostname:" },
   { TYPE_STRING,       &network_server_text,   ""                      },
+  { TYPE_SWITCH,       &setup.use_api_server,  "Use Highscore Server:" },
+  { TYPE_SWITCH,       &setup.only_show_local_scores, "Only Show Local Scores:" },
+  { TYPE_ENTER_LIST,   execOfferUploadTapes,   "Upload All Tapes to Server" },
   { 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:"           },
@@ -6671,8 +6926,11 @@ static struct TokenInfo setup_info_game[] =
   { TYPE_SWITCH,       &setup.skip_levels,     "Skip Unsolved 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:" },
   { TYPE_SWITCH,       &setup.show_scores_after_game,"Show Scores After Game:" },
   { TYPE_YES_NO,       &setup.ask_on_game_over, "Ask on Game Over:"    },
+  { TYPE_YES_NO,       &setup.ask_on_quit_game, "Ask on Quit Game:"    },
+  { TYPE_YES_NO,       &setup.ask_on_quit_program, "Ask on Quit Program:" },
   { TYPE_SWITCH,       &setup.autorecord,      "Auto-Record Tapes:"    },
   { TYPE_ENTER_LIST,   execSetupChooseGameSpeed, "Game Speed:"         },
   { TYPE_STRING,       &game_speed_text,       ""                      },
@@ -6683,7 +6941,8 @@ static struct TokenInfo setup_info_game[] =
 #endif
   { TYPE_ENTER_LIST, execSetupChooseSnapshotMode,"Game Engine Snapshot Mode:" },
   { TYPE_STRING,       &snapshot_mode_text,    ""                      },
-  { TYPE_SWITCH,       &setup.show_snapshot_buttons,"Show Snapshot Buttons:" },
+  { TYPE_SWITCH,       &setup.show_load_save_buttons,"Show Load/Save Buttons:" },
+  { TYPE_SWITCH,       &setup.show_undo_redo_buttons,"Show Undo/Redo Buttons:" },
   { TYPE_EMPTY,                NULL,                   ""                      },
   { TYPE_LEAVE_MENU,   execSetupMain,          "Back"                  },
 
@@ -7262,6 +7521,10 @@ static void changeSetupValue(int screen_pos, int setup_info_pos_raw, int dx)
   if (si->value == &setup.network_mode)
     ToggleNetworkModeIfNeeded();
 
+  // API server mode may have changed at this point
+  if (si->value == &setup.use_api_server)
+    runtime.use_api_server = setup.use_api_server;
+
   // game speed list may have changed at this point
   if (si->value == &setup.game_speed_extended)
     ToggleGameSpeedsListIfNeeded();
@@ -8820,10 +9083,10 @@ void HandleGameActions(void)
   if (game_status != GAME_MODE_PLAYING)
     return;
 
-  GameActions();       // main game loop
+  GameActions();               // main game loop
 
   if (tape.auto_play && !tape.playing)
-    AutoPlayTapes();   // continue automatically playing next tape
+    AutoPlayTapesContinue();   // continue automatically playing next tape
 }
 
 
@@ -9614,3 +9877,78 @@ void DrawScreenAfterAddingSet(char *tree_subdir_new, int tree_type)
     }
   }
 }
+
+static int UploadTapes(void)
+{
+  SetGameStatus(GAME_MODE_LOADING);
+
+  FadeSetEnterScreen();
+  FadeOut(REDRAW_ALL);
+
+  ClearRectangle(drawto, 0, 0, WIN_XSIZE, WIN_YSIZE);
+
+  FadeIn(REDRAW_ALL);
+
+  DrawInitTextHead("Uploading tapes");
+
+  global.autoplay_mode = AUTOPLAY_MODE_UPLOAD;
+  global.autoplay_leveldir = "ALL";
+  global.autoplay_all = TRUE;
+
+  int num_tapes_uploaded = AutoPlayTapes();
+
+  global.autoplay_mode = AUTOPLAY_MODE_NONE;
+  global.autoplay_leveldir = NULL;
+  global.autoplay_all = FALSE;
+
+  SetGameStatus(GAME_MODE_MAIN);
+
+  DrawMainMenu();
+
+  return num_tapes_uploaded;
+}
+
+static boolean OfferUploadTapes(void)
+{
+  if (!Request("Upload all your tapes to the high score server now?", REQ_ASK))
+    return FALSE;
+
+  int num_tapes_uploaded = UploadTapes();
+  char message[100];
+
+  sprintf(message, "%d tapes uploaded!", num_tapes_uploaded);
+
+  Request(message, REQ_CONFIRM);
+
+  // after all tapes have been uploaded, remove entry from setup menu
+  setup.provide_uploading_tapes = FALSE;
+
+  SaveSetup();
+
+  return (num_tapes_uploaded > 0);
+}
+
+void CheckUploadTapes(void)
+{
+  if (!setup.ask_for_uploading_tapes)
+    return;
+
+  if (directoryExists(getTapeDir(NULL)))
+  {
+    boolean tapes_uploaded = OfferUploadTapes();
+
+    if (!tapes_uploaded)
+      Request("You can upload your tapes from the setup menu later!",
+             REQ_CONFIRM);
+  }
+  else
+  {
+    // if tapes directory does not exist yet, never offer uploading all tapes
+    setup.provide_uploading_tapes = FALSE;
+  }
+
+  // after asking for uploading all tapes once, do not ask again
+  setup.ask_for_uploading_tapes = FALSE;
+
+  SaveSetup();
+}