added support for level specific information text messages
authorHolger Schemel <holger.schemel@virtion.de>
Wed, 9 Oct 2024 17:05:07 +0000 (19:05 +0200)
committerHolger Schemel <holger.schemel@virtion.de>
Wed, 9 Oct 2024 17:13:58 +0000 (19:13 +0200)
By placing a text file into "docs/levels/XXX.txt" inside the level set
directory (or directly inside the level set directory just next to the
level file itself), the text message can be shown by pressing the
envelope icon that appears in the bottom left corner of the main menu
(similar to the level set information by the icon on the bottom right,
if available).

The level information text can also be accessed from the info screen.

src/conf_gfx.c
src/conf_mus.c
src/conf_snd.c
src/init.c
src/libgame/setup.c
src/libgame/setup.h
src/libgame/system.h
src/main.c
src/main.h
src/screens.c

index 8eece06a55bcfb487de041af6378b53cbd8e6d06..885f915142c776f0b5eb2d8aa9ba7819241ce772 100644 (file)
@@ -7379,6 +7379,13 @@ struct ConfigInfo image_config[] =
   { "menu.button_levelset_info.active",                        UNDEFINED_FILENAME              },
   { "menu.button_levelset_info.active.clone_from",     "envelope_1"                    },
 
+  { "menu.button_level_info",                          UNDEFINED_FILENAME              },
+  { "menu.button_level_info.clone_from",               "envelope_2"                    },
+  { "menu.button_level_info.pressed",                  UNDEFINED_FILENAME              },
+  { "menu.button_level_info.pressed.clone_from",       "envelope_2.collecting"         },
+  { "menu.button_level_info.active",                   UNDEFINED_FILENAME              },
+  { "menu.button_level_info.active.clone_from",                "envelope_2"                    },
+
   { "menu.button_switch_ecs_aga",                      UNDEFINED_FILENAME              },
   { "menu.button_switch_ecs_aga.active",               UNDEFINED_FILENAME              },
 
@@ -8050,6 +8057,9 @@ struct ConfigInfo image_config[] =
   { "font.info.levelset",                              UNDEFINED_FILENAME              },
   { "font.info.levelset.clone_from",                   "font.level_number"             },
 
+  { "font.info.level",                                 UNDEFINED_FILENAME              },
+  { "font.info.level.clone_from",                      "font.level_number"             },
+
   { "font.main.network_players",                       UNDEFINED_FILENAME              },
   { "font.main.network_players.clone_from",            "font.level_number"             },
 
@@ -8581,6 +8591,7 @@ struct ConfigInfo image_config[] =
   { "background.INFO[PROGRAM]",                                UNDEFINED_FILENAME              },
   { "background.INFO[VERSION]",                                UNDEFINED_FILENAME              },
   { "background.INFO[LEVELSET]",                       UNDEFINED_FILENAME              },
+  { "background.INFO[LEVEL]",                          UNDEFINED_FILENAME              },
   { "background.SETUP",                                        UNDEFINED_FILENAME              },
   { "background.PLAYING",                              UNDEFINED_FILENAME              },
   { "background.DOOR",                                 UNDEFINED_FILENAME              },
@@ -9215,6 +9226,8 @@ struct ConfigInfo image_config[] =
   { "menu.draw_yoffset.INFO[VERSION]",                 "0"                             },
   { "menu.draw_xoffset.INFO[LEVELSET]",                        "0"                             },
   { "menu.draw_yoffset.INFO[LEVELSET]",                        "0"                             },
+  { "menu.draw_xoffset.INFO[LEVEL]",                   "0"                             },
+  { "menu.draw_yoffset.INFO[LEVEL]",                   "0"                             },
   { "menu.draw_xoffset.SETUP",                         "0"                             },
   { "menu.draw_yoffset.SETUP",                         "0"                             },
   { "menu.draw_xoffset.SETUP[GAME]",                   "0"                             },
@@ -9279,6 +9292,7 @@ struct ConfigInfo image_config[] =
   { "menu.left_spacing.INFO[PROGRAM]",                 "16"                            },
   { "menu.left_spacing.INFO[VERSION]",                 "16"                            },
   { "menu.left_spacing.INFO[LEVELSET]",                        "16"                            },
+  { "menu.left_spacing.INFO[LEVEL]",                   "16"                            },
   { "menu.left_spacing.SETUP[INPUT]",                  "16"                            },
 
   { "menu.middle_spacing.INFO[ELEMENTS]",              "16"                            },
