added showing optional title screen for native BD cavesets
[rocksndiamonds.git] / src / game.c
index 2372aac5f4106df0f5eb1610271cc96df6c1bc9e..9127a1b6a5be48bc22c593b6ff728b8a02269192 100644 (file)
 // game panel display and control definitions
 #define GAME_PANEL_LEVEL_NUMBER                        0
 #define GAME_PANEL_GEMS                                1
-#define GAME_PANEL_INVENTORY_COUNT             2
-#define GAME_PANEL_INVENTORY_FIRST_1           3
-#define GAME_PANEL_INVENTORY_FIRST_2           4
-#define GAME_PANEL_INVENTORY_FIRST_3           5
-#define GAME_PANEL_INVENTORY_FIRST_4           6
-#define GAME_PANEL_INVENTORY_FIRST_5           7
-#define GAME_PANEL_INVENTORY_FIRST_6           8
-#define GAME_PANEL_INVENTORY_FIRST_7           9
-#define GAME_PANEL_INVENTORY_FIRST_8           10
-#define GAME_PANEL_INVENTORY_LAST_1            11
-#define GAME_PANEL_INVENTORY_LAST_2            12
-#define GAME_PANEL_INVENTORY_LAST_3            13
-#define GAME_PANEL_INVENTORY_LAST_4            14
-#define GAME_PANEL_INVENTORY_LAST_5            15
-#define GAME_PANEL_INVENTORY_LAST_6            16
-#define GAME_PANEL_INVENTORY_LAST_7            17
-#define GAME_PANEL_INVENTORY_LAST_8            18
-#define GAME_PANEL_KEY_1                       19
-#define GAME_PANEL_KEY_2                       20
-#define GAME_PANEL_KEY_3                       21
-#define GAME_PANEL_KEY_4                       22
-#define GAME_PANEL_KEY_5                       23
-#define GAME_PANEL_KEY_6                       24
-#define GAME_PANEL_KEY_7                       25
-#define GAME_PANEL_KEY_8                       26
-#define GAME_PANEL_KEY_WHITE                   27
-#define GAME_PANEL_KEY_WHITE_COUNT             28
-#define GAME_PANEL_SCORE                       29
-#define GAME_PANEL_HIGHSCORE                   30
-#define GAME_PANEL_TIME                                31
-#define GAME_PANEL_TIME_HH                     32
-#define GAME_PANEL_TIME_MM                     33
-#define GAME_PANEL_TIME_SS                     34
-#define GAME_PANEL_TIME_ANIM                   35
-#define GAME_PANEL_HEALTH                      36
-#define GAME_PANEL_HEALTH_ANIM                 37
-#define GAME_PANEL_FRAME                       38
-#define GAME_PANEL_SHIELD_NORMAL               39
-#define GAME_PANEL_SHIELD_NORMAL_TIME          40
-#define GAME_PANEL_SHIELD_DEADLY               41
-#define GAME_PANEL_SHIELD_DEADLY_TIME          42
-#define GAME_PANEL_EXIT                                43
-#define GAME_PANEL_EMC_MAGIC_BALL              44
-#define GAME_PANEL_EMC_MAGIC_BALL_SWITCH       45
-#define GAME_PANEL_LIGHT_SWITCH                        46
-#define GAME_PANEL_LIGHT_SWITCH_TIME           47
-#define GAME_PANEL_TIMEGATE_SWITCH             48
-#define GAME_PANEL_TIMEGATE_SWITCH_TIME                49
-#define GAME_PANEL_SWITCHGATE_SWITCH           50
-#define GAME_PANEL_EMC_LENSES                  51
-#define GAME_PANEL_EMC_LENSES_TIME             52
-#define GAME_PANEL_EMC_MAGNIFIER               53
-#define GAME_PANEL_EMC_MAGNIFIER_TIME          54
-#define GAME_PANEL_BALLOON_SWITCH              55
-#define GAME_PANEL_DYNABOMB_NUMBER             56
-#define GAME_PANEL_DYNABOMB_SIZE               57
-#define GAME_PANEL_DYNABOMB_POWER              58
-#define GAME_PANEL_PENGUINS                    59
-#define GAME_PANEL_SOKOBAN_OBJECTS             60
-#define GAME_PANEL_SOKOBAN_FIELDS              61
-#define GAME_PANEL_ROBOT_WHEEL                 62
-#define GAME_PANEL_CONVEYOR_BELT_1             63
-#define GAME_PANEL_CONVEYOR_BELT_2             64
-#define GAME_PANEL_CONVEYOR_BELT_3             65
-#define GAME_PANEL_CONVEYOR_BELT_4             66
-#define GAME_PANEL_CONVEYOR_BELT_1_SWITCH      67
-#define GAME_PANEL_CONVEYOR_BELT_2_SWITCH      68
-#define GAME_PANEL_CONVEYOR_BELT_3_SWITCH      69
-#define GAME_PANEL_CONVEYOR_BELT_4_SWITCH      70
-#define GAME_PANEL_MAGIC_WALL                  71
-#define GAME_PANEL_MAGIC_WALL_TIME             72
-#define GAME_PANEL_GRAVITY_STATE               73
-#define GAME_PANEL_GRAPHIC_1                   74
-#define GAME_PANEL_GRAPHIC_2                   75
-#define GAME_PANEL_GRAPHIC_3                   76
-#define GAME_PANEL_GRAPHIC_4                   77
-#define GAME_PANEL_GRAPHIC_5                   78
-#define GAME_PANEL_GRAPHIC_6                   79
-#define GAME_PANEL_GRAPHIC_7                   80
-#define GAME_PANEL_GRAPHIC_8                   81
-#define GAME_PANEL_ELEMENT_1                   82
-#define GAME_PANEL_ELEMENT_2                   83
-#define GAME_PANEL_ELEMENT_3                   84
-#define GAME_PANEL_ELEMENT_4                   85
-#define GAME_PANEL_ELEMENT_5                   86
-#define GAME_PANEL_ELEMENT_6                   87
-#define GAME_PANEL_ELEMENT_7                   88
-#define GAME_PANEL_ELEMENT_8                   89
-#define GAME_PANEL_ELEMENT_COUNT_1             90
-#define GAME_PANEL_ELEMENT_COUNT_2             91
-#define GAME_PANEL_ELEMENT_COUNT_3             92
-#define GAME_PANEL_ELEMENT_COUNT_4             93
-#define GAME_PANEL_ELEMENT_COUNT_5             94
-#define GAME_PANEL_ELEMENT_COUNT_6             95
-#define GAME_PANEL_ELEMENT_COUNT_7             96
-#define GAME_PANEL_ELEMENT_COUNT_8             97
-#define GAME_PANEL_CE_SCORE_1                  98
-#define GAME_PANEL_CE_SCORE_2                  99
-#define GAME_PANEL_CE_SCORE_3                  100
-#define GAME_PANEL_CE_SCORE_4                  101
-#define GAME_PANEL_CE_SCORE_5                  102
-#define GAME_PANEL_CE_SCORE_6                  103
-#define GAME_PANEL_CE_SCORE_7                  104
-#define GAME_PANEL_CE_SCORE_8                  105
-#define GAME_PANEL_CE_SCORE_1_ELEMENT          106
-#define GAME_PANEL_CE_SCORE_2_ELEMENT          107
-#define GAME_PANEL_CE_SCORE_3_ELEMENT          108
-#define GAME_PANEL_CE_SCORE_4_ELEMENT          109
-#define GAME_PANEL_CE_SCORE_5_ELEMENT          110
-#define GAME_PANEL_CE_SCORE_6_ELEMENT          111
-#define GAME_PANEL_CE_SCORE_7_ELEMENT          112
-#define GAME_PANEL_CE_SCORE_8_ELEMENT          113
-#define GAME_PANEL_PLAYER_NAME                 114
-#define GAME_PANEL_LEVEL_NAME                  115
-#define GAME_PANEL_LEVEL_AUTHOR                        116
-
-#define NUM_GAME_PANEL_CONTROLS                        117
+#define GAME_PANEL_GEMS_NEEDED                 2
+#define GAME_PANEL_GEMS_COLLECTED              3
+#define GAME_PANEL_GEMS_SCORE                  4
+#define GAME_PANEL_INVENTORY_COUNT             5
+#define GAME_PANEL_INVENTORY_FIRST_1           6
+#define GAME_PANEL_INVENTORY_FIRST_2           7
+#define GAME_PANEL_INVENTORY_FIRST_3           8
+#define GAME_PANEL_INVENTORY_FIRST_4           9
+#define GAME_PANEL_INVENTORY_FIRST_5           10
+#define GAME_PANEL_INVENTORY_FIRST_6           11
+#define GAME_PANEL_INVENTORY_FIRST_7           12
+#define GAME_PANEL_INVENTORY_FIRST_8           13
+#define GAME_PANEL_INVENTORY_LAST_1            14
+#define GAME_PANEL_INVENTORY_LAST_2            15
+#define GAME_PANEL_INVENTORY_LAST_3            16
+#define GAME_PANEL_INVENTORY_LAST_4            17
+#define GAME_PANEL_INVENTORY_LAST_5            18
+#define GAME_PANEL_INVENTORY_LAST_6            19
+#define GAME_PANEL_INVENTORY_LAST_7            20
+#define GAME_PANEL_INVENTORY_LAST_8            21
+#define GAME_PANEL_KEY_1                       22
+#define GAME_PANEL_KEY_2                       23
+#define GAME_PANEL_KEY_3                       24
+#define GAME_PANEL_KEY_4                       25
+#define GAME_PANEL_KEY_5                       26
+#define GAME_PANEL_KEY_6                       27
+#define GAME_PANEL_KEY_7                       28
+#define GAME_PANEL_KEY_8                       29
+#define GAME_PANEL_KEY_WHITE                   30
+#define GAME_PANEL_KEY_WHITE_COUNT             31
+#define GAME_PANEL_SCORE                       32
+#define GAME_PANEL_HIGHSCORE                   33
+#define GAME_PANEL_TIME                                34
+#define GAME_PANEL_TIME_HH                     35
+#define GAME_PANEL_TIME_MM                     36
+#define GAME_PANEL_TIME_SS                     37
+#define GAME_PANEL_TIME_ANIM                   38
+#define GAME_PANEL_HEALTH                      39
+#define GAME_PANEL_HEALTH_ANIM                 40
+#define GAME_PANEL_FRAME                       41
+#define GAME_PANEL_SHIELD_NORMAL               42
+#define GAME_PANEL_SHIELD_NORMAL_TIME          43
+#define GAME_PANEL_SHIELD_DEADLY               44
+#define GAME_PANEL_SHIELD_DEADLY_TIME          45
+#define GAME_PANEL_EXIT                                46
+#define GAME_PANEL_EMC_MAGIC_BALL              47
+#define GAME_PANEL_EMC_MAGIC_BALL_SWITCH       48
+#define GAME_PANEL_LIGHT_SWITCH                        49
+#define GAME_PANEL_LIGHT_SWITCH_TIME           50
+#define GAME_PANEL_TIMEGATE_SWITCH             51
+#define GAME_PANEL_TIMEGATE_SWITCH_TIME                52
+#define GAME_PANEL_SWITCHGATE_SWITCH           53
+#define GAME_PANEL_EMC_LENSES                  54
+#define GAME_PANEL_EMC_LENSES_TIME             55
+#define GAME_PANEL_EMC_MAGNIFIER               56
+#define GAME_PANEL_EMC_MAGNIFIER_TIME          57
+#define GAME_PANEL_BALLOON_SWITCH              58
+#define GAME_PANEL_DYNABOMB_NUMBER             59
+#define GAME_PANEL_DYNABOMB_SIZE               60
+#define GAME_PANEL_DYNABOMB_POWER              61
+#define GAME_PANEL_PENGUINS                    62
+#define GAME_PANEL_SOKOBAN_OBJECTS             63
+#define GAME_PANEL_SOKOBAN_FIELDS              64
+#define GAME_PANEL_ROBOT_WHEEL                 65
+#define GAME_PANEL_CONVEYOR_BELT_1             66
+#define GAME_PANEL_CONVEYOR_BELT_2             67
+#define GAME_PANEL_CONVEYOR_BELT_3             68
+#define GAME_PANEL_CONVEYOR_BELT_4             69
+#define GAME_PANEL_CONVEYOR_BELT_1_SWITCH      70
+#define GAME_PANEL_CONVEYOR_BELT_2_SWITCH      71
+#define GAME_PANEL_CONVEYOR_BELT_3_SWITCH      72
+#define GAME_PANEL_CONVEYOR_BELT_4_SWITCH      73
+#define GAME_PANEL_MAGIC_WALL                  74
+#define GAME_PANEL_MAGIC_WALL_TIME             75
+#define GAME_PANEL_GRAVITY_STATE               76
+#define GAME_PANEL_GRAPHIC_1                   77
+#define GAME_PANEL_GRAPHIC_2                   78
+#define GAME_PANEL_GRAPHIC_3                   79
+#define GAME_PANEL_GRAPHIC_4                   80
+#define GAME_PANEL_GRAPHIC_5                   81
+#define GAME_PANEL_GRAPHIC_6                   82
+#define GAME_PANEL_GRAPHIC_7                   83
+#define GAME_PANEL_GRAPHIC_8                   84
+#define GAME_PANEL_ELEMENT_1                   85
+#define GAME_PANEL_ELEMENT_2                   86
+#define GAME_PANEL_ELEMENT_3                   87
+#define GAME_PANEL_ELEMENT_4                   88
+#define GAME_PANEL_ELEMENT_5                   89
+#define GAME_PANEL_ELEMENT_6                   90
+#define GAME_PANEL_ELEMENT_7                   91
+#define GAME_PANEL_ELEMENT_8                   92
+#define GAME_PANEL_ELEMENT_COUNT_1             93
+#define GAME_PANEL_ELEMENT_COUNT_2             94
+#define GAME_PANEL_ELEMENT_COUNT_3             95
+#define GAME_PANEL_ELEMENT_COUNT_4             96
+#define GAME_PANEL_ELEMENT_COUNT_5             97
+#define GAME_PANEL_ELEMENT_COUNT_6             98
+#define GAME_PANEL_ELEMENT_COUNT_7             99
+#define GAME_PANEL_ELEMENT_COUNT_8             100
+#define GAME_PANEL_CE_SCORE_1                  101
+#define GAME_PANEL_CE_SCORE_2                  102
+#define GAME_PANEL_CE_SCORE_3                  103
+#define GAME_PANEL_CE_SCORE_4                  104
+#define GAME_PANEL_CE_SCORE_5                  105
+#define GAME_PANEL_CE_SCORE_6                  106
+#define GAME_PANEL_CE_SCORE_7                  107
+#define GAME_PANEL_CE_SCORE_8                  108
+#define GAME_PANEL_CE_SCORE_1_ELEMENT          109
+#define GAME_PANEL_CE_SCORE_2_ELEMENT          110
+#define GAME_PANEL_CE_SCORE_3_ELEMENT          111
+#define GAME_PANEL_CE_SCORE_4_ELEMENT          112
+#define GAME_PANEL_CE_SCORE_5_ELEMENT          113
+#define GAME_PANEL_CE_SCORE_6_ELEMENT          114
+#define GAME_PANEL_CE_SCORE_7_ELEMENT          115
+#define GAME_PANEL_CE_SCORE_8_ELEMENT          116
+#define GAME_PANEL_PLAYER_NAME                 117
+#define GAME_PANEL_LEVEL_NAME                  118
+#define GAME_PANEL_LEVEL_AUTHOR                        119
+
+#define NUM_GAME_PANEL_CONTROLS                        120
 
 struct GamePanelOrderInfo
 {
@@ -242,6 +245,21 @@ static struct GamePanelControlInfo game_panel_controls[] =
     &game.panel.gems,
     TYPE_INTEGER,
   },
