added support for multiple pages (files) for level set info
[rocksndiamonds.git] / src / screens.c
index e2308b4d4e455a345f36f9442b7f92f88cb1bf8d..379a9eaaafc01022961a50eb94f13432f2616f08 100644 (file)
@@ -288,6 +288,7 @@ 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 HandleInfoScreen_LevelSet(int, int, int);
 
 static void ModifyGameSpeedIfNeeded(void);
 static void DisableVsyncIfNeeded(void);
@@ -703,6 +704,11 @@ static boolean use_global_credits_screens = FALSE;
 static int num_program_info_screens = 0;
 
 
+// level set info screens definitions
+
+static int num_levelset_info_screens = 0;
+
+
 // main menu display and control definitions
 
 #define MAIN_CONTROL_NAME                      0
@@ -1729,6 +1735,11 @@ void DrawMainMenu(void)
 
   if (leveldir_current != leveldir_last_valid)
   {
+    // level setup config may have been loaded to "last played" tree node copy,
+    // but "leveldir_current" now points to the "original" level set tree node,
+    // in which case "handicap_level" may still default to the first level
+    LoadLevelSetup_SeriesInfo();
+
     UpdateLastPlayedLevels_TreeInfo();
 
     levelset_has_changed = TRUE;
@@ -1872,7 +1883,7 @@ static unsigned int getAutoDelayCounter(struct TitleFadingInfo *fi)
 static boolean TitleAutoDelayReached(unsigned int *counter_var,
                                     struct TitleFadingInfo *fi)
 {
-  return DelayReachedExt(counter_var, fi->auto_delay, getAutoDelayCounter(fi));
+  return DelayReachedExt2(counter_var, fi->auto_delay, getAutoDelayCounter(fi));
 }
 
 static void ResetTitleAutoDelay(unsigned int *counter_var,
@@ -3161,13 +3172,15 @@ static void DrawInfoScreen_Elements(void)
 
 void HandleInfoScreen_Elements(int dx, int dy, int button)
 {
-  static unsigned int info_delay = 0;
+  static DelayCounter info_delay = { 0 };
   static int num_anims;
   static int num_pages;
   static int page;
   int anims_per_page = NUM_INFO_ELEMENTS_ON_SCREEN;
   int i;
 
+  info_delay.value = GameFrameDelay;
+
   if (button == MB_MENU_INITIALIZE)
   {
     boolean new_element = TRUE;
@@ -3230,7 +3243,7 @@ void HandleInfoScreen_Elements(int dx, int dy, int button)
   }
   else
   {
-    if (DelayReached(&info_delay, GameFrameDelay))
+    if (DelayReached(&info_delay))
       if (page < num_pages)
        DrawInfoScreen_HelpAnim(page * anims_per_page, num_anims, FALSE);
 
@@ -3862,30 +3875,19 @@ void HandleInfoScreen_Version(int button)
   }
 }
 
-static void DrawInfoScreen_LevelSet(void)
+static void DrawInfoScreen_LevelSetScreen(int screen_nr)
 {
   struct TitleMessageInfo *tmi = &readme;
-  char *filename = getLevelSetInfoFilename();
-  char *title = "Level Set Information:";
-  int font_foot = MENU_INFO_FONT_FOOT;
+  char *filename = getLevelSetInfoFilename(screen_nr);
+  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;
 
-  if (filename == NULL)
-  {
-    DrawInfoScreen_NotAvailable(title, "No information for this level set.");
-
-    return;
-  }
-
-  SetMainBackgroundImageIfDefined(IMG_BACKGROUND_INFO_LEVELSET);
-
-  FadeOut(REDRAW_FIELD);
-
   ClearField();
   DrawHeadline();
 
-  DrawTextSCentered(ystart, FONT_TEXT_1, title);
+  DrawTextSCentered(ystart, font_title, "Level Set Information:");
 
   // if x position set to "-1", automatically determine by playfield width
   if (tmi->x == -1)
@@ -3919,14 +3921,58 @@ 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_foot, TEXT_INFO_MENU);
+  boolean last_screen = (screen_nr == num_levelset_info_screens - 1);
+  char *text_foot = (last_screen ? TEXT_INFO_MENU : TEXT_NEXT_PAGE);
+
+  DrawTextSCentered(ybottom, font_foot, text_foot);
+}
+
+static void DrawInfoScreen_LevelSet(void)
+{
+  SetMainBackgroundImageIfDefined(IMG_BACKGROUND_INFO_LEVELSET);
+
+  FadeMenuSoundsAndMusic();
+
+  FadeOut(REDRAW_FIELD);
+
+  HandleInfoScreen_LevelSet(0, 0, MB_MENU_INITIALIZE);
 
   FadeIn(REDRAW_FIELD);
 }
 
-static void HandleInfoScreen_LevelSet(int button)
+void HandleInfoScreen_LevelSet(int dx, int dy, int button)
 {
-  if (button == MB_MENU_LEAVE)
+  static int screen_nr = 0;
+
+  if (button == MB_MENU_INITIALIZE)
+  {
+    // determine number of levelset info screens
+    num_levelset_info_screens = 0;
+
+    while (getLevelSetInfoFilename(num_levelset_info_screens) != NULL)
+      num_levelset_info_screens++;
+
+    if (num_levelset_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 level set info available.");
+      DrawTextSCentered(ybottom, font_foot, TEXT_INFO_MENU);
+
+      return;
+    }
+
+    screen_nr = 0;
+
+    DrawInfoScreen_LevelSetScreen(screen_nr);
+  }
+  else if (button == MB_MENU_LEAVE)
   {
     PlaySound(SND_MENU_ITEM_SELECTING);
 
@@ -3935,14 +3981,29 @@ static void HandleInfoScreen_LevelSet(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_levelset_info_screens)
+    {
+      FadeMenuSoundsAndMusic();
+
+      info_mode = INFO_MODE_MAIN;
+      DrawInfoScreen();
+
+      return;
+    }
+
+    FadeSetNextScreen();
+
+    FadeOut(REDRAW_FIELD);
+
+    DrawInfoScreen_LevelSetScreen(screen_nr);
+
+    FadeIn(REDRAW_FIELD);
   }
   else
   {
@@ -3990,7 +4051,7 @@ void HandleInfoScreen(int mx, int my, int dx, int dy, int button)
   else if (info_mode == INFO_MODE_VERSION)
     HandleInfoScreen_Version(button);
   else if (info_mode == INFO_MODE_LEVELSET)
-    HandleInfoScreen_LevelSet(button);
+    HandleInfoScreen_LevelSet(dx, dy, button);
   else
     HandleInfoScreen_Main(mx, my, dx, dy, button);
 }
@@ -4339,12 +4400,23 @@ 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;
 
@@ -4354,7 +4426,11 @@ static void DrawChooseTree(TreeInfo **ti_ptr)
     {
       execSetupArtwork();
     }
-    else       // GAME_MODE_LEVELS
+    else if (game_status == GAME_MODE_SCORES && scores.continue_playing)
+    {
+      StartPlayingFromHallOfFame();
+    }
+    else
     {
       SetGameStatus(GAME_MODE_MAIN);
 
@@ -4798,18 +4874,18 @@ static void HandleChooseTree(int mx, int my, int dx, int dy, int button,
   }
 
 #if defined(PLATFORM_ANDROID)
-  // touching the screen anywhere continues playing the next level
-  if ((mx || my) && scores.continue_playing)
+  // directly continue when touching the screen after playing
+  if ((mx || my) && scores.continue_on_return)
   {
+    // ignore touch events until released
     mx = my = 0;
-    button = MB_MENU_CHOICE;
   }
 #endif
 
-  // any mouse click or direction input stops playing the next level
-  if ((mx || my || dx || dy) && scores.continue_playing)
+  // any mouse click or cursor key stops leaving scores by "Return" key
+  if ((mx || my || dx || dy) && scores.continue_on_return)
   {
-    scores.continue_playing = FALSE;
+    scores.continue_on_return = FALSE;
     level_nr = scores.last_level_nr;
     LoadLevel(level_nr);
   }
@@ -4893,15 +4969,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);
 
@@ -4984,6 +5062,7 @@ 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);
       }