@@ -9292,6 +9306,7 @@ struct ConfigInfo image_config[] =
   { "menu.right_spacing.INFO[PROGRAM]",                        "16"                            },
   { "menu.right_spacing.INFO[VERSION]",                        "16"                            },
   { "menu.right_spacing.INFO[LEVELSET]",               "16"                            },
+  { "menu.right_spacing.INFO[LEVEL]",                  "16"                            },
   { "menu.right_spacing.SETUP[INPUT]",                 "16"                            },
 
   { "menu.top_spacing.SCOREINFO",                      "100"                           },
@@ -9303,6 +9318,7 @@ struct ConfigInfo image_config[] =
   { "menu.top_spacing.INFO[PROGRAM]",                  "100"                           },
   { "menu.top_spacing.INFO[VERSION]",                  "100"                           },
   { "menu.top_spacing.INFO[LEVELSET]",                 "100"                           },
+  { "menu.top_spacing.INFO[LEVEL]",                    "100"                           },
   { "menu.top_spacing.SETUP[INPUT]",                   "100"                           },
 
   { "menu.bottom_spacing.SCOREINFO",                   "20"                            },
@@ -9314,6 +9330,7 @@ struct ConfigInfo image_config[] =
   { "menu.bottom_spacing.INFO[PROGRAM]",               "20"                            },
   { "menu.bottom_spacing.INFO[VERSION]",               "20"                            },
   { "menu.bottom_spacing.INFO[LEVELSET]",              "20"                            },
+  { "menu.bottom_spacing.INFO[LEVEL]",                 "20"                            },
   { "menu.bottom_spacing.SETUP[INPUT]",                        "20"                            },
 
   { "menu.paragraph_spacing.SCOREINFO",                        "-2"                            },
@@ -9325,6 +9342,7 @@ struct ConfigInfo image_config[] =
   { "menu.paragraph_spacing.INFO[PROGRAM]",            "-3"                            },
   { "menu.paragraph_spacing.INFO[VERSION]",            "-2"                            },
   { "menu.paragraph_spacing.INFO[LEVELSET]",           "-3"                            },
+  { "menu.paragraph_spacing.INFO[LEVEL]",              "-3"                            },
   { "menu.paragraph_spacing.SETUP[INPUT]",             "-1"                            },
 
   { "menu.headline1_spacing.SCOREINFO",                        "-2"                            },
@@ -9336,6 +9354,7 @@ struct ConfigInfo image_config[] =
   { "menu.headline1_spacing.INFO[PROGRAM]",            "-2"                            },
   { "menu.headline1_spacing.INFO[VERSION]",            "-2"                            },
   { "menu.headline1_spacing.INFO[LEVELSET]",           "-2"                            },
+  { "menu.headline1_spacing.INFO[LEVEL]",              "-2"                            },
   { "menu.headline1_spacing.SETUP[INPUT]",             "-2"                            },
 
   { "menu.headline2_spacing.SCOREINFO",                        "-1"                            },
@@ -9347,6 +9366,7 @@ struct ConfigInfo image_config[] =
   { "menu.headline2_spacing.INFO[PROGRAM]",            "-1"                            },
   { "menu.headline2_spacing.INFO[VERSION]",            "-1"                            },
   { "menu.headline2_spacing.INFO[LEVELSET]",           "-1"                            },
+  { "menu.headline2_spacing.INFO[LEVEL]",              "-1"                            },
   { "menu.headline2_spacing.SETUP[INPUT]",             "-1"                            },
 
   { "menu.line_spacing.SCOREINFO",                     "0"                             },
@@ -9358,6 +9378,7 @@ struct ConfigInfo image_config[] =
   { "menu.line_spacing.INFO[PROGRAM]",                 "0"                             },
   { "menu.line_spacing.INFO[VERSION]",                 "0"                             },
   { "menu.line_spacing.INFO[LEVELSET]",                        "0"                             },
+  { "menu.line_spacing.INFO[LEVEL]",                   "0"                             },
   { "menu.line_spacing.SETUP[INPUT]",                  "0"                             },
 
   { "menu.extra_spacing.SCOREINFO",                    "2"                             },