+  {
+    GAME_PANEL_GEMS_NEEDED,
+    &game.panel.gems_needed,
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_GEMS_COLLECTED,
+    &game.panel.gems_collected,
+    TYPE_INTEGER,
+  },
+  {
+    GAME_PANEL_GEMS_SCORE,
+    &game.panel.gems_score,
+    TYPE_INTEGER,
+  },
   {
     GAME_PANEL_INVENTORY_COUNT,
     &game.panel.inventory_count,
@@ -1017,19 +1035,22 @@ static struct GamePanelControlInfo game_panel_controls[] =
 #define GAME_CTRL_ID_SAVE              5
 #define GAME_CTRL_ID_PAUSE2            6
 #define GAME_CTRL_ID_LOAD              7
-#define GAME_CTRL_ID_PANEL_STOP                8
-#define GAME_CTRL_ID_PANEL_PAUSE       9
-#define GAME_CTRL_ID_PANEL_PLAY                10
-#define GAME_CTRL_ID_TOUCH_STOP                11
-#define GAME_CTRL_ID_TOUCH_PAUSE       12
-#define SOUND_CTRL_ID_MUSIC            13
-#define SOUND_CTRL_ID_LOOPS            14
-#define SOUND_CTRL_ID_SIMPLE           15
-#define SOUND_CTRL_ID_PANEL_MUSIC      16
-#define SOUND_CTRL_ID_PANEL_LOOPS      17
-#define SOUND_CTRL_ID_PANEL_SIMPLE     18
-
-#define NUM_GAME_BUTTONS               19
+#define GAME_CTRL_ID_RESTART           8
+#define GAME_CTRL_ID_PANEL_STOP                9
+#define GAME_CTRL_ID_PANEL_PAUSE       10
+#define GAME_CTRL_ID_PANEL_PLAY                11
+#define GAME_CTRL_ID_PANEL_RESTART     12
+#define GAME_CTRL_ID_TOUCH_STOP                13
+#define GAME_CTRL_ID_TOUCH_PAUSE       14
+#define GAME_CTRL_ID_TOUCH_RESTART     15
+#define SOUND_CTRL_ID_MUSIC            16
+#define SOUND_CTRL_ID_LOOPS            17
+#define SOUND_CTRL_ID_SIMPLE           18
+#define SOUND_CTRL_ID_PANEL_MUSIC      19
+#define SOUND_CTRL_ID_PANEL_LOOPS      20
+#define SOUND_CTRL_ID_PANEL_SIMPLE     21
+
+#define NUM_GAME_BUTTONS               22
 
 
 // forward declaration for internal use
@@ -1814,6 +1835,29 @@ static void InitPlayerField(int x, int y, int element, boolean init_game)
   }
 }
 