@@ -5002,6 +5081,7 @@ 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;
 
        if (ti->type == TREE_TYPE_LEVEL_DIR)
@@ -5092,16 +5172,13 @@ static void HandleChooseTree(int mx, int my, int dx, int dy, int button,
          }
          else if (game_status == GAME_MODE_SCORES)
          {
-           if (setup.auto_play_next_level && setup.increment_levels &&
-               scores.last_level_nr < leveldir_current->last_level &&
-               scores.continue_playing &&
-               !network_playing)
+           if (scores.continue_playing && scores.continue_on_return)
            {
-             StartGameActions(network.enabled, setup.autorecord,
-                              level.random_seed);
+             StartPlayingFromHallOfFame();
+
              return;
            }
-           else if (!scores.continue_playing)
+           else if (!scores.continue_on_return)
            {
              SetGameStatus(GAME_MODE_SCOREINFO);
 
@@ -5298,6 +5375,9 @@ static void DrawHallOfFame_setScoreEntries(void)
   if (score_entry_current == NULL)
     score_entry_current = getFirstValidTreeInfoEntry(score_entries);
 
+  if (score_entries != NULL && scores.continue_playing)
+    setString(&score_entries->node_group->name, BACKLINK_TEXT_NEXT);
+
   // ("score_entries" and "score_entry_current" may be NULL here)
 }
 
@@ -7112,7 +7192,12 @@ static void ToggleUseApiServerIfNeeded(void)
   runtime.use_api_server = setup.use_api_server;
 
   if (runtime.use_api_server)
+  {
+    if (setup.has_remaining_tapes)
+      setup.ask_for_uploading_tapes = TRUE;
+
     CheckApiServerTasks();
+  }
 }
 
 static void ModifyGameSpeedIfNeeded(void)