@@ -9369,6 +9390,7 @@ struct ConfigInfo image_config[] =
   { "menu.extra_spacing.INFO[PROGRAM]",                        "2"                             },
   { "menu.extra_spacing.INFO[VERSION]",                        "2"                             },
   { "menu.extra_spacing.INFO[LEVELSET]",               "2"                             },
+  { "menu.extra_spacing.INFO[LEVEL]",                  "2"                             },
   { "menu.extra_spacing.SETUP[INPUT]",                 "2"                             },
 
   { "main.button.name.x",                              "0"                             },
@@ -9407,6 +9429,8 @@ struct ConfigInfo image_config[] =
 
   { "main.button.levelset_info.x",                     "-1"                            },
   { "main.button.levelset_info.y",                     "-1"                            },
+  { "main.button.level_info.x",                                "-1"                            },
+  { "main.button.level_info.y",                                "-1"                            },
 
   { "main.button.switch_ecs_aga.x",                    "-1"                            },
   { "main.button.switch_ecs_aga.y",                    "-1"                            },
index 0df8a79bf21f547d6aa7656f9d5524aca7ae57c5..786035afde214a1882878f3dccbec3b3eda6ad6c 100644 (file)
@@ -41,6 +41,7 @@ struct ConfigInfo music_config[] =
   { "background.INFO[PROGRAM]",                        UNDEFINED_FILENAME              },
   { "background.INFO[VERSION]",                        UNDEFINED_FILENAME              },
   { "background.INFO[LEVELSET]",               UNDEFINED_FILENAME              },
+  { "background.INFO[LEVEL]",                  UNDEFINED_FILENAME              },
   { "background.SETUP",                                UNDEFINED_FILENAME              },
 
   { "background.titlescreen_initial_1",                UNDEFINED_FILENAME              },
index 815ed40063c32a2eeed7b9b2f46726b083af8c0c..5b351495e49730c167863fb47fee93725cec95be 100644 (file)
@@ -424,6 +424,7 @@ struct ConfigInfo sound_config[] =
   { "background.INFO[PROGRAM]",                        UNDEFINED_FILENAME              },
   { "background.INFO[VERSION]",                        UNDEFINED_FILENAME              },
   { "background.INFO[LEVELSET]",               UNDEFINED_FILENAME              },
+  { "background.INFO[LEVEL]",                  UNDEFINED_FILENAME              },
   { "background.SETUP",                                UNDEFINED_FILENAME              },
 
   { "background.titlescreen_initial_1",                UNDEFINED_FILENAME              },
index cf131e54fd5558f75ac8e0d0b43e9d8faa2fc629..956a5d01bffb21b51f3dcaad428abfe893703e61 100644 (file)
@@ -1879,6 +1879,7 @@ static void InitGraphicInfo(void)
     IMG_BACKGROUND_INFO_PROGRAM,
     IMG_BACKGROUND_INFO_VERSION,
     IMG_BACKGROUND_INFO_LEVELSET,
+    IMG_BACKGROUND_INFO_LEVEL,
     IMG_BACKGROUND_SETUP,
     IMG_BACKGROUND_PLAYING,
     IMG_BACKGROUND_DOOR,
index 23170fa630da05b4eb9234266716ce7319982392..07e0969e42db573236e0d03ad070c8d419778b32 100644 (file)
@@ -870,6 +870,15 @@ static char *getLevelSetInfoBasename(int nr)
   return basename;
 }
 
+static char *getLevelInfoBasename(int level_nr)
+{
+  static char basename[32];
+
+  sprintf(basename, "%03d.txt", level_nr);
+
+  return basename;
+}
+
 char *getLevelSetInfoFilename(int nr)
 {
   char *basename = getLevelSetInfoBasename(nr);
@@ -881,7 +890,7 @@ char *getLevelSetInfoFilename(int nr)
 
   checked_free(filename);
 
-  // look for level set info file the current level set directory
+  // look for level set info file in the current level set's "docs/levelset" sub-directory
   filename = getPath3(getCurrentLevelDir(), info_subdir, basename);
   if (fileExists(filename))
     return filename;
@@ -903,6 +912,7 @@ char *getLevelSetInfoFilename(int nr)
   };
   int i;
 
+  // look for README style level set info file directly in the current level set directory
   for (i = 0; basenames[i] != NULL; i++)
   {
     checked_free(filename);
@@ -915,6 +925,32 @@ char *getLevelSetInfoFilename(int nr)
   return NULL;
 }
 