+static void InitFieldForEngine_RND(int x, int y)
+{
+  int element = Tile[x][y];
+
+  // convert BD engine elements to corresponding R'n'D engine elements
+  element = (element == EL_BD_EMPTY            ? EL_EMPTY :
+            element == EL_BD_PLAYER            ? EL_PLAYER_1 :
+            element == EL_BD_INBOX             ? EL_PLAYER_1 :
+            element == EL_BD_SAND              ? EL_SAND :
+            element == EL_BD_STEELWALL         ? EL_STEELWALL :
+            element == EL_BD_EXIT_CLOSED       ? EL_EXIT_CLOSED :
+            element == EL_BD_EXIT_OPEN         ? EL_EXIT_OPEN :
+            element);
+
+  Tile[x][y] = element;
+}
+
+static void InitFieldForEngine(int x, int y)
+{
+  if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
+    InitFieldForEngine_RND(x, y);
+}
+
 static void InitField(int x, int y, boolean init_game)
 {
   int element = Tile[x][y];
@@ -2241,6 +2285,8 @@ static void UpdateGameControlValues(void)
   int i, k;
   int time = (game.LevelSolved ?
              game.LevelSolved_CountingTime :
+             level.game_engine_type == GAME_ENGINE_TYPE_BD ?
+             game_bd.time_played :
              level.game_engine_type == GAME_ENGINE_TYPE_EM ?
              game_em.lev->time :
              level.game_engine_type == GAME_ENGINE_TYPE_SP ?
@@ -2250,6 +2296,8 @@ static void UpdateGameControlValues(void)
              game.no_level_time_limit ? TimePlayed : TimeLeft);
   int score = (game.LevelSolved ?
               game.LevelSolved_CountingScore :
+              level.game_engine_type == GAME_ENGINE_TYPE_BD ?
+              game_bd.score :
               level.game_engine_type == GAME_ENGINE_TYPE_EM ?
               game_em.lev->score :
               level.game_engine_type == GAME_ENGINE_TYPE_SP ?
@@ -2257,14 +2305,25 @@ static void UpdateGameControlValues(void)
               level.game_engine_type == GAME_ENGINE_TYPE_MM ?
               game_mm.score :
               game.score);
-  int gems = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
+  int gems = (level.game_engine_type == GAME_ENGINE_TYPE_BD ?
+             game_bd.gems_still_needed :
+             level.game_engine_type == GAME_ENGINE_TYPE_EM ?
              game_em.lev->gems_needed :
              level.game_engine_type == GAME_ENGINE_TYPE_SP ?
              game_sp.infotrons_still_needed :
              level.game_engine_type == GAME_ENGINE_TYPE_MM ?
              game_mm.kettles_still_needed :
              game.gems_still_needed);
-  int exit_closed = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
+  int gems_needed = level.gems_needed;
+  int gems_collected = (level.game_engine_type == GAME_ENGINE_TYPE_BD ?
+                       game_bd.game->cave->diamonds_collected :
+                       gems_needed - gems);
+  int gems_score = (level.game_engine_type == GAME_ENGINE_TYPE_BD ?
+                   game_bd.game->cave->diamond_value :
+                   level.score[SC_EMERALD]);
+  int exit_closed = (level.game_engine_type == GAME_ENGINE_TYPE_BD ?
+                    game_bd.gems_still_needed > 0 :
+                    level.game_engine_type == GAME_ENGINE_TYPE_EM ?
                     game_em.lev->gems_needed > 0 :
                     level.game_engine_type == GAME_ENGINE_TYPE_SP ?
                     game_sp.infotrons_still_needed > 0 :
@@ -2289,6 +2348,9 @@ static void UpdateGameControlValues(void)
   // used instead of "level_nr" (for network games)
   game_panel_controls[GAME_PANEL_LEVEL_NUMBER].value = levelset.level_nr;
   game_panel_controls[GAME_PANEL_GEMS].value = gems;
+  game_panel_controls[GAME_PANEL_GEMS_NEEDED].value = gems_needed;
+  game_panel_controls[GAME_PANEL_GEMS_COLLECTED].value = gems_collected;
+  game_panel_controls[GAME_PANEL_GEMS_SCORE].value = gems_score;
 
   game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value = 0;
   for (i = 0; i < MAX_NUM_KEYS; i++)
@@ -2683,7 +2745,7 @@ static void DisplayGameControlValues(void)
     if (PANEL_DEACTIVATED(pos))
       continue;
 
-    if (pos->class == get_hash_from_key("extra_panel_items") &&
+    if (pos->class == get_hash_from_string("extra_panel_items") &&
        !setup.prefer_extra_panel_items)
       continue;
 
@@ -2775,7 +2837,7 @@ static void DisplayGameControlValues(void)
       int width, height;
       int dst_x = PANEL_XPOS(pos);
       int dst_y = PANEL_YPOS(pos);
-      boolean skip = (pos->class == get_hash_from_key("mm_engine_only") &&
+      boolean skip = (pos->class == get_hash_from_string("mm_engine_only") &&
                      level.game_engine_type != GAME_ENGINE_TYPE_MM);
 
       if (graphic != IMG_UNDEFINED && !skip)
@@ -3095,6 +3157,9 @@ static void InitGameEngine(void)
   game_em.use_single_button =
     (game.engine_version > VERSION_IDENT(4,0,0,2));
 
+  game_em.use_push_delay =
+    (game.engine_version > VERSION_IDENT(4,3,7,1));
+
   game_em.use_snap_key_bug =
     (game.engine_version < VERSION_IDENT(4,0,1,0));
 
@@ -3195,6 +3260,17 @@ static void InitGameEngine(void)
     SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE);
   }
 
+  // ---------- initialize if element can trigger global animations -----------
+
+  for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+  {
+    struct ElementInfo *ei = &element_info[i];
+
+    ei->has_anim_event = FALSE;
+  }
+
+  InitGlobalAnimEventsForCustomElements();
+
   // ---------- initialize internal run-time variables ------------------------
 
   for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
@@ -3268,6 +3344,8 @@ static void InitGameEngine(void)
       change->actual_trigger_side = CH_SIDE_NONE;
       change->actual_trigger_ce_value = 0;
       change->actual_trigger_ce_score = 0;
+      change->actual_trigger_x = -1;
+      change->actual_trigger_y = -1;
     }
   }
 
@@ -3463,8 +3541,9 @@ static void InitGameEngine(void)
      level.game_engine_type == GAME_ENGINE_TYPE_EM &&
      !setup.forced_scroll_delay           ? 0 :
      setup.scroll_delay                   ? setup.scroll_delay_value       : 0);
-  game.scroll_delay_value =
-    MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
+  if (game.forced_scroll_delay_value == -1)
+    game.scroll_delay_value =
+      MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
 
   // ---------- initialize game engine snapshots ------------------------------
   for (i = 0; i < MAX_PLAYERS; i++)
@@ -3505,10 +3584,10 @@ static void InitGameEngine(void)
     {
       int element = EL_CUSTOM_START + i;
 
-      if (HAS_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
-         HAS_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE) ||
-         HAS_CHANGE_EVENT(element, CE_MOUSE_CLICKED_ON_X) ||
-         HAS_CHANGE_EVENT(element, CE_MOUSE_PRESSED_ON_X))
+      if (HAS_ANY_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
+         HAS_ANY_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE) ||
+         HAS_ANY_CHANGE_EVENT(element, CE_MOUSE_CLICKED_ON_X) ||
+         HAS_ANY_CHANGE_EVENT(element, CE_MOUSE_PRESSED_ON_X))
        game.use_mouse_actions = TRUE;
     }
   }
@@ -3618,6 +3697,10 @@ void InitGame(void)
     // force restarting global animations displayed during game play
     RestartGlobalAnimsByStatus(GAME_MODE_PSEUDO_RESTARTING);
 
+    // this is required for "transforming" fade modes like cross-fading
+    // (else global animations will be stopped, but not restarted here)
+    SetAnimStatusBeforeFading(GAME_MODE_PSEUDO_RESTARTING);
+
     SetGameStatus(GAME_MODE_PLAYING);
   }
 