@@ -7162,6 +7247,9 @@ static struct
   void *related_value;
 } hide_related_entry_list[] =
 {
+  { &setup.network_server_hostname,    execGadgetNetworkServer         },
+  { &setup.network_server_hostname,    &network_server_text            },
+
   { &setup.scores_in_highscore_list,   execSetupChooseScoresType       },
   { &setup.scores_in_highscore_list,   &scores_type_text               },
 
@@ -7240,6 +7328,12 @@ static struct
   { &setup.internal.menu_exit,         execExitSetup                   },
   { &setup.internal.menu_save_and_exit,        execSaveAndExitSetup            },
 
+  { &setup.internal.menu_shortcuts_various,    execSetupShortcuts1     },
+  { &setup.internal.menu_shortcuts_focus,      execSetupShortcuts2     },
+  { &setup.internal.menu_shortcuts_tape,       execSetupShortcuts3     },
+  { &setup.internal.menu_shortcuts_sound,      execSetupShortcuts4     },
+  { &setup.internal.menu_shortcuts_snap,       execSetupShortcuts5     },
+
   { &setup.internal.info_title,                execInfoTitleScreen             },
   { &setup.internal.info_elements,     execInfoElements                },
   { &setup.internal.info_music,                execInfoMusic                   },
@@ -7375,7 +7469,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,      ""                      },
@@ -7388,8 +7482,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:"     },
@@ -7649,7 +7745,7 @@ static Key getSetupKey(void)
       {
         case EVENT_KEYPRESS:
          {
-           key = GetEventKey((KeyEvent *)&event, TRUE);
+           key = GetEventKey((KeyEvent *)&event);
 
            // press 'Escape' or 'Enter' to keep the existing key binding
            if (key == KSYM_Escape || key == KSYM_Return)
@@ -8435,14 +8531,18 @@ static boolean CustomizeKeyboardMain(int player_nr)
   while (!finished)
   {
     Event event;
+    DelayCounter event_frame_delay = { GAME_FRAME_DELAY };
 
-    if (NextValidEvent(&event))
+    // reset frame delay counter directly after updating screen
+    ResetDelayCounter(&event_frame_delay);
+
+    while (NextValidEvent(&event))
     {
       switch (event.type)
       {
         case EVENT_KEYPRESS:
          {
-           Key key = GetEventKey((KeyEvent *)&event, FALSE);
+           Key key = GetEventKey((KeyEvent *)&event);
 
            // press 'Escape' to abort and keep the old key bindings
            if (key == KSYM_Escape)
@@ -8507,6 +8607,10 @@ static boolean CustomizeKeyboardMain(int player_nr)
          HandleOtherEvents(&event);
          break;
       }
+
+      // do not handle events for longer than standard frame delay period
+      if (DelayReached(&event_frame_delay))
+       break;
     }
 
     BackToFront();
@@ -8529,8 +8633,7 @@ void CustomizeKeyboard(int player_nr)
     int font_height = getFontHeight(font_nr);
     int ypos1 = SYSIZE / 2 - font_height * 2;
     int ypos2 = SYSIZE / 2 - font_height * 1;
-    unsigned int wait_frame_delay = 0;
-    unsigned int wait_frame_delay_value = 2000;
+    DelayCounter wait_frame_delay = { 2000 };
 
     ResetDelayCounter(&wait_frame_delay);
 
@@ -8539,7 +8642,7 @@ void CustomizeKeyboard(int player_nr)
     DrawTextSCentered(ypos1, font_nr, "Keyboard");
     DrawTextSCentered(ypos2, font_nr, "configured!");
 
-    while (!DelayReached(&wait_frame_delay, wait_frame_delay_value))
+    while (!DelayReached(&wait_frame_delay))
       BackToFront();
 
     ClearEventQueue();
@@ -8609,11 +8712,6 @@ static boolean ConfigureJoystickMapButtonsAndAxes(SDL_Joystick *joystick)
     { 282, 210, MARKER_AXIS_Y, "righty",       },
   };
 
-  unsigned int event_frame_delay = 0;
-  unsigned int event_frame_delay_value = GAME_FRAME_DELAY;
-
-  ResetDelayCounter(&event_frame_delay);
-
   if (!bitmaps_initialized)
   {
     controller = LoadCustomImage("joystick/controller.png");
@@ -8745,6 +8843,11 @@ static boolean ConfigureJoystickMapButtonsAndAxes(SDL_Joystick *joystick)
 
       screen_initialized = TRUE;
 
+      DelayCounter event_frame_delay = { GAME_FRAME_DELAY };
+
+      // reset frame delay counter directly after updating screen
+      ResetDelayCounter(&event_frame_delay);
+
       while (NextValidEvent(&event))
       {
        switch (event.type)
@@ -8878,7 +8981,7 @@ static boolean ConfigureJoystickMapButtonsAndAxes(SDL_Joystick *joystick)
        }
 
        // do not handle events for longer than standard frame delay period
-       if (DelayReached(&event_frame_delay, event_frame_delay_value))
+       if (DelayReached(&event_frame_delay))
          break;
       }
     }
@@ -8962,8 +9065,7 @@ void ConfigureJoystick(int player_nr)
     int font_height = getFontHeight(font_nr);
     int ypos1 = SYSIZE / 2 - font_height * 2;
     int ypos2 = SYSIZE / 2 - font_height * 1;
-    unsigned int wait_frame_delay = 0;
-    unsigned int wait_frame_delay_value = 2000;
+    DelayCounter wait_frame_delay = { 2000 };
 
     ResetDelayCounter(&wait_frame_delay);
 
@@ -8974,7 +9076,7 @@ void ConfigureJoystick(int player_nr)
     DrawTextSCentered(ypos1, font_nr, message1);
     DrawTextSCentered(ypos2, font_nr, message2);
 
-    while (!DelayReached(&wait_frame_delay, wait_frame_delay_value))
+    while (!DelayReached(&wait_frame_delay))
       BackToFront();
 
     ClearEventQueue();
@@ -9091,7 +9193,7 @@ static boolean ConfigureVirtualButtonsMain(void)
 
         case EVENT_KEYPRESS:
          {
-           Key key = GetEventKey((KeyEvent *)&event, FALSE);
+           Key key = GetEventKey((KeyEvent *)&event);
 
            action = (key == KSYM_Escape ?      ACTION_ESCAPE :
                      key == KSYM_BackSpace ||
@@ -9292,8 +9394,7 @@ void ConfigureVirtualButtons(void)
     int font_height = getFontHeight(font_nr);
     int ypos1 = SYSIZE / 2 - font_height * 2;
     int ypos2 = SYSIZE / 2 - font_height * 1;
-    unsigned int wait_frame_delay = 0;
-    unsigned int wait_frame_delay_value = 2000;
+    DelayCounter wait_frame_delay = { 2000 };
 
     ResetDelayCounter(&wait_frame_delay);
 
@@ -9302,7 +9403,7 @@ void ConfigureVirtualButtons(void)
     DrawTextSCentered(ypos1, font_nr, "Virtual buttons");
     DrawTextSCentered(ypos2, font_nr, "configured!");
 
-    while (!DelayReached(&wait_frame_delay, wait_frame_delay_value))
+    while (!DelayReached(&wait_frame_delay))
       BackToFront();
 
     ClearEventQueue();
@@ -9724,6 +9825,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));
@@ -10445,6 +10550,9 @@ static boolean OfferUploadTapes(void)
               "Upload all your tapes to the high score server now?", REQ_ASK))
     return FALSE;
 
+  // when uploading tapes, make sure that high score server is enabled
+  runtime.use_api_server = setup.use_api_server = TRUE;
+
   int num_tapes_uploaded = UploadTapes();
   char message[100];