+char *getLevelInfoFilename(int level_nr)
+{
+  char *basename = getLevelInfoBasename(level_nr);
+  static char *info_subdir = NULL;
+  static char *filename = NULL;
+
+  if (info_subdir == NULL)
+    info_subdir = getPath2(DOCS_DIRECTORY, LEVEL_INFO_DIRECTORY);
+
+  checked_free(filename);
+
+  // look for level info file in the current level set's "docs/levels" sub-directory
+  filename = getPath3(getCurrentLevelDir(), info_subdir, basename);
+  if (fileExists(filename))
+    return filename;
+
+  checked_free(filename);
+
+  // look for level style level info file directly in the current level set directory
+  filename = getPath2(getCurrentLevelDir(), basename);
+  if (fileExists(filename))
+    return filename;
+
+  return NULL;
+}
+
 static char *getLevelSetTitleMessageBasename(int nr, boolean initial)
 {
   static char basename[32];
index 5692c9da39e56c5c035563ca569f860335c841db..c540b801dac4cc30c15b8ed06b4132ec9096837a 100644 (file)
@@ -283,6 +283,7 @@ char *getFilenameFromCurrentLevelDirUpward(char *);
 char *getHelpAnimFilename(void);
 char *getHelpTextFilename(void);
 char *getLevelSetInfoFilename(int);
+char *getLevelInfoFilename(int);
 char *getLevelSetTitleMessageFilename(int, boolean);
 char *getCreditsFilename(int, boolean);
 char *getProgramInfoFilename(int);
index 1c1b50be7bd6c82a91adb54dc46e0fa312cf4b2c..62547122f8715c4813e53aff07105f109d6a4890 100644 (file)
 #define CREDITS_DIRECTORY              "credits"
 #define PROGRAM_INFO_DIRECTORY         "program"
 #define LEVELSET_INFO_DIRECTORY                "levelset"
+#define LEVEL_INFO_DIRECTORY           "levels"
 #define CACHE_DIRECTORY                        "cache"
 #define CONF_DIRECTORY                 "conf"
 #define NETWORK_DIRECTORY              "network"
@@ -1460,6 +1461,7 @@ struct SetupInternalInfo
   boolean info_program;
   boolean info_version;
   boolean info_levelset;
+  boolean info_level;
   boolean info_exit;
 };
 
index 37e7cbbb2402403ac9449124d9bb97bbc3e01223..24e3ffd857d576185e87e1112e4df8253810fde7 100644 (file)
@@ -9378,6 +9378,7 @@ struct FontInfo font_info[NUM_FONTS + 1] =
   { "font.game_info"           },
   { "font.info.elements"       },
   { "font.info.levelset"       },
+  { "font.info.level"          },
   { "font.main.network_players"        },
 
   { NULL                       }
index 8b477c853dcfe6490a9e433166173792254c666c..7eb0e47f8e6623f6e1963b6350f99d3787af42f8 100644 (file)
@@ -2856,6 +2856,7 @@ enum
   GFX_SPECIAL_ARG_INFO_PROGRAM,
   GFX_SPECIAL_ARG_INFO_VERSION,
   GFX_SPECIAL_ARG_INFO_LEVELSET,
+  GFX_SPECIAL_ARG_INFO_LEVEL,
 
   NUM_SPECIAL_GFX_INFO_ARGS
 };
@@ -3031,6 +3032,7 @@ enum
   FONT_GAME_INFO,
   FONT_INFO_ELEMENTS,
   FONT_INFO_LEVELSET,
+  FONT_INFO_LEVEL,
   FONT_MAIN_NETWORK_PLAYERS,
 
   NUM_FONTS
@@ -3261,6 +3263,7 @@ struct MenuMainButtonInfo
   struct MenuPosInfo play_solution;
 
   struct MenuPosInfo levelset_info;
+  struct MenuPosInfo level_info;
   struct MenuPosInfo switch_ecs_aga;
 };
 
index 5e7bd315b7b7395e747e7c686cee545bbc1655b4..b902af3f1fce66034fcab0b740e891354fdbf74e 100644 (file)
@@ -37,8 +37,9 @@
 #define INFO_MODE_PROGRAM                      5
 #define INFO_MODE_VERSION                      6
 #define INFO_MODE_LEVELSET                     7
+#define INFO_MODE_LEVEL                                8
 