@@ -3819,6 +3902,8 @@ void InitGame(void)
   TimeFrames = 0;
   TimePlayed = 0;
   TimeLeft = level.time;
+
+  TapeTimeFrames = 0;
   TapeTime = 0;
 
   ScreenMovDir = MV_NONE;
@@ -3849,6 +3934,8 @@ void InitGame(void)
   game.LevelSolved_CountingScore = 0;
   game.LevelSolved_CountingHealth = 0;
 
+  game.RestartGameRequested = FALSE;
+
   game.panel.active = TRUE;
 
   game.no_level_time_limit = (level.time == 0);
@@ -3943,6 +4030,8 @@ void InitGame(void)
 
   SCAN_PLAYFIELD(x, y)
   {
+    InitFieldForEngine(x, y);
+
     if (emulate_bd && !IS_BD_ELEMENT(Tile[x][y]))
       emulate_bd = FALSE;
     if (emulate_sp && !IS_SP_ELEMENT(Tile[x][y]))
@@ -3955,6 +4044,10 @@ void InitGame(void)
 
   InitBeltMovement();
 
+  // required if level does not contain any "empty space" element
+  if (element_info[EL_EMPTY].use_gfx_element)
+    game.use_masked_elements = TRUE;
+
   for (i = 0; i < MAX_PLAYERS; i++)
   {
     struct PlayerInfo *player = &stored_player[i];
@@ -4429,8 +4522,17 @@ void InitGame(void)
     scroll_y = SCROLL_POSITION_Y(local_player->jy);
   }
 
+  if (game.forced_scroll_x != ARG_UNDEFINED_VALUE)
+    scroll_x = game.forced_scroll_x;
+  if (game.forced_scroll_y != ARG_UNDEFINED_VALUE)
+    scroll_y = game.forced_scroll_y;
+
   // !!! FIX THIS (START) !!!
-  if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+  if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
+  {
+    InitGameEngine_BD();
+  }
+  else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
   {
     InitGameEngine_EM();
   }
@@ -4519,9 +4621,7 @@ void InitGame(void)
   }
 
   game.restart_level = FALSE;
-
   game.request_active = FALSE;
-  game.request_active_or_moving = FALSE;
 
   if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
     InitGameActions_MM();
@@ -4759,14 +4859,14 @@ void InitAmoebaNr(int x, int y)
 
 static void LevelSolved_SetFinalGameValues(void)
 {
-  game.time_final = (game.no_level_time_limit ? TimePlayed : TimeLeft);
+  game.time_final = (level.game_engine_type == GAME_ENGINE_TYPE_BD ? game_bd.time_played :
+                    game.no_level_time_limit ? TimePlayed : TimeLeft);
   game.score_time_final = (level.use_step_counter ? TimePlayed :
                           TimePlayed * FRAMES_PER_SECOND + TimeFrames);
 
-  game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
-                     game_em.lev->score :
-                     level.game_engine_type == GAME_ENGINE_TYPE_MM ?
-                     game_mm.score :
+  game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_BD ? game_bd.score :
+                     level.game_engine_type == GAME_ENGINE_TYPE_EM ? game_em.lev->score :
+                     level.game_engine_type == GAME_ENGINE_TYPE_MM ? game_mm.score :
                      game.score);
 
   game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
@@ -4806,6 +4906,31 @@ static void LevelSolved(void)
   LevelSolved_SetFinalGameValues();
 }
 