-#define MAX_INFO_MODES                         8
+#define MAX_INFO_MODES                         9
 
 // screens on the setup screen
 // (must match GFX_SPECIAL_ARG_SETUP_* values as defined in src/main.h)
 #define STR_INFO_PROGRAM                       "Program Info"
 #define STR_INFO_VERSION                       "Version Info"
 #define STR_INFO_LEVELSET                      "Level Set Info"
+#define STR_INFO_LEVEL                         "Level Info"
 #define STR_INFO_EXIT                          "Exit"
 
 // setup screen titles
 #define SCREEN_CTRL_ID_INSERT_SOLUTION         12
 #define SCREEN_CTRL_ID_PLAY_SOLUTION           13
 #define SCREEN_CTRL_ID_LEVELSET_INFO           14
-#define SCREEN_CTRL_ID_SWITCH_ECS_AGA          15
-#define SCREEN_CTRL_ID_TOUCH_PREV_PAGE         16
-#define SCREEN_CTRL_ID_TOUCH_NEXT_PAGE         17
-#define SCREEN_CTRL_ID_TOUCH_PREV_PAGE2                18
-#define SCREEN_CTRL_ID_TOUCH_NEXT_PAGE2                19
+#define SCREEN_CTRL_ID_LEVEL_INFO              15
+#define SCREEN_CTRL_ID_SWITCH_ECS_AGA          16
+#define SCREEN_CTRL_ID_TOUCH_PREV_PAGE         17
+#define SCREEN_CTRL_ID_TOUCH_NEXT_PAGE         18
+#define SCREEN_CTRL_ID_TOUCH_PREV_PAGE2                19
+#define SCREEN_CTRL_ID_TOUCH_NEXT_PAGE2                20
 
-#define NUM_SCREEN_MENUBUTTONS                 20
+#define NUM_SCREEN_MENUBUTTONS                 21
 
-#define SCREEN_CTRL_ID_SCROLL_UP               20
-#define SCREEN_CTRL_ID_SCROLL_DOWN             21
-#define SCREEN_CTRL_ID_SCROLL_VERTICAL         22
-#define SCREEN_CTRL_ID_NETWORK_SERVER          23
+#define SCREEN_CTRL_ID_SCROLL_UP               21
+#define SCREEN_CTRL_ID_SCROLL_DOWN             22
+#define SCREEN_CTRL_ID_SCROLL_VERTICAL         23
+#define SCREEN_CTRL_ID_NETWORK_SERVER          24
 
-#define NUM_SCREEN_GADGETS                     24
+#define NUM_SCREEN_GADGETS                     25
 
 #define NUM_SCREEN_SCROLLBUTTONS               2
 #define NUM_SCREEN_SCROLLBARS                  1
 
 #define SCREEN_MASK_MAIN                       (1 << 0)
 #define SCREEN_MASK_MAIN_HAS_SOLUTION          (1 << 1)
-#define SCREEN_MASK_MAIN_HAS_SET_INFO          (1 << 2)
-#define SCREEN_MASK_INPUT                      (1 << 3)
-#define SCREEN_MASK_TOUCH                      (1 << 4)
-#define SCREEN_MASK_TOUCH2                     (1 << 5)
-#define SCREEN_MASK_SCORES                     (1 << 6)
-#define SCREEN_MASK_SCORES_INFO                        (1 << 7)
+#define SCREEN_MASK_MAIN_HAS_LEVELSET_INFO     (1 << 2)
+#define SCREEN_MASK_MAIN_HAS_LEVEL_INFO                (1 << 3)
+#define SCREEN_MASK_INPUT                      (1 << 4)
+#define SCREEN_MASK_TOUCH                      (1 << 5)
+#define SCREEN_MASK_TOUCH2                     (1 << 6)
+#define SCREEN_MASK_SCORES                     (1 << 7)
+#define SCREEN_MASK_SCORES_INFO                        (1 << 8)
 
 // graphic position and size values for buttons and scrollbars
 #define SC_MENUBUTTON_XSIZE                    TILEX
@@ -720,7 +724,7 @@ static int align_yoffset = 0;
 
 // (there are no draw offset definitions needed for INFO_MODE_TITLE)
 #define DRAW_MODE_INFO(i)      ((i) >= INFO_MODE_TITLE &&                      \
-                                (i) <= INFO_MODE_LEVELSET ? (i) :              \
+                                (i) <= INFO_MODE_LEVEL ? (i) :                 \
                                 INFO_MODE_MAIN)
 
 #define DRAW_MODE_SETUP(i)     ((i) >= SETUP_MODE_MAIN &&                      \
@@ -1033,6 +1037,11 @@ static boolean hasLevelSetInfo(void)
   return (getLevelSetInfoFilename(0) != NULL);
 }
 
+static boolean hasLevelInfo(void)
+{
+  return (getLevelInfoFilename(level_nr) != NULL);
+}
+
 static int getTitleScreenGraphic(int nr, boolean initial)
 {
   return (initial ? IMG_TITLESCREEN_INITIAL_1 : IMG_TITLESCREEN_1) + nr;
@@ -1688,6 +1697,10 @@ static void DrawInfoScreen_Headline(int screen_nr, int num_screens,
   {
     sprintf(info_text_title_2, "Page %d of %d", screen_nr + 1, num_screens);
   }
+  else if (info_mode == INFO_MODE_LEVEL)
+  {
+    snprintf(info_text_title_2, MAX_LINE_LEN, "for level %d", level_nr);
+  }
   else
   {
     char *text_format = (use_global_screens ? "for %s" : "for \"%s\"");
@@ -2002,7 +2015,8 @@ void DrawMainMenu(void)
   MapTapeButtons();
   MapScreenMenuGadgets(SCREEN_MASK_MAIN);
   UpdateScreenMenuGadgets(SCREEN_MASK_MAIN_HAS_SOLUTION, hasSolutionTape());
-  UpdateScreenMenuGadgets(SCREEN_MASK_MAIN_HAS_SET_INFO, hasLevelSetInfo());
+  UpdateScreenMenuGadgets(SCREEN_MASK_MAIN_HAS_LEVELSET_INFO, hasLevelSetInfo());
+  UpdateScreenMenuGadgets(SCREEN_MASK_MAIN_HAS_LEVEL_INFO, hasLevelInfo());
 
   // copy actual game door content to door double buffer for OpenDoor()
   BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
@@ -2324,6 +2338,7 @@ static void HandleMainMenu_SelectLevel(int step, int direction,
     SaveLevelSetup_SeriesInfo();
 
     UpdateScreenMenuGadgets(SCREEN_MASK_MAIN_HAS_SOLUTION, hasSolutionTape());
+    UpdateScreenMenuGadgets(SCREEN_MASK_MAIN_HAS_LEVEL_INFO, hasLevelInfo());
 
     // force redraw of playfield area (may be reset at this point)
     redraw_mask |= REDRAW_FIELD;
@@ -2607,6 +2622,13 @@ static void execInfoLevelSet(void)
   DrawInfoScreen();
 }
 
+static void execInfoLevel(void)
+{
+  info_mode = INFO_MODE_LEVEL;
+
+  DrawInfoScreen();
+}
+
 static void execExitInfo(void)
 {
   SetGameStatus(GAME_MODE_MAIN);
@@ -2623,6 +2645,7 @@ static struct TokenInfo info_info_main[] =
   { TYPE_ENTER_SCREEN, execInfoProgram,        STR_INFO_PROGRAM        },
   { TYPE_ENTER_SCREEN, execInfoVersion,        STR_INFO_VERSION        },
   { TYPE_ENTER_SCREEN, execInfoLevelSet,       STR_INFO_LEVELSET       },
+  { TYPE_ENTER_SCREEN, execInfoLevel,          STR_INFO_LEVEL          },
   { TYPE_EMPTY,                NULL,                   ""                      },
   { TYPE_LEAVE_MENU,   execExitInfo,           STR_INFO_EXIT           },
 
@@ -4012,6 +4035,7 @@ static char *getInfoScreenTitle_Generic(void)
          info_mode == INFO_MODE_PROGRAM  ? STR_INFO_PROGRAM  :
          info_mode == INFO_MODE_VERSION  ? STR_INFO_VERSION  :
          info_mode == INFO_MODE_LEVELSET ? STR_INFO_LEVELSET :
+         info_mode == INFO_MODE_LEVEL    ? STR_INFO_LEVEL    :
          "");
 }
 
@@ -4023,6 +4047,7 @@ static int getInfoScreenBackgroundImage_Generic(void)
          info_mode == INFO_MODE_PROGRAM  ? IMG_BACKGROUND_INFO_PROGRAM  :
          info_mode == INFO_MODE_VERSION  ? IMG_BACKGROUND_INFO_VERSION  :
          info_mode == INFO_MODE_LEVELSET ? IMG_BACKGROUND_INFO_LEVELSET :
+         info_mode == INFO_MODE_LEVEL    ? IMG_BACKGROUND_INFO_LEVEL    :
          IMG_BACKGROUND_INFO);
 }
 