+static boolean AdvanceToNextLevel(void)
+{
+  if (setup.increment_levels &&
+      level_nr < leveldir_current->last_level &&
+      !network_playing)
+  {
+    level_nr++;                // advance to next level
+    TapeErase();       // start with empty tape
+
+    if (setup.auto_play_next_level)
+    {
+      scores.continue_playing = TRUE;
+      scores.next_level_nr = level_nr;
+
+      LoadLevel(level_nr);
+
+      SaveLevelSetup_SeriesInfo();
+    }
+
+    return TRUE;
+  }
+
+  return FALSE;
+}
+
 void GameWon(void)
 {
   static int time_count_steps;
@@ -4880,7 +5005,13 @@ void GameWon(void)
 
       time_count_steps = MAX(1, ABS(time_final - time) / 100);
 
-      if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
+      if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
+      {
+       // keep previous values (final values already processed here)
+       time_final = time;
+       score_final = score;
+      }
+      else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
       {
        health_final = 0;
        score_final += health * time_score;
@@ -4891,7 +5022,8 @@ void GameWon(void)
     }
 
     // if not counting score after game, immediately update game panel values
-    if (level_editor_test_game || !setup.count_score_after_game)
+    if (level_editor_test_game || !setup.count_score_after_game ||
+       level.game_engine_type == GAME_ENGINE_TYPE_BD)
     {
       time = time_final;
       score = score_final;
@@ -5030,7 +5162,11 @@ void GameEnd(void)
   int last_level_nr = levelset.level_nr;
   boolean tape_saved = FALSE;
 
-  game.LevelSolved_GameEnd = TRUE;
+  // Important note: This function is not only called after "GameWon()", but also after
+  // "game over" (if automatically asking for restarting the game is disabled in setup)
+
+  if (game.LevelSolved)
+    game.LevelSolved_GameEnd = TRUE;
 
   if (game.LevelSolved_SaveTape && !score_info_tape_play)
   {
@@ -5057,7 +5193,7 @@ void GameEnd(void)
     return;
   }
 
-  if (!game.LevelSolved_SaveScore)
+  if (!game.GamePlayed || (!game.LevelSolved_SaveScore && !level.bd_intermission))
   {
     SetGameStatus(GAME_MODE_MAIN);
 
@@ -5074,27 +5210,13 @@ void GameEnd(void)
   }
 
   // save score and score tape before potentially erasing tape below
-  NewHighScore(last_level_nr, tape_saved);
+  if (game.LevelSolved_SaveScore)
+    NewHighScore(last_level_nr, tape_saved);
 
-  if (setup.increment_levels &&
-      level_nr < leveldir_current->last_level &&
-      !network_playing)
-  {
-    level_nr++;                // advance to next level
-    TapeErase();       // start with empty tape
-
-    if (setup.auto_play_next_level)
-    {
-      scores.continue_playing = TRUE;
-      scores.next_level_nr = level_nr;
-
-      LoadLevel(level_nr);
-
-      SaveLevelSetup_SeriesInfo();
-    }
-  }
+  // increment and load next level (if possible and not configured otherwise)
+  AdvanceToNextLevel();
 
-  if (scores.last_added >= 0 && setup.show_scores_after_game)
+  if (game.LevelSolved_SaveScore && scores.last_added >= 0 && setup.show_scores_after_game)
   {
     SetGameStatus(GAME_MODE_SCORES);
 
@@ -5659,14 +5781,47 @@ static void DrawRelocateScreen(int old_x, int old_y, int x, int y,
   {
     // relocation _without_ centering of screen
 
-    int center_scroll_x = SCROLL_POSITION_X(old_x);
-    int center_scroll_y = SCROLL_POSITION_Y(old_y);
-    int offset_x = x + (scroll_x - center_scroll_x);
-    int offset_y = y + (scroll_y - center_scroll_y);
+    // apply distance between old and new player position to scroll position
+    int shifted_scroll_x = scroll_x + (x - old_x);
+    int shifted_scroll_y = scroll_y + (y - old_y);
+
+    // make sure that shifted scroll position does not scroll beyond screen
+    new_scroll_x = SCROLL_POSITION_X(shifted_scroll_x + MIDPOSX);
+    new_scroll_y = SCROLL_POSITION_Y(shifted_scroll_y + MIDPOSY);
 
-    // for new screen position, apply previous offset to center position
-    new_scroll_x = SCROLL_POSITION_X(offset_x);
-    new_scroll_y = SCROLL_POSITION_Y(offset_y);
+    // special case for teleporting from one end of the playfield to the other
+    // (this kludge prevents the destination area to be shifted by half a tile
+    // against the source destination for even screen width or screen height;
+    // probably most useful when used with high "game.forced_scroll_delay_value"
+    // in combination with "game.forced_scroll_x" and "game.forced_scroll_y")
+    if (quick_relocation)
+    {
+      if (EVEN(SCR_FIELDX))
+      {
+       // relocate (teleport) between left and right border (half or full)
+       if (scroll_x == SBX_Left && new_scroll_x == SBX_Right - 1)
+         new_scroll_x = SBX_Right;
+       else if (scroll_x == SBX_Left + 1 && new_scroll_x == SBX_Right)
+         new_scroll_x = SBX_Right - 1;
+       else if (scroll_x == SBX_Right && new_scroll_x == SBX_Left + 1)
+         new_scroll_x = SBX_Left;
+       else if (scroll_x == SBX_Right - 1 && new_scroll_x == SBX_Left)
+         new_scroll_x = SBX_Left + 1;
+      }
+
+      if (EVEN(SCR_FIELDY))
+      {
+       // relocate (teleport) between top and bottom border (half or full)
+       if (scroll_y == SBY_Upper && new_scroll_y == SBY_Lower - 1)
+         new_scroll_y = SBY_Lower;
+       else if (scroll_y == SBY_Upper + 1 && new_scroll_y == SBY_Lower)
+         new_scroll_y = SBY_Lower - 1;
+       else if (scroll_y == SBY_Lower && new_scroll_y == SBY_Upper + 1)
+         new_scroll_y = SBY_Upper;
+       else if (scroll_y == SBY_Lower - 1 && new_scroll_y == SBY_Upper)
+         new_scroll_y = SBY_Upper + 1;
+      }
+    }
   }
 
   if (quick_relocation)
@@ -9382,7 +9537,7 @@ static void Life(int ax, int ay)
 
   for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
   {
-    int xx = ax+x1, yy = ay+y1;
+    int xx = ax + x1, yy = ay + y1;
     int old_element = Tile[xx][yy];
     int num_neighbours = 0;
 
@@ -9391,7 +9546,7 @@ static void Life(int ax, int ay)
 
     for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
     {
-      int x = xx+x2, y = yy+y2;
+      int x = xx + x2, y = yy + y2;
 
       if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
        continue;
@@ -10626,17 +10781,26 @@ static void CreateFieldExt(int x, int y, int element, boolean is_change)
 
     if (GFX_CRUMBLED(new_element))
       TEST_DrawLevelFieldCrumbledNeighbours(x, y);
-  }
 
-  // check if element under the player changes from accessible to unaccessible
-  // (needed for special case of dropping element which then changes)
-  // (must be checked after creating new element for walkable group elements)
-  if (IS_PLAYER(x, y) && !player_explosion_protected &&
-      IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
-  {
-    Bang(x, y);
+    if (old_element == EL_EXPLOSION)
+    {
+      Store[x][y] = Store2[x][y] = 0;
 
-    return;
+      // check if new element replaces an exploding player, requiring cleanup
+      if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
+       StorePlayer[x][y] = 0;
+    }
+
+    // check if element under the player changes from accessible to unaccessible
+    // (needed for special case of dropping element which then changes)
+    // (must be checked after creating new element for walkable group elements)
+    if (IS_PLAYER(x, y) && !player_explosion_protected &&
+       IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
+    {
+      KillPlayer(PLAYERINFO(x, y));
+
+      return;
+    }
   }
 
   // "ChangeCount" not set yet to allow "entered by player" change one time
@@ -10696,6 +10860,8 @@ static boolean ChangeElement(int x, int y, int element, int page)
     change->actual_trigger_side = CH_SIDE_NONE;
     change->actual_trigger_ce_value = 0;
     change->actual_trigger_ce_score = 0;
+    change->actual_trigger_x = -1;
+    change->actual_trigger_y = -1;
   }
 
   // do not change elements more than a specified maximum number of changes
@@ -10704,6 +10870,11 @@ static boolean ChangeElement(int x, int y, int element, int page)
 
   ChangeCount[x][y]++;         // count number of changes in the same frame
 
+  if (ei->has_anim_event)
+    HandleGlobalAnimEventByElementChange(element, page, x, y,
+                                        change->actual_trigger_x,
+                                        change->actual_trigger_y);
+
   if (change->explode)
   {
     Bang(x, y);
@@ -11032,6 +11203,8 @@ static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
        change->actual_trigger_side = trigger_side;
        change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
        change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
+       change->actual_trigger_x = trigger_x;
+       change->actual_trigger_y = trigger_y;
 
        if ((change->can_change && !change_done) || change->has_action)
        {
@@ -11146,6 +11319,8 @@ static boolean CheckElementChangeExt(int x, int y,
       change->actual_trigger_side = trigger_side;
       change->actual_trigger_ce_value = CustomValue[x][y];
       change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
+      change->actual_trigger_x = x;
+      change->actual_trigger_y = y;
 
       // special case: trigger element not at (x,y) position for some events
       if (check_trigger_element)
@@ -11169,6 +11344,8 @@ static boolean CheckElementChangeExt(int x, int y,
 
        change->actual_trigger_ce_value = CustomValue[xx][yy];
        change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
+       change->actual_trigger_x = xx;
+       change->actual_trigger_y = yy;
       }
 
       if (change->can_change && !change_done)
@@ -11508,7 +11685,22 @@ static void SetTapeActionFromMouseAction(byte *tape_action,
 
 static void CheckLevelSolved(void)
 {
-  if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+  if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
+  {
+    if (game_bd.level_solved &&
+       !game_bd.game_over)                             // game won
+    {
+      LevelSolved();
+
+      game_bd.game_over = TRUE;
+
+      game.all_players_gone = TRUE;
+    }
+
+    if (game_bd.game_over)                             // game lost
+      game.all_players_gone = TRUE;
+  }
+  else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
   {
     if (game_em.level_solved &&
        !game_em.game_over)                             // game won
@@ -11555,6 +11747,27 @@ static void CheckLevelSolved(void)
   }
 }
 
+static void PlayTimeoutSound(int seconds_left)
+{
+  // will be played directly by BD engine (for classic bonus time sounds)
+  if (level.game_engine_type == GAME_ENGINE_TYPE_BD && checkBonusTime_BD())
+    return;
+
+  // try to use individual "running out of time" sound for each second left
+  int sound = SND_GAME_RUNNING_OUT_OF_TIME_0 - seconds_left;
+
+  // if special sound per second not defined, use default sound
+  if (getSoundInfoEntryFilename(sound) == NULL)
+    sound = SND_GAME_RUNNING_OUT_OF_TIME;
+
+  // if out of time, but player still alive, play special "timeout" sound, if defined
+  if (seconds_left == 0 && !checkGameFailed())
+    if (getSoundInfoEntryFilename(SND_GAME_TIMEOUT) != NULL)
+      sound = SND_GAME_TIMEOUT;
+
+  PlaySound(sound);
+}
+
 static void CheckLevelTime_StepCounter(void)
 {
   int i;
@@ -11566,7 +11779,7 @@ static void CheckLevelTime_StepCounter(void)
     TimeLeft--;
 
     if (TimeLeft <= 10 && game.time_limit && !game.LevelSolved)
-      PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
+      PlayTimeoutSound(TimeLeft);
 
     game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
 
@@ -11586,12 +11799,26 @@ static void CheckLevelTime_StepCounter(void)
 
 static void CheckLevelTime(void)
 {
+  int frames_per_second = FRAMES_PER_SECOND;
   int i;
 
-  if (TimeFrames >= FRAMES_PER_SECOND)
+  if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
+  {
+    // level time may be running slower in native BD engine
+    frames_per_second = getFramesPerSecond_BD();
+
+    // if native engine time changed, force main engine time change
+    if (getTimeLeft_BD() < TimeLeft)
+      TimeFrames = frames_per_second;
+
+    // if last second running, wait for native engine time to exactly reach zero
+    if (getTimeLeft_BD() == 1 && TimeLeft == 1)
+      TimeFrames = frames_per_second - 1;
+  }
+
+  if (TimeFrames >= frames_per_second)
   {
     TimeFrames = 0;
-    TapeTime++;
 
     for (i = 0; i < MAX_PLAYERS; i++)
     {
@@ -11615,7 +11842,7 @@ static void CheckLevelTime(void)
        TimeLeft--;
 
        if (TimeLeft <= 10 && game.time_limit)
-         PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
+         PlayTimeoutSound(TimeLeft);
 
        /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
           is reset from other values in UpdateGameDoorValues() -- FIX THIS */
@@ -11624,11 +11851,20 @@ static void CheckLevelTime(void)
 
        if (!TimeLeft && game.time_limit)
        {
-         if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+         if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
+         {
+           if (game_bd.game->cave->player_state == GD_PL_LIVING)
+             game_bd.game->cave->player_state = GD_PL_TIMEOUT;
+         }
+         else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+         {
            game_em.lev->killed_out_of_time = TRUE;
+         }
          else
+         {
            for (i = 0; i < MAX_PLAYERS; i++)
              KillPlayer(&stored_player[i]);
+         }
        }
       }
       else if (game.no_level_time_limit && !game.all_players_gone)
@@ -11638,6 +11874,12 @@ static void CheckLevelTime(void)
 
       game_em.lev->time = (game.no_level_time_limit ? TimePlayed : TimeLeft);
     }
+  }
+
+  if (TapeTimeFrames >= FRAMES_PER_SECOND)
+  {
+    TapeTimeFrames = 0;
+    TapeTime++;
 
     if (tape.recording || tape.playing)
       DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
@@ -11653,8 +11895,21 @@ void AdvanceFrameAndPlayerCounters(int player_nr)
 {
   int i;
 
-  // advance frame counters (global frame counter and time frame counter)
+  // handle game and tape time differently for native BD game engine
+
+  // tape time is running in native BD engine even if player is not hatched yet
+  if (!checkGameRunning())
+    return;
+
+  // advance frame counters (global frame counter and tape time frame counter)
   FrameCounter++;
+  TapeTimeFrames++;
+
+  // level time is running in native BD engine after player is being hatched
+  if (!checkGamePlaying())
+    return;
+
+  // advance time frame counter (used to control available time to solve level)
   TimeFrames++;
 
   // advance player counters (counters for move delay, move animation etc.)
@@ -12004,7 +12259,11 @@ static void GameActionsExt(void)
     game.snapshot.last_action[i] = stored_player[i].effective_action;
   }
 
-  if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+  if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
+  {
+    GameActions_BD_Main();
+  }
+  else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
   {
     GameActions_EM_Main();
   }
@@ -12071,6 +12330,17 @@ void GameActions(void)
   GameActions_CheckSaveEngineSnapshot();
 }
 
+void GameActions_BD_Main(void)
+{
+  byte effective_action[MAX_PLAYERS];
+  int i;
+
+  for (i = 0; i < MAX_PLAYERS; i++)
+    effective_action[i] = stored_player[i].effective_action;
+
+  GameActions_BD(effective_action);
+}
+
 void GameActions_EM_Main(void)
 {
   byte effective_action[MAX_PLAYERS];
@@ -13447,8 +13717,9 @@ void TestIfPlayerTouchesCustomElement(int x, int y)
           incorrectly give EL_PLAYER_1 for "player->element_nr") */
        int player_element = PLAYERINFO(x, y)->initial_element;
 
+       // as element "X" is the player here, check opposite (center) side
        CheckElementChangeBySide(xx, yy, border_element, player_element,
-                                CE_TOUCHING_X, border_side);
+                                CE_TOUCHING_X, center_side);
       }
     }
     else if (IS_PLAYER(xx, yy))                // player found at border element
@@ -13474,8 +13745,9 @@ void TestIfPlayerTouchesCustomElement(int x, int y)
           incorrectly give EL_PLAYER_1 for "player->element_nr") */
        int player_element = PLAYERINFO(xx, yy)->initial_element;
 
+       // as element "X" is the player here, check opposite (border) side
        CheckElementChangeBySide(x, y, center_element, player_element,
-                                CE_TOUCHING_X, center_side);
+                                CE_TOUCHING_X, border_side);
       }
 
       break;
@@ -13582,7 +13854,7 @@ void TestIfElementTouchesCustomElement(int x, int y)
     CheckElementChangeBySide(xx, yy, border_element, center_element,
                             CE_TOUCHING_X, center_side);
 
-    // (center element cannot be player, so we dont have to check this here)
+    // (center element cannot be player, so we don't have to check this here)
   }
 
   for (i = 0; i < NUM_DIRECTIONS; i++)
@@ -13609,6 +13881,7 @@ void TestIfElementTouchesCustomElement(int x, int y)
         incorrectly give EL_PLAYER_1 for "player->element_nr") */
       int player_element = PLAYERINFO(xx, yy)->initial_element;
 
+      // as element "X" is the player here, check opposite (border) side
       CheckElementChangeBySide(x, y, center_element, player_element,
                               CE_TOUCHING_X, border_side);
     }
@@ -15162,15 +15435,15 @@ void InitPlayLevelSound(void)
   loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
 }
 
-static void PlayLevelSound(int x, int y, int nr)
+static void PlayLevelSoundExt(int x, int y, int nr, boolean is_loop_sound)
 {
   int sx = SCREENX(x), sy = SCREENY(y);
   int volume, stereo_position;
   int max_distance = 8;
-  int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
+  int type = (is_loop_sound ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
 
-  if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
-      (!setup.sound_loops && IS_LOOP_SOUND(nr)))
+  if ((!setup.sound_simple && !is_loop_sound) ||
+      (!setup.sound_loops && is_loop_sound))
     return;
 
   if (!IN_LEV_FIELD(x, y) ||
@@ -15192,7 +15465,7 @@ static void PlayLevelSound(int x, int y, int nr)
                     (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
                     (SCR_FIELDX + 2 * max_distance));
 
-  if (IS_LOOP_SOUND(nr))
+  if (is_loop_sound)
   {
     /* This assures that quieter loop sounds do not overwrite louder ones,
        while restarting sound volume comparison with each new game frame. */
@@ -15207,6 +15480,11 @@ static void PlayLevelSound(int x, int y, int nr)
   PlaySoundExt(nr, volume, stereo_position, type);
 }
 
+static void PlayLevelSound(int x, int y, int nr)
+{
+  PlayLevelSoundExt(x, y, nr, IS_LOOP_SOUND(nr));
+}
+
 static void PlayLevelSoundNearest(int x, int y, int sound_action)
 {
   PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
@@ -15295,6 +15573,338 @@ static void PlayLevelMusic(void)
     PlayMusicLoop(music_nr);
 }
 
+static int getSoundAction_BD(int sample)
+{
+  switch (sample)
+  {
+    case GD_S_STONE_PUSHING:
+    case GD_S_MEGA_STONE_PUSHING:
+    case GD_S_FLYING_STONE_PUSHING:
+    case GD_S_WAITING_STONE_PUSHING:
+    case GD_S_CHASING_STONE_PUSHING:
+    case GD_S_NUT_PUSHING:
+    case GD_S_NITRO_PACK_PUSHING:
+    case GD_S_BLADDER_PUSHING:
+    case GD_S_BOX_PUSHING:
+      return ACTION_PUSHING;
+
+    case GD_S_STONE_FALLING:
+    case GD_S_MEGA_STONE_FALLING:
+    case GD_S_FLYING_STONE_FALLING:
+    case GD_S_NUT_FALLING:
+    case GD_S_DIRT_BALL_FALLING:
+    case GD_S_DIRT_LOOSE_FALLING:
+    case GD_S_NITRO_PACK_FALLING:
+    case GD_S_FALLING_WALL_FALLING:
+      return ACTION_FALLING;
+
+    case GD_S_STONE_IMPACT:
+    case GD_S_MEGA_STONE_IMPACT:
+    case GD_S_FLYING_STONE_IMPACT:
+    case GD_S_NUT_IMPACT:
+    case GD_S_DIRT_BALL_IMPACT:
+    case GD_S_DIRT_LOOSE_IMPACT:
+    case GD_S_NITRO_PACK_IMPACT:
+    case GD_S_FALLING_WALL_IMPACT:
+      return ACTION_IMPACT;
+
+    case GD_S_NUT_CRACKING:
+      return ACTION_BREAKING;
+
+    case GD_S_EXPANDING_WALL:
+    case GD_S_WALL_REAPPEARING:
+    case GD_S_SLIME:
+    case GD_S_LAVA:
+    case GD_S_ACID_SPREADING:
+      return ACTION_GROWING;
+
+    case GD_S_DIAMOND_COLLECTING:
+    case GD_S_FLYING_DIAMOND_COLLECTING:
+    case GD_S_SKELETON_COLLECTING:
+    case GD_S_PNEUMATIC_COLLECTING:
+    case GD_S_BOMB_COLLECTING:
+    case GD_S_CLOCK_COLLECTING:
+    case GD_S_SWEET_COLLECTING:
+    case GD_S_KEY_COLLECTING:
+    case GD_S_DIAMOND_KEY_COLLECTING:
+      return ACTION_COLLECTING;
+
+    case GD_S_BOMB_PLACING:
+    case GD_S_REPLICATOR:
+      return ACTION_DROPPING;
+
+    case GD_S_BLADDER_MOVING:
+      return ACTION_MOVING;
+
+    case GD_S_BLADDER_SPENDER:
+    case GD_S_BLADDER_CONVERTING:
+    case GD_S_GRAVITY_CHANGING:
+      return ACTION_CHANGING;
+
+    case GD_S_BITER_EATING:
+      return ACTION_EATING;
+
+    case GD_S_DOOR_OPENING:
+    case GD_S_CRACKING:
+      return ACTION_OPENING;
+
+    case GD_S_DIRT_WALKING:
+      return ACTION_DIGGING;
+
+    case GD_S_EMPTY_WALKING:
+      return ACTION_WALKING;
+
+    case GD_S_SWITCH_BITER:
+    case GD_S_SWITCH_CREATURES:
+    case GD_S_SWITCH_GRAVITY:
+    case GD_S_SWITCH_EXPANDING:
+    case GD_S_SWITCH_CONVEYOR:
+    case GD_S_SWITCH_REPLICATOR:
+    case GD_S_STIRRING:
+      return ACTION_ACTIVATING;
+
+    case GD_S_TELEPORTER:
+      return ACTION_PASSING;
+
+    case GD_S_EXPLODING:
+    case GD_S_BOMB_EXPLODING:
+    case GD_S_GHOST_EXPLODING:
+    case GD_S_VOODOO_EXPLODING:
+    case GD_S_NITRO_PACK_EXPLODING:
+      return ACTION_EXPLODING;
+
+    case GD_S_COVERING:
+    case GD_S_AMOEBA:
+    case GD_S_MAGIC_WALL:
+    case GD_S_PNEUMATIC_HAMMER:
+    case GD_S_WATER:
+      return ACTION_ACTIVE;
+
+    case GD_S_DIAMOND_FALLING_RANDOM:
+    case GD_S_DIAMOND_FALLING_1:
+    case GD_S_DIAMOND_FALLING_2:
+    case GD_S_DIAMOND_FALLING_3:
+    case GD_S_DIAMOND_FALLING_4:
+    case GD_S_DIAMOND_FALLING_5:
+    case GD_S_DIAMOND_FALLING_6:
+    case GD_S_DIAMOND_FALLING_7:
+    case GD_S_DIAMOND_FALLING_8:
+    case GD_S_DIAMOND_IMPACT_RANDOM:
+    case GD_S_DIAMOND_IMPACT_1:
+    case GD_S_DIAMOND_IMPACT_2:
+    case GD_S_DIAMOND_IMPACT_3:
+    case GD_S_DIAMOND_IMPACT_4:
+    case GD_S_DIAMOND_IMPACT_5:
+    case GD_S_DIAMOND_IMPACT_6:
+    case GD_S_DIAMOND_IMPACT_7:
+    case GD_S_DIAMOND_IMPACT_8:
+    case GD_S_FLYING_DIAMOND_FALLING_RANDOM:
+    case GD_S_FLYING_DIAMOND_FALLING_1:
+    case GD_S_FLYING_DIAMOND_FALLING_2:
+    case GD_S_FLYING_DIAMOND_FALLING_3:
+    case GD_S_FLYING_DIAMOND_FALLING_4:
+    case GD_S_FLYING_DIAMOND_FALLING_5:
+    case GD_S_FLYING_DIAMOND_FALLING_6:
+    case GD_S_FLYING_DIAMOND_FALLING_7:
+    case GD_S_FLYING_DIAMOND_FALLING_8:
+    case GD_S_FLYING_DIAMOND_IMPACT_RANDOM:
+    case GD_S_FLYING_DIAMOND_IMPACT_1:
+    case GD_S_FLYING_DIAMOND_IMPACT_2:
+    case GD_S_FLYING_DIAMOND_IMPACT_3:
+    case GD_S_FLYING_DIAMOND_IMPACT_4:
+    case GD_S_FLYING_DIAMOND_IMPACT_5:
+    case GD_S_FLYING_DIAMOND_IMPACT_6:
+    case GD_S_FLYING_DIAMOND_IMPACT_7:
+    case GD_S_FLYING_DIAMOND_IMPACT_8:
+    case GD_S_TIMEOUT_0:
+    case GD_S_TIMEOUT_1:
+    case GD_S_TIMEOUT_2:
+    case GD_S_TIMEOUT_3:
+    case GD_S_TIMEOUT_4:
+    case GD_S_TIMEOUT_5:
+    case GD_S_TIMEOUT_6:
+    case GD_S_TIMEOUT_7:
+    case GD_S_TIMEOUT_8:
+    case GD_S_TIMEOUT_9:
+    case GD_S_TIMEOUT_10:
+    case GD_S_BONUS_LIFE:
+      // trigger special post-processing (and force sound to be non-looping)
+      return ACTION_OTHER;
+
+    case GD_S_AMOEBA_MAGIC:
+    case GD_S_FINISHED:
+      // trigger special post-processing (and force sound to be looping)
+      return ACTION_DEFAULT;
+
+    default:
+      return ACTION_DEFAULT;
+  }
+}
+
+static int getSoundEffect_BD(int element_bd, int sample)
+{
+  int sound_action = getSoundAction_BD(sample);
+  int sound_effect = element_info[SND_ELEMENT(element_bd)].sound[sound_action];
+  int nr;
+
+  // standard sounds
+  if (sound_action != ACTION_OTHER &&
+      sound_action != ACTION_DEFAULT)
+    return sound_effect;
+
+  // special post-processing for some sounds
+  switch (sample)
+  {
+    case GD_S_DIAMOND_FALLING_RANDOM:
+    case GD_S_DIAMOND_FALLING_1:
+    case GD_S_DIAMOND_FALLING_2:
+    case GD_S_DIAMOND_FALLING_3:
+    case GD_S_DIAMOND_FALLING_4:
+    case GD_S_DIAMOND_FALLING_5:
+    case GD_S_DIAMOND_FALLING_6:
+    case GD_S_DIAMOND_FALLING_7:
+    case GD_S_DIAMOND_FALLING_8:
+      nr = (sample == GD_S_DIAMOND_FALLING_RANDOM ? GetSimpleRandom(8) :
+           sample - GD_S_DIAMOND_FALLING_1);
+      sound_effect = SND_BD_DIAMOND_FALLING_RANDOM_1 + nr;
+
+      if (getSoundInfoEntryFilename(sound_effect) == NULL)
+       sound_effect = SND_BD_DIAMOND_FALLING;
+      break;
+
+    case GD_S_DIAMOND_IMPACT_RANDOM:
+    case GD_S_DIAMOND_IMPACT_1:
+    case GD_S_DIAMOND_IMPACT_2:
+    case GD_S_DIAMOND_IMPACT_3:
+    case GD_S_DIAMOND_IMPACT_4:
+    case GD_S_DIAMOND_IMPACT_5:
+    case GD_S_DIAMOND_IMPACT_6:
+    case GD_S_DIAMOND_IMPACT_7:
+    case GD_S_DIAMOND_IMPACT_8:
+      nr = (sample == GD_S_DIAMOND_IMPACT_RANDOM ? GetSimpleRandom(8) :
+           sample - GD_S_DIAMOND_IMPACT_1);
+      sound_effect = SND_BD_DIAMOND_IMPACT_RANDOM_1 + nr;
+
+      if (getSoundInfoEntryFilename(sound_effect) == NULL)
+       sound_effect = SND_BD_DIAMOND_IMPACT;
+      break;
+
+    case GD_S_FLYING_DIAMOND_FALLING_RANDOM:
+    case GD_S_FLYING_DIAMOND_FALLING_1:
+    case GD_S_FLYING_DIAMOND_FALLING_2:
+    case GD_S_FLYING_DIAMOND_FALLING_3:
+    case GD_S_FLYING_DIAMOND_FALLING_4:
+    case GD_S_FLYING_DIAMOND_FALLING_5:
+    case GD_S_FLYING_DIAMOND_FALLING_6:
+    case GD_S_FLYING_DIAMOND_FALLING_7:
+    case GD_S_FLYING_DIAMOND_FALLING_8:
+      nr = (sample == GD_S_FLYING_DIAMOND_FALLING_RANDOM ? GetSimpleRandom(8) :
+           sample - GD_S_FLYING_DIAMOND_FALLING_1);
+      sound_effect = SND_BD_FLYING_DIAMOND_FALLING_RANDOM_1 + nr;
+
+      if (getSoundInfoEntryFilename(sound_effect) == NULL)
+       sound_effect = SND_BD_FLYING_DIAMOND_FALLING;
+      break;
+
+    case GD_S_FLYING_DIAMOND_IMPACT_RANDOM:
+    case GD_S_FLYING_DIAMOND_IMPACT_1:
+    case GD_S_FLYING_DIAMOND_IMPACT_2:
+    case GD_S_FLYING_DIAMOND_IMPACT_3:
+    case GD_S_FLYING_DIAMOND_IMPACT_4:
+    case GD_S_FLYING_DIAMOND_IMPACT_5:
+    case GD_S_FLYING_DIAMOND_IMPACT_6:
+    case GD_S_FLYING_DIAMOND_IMPACT_7:
+    case GD_S_FLYING_DIAMOND_IMPACT_8:
+      nr = (sample == GD_S_FLYING_DIAMOND_IMPACT_RANDOM ? GetSimpleRandom(8) :
+           sample - GD_S_FLYING_DIAMOND_IMPACT_1);
+      sound_effect = SND_BD_FLYING_DIAMOND_IMPACT_RANDOM_1 + nr;
+
+      if (getSoundInfoEntryFilename(sound_effect) == NULL)
+       sound_effect = SND_BD_FLYING_DIAMOND_IMPACT;
+      break;
+
+    case GD_S_TIMEOUT_0:
+    case GD_S_TIMEOUT_1:
+    case GD_S_TIMEOUT_2:
+    case GD_S_TIMEOUT_3:
+    case GD_S_TIMEOUT_4:
+    case GD_S_TIMEOUT_5:
+    case GD_S_TIMEOUT_6:
+    case GD_S_TIMEOUT_7:
+    case GD_S_TIMEOUT_8:
+    case GD_S_TIMEOUT_9:
+    case GD_S_TIMEOUT_10:
+      nr = sample - GD_S_TIMEOUT_0;
+      sound_effect = SND_GAME_RUNNING_OUT_OF_TIME_0 + nr;
+
+      if (getSoundInfoEntryFilename(sound_effect) == NULL && sample != GD_S_TIMEOUT_0)
+       sound_effect = SND_GAME_RUNNING_OUT_OF_TIME;
+      break;
+
+    case GD_S_BONUS_LIFE:
+      sound_effect = SND_GAME_HEALTH_BONUS;
+      break;
+
+    case GD_S_AMOEBA_MAGIC:
+      sound_effect = SND_BD_AMOEBA_OTHER;
+      break;
+
+    case GD_S_FINISHED:
+      sound_effect = SND_GAME_LEVELTIME_BONUS;
+      break;
+
+    default:
+      sound_effect = SND_UNDEFINED;
+      break;
+  }
+
+  return sound_effect;
+}
+
+void PlayLevelSound_BD(int xx, int yy, int element_bd, int sample)
+{
+  int element = (element_bd > -1 ? map_element_BD_to_RND(element_bd) : 0);
+  int sound_effect = getSoundEffect_BD(element, sample);
+  int sound_action = getSoundAction_BD(sample);
+  boolean is_loop_sound = IS_LOOP_SOUND(sound_effect);
+  int offset = 0;
+  int x = xx - offset;
+  int y = yy - offset;
+
+  // some sound actions are always looping in native BD game engine
+  if (sound_action == ACTION_DEFAULT)
+    is_loop_sound = TRUE;
+
+  // some sound actions are always non-looping in native BD game engine
+  if (sound_action == ACTION_FALLING ||
+      sound_action == ACTION_MOVING ||
+      sound_action == ACTION_OTHER)
+    is_loop_sound = FALSE;
+
+  if (sound_effect != SND_UNDEFINED)
+    PlayLevelSoundExt(x, y, sound_effect, is_loop_sound);
+}
+
+void StopSound_BD(int element_bd, int sample)
+{
+  int element = (element_bd > -1 ? map_element_BD_to_RND(element_bd) : 0);
+  int sound_effect = getSoundEffect_BD(element, sample);
+
+  if (sound_effect != SND_UNDEFINED)
+    StopSound(sound_effect);
+}
+
+boolean isSoundPlaying_BD(int element_bd, int sample)
+{
+  int element = (element_bd > -1 ? map_element_BD_to_RND(element_bd) : 0);
+  int sound_effect = getSoundEffect_BD(element, sample);
+
+  if (sound_effect != SND_UNDEFINED)
+    return isSoundPlaying(sound_effect);
+
+  return FALSE;
+}
+
 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
 {
   int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
@@ -15614,9 +16224,15 @@ void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
     }
 
     if (network.enabled)
+    {
       SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
+    }
     else
     {
+      // when using BD game engine, cover screen before fading out
+      if (!quick_quit && level.game_engine_type == GAME_ENGINE_TYPE_BD)
+       game_bd.cover_screen = TRUE;
+
       if (quick_quit)
        FadeSkipNextFadeIn();
 
@@ -15673,7 +16289,17 @@ static void RequestRestartGame(void)
   int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
   int door_state = DOOR_CLOSE_1;
 
-  if (Request(message, request_mode | REQ_STAY_OPEN) && has_started_game)
+  boolean restart_wanted = (Request(message, request_mode | REQ_STAY_OPEN) && has_started_game);
+
+  // if no restart wanted, continue with next level for BD style intermission levels
+  if (!restart_wanted && !level_editor_test_game && level.bd_intermission)
+  {
+    boolean success = AdvanceToNextLevel();
+
+    restart_wanted = (success && setup.auto_play_next_level);
+  }
+
+  if (restart_wanted)
   {
     CloseDoor(door_state);
 
@@ -15716,10 +16342,14 @@ boolean CheckRestartGame(void)
     return FALSE;
   }
 
-  // do not handle game over if request dialog is already active
+  // do not ask to play again if request dialog is already active
   if (game.request_active)
     return FALSE;
 
+  // do not ask to play again if request dialog already handled
+  if (game.RestartGameRequested)
+    return FALSE;
+
   // do not ask to play again if game was never actually played
   if (!game.GamePlayed)
     return FALSE;
@@ -15728,11 +16358,35 @@ boolean CheckRestartGame(void)
   if (!setup.ask_on_game_over)
     return FALSE;
 
+  game.RestartGameRequested = TRUE;
+
   RequestRestartGame();
 
   return TRUE;
 }
 
+boolean checkGameRunning(void)
+{
+  if (game_status != GAME_MODE_PLAYING)
+    return FALSE;
+
+  if (level.game_engine_type == GAME_ENGINE_TYPE_BD && !checkGameRunning_BD())
+    return FALSE;
+
+  return TRUE;
+}
+
+boolean checkGamePlaying(void)
+{
+  if (game_status != GAME_MODE_PLAYING)
+    return FALSE;
+
+  if (level.game_engine_type == GAME_ENGINE_TYPE_BD && !checkGamePlaying_BD())
+    return FALSE;
+
+  return TRUE;
+}
+
 boolean checkGameSolved(void)
 {
   // set for all game engines if level was solved
@@ -15741,7 +16395,9 @@ boolean checkGameSolved(void)
 
 boolean checkGameFailed(void)
 {
-  if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+  if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
+    return (game_bd.game_over && !game_bd.level_solved);
+  else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
     return (game_em.game_over && !game_em.level_solved);
   else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
     return (game_sp.game_over && !game_sp.level_solved);
@@ -15937,6 +16593,7 @@ static ListNode *SaveEngineSnapshotBuffers(void)
   SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
   SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
   SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTimeFrames));
   SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
 
   SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
@@ -16152,6 +16809,11 @@ static struct
     GAME_CTRL_ID_LOAD,                         NULL,
     TRUE, FALSE,                               "load game"
   },
+  {
+    IMG_GFX_GAME_BUTTON_RESTART,               &game.button.restart,
+    GAME_CTRL_ID_RESTART,                      NULL,
+    TRUE, FALSE,                               "restart game"
+  },
   {
     IMG_GFX_GAME_BUTTON_PANEL_STOP,            &game.button.panel_stop,
     GAME_CTRL_ID_PANEL_STOP,                   NULL,
@@ -16167,6 +16829,11 @@ static struct
     GAME_CTRL_ID_PANEL_PLAY,                   NULL,
     FALSE, FALSE,                              "play game"
   },
+  {
+    IMG_GFX_GAME_BUTTON_PANEL_RESTART,         &game.button.panel_restart,
+    GAME_CTRL_ID_PANEL_RESTART,                        NULL,
+    FALSE, FALSE,                              "restart game"
+  },
   {
     IMG_GFX_GAME_BUTTON_TOUCH_STOP,            &game.button.touch_stop,
     GAME_CTRL_ID_TOUCH_STOP,                   NULL,
@@ -16177,6 +16844,11 @@ static struct
     GAME_CTRL_ID_TOUCH_PAUSE,                  NULL,
     FALSE, TRUE,                               "pause game"
   },
+  {
+    IMG_GFX_GAME_BUTTON_TOUCH_RESTART,         &game.button.touch_restart,
+    GAME_CTRL_ID_TOUCH_RESTART,                        NULL,
+    FALSE, TRUE,                               "restart game"
+  },
   {
     IMG_GFX_GAME_BUTTON_SOUND_MUSIC,           &game.button.sound_music,
     SOUND_CTRL_ID_MUSIC,                       &setup.sound_music,
@@ -16256,7 +16928,10 @@ void CreateGameButtons(void)
        id == GAME_CTRL_ID_PLAY ||
        id == GAME_CTRL_ID_PANEL_PLAY ||
        id == GAME_CTRL_ID_SAVE ||
-       id == GAME_CTRL_ID_LOAD)
+       id == GAME_CTRL_ID_LOAD ||
+       id == GAME_CTRL_ID_RESTART ||
+       id == GAME_CTRL_ID_PANEL_RESTART ||
+       id == GAME_CTRL_ID_TOUCH_RESTART)
     {
       button_type = GD_TYPE_NORMAL_BUTTON;
       checked = FALSE;
@@ -16379,6 +17054,10 @@ void ModifyPauseButtons(void)
   };
   int i;
 
+  // do not redraw pause button on closed door (may happen when restarting game)
+  if (!(GetDoorState() & DOOR_OPEN_1))
+    return;
+
   for (i = 0; ids[i] > -1; i++)
     ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
 }
@@ -16600,6 +17279,13 @@ static void HandleGameButtonsExt(int id, int button)
       TapeQuickLoad();
       break;
 
+    case GAME_CTRL_ID_RESTART:
+    case GAME_CTRL_ID_PANEL_RESTART:
+    case GAME_CTRL_ID_TOUCH_RESTART:
+      TapeRestartGame();
+
+      break;
+
     case SOUND_CTRL_ID_MUSIC:
     case SOUND_CTRL_ID_PANEL_MUSIC:
       if (setup.sound_music)