@@ -4033,6 +4058,7 @@ static int getInfoScreenBackgroundSound_Generic(void)
          info_mode == INFO_MODE_PROGRAM  ? SND_BACKGROUND_INFO_PROGRAM  :
          info_mode == INFO_MODE_VERSION  ? SND_BACKGROUND_INFO_VERSION  :
          info_mode == INFO_MODE_LEVELSET ? SND_BACKGROUND_INFO_LEVELSET :
+         info_mode == INFO_MODE_LEVEL    ? SND_BACKGROUND_INFO_LEVEL    :
          SND_BACKGROUND_INFO);
 }
 
@@ -4043,6 +4069,7 @@ static int getInfoScreenBackgroundMusic_Generic(void)
          info_mode == INFO_MODE_PROGRAM  ? MUS_BACKGROUND_INFO_PROGRAM  :
          info_mode == INFO_MODE_VERSION  ? MUS_BACKGROUND_INFO_VERSION  :
          info_mode == INFO_MODE_LEVELSET ? MUS_BACKGROUND_INFO_LEVELSET :
+         info_mode == INFO_MODE_LEVEL    ? MUS_BACKGROUND_INFO_LEVEL    :
          MUS_BACKGROUND_INFO);
 }
 
@@ -4051,6 +4078,7 @@ static char *getInfoScreenFilename_Generic(int nr, boolean global)
   return (info_mode == INFO_MODE_CREDITS  ? getCreditsFilename(nr, global) :
          info_mode == INFO_MODE_PROGRAM  ? getProgramInfoFilename(nr)     :
          info_mode == INFO_MODE_LEVELSET ? getLevelSetInfoFilename(nr)    :
+         info_mode == INFO_MODE_LEVEL    ? getLevelInfoFilename(level_nr) :
          NULL);
 }
 
@@ -4086,9 +4114,12 @@ static void DrawInfoScreen_GenericScreen(int screen_nr, int num_screens,
                 filename, font_text, chars, -1, lines, line_spacing, -1,
                 autowrap, centered, parse_comments);
   }
-  else if (info_mode == INFO_MODE_LEVELSET)
+  else if (info_mode == INFO_MODE_LEVELSET ||
+           info_mode == INFO_MODE_LEVEL)
   {
     struct TitleMessageInfo *tmi = &readme;
+    int font = (info_mode == INFO_MODE_LEVEL && tmi->font == FONT_INFO_LEVELSET ? FONT_INFO_LEVEL :
+                tmi->font);
 
     // if x position set to "-1", automatically determine by playfield width
     if (tmi->x == -1)
@@ -4119,7 +4150,7 @@ static void DrawInfoScreen_GenericScreen(int screen_nr, int num_screens,
       tmi->height = tmi->lines * getFontHeight(tmi->font);
 
     DrawTextFile(mSX + ALIGNED_TEXT_XPOS(tmi), mSY + ALIGNED_TEXT_YPOS(tmi),
-                filename, tmi->font, tmi->chars, -1, tmi->lines, 0, -1,
+                filename, font, tmi->chars, -1, tmi->lines, 0, -1,
                 tmi->autowrap, tmi->centered, tmi->parse_comments);
   }
 
@@ -4195,6 +4226,16 @@ void HandleInfoScreen_Generic(int dx, int dy, int button)
 
       text_no_info = "No level set info available.";
     }
+    else if (info_mode == INFO_MODE_LEVEL)
+    {
+      use_global_screens = FALSE;
+
+      // determine number of level info screens
+      if (getLevelInfoFilename(level_nr) != NULL)
+       num_screens = 1;
+
+      text_no_info = "No level info available.";
+    }
 
     if (num_screens == 0)
     {
@@ -4268,6 +4309,8 @@ static void DrawInfoScreen(void)
     DrawInfoScreen_Version();
   else if (info_mode == INFO_MODE_LEVELSET)
     DrawInfoScreen_Generic();
+  else if (info_mode == INFO_MODE_LEVEL)
+    DrawInfoScreen_Generic();
   else
     DrawInfoScreen_Main();
 }
@@ -4325,6 +4368,8 @@ void HandleInfoScreen(int mx, int my, int dx, int dy, int button)
     HandleInfoScreen_Version(button);
   else if (info_mode == INFO_MODE_LEVELSET)
     HandleInfoScreen_Generic(dx, dy, button);
+  else if (info_mode == INFO_MODE_LEVEL)
+    HandleInfoScreen_Generic(dx, dy, button);
   else
     HandleInfoScreen_Main(mx, my, dx, dy, button);
 }
@@ -7909,6 +7954,7 @@ static struct
   { &setup.internal.info_program,              execInfoProgram                 },
   { &setup.internal.info_version,              execInfoVersion                 },
   { &setup.internal.info_levelset,             execInfoLevelSet                },
+  { &setup.internal.info_level,                        execInfoLevel                   },
   { &setup.internal.info_exit,                 execExitInfo                    },
 
   { NULL,                                      NULL                            }
@@ -10332,10 +10378,19 @@ static struct
     IMG_MENU_BUTTON_LEVELSET_INFO_ACTIVE,
     &menu.main.button.levelset_info, NULL,
     SCREEN_CTRL_ID_LEVELSET_INFO,
-    SCREEN_MASK_MAIN_HAS_SET_INFO,
+    SCREEN_MASK_MAIN_HAS_LEVELSET_INFO,
     GD_EVENT_RELEASED,
     FALSE, "show level set info"
   },
+  {
+    IMG_MENU_BUTTON_LEVEL_INFO, IMG_MENU_BUTTON_LEVEL_INFO_PRESSED,
+    IMG_MENU_BUTTON_LEVEL_INFO_ACTIVE,
+    &menu.main.button.level_info, NULL,
+    SCREEN_CTRL_ID_LEVEL_INFO,
+    SCREEN_MASK_MAIN_HAS_LEVEL_INFO,
+    GD_EVENT_RELEASED,
+    FALSE, "show level info"
+  },
   {
     IMG_MENU_BUTTON_SWITCH_ECS_AGA, IMG_MENU_BUTTON_SWITCH_ECS_AGA_ACTIVE, -1,
     &menu.main.button.switch_ecs_aga, &setup.prefer_aga_graphics,
@@ -10570,6 +10625,15 @@ static void CreateScreenMenubuttons(void)
        }
       }
     }
+    else if (id == SCREEN_CTRL_ID_LEVEL_INFO)
+    {
+      if (pos->x == -1 && pos->y == -1)
+      {
+       // use "SX" here to place button (ignore draw offsets)
+       x = SX + TILESIZE;
+       y = SY + SYSIZE - 2 * TILESIZE;
+      }
+    }
 
     gi = CreateGadget(GDI_CUSTOM_ID, id,
                      GDI_CUSTOM_TYPE_ID, i,
@@ -10840,7 +10904,9 @@ static void UnmapScreenMenuGadgets(int screen_mask)
     {
       UnmapGadget(screen_gadget[menubutton_info[i].gadget_id]);
 
-      if (screen_mask & SCREEN_MASK_MAIN_HAS_SOLUTION)
+      // undraw buttons for solution tapes or level info that may not exist for the selected level
+      if (screen_mask & SCREEN_MASK_MAIN_HAS_SOLUTION ||
+          screen_mask & SCREEN_MASK_MAIN_HAS_LEVEL_INFO)
        DrawBackground(screen_gadget[menubutton_info[i].gadget_id]->x,
                       screen_gadget[menubutton_info[i].gadget_id]->y,
                       screen_gadget[menubutton_info[i].gadget_id]->width,
@@ -10991,6 +11057,10 @@ static void HandleScreenGadgets(struct GadgetInfo *gi)
       DrawInfoScreen_FromMainMenu(INFO_MODE_LEVELSET);
       break;
 
+    case SCREEN_CTRL_ID_LEVEL_INFO:
+      DrawInfoScreen_FromMainMenu(INFO_MODE_LEVEL);
+      break;
+
     case SCREEN_CTRL_ID_SWITCH_ECS_AGA:
       setup.prefer_aga_graphics = !setup.prefer_aga_graphics;
       DrawMainMenu();