changed "http" to "https" in URLs
[rocksndiamonds.git] / src / game.c
index acba22e9b2f67838bbde1ba74c84b6bedf5fb9ab..803eebb327f93d0ca0f6cc636a4f388025ceafbb 100644 (file)
@@ -4,7 +4,7 @@
 // (c) 1995-2014 by Artsoft Entertainment
 //                         Holger Schemel
 //                 info@artsoft.org
-//                 http://www.artsoft.org/
+//                 https://www.artsoft.org/
 // ----------------------------------------------------------------------------
 // game.c
 // ============================================================================
@@ -1016,14 +1016,16 @@ static struct GamePanelControlInfo game_panel_controls[] =
 #define GAME_CTRL_ID_PANEL_STOP                8
 #define GAME_CTRL_ID_PANEL_PAUSE       9
 #define GAME_CTRL_ID_PANEL_PLAY                10
-#define SOUND_CTRL_ID_MUSIC            11
-#define SOUND_CTRL_ID_LOOPS            12
-#define SOUND_CTRL_ID_SIMPLE           13
-#define SOUND_CTRL_ID_PANEL_MUSIC      14
-#define SOUND_CTRL_ID_PANEL_LOOPS      15
-#define SOUND_CTRL_ID_PANEL_SIMPLE     16
+#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               17
+#define NUM_GAME_BUTTONS               19
 
 
 // forward declaration for internal use
@@ -1783,7 +1785,7 @@ static void InitPlayerField(int x, int y, int element, boolean init_game)
     player->jy = player->last_jy = y;
   }
 
-  if (!init_game)
+  // always check if player was just killed and should be reanimated
   {
     int player_nr = GET_PLAYER_NR(element);
     struct PlayerInfo *player = &stored_player[player_nr];
@@ -1875,6 +1877,8 @@ static void InitField(int x, int y, boolean init_game)
     case EL_MOLE_RIGHT:
     case EL_MOLE_UP:
     case EL_MOLE_DOWN:
+    case EL_SPRING_LEFT:
+    case EL_SPRING_RIGHT:
       InitMovDir(x, y);
       break;
 
@@ -1961,12 +1965,12 @@ static void InitField(int x, int y, boolean init_game)
       break;
 
     case EL_EMC_MAGIC_BALL:
-      if (game.ball_state)
+      if (game.ball_active)
        Feld[x][y] = EL_EMC_MAGIC_BALL_ACTIVE;
       break;
 
     case EL_EMC_MAGIC_BALL_SWITCH:
-      if (game.ball_state)
+      if (game.ball_active)
        Feld[x][y] = EL_EMC_MAGIC_BALL_SWITCH_ACTIVE;
       break;
 
@@ -2124,9 +2128,9 @@ static int compareGamePanelOrderInfo(const void *object1, const void *object2)
 int getPlayerInventorySize(int player_nr)
 {
   if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
-    return level.native_em_level->ply[player_nr]->dynamite;
+    return game_em.ply[player_nr]->dynamite;
   else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
-    return level.native_sp_level->game_sp->red_disk_count;
+    return game_sp.red_disk_count;
   else
     return stored_player[player_nr].inventory_size;
 }
@@ -2210,32 +2214,32 @@ static void UpdateGameControlValues(void)
   int time = (game.LevelSolved ?
              game.LevelSolved_CountingTime :
              level.game_engine_type == GAME_ENGINE_TYPE_EM ?
-             level.native_em_level->lev->time :
+             game_em.lev->time :
              level.game_engine_type == GAME_ENGINE_TYPE_SP ?
-             level.native_sp_level->game_sp->time_played :
+             game_sp.time_played :
              level.game_engine_type == GAME_ENGINE_TYPE_MM ?
              game_mm.energy_left :
              game.no_time_limit ? TimePlayed : TimeLeft);
   int score = (game.LevelSolved ?
               game.LevelSolved_CountingScore :
               level.game_engine_type == GAME_ENGINE_TYPE_EM ?
-              level.native_em_level->lev->score :
+              game_em.lev->score :
               level.game_engine_type == GAME_ENGINE_TYPE_SP ?
-              level.native_sp_level->game_sp->score :
+              game_sp.score :
               level.game_engine_type == GAME_ENGINE_TYPE_MM ?
               game_mm.score :
-              local_player->score);
+              game.score);
   int gems = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
-             level.native_em_level->lev->required :
+             game_em.lev->gems_needed :
              level.game_engine_type == GAME_ENGINE_TYPE_SP ?
-             level.native_sp_level->game_sp->infotrons_still_needed :
+             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 ?
-                    level.native_em_level->lev->required > 0 :
+                    game_em.lev->gems_needed > 0 :
                     level.game_engine_type == GAME_ENGINE_TYPE_SP ?
-                    level.native_sp_level->game_sp->infotrons_still_needed > 0 :
+                    game_sp.infotrons_still_needed > 0 :
                     level.game_engine_type == GAME_ENGINE_TYPE_MM ?
                     game_mm.kettles_still_needed > 0 ||
                     game_mm.lights_still_needed > 0 :
@@ -2247,7 +2251,7 @@ static void UpdateGameControlValues(void)
                game.LevelSolved_CountingHealth :
                level.game_engine_type == GAME_ENGINE_TYPE_MM ?
                MM_HEALTH(game_mm.laser_overload_value) :
-               local_player->health);
+               game.health);
 
   UpdatePlayfieldElementCount();
 
@@ -2275,7 +2279,7 @@ static void UpdateGameControlValues(void)
       {
        if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
        {
-         if (level.native_em_level->ply[i]->keys & (1 << k))
+         if (game_em.ply[i]->keys & (1 << k))
            game_panel_controls[GAME_PANEL_KEY_1 + k].value =
              get_key_element_from_nr(k);
        }
@@ -2303,7 +2307,7 @@ static void UpdateGameControlValues(void)
     {
       if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
       {
-       if (level.native_em_level->ply[player_nr]->keys & (1 << k))
+       if (game_em.ply[player_nr]->keys & (1 << k))
          game_panel_controls[GAME_PANEL_KEY_1 + k].value =
            get_key_element_from_nr(k);
       }
@@ -2364,9 +2368,9 @@ static void UpdateGameControlValues(void)
     (exit_closed ? EL_EXIT_CLOSED : EL_EXIT_OPEN);
 
   game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL].value =
-    (game.ball_state ? EL_EMC_MAGIC_BALL_ACTIVE : EL_EMC_MAGIC_BALL);
+    (game.ball_active ? EL_EMC_MAGIC_BALL_ACTIVE : EL_EMC_MAGIC_BALL);
   game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL_SWITCH].value =
-    (game.ball_state ? EL_EMC_MAGIC_BALL_SWITCH_ACTIVE :
+    (game.ball_active ? EL_EMC_MAGIC_BALL_SWITCH_ACTIVE :
      EL_EMC_MAGIC_BALL_SWITCH);
 
   game_panel_controls[GAME_PANEL_LIGHT_SWITCH].value =
@@ -2838,10 +2842,84 @@ static void InitGameEngine(void)
     game.team_mode = (num_players > 1);
   }
 
+#if 0
+  printf("level %d: level.game_version  == %06d\n", level_nr,
+        level.game_version);
+  printf("          tape.file_version   == %06d\n",
+        tape.file_version);
+  printf("          tape.game_version   == %06d\n",
+        tape.game_version);
+  printf("          tape.engine_version == %06d\n",
+        tape.engine_version);
+  printf("       => game.engine_version == %06d [tape mode: %s]\n",
+        game.engine_version, (tape.playing ? "PLAYING" : "RECORDING"));
+#endif
+
   // --------------------------------------------------------------------------
   // set flags for bugs and changes according to active game engine version
   // --------------------------------------------------------------------------
 
+  /*
+    Summary of bugfix:
+    Fixed property "can fall" for run-time element "EL_AMOEBA_DROPPING"
+
+    Bug was introduced in version:
+    2.0.1
+
+    Bug was fixed in version:
+    4.1.4.2
+
+    Description:
+    In version 2.0.1, a new run-time element "EL_AMOEBA_DROPPING" was added,
+    but the property "can fall" was missing, which caused some levels to be
+    unsolvable. This was fixed in version 4.1.4.2.
+
+    Affected levels/tapes:
+    An example for a tape that was fixed by this bugfix is tape 029 from the
+    level set "rnd_sam_bateman".
+    The wrong behaviour will still be used for all levels or tapes that were
+    created/recorded with it. An example for this is tape 023 from the level
+    set "rnd_gerhard_haeusler", which was recorded with a buggy game engine.
+  */
+
+  boolean use_amoeba_dropping_cannot_fall_bug =
+    ((game.engine_version >= VERSION_IDENT(2,0,1,0) &&
+      game.engine_version <= VERSION_IDENT(4,1,4,1)) ||
+     (tape.playing &&
+      tape.game_version >= VERSION_IDENT(2,0,1,0) &&
+      tape.game_version <= VERSION_IDENT(4,1,4,1)));
+
+  /*
+    Summary of bugfix/change:
+    Fixed move speed of elements entering or leaving magic wall.
+
+    Fixed/changed in version:
+    2.0.1
+
+    Description:
+    Before 2.0.1, move speed of elements entering or leaving magic wall was
+    twice as fast as it is now.
+    Since 2.0.1, this is set to a lower value by using move_stepsize_list[].
+
+    Affected levels/tapes:
+    The first condition is generally needed for all levels/tapes before version
+    2.0.1, which might use the old behaviour before it was changed; known tapes
+    that are affected: Tape 014 from the level set "rnd_conor_mancone".
+    The second condition is an exception from the above case and is needed for
+    the special case of tapes recorded with game (not engine!) version 2.0.1 or
+    above, but before it was known that this change would break tapes like the
+    above and was fixed in 4.1.4.2, so that the changed behaviour was active
+    although the engine version while recording maybe was before 2.0.1. There
+    are a lot of tapes that are affected by this exception, like tape 006 from
+    the level set "rnd_conor_mancone".
+  */
+
+  boolean use_old_move_stepsize_for_magic_wall =
+    (game.engine_version < VERSION_IDENT(2,0,1,0) &&
+     !(tape.playing &&
+       tape.game_version >= VERSION_IDENT(2,0,1,0) &&
+       tape.game_version <  VERSION_IDENT(4,1,4,2)));
+
   /*
     Summary of bugfix/change:
     Fixed handling for custom elements that change when pushed by the player.
@@ -2904,12 +2982,29 @@ static void InitGameEngine(void)
   game.use_block_last_field_bug =
     (game.engine_version < VERSION_IDENT(3,1,1,0));
 
+  /* various special flags and settings for native Emerald Mine game engine */
+
   game_em.use_single_button =
     (game.engine_version > VERSION_IDENT(4,0,0,2));
 
   game_em.use_snap_key_bug =
     (game.engine_version < VERSION_IDENT(4,0,1,0));
 
+  game_em.use_old_explosions =
+    (game.engine_version < VERSION_IDENT(4,1,4,2));
+
+  game_em.use_old_android =
+    (game.engine_version < VERSION_IDENT(4,1,4,2));
+
+  game_em.use_old_push_elements =
+    (game.engine_version < VERSION_IDENT(4,1,4,2));
+
+  game_em.use_old_push_into_acid =
+    (game.engine_version < VERSION_IDENT(4,1,4,2));
+
+  game_em.use_wrap_around =
+    (game.engine_version > VERSION_IDENT(4,1,4,1));
+
   // --------------------------------------------------------------------------
 
   // set maximal allowed number of custom element changes per game frame
@@ -2921,13 +3016,11 @@ static void InitGameEngine(void)
   // dynamically adjust element properties according to game engine version
   InitElementPropertiesEngine(game.engine_version);
 
-#if 0
-  printf("level %d: level version == %06d\n", level_nr, level.game_version);
-  printf("          tape version == %06d [%s] [file: %06d]\n",
-        tape.engine_version, (tape.playing ? "PLAYING" : "RECORDING"),
-        tape.file_version);
-  printf("       => game.engine_version == %06d\n", game.engine_version);
-#endif
+  // ---------- initialize special element properties -------------------------
+
+  // "EL_AMOEBA_DROPPING" missed property "can fall" between 2.0.1 and 4.1.4.1
+  if (use_amoeba_dropping_cannot_fall_bug)
+    SET_PROPERTY(EL_AMOEBA_DROPPING, EP_CAN_FALL, FALSE);
 
   // ---------- initialize player's initial move delay ------------------------
 
@@ -3174,6 +3267,16 @@ static void InitGameEngine(void)
     int e = move_stepsize_list[i].element;
 
     element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
+
+    // set move stepsize value for certain elements for older engine versions
+    if (use_old_move_stepsize_for_magic_wall)
+    {
+      if (e == EL_MAGIC_WALL_FILLING ||
+         e == EL_MAGIC_WALL_EMPTYING ||
+         e == EL_BD_MAGIC_WALL_FILLING ||
+         e == EL_BD_MAGIC_WALL_EMPTYING)
+       element_info[e].move_stepsize *= 2;
+    }
   }
 
   // ---------- initialize collect score --------------------------------------
@@ -3248,6 +3351,8 @@ static void InitGameEngine(void)
   // ---------- initialize graphics engine ------------------------------------
   game.scroll_delay_value =
     (game.forced_scroll_delay_value != -1 ? game.forced_scroll_delay_value :
+     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);
@@ -3270,6 +3375,34 @@ static void InitGameEngine(void)
   // Supaplex levels with time limit currently unsupported -- should be added
   if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
     level.time = 0;
+
+  // ---------- initialize flags for handling game actions --------------------
+
+  // set flags for game actions to default values
+  game.use_key_actions = TRUE;
+  game.use_mouse_actions = FALSE;
+
+  // when using Mirror Magic game engine, handle mouse events only
+  if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
+  {
+    game.use_key_actions = FALSE;
+    game.use_mouse_actions = TRUE;
+  }
+
+  // check for custom elements with mouse click events
+  if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
+  {
+    for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
+    {
+      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))
+       game.use_mouse_actions = TRUE;
+    }
+  }
 }
 
 static int get_num_special_action(int element, int action_first,
@@ -3354,11 +3487,11 @@ void InitGame(void)
   SetGameStatus(GAME_MODE_PLAYING);
 
   if (level_editor_test_game)
-    FadeSkipNextFadeIn();
+    FadeSkipNextFadeOut();
   else
     FadeSetEnterScreen();
 
-  if (CheckIfGlobalBorderOrPlayfieldViewportHasChanged())
+  if (CheckFadeAll())
     fade_mask = REDRAW_ALL;
 
   FadeLevelSoundsAndMusic();
@@ -3367,6 +3500,9 @@ void InitGame(void)
 
   FadeOut(fade_mask);
 
+  if (level_editor_test_game)
+    FadeSkipNextFadeIn();
+
   // needed if different viewport properties defined for playing
   ChangeViewportPropertiesIfNeeded();
 
@@ -3379,6 +3515,13 @@ void InitGame(void)
   InitGameEngine();
   InitGameControlValues();
 
+  // initialize tape actions from game when recording tape
+  if (tape.recording)
+  {
+    tape.use_key_actions   = game.use_key_actions;
+    tape.use_mouse_actions = game.use_mouse_actions;
+  }
+
   // don't play tapes over network
   network_playing = (network.enabled && !tape.playing);
 
@@ -3396,10 +3539,12 @@ void InitGame(void)
 
     player->killed = FALSE;
     player->reanimated = FALSE;
+    player->buried = FALSE;
 
     player->action = 0;
     player->effective_action = 0;
     player->programmed_action = 0;
+    player->snap_action = 0;
 
     player->mouse_action.lx = 0;
     player->mouse_action.ly = 0;
@@ -3411,12 +3556,6 @@ void InitGame(void)
     player->effective_mouse_action.button = 0;
     player->effective_mouse_action.button_hint = 0;
 
-    player->score = 0;
-    player->score_final = 0;
-
-    player->health = MAX_HEALTH;
-    player->health_final = MAX_HEALTH;
-
     for (j = 0; j < MAX_NUM_KEYS; j++)
       player->key[j] = FALSE;
 
@@ -3539,8 +3678,6 @@ void InitGame(void)
     DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
     SnapField(player, 0, 0);
 
-    player->GameOver = FALSE;
-
     map_player_action[i] = i;
   }
 
@@ -3550,9 +3687,6 @@ void InitGame(void)
   if (network_playing)
     SendToServer_MovePlayer(MV_NONE);
 
-  ZX = ZY = -1;
-  ExitX = ExitY = -1;
-
   FrameCounter = 0;
   TimeFrames = 0;
   TimePlayed = 0;
@@ -3565,9 +3699,18 @@ void InitGame(void)
 
   ScrollStepSize = 0;  // will be correctly initialized by ScrollScreen()
 
-  AllPlayersGone = FALSE;
+  game.robot_wheel_x = -1;
+  game.robot_wheel_y = -1;
+
+  game.exit_x = -1;
+  game.exit_y = -1;
+
+  game.all_players_gone = FALSE;
 
   game.LevelSolved = FALSE;
+  game.GameOver = FALSE;
+
+  game.GamePlayed = !tape.playing;
 
   game.LevelSolved_GameWon = FALSE;
   game.LevelSolved_GameEnd = FALSE;
@@ -3591,6 +3734,12 @@ void InitGame(void)
   game.switchgate_pos = 0;
   game.wind_direction = level.wind_direction_initial;
 
+  game.score = 0;
+  game.score_final = 0;
+
+  game.health = MAX_HEALTH;
+  game.health_final = MAX_HEALTH;
+
   game.gems_still_needed = level.gems_needed;
   game.sokoban_fields_still_needed = 0;
   game.sokoban_objects_still_needed = 0;
@@ -3601,7 +3750,7 @@ void InitGame(void)
   game.lenses_time_left = 0;
   game.magnify_time_left = 0;
 
-  game.ball_state = level.ball_state_initial;
+  game.ball_active = level.ball_active_initial;
   game.ball_content_nr = 0;
 
   game.explosions_delayed = TRUE;
@@ -3732,6 +3881,20 @@ void InitGame(void)
       game.belt_dir_nr[i] = 3;         // not moving, next moving left
 
 #if USE_NEW_PLAYER_ASSIGNMENTS
+  // use preferred player also in local single-player mode
+  if (!network.enabled && !game.team_mode)
+  {
+    int new_index_nr = setup.network_player_nr;
+
+    if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
+    {
+      for (i = 0; i < MAX_PLAYERS; i++)
+       stored_player[i].connected_locally = FALSE;
+
+      stored_player[new_index_nr].connected_locally = TRUE;
+    }
+  }
+
   for (i = 0; i < MAX_PLAYERS; i++)
   {
     stored_player[i].connected = FALSE;
@@ -3909,6 +4072,7 @@ void InitGame(void)
   game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
   game.centered_player_nr_next = game.centered_player_nr;
   game.set_centered_player = FALSE;
+  game.set_centered_player_wrap = FALSE;
 
   if (network_playing && tape.recording)
   {
@@ -4244,12 +4408,6 @@ void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
 {
   // this is used for non-R'n'D game engines to update certain engine values
 
-  if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
-  {
-    actual_player_x = correctLevelPosX_EM(actual_player_x);
-    actual_player_y = correctLevelPosY_EM(actual_player_y);
-  }
-
   // needed to determine if sounds are played within the visible screen area
   scroll_x = actual_scroll_x;
   scroll_y = actual_scroll_y;
@@ -4342,6 +4500,12 @@ void InitMovDir(int x, int y)
       MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
       break;
 
+    case EL_SPRING_LEFT:
+    case EL_SPRING_RIGHT:
+      Feld[x][y] = EL_SPRING;
+      MovDir[x][y] = direction[2][element - EL_SPRING_LEFT];
+      break;
+
     default:
       if (IS_CUSTOM_ELEMENT(element))
       {
@@ -4464,21 +4628,20 @@ static void LevelSolved(void)
     return;
 
   game.LevelSolved = TRUE;
+  game.GameOver = TRUE;
 
-  local_player->GameOver = TRUE;
-
-  local_player->score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
-                              level.native_em_level->lev->score :
-                              level.game_engine_type == GAME_ENGINE_TYPE_MM ?
-                              game_mm.score :
-                              local_player->score);
-  local_player->health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
-                               MM_HEALTH(game_mm.laser_overload_value) :
-                               local_player->health);
+  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);
+  game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
+                      MM_HEALTH(game_mm.laser_overload_value) :
+                      game.health);
 
   game.LevelSolved_CountingTime = (game.no_time_limit ? TimePlayed : TimeLeft);
-  game.LevelSolved_CountingScore = local_player->score_final;
-  game.LevelSolved_CountingHealth = local_player->health_final;
+  game.LevelSolved_CountingScore = game.score_final;
+  game.LevelSolved_CountingHealth = game.health_final;
 }
 
 void GameWon(void)
@@ -4499,7 +4662,7 @@ void GameWon(void)
     int i;
 
     // do not start end game actions before the player stops moving (to exit)
-    if (local_player->MovPos)
+    if (local_player->active && local_player->MovPos)
       return;
 
     game.LevelSolved_GameWon = TRUE;
@@ -4523,8 +4686,8 @@ void GameWon(void)
     game_over_delay_3 = game_over_delay_value_3;
 
     time = time_final = (game.no_time_limit ? TimePlayed : TimeLeft);
-    score = score_final = local_player->score_final;
-    health = health_final = local_player->health_final;
+    score = score_final = game.score_final;
+    health = health_final = game.health_final;
 
     if (level.score[SC_TIME_BONUS] > 0)
     {
@@ -4551,8 +4714,8 @@ void GameWon(void)
        game_over_delay_2 = game_over_delay_value_2;
       }
 
-      local_player->score_final = score_final;
-      local_player->health_final = health_final;
+      game.score_final = score_final;
+      game.health_final = health_final;
     }
 
     if (level_editor_test_game)
@@ -4571,30 +4734,35 @@ void GameWon(void)
 
     if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
     {
-      if (ExitX >= 0 && ExitY >= 0)    // local player has left the level
+      // check if last player has left the level
+      if (game.exit_x >= 0 &&
+         game.exit_y >= 0)
       {
+       int x = game.exit_x;
+       int y = game.exit_y;
+       int element = Feld[x][y];
+
        // close exit door after last player
-       if ((AllPlayersGone &&
-            (Feld[ExitX][ExitY] == EL_EXIT_OPEN ||
-             Feld[ExitX][ExitY] == EL_SP_EXIT_OPEN ||
-             Feld[ExitX][ExitY] == EL_STEEL_EXIT_OPEN)) ||
-           Feld[ExitX][ExitY] == EL_EM_EXIT_OPEN ||
-           Feld[ExitX][ExitY] == EL_EM_STEEL_EXIT_OPEN)
+       if ((game.all_players_gone &&
+            (element == EL_EXIT_OPEN ||
+             element == EL_SP_EXIT_OPEN ||
+             element == EL_STEEL_EXIT_OPEN)) ||
+           element == EL_EM_EXIT_OPEN ||
+           element == EL_EM_STEEL_EXIT_OPEN)
        {
-         int element = Feld[ExitX][ExitY];
 
-         Feld[ExitX][ExitY] =
+         Feld[x][y] =
            (element == EL_EXIT_OPEN            ? EL_EXIT_CLOSING :
             element == EL_EM_EXIT_OPEN         ? EL_EM_EXIT_CLOSING :
             element == EL_SP_EXIT_OPEN         ? EL_SP_EXIT_CLOSING:
             element == EL_STEEL_EXIT_OPEN      ? EL_STEEL_EXIT_CLOSING:
             EL_EM_STEEL_EXIT_CLOSING);
 
-         PlayLevelSoundElementAction(ExitX, ExitY, element, ACTION_CLOSING);
+         PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
        }
 
        // player disappears
-       DrawLevelField(ExitX, ExitY);
+       DrawLevelField(x, y);
       }
 
       for (i = 0; i < MAX_PLAYERS; i++)
@@ -4785,12 +4953,12 @@ int NewHiScore(int level_nr)
   LoadScore(level_nr);
 
   if (strEqual(setup.player_name, EMPTY_PLAYER_NAME) ||
-      local_player->score_final < highscore[MAX_SCORE_ENTRIES - 1].Score) 
+      game.score_final < highscore[MAX_SCORE_ENTRIES - 1].Score)
     return -1;
 
-  for (k = 0; k < MAX_SCORE_ENTRIES; k++) 
+  for (k = 0; k < MAX_SCORE_ENTRIES; k++)
   {
-    if (local_player->score_final > highscore[k].Score)
+    if (game.score_final > highscore[k].Score)
     {
       // player has made it to the hall of fame
 
@@ -4819,7 +4987,7 @@ int NewHiScore(int level_nr)
 
       strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
       highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
-      highscore[k].Score = local_player->score_final; 
+      highscore[k].Score = game.score_final;
       position = k;
 
       break;
@@ -5249,11 +5417,8 @@ static void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
 
   while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
   {
-    int dx = 0, dy = 0;
-    int fx = FX, fy = FY;
-
-    dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
-    dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
+    int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
+    int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
 
     if (dx == 0 && dy == 0)            // no scrolling needed at all
       break;
@@ -5261,14 +5426,19 @@ static void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
     scroll_x -= dx;
     scroll_y -= dy;
 
-    fx += dx * TILEX / 2;
-    fy += dy * TILEY / 2;
+    // set values for horizontal/vertical screen scrolling (half tile size)
+    int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
+    int dir_y = (dy != 0 ? MV_VERTICAL   : 0);
+    int pos_x = dx * TILEX / 2;
+    int pos_y = dy * TILEY / 2;
+    int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
+    int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
 
     ScrollLevel(dx, dy);
     DrawAllPlayers();
 
     // scroll in two steps of half tile size to make things smoother
-    BlitBitmap(drawto_field, window, fx, fy, SXSIZE, SYSIZE, SX, SY);
+    BlitScreenToBitmapExt_RND(window, fx, fy);
 
     // scroll second step to align at full tile size
     BlitScreenToBitmap(window);
@@ -5304,7 +5474,7 @@ static void RelocatePlayer(int jx, int jy, int el_player_raw)
   int enter_side = enter_side_horiz | enter_side_vert;
   int leave_side = leave_side_horiz | leave_side_vert;
 
-  if (player->GameOver)                // do not reanimate dead player
+  if (player->buried)          // do not reanimate dead player
     return;
 
   if (!player_relocated)       // no need to relocate the player
@@ -6814,10 +6984,10 @@ static void TurnRoundExt(int x, int y)
   {
     int attr_x = -1, attr_y = -1;
 
-    if (AllPlayersGone)
+    if (game.all_players_gone)
     {
-      attr_x = ExitX;
-      attr_y = ExitY;
+      attr_x = game.exit_x;
+      attr_y = game.exit_y;
     }
     else
     {
@@ -6840,12 +7010,14 @@ static void TurnRoundExt(int x, int y)
       }
     }
 
-    if (element == EL_ROBOT && ZX >= 0 && ZY >= 0 &&
-       (Feld[ZX][ZY] == EL_ROBOT_WHEEL_ACTIVE ||
+    if (element == EL_ROBOT &&
+       game.robot_wheel_x >= 0 &&
+       game.robot_wheel_y >= 0 &&
+       (Feld[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
         game.engine_version < VERSION_IDENT(3,1,0,0)))
     {
-      attr_x = ZX;
-      attr_y = ZY;
+      attr_x = game.robot_wheel_x;
+      attr_y = game.robot_wheel_y;
     }
 
     if (element == EL_PENGUIN)
@@ -6878,13 +7050,13 @@ static void TurnRoundExt(int x, int y)
 
     MovDir[x][y] = MV_NONE;
     if (attr_x < x)
-      MovDir[x][y] |= (AllPlayersGone ? MV_RIGHT : MV_LEFT);
+      MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
     else if (attr_x > x)
-      MovDir[x][y] |= (AllPlayersGone ? MV_LEFT : MV_RIGHT);
+      MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
     if (attr_y < y)
-      MovDir[x][y] |= (AllPlayersGone ? MV_DOWN : MV_UP);
+      MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
     else if (attr_y > y)
-      MovDir[x][y] |= (AllPlayersGone ? MV_UP : MV_DOWN);
+      MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
 
     if (element == EL_ROBOT)
     {
@@ -7183,10 +7355,10 @@ static void TurnRoundExt(int x, int y)
     int newx, newy;
     boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
 
-    if (AllPlayersGone)
+    if (game.all_players_gone)
     {
-      attr_x = ExitX;
-      attr_y = ExitY;
+      attr_x = game.exit_x;
+      attr_y = game.exit_y;
     }
     else
     {
@@ -7951,7 +8123,8 @@ static void StartMoving(int x, int y)
 
        game.friends_still_needed--;
        if (!game.friends_still_needed &&
-           !local_player->GameOver && AllPlayersGone)
+           !game.GameOver &&
+           game.all_players_gone)
          LevelSolved();
 
        return;
@@ -9026,10 +9199,11 @@ static void RunRobotWheel(int x, int y)
 
 static void StopRobotWheel(int x, int y)
 {
-  if (ZX == x && ZY == y)
+  if (game.robot_wheel_x == x &&
+      game.robot_wheel_y == y)
   {
-    ZX = ZY = -1;
-
+    game.robot_wheel_x = -1;
+    game.robot_wheel_y = -1;
     game.robot_wheel_active = FALSE;
   }
 }
@@ -9097,7 +9271,8 @@ static void CheckExit(int x, int y)
     return;
   }
 
-  if (AllPlayersGone)  // do not re-open exit door closed after last player
+  // do not re-open exit door closed after last player
+  if (game.all_players_gone)
     return;
 
   Feld[x][y] = EL_EXIT_OPENING;
@@ -9121,7 +9296,8 @@ static void CheckExitEM(int x, int y)
     return;
   }
 
-  if (AllPlayersGone)  // do not re-open exit door closed after last player
+  // do not re-open exit door closed after last player
+  if (game.all_players_gone)
     return;
 
   Feld[x][y] = EL_EM_EXIT_OPENING;
@@ -9145,7 +9321,8 @@ static void CheckExitSteel(int x, int y)
     return;
   }
 
-  if (AllPlayersGone)  // do not re-open exit door closed after last player
+  // do not re-open exit door closed after last player
+  if (game.all_players_gone)
     return;
 
   Feld[x][y] = EL_STEEL_EXIT_OPENING;
@@ -9169,7 +9346,8 @@ static void CheckExitSteelEM(int x, int y)
     return;
   }
 
-  if (AllPlayersGone)  // do not re-open exit door closed after last player
+  // do not re-open exit door closed after last player
+  if (game.all_players_gone)
     return;
 
   Feld[x][y] = EL_EM_STEEL_EXIT_OPENING;
@@ -9190,7 +9368,8 @@ static void CheckExitSP(int x, int y)
     return;
   }
 
-  if (AllPlayersGone)  // do not re-open exit door closed after last player
+  // do not re-open exit door closed after last player
+  if (game.all_players_gone)
     return;
 
   Feld[x][y] = EL_SP_EXIT_OPENING;
@@ -9707,7 +9886,7 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
      action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
      action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
      action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
-     action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? local_player->score :
+     action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
      action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
      action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
      action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
@@ -9722,7 +9901,7 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
   int action_arg_number_old =
     (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
      action_type == CA_SET_LEVEL_TIME ? TimeLeft :
-     action_type == CA_SET_LEVEL_SCORE ? local_player->score :
+     action_type == CA_SET_LEVEL_SCORE ? game.score :
      action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
      action_type == CA_SET_CE_SCORE ? ei->collect_score :
      0);
@@ -9792,9 +9971,9 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
 
     case CA_SET_LEVEL_SCORE:
     {
-      local_player->score = action_arg_number_new;
+      game.score = action_arg_number_new;
 
-      game_panel_controls[GAME_PANEL_SCORE].value = local_player->score;
+      game_panel_controls[GAME_PANEL_SCORE].value = game.score;
 
       DisplayGameControlValues();
 
@@ -9832,11 +10011,14 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
     // ---------- player actions  ---------------------------------------------
 
     case CA_MOVE_PLAYER:
+    case CA_MOVE_PLAYER_NEW:
     {
       // automatically move to the next field in specified direction
       for (i = 0; i < MAX_PLAYERS; i++)
        if (trigger_player_bits & (1 << i))
-         stored_player[i].programmed_action = action_arg_direction;
+         if (action_type == CA_MOVE_PLAYER ||
+             stored_player[i].MovPos == 0)
+           stored_player[i].programmed_action = action_arg_direction;
 
       break;
     }
@@ -9847,7 +10029,7 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
        if (action_arg_player_bits & (1 << i))
          ExitPlayer(&stored_player[i]);
 
-      if (AllPlayersGone)
+      if (game.players_still_needed == 0)
        LevelSolved();
 
       break;
@@ -10112,6 +10294,9 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
 
        if (CustomValue[x][y] == 0)
        {
+         // reset change counter (else CE_VALUE_GETS_ZERO would not work)
+         ChangeCount[x][y] = 0;        // allow at least one more change
+
          CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
          CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
        }
@@ -10135,6 +10320,9 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
        {
          int xx, yy;
 
+         // reset change counter (else CE_SCORE_GETS_ZERO would not work)
+         ChangeCount[x][y] = 0;        // allow at least one more change
+
          CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
          CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
 
@@ -11026,10 +11214,7 @@ static void CheckSingleStepMode(struct PlayerInfo *player)
     if (!player->is_moving &&
        !player->is_pushing &&
        !player->is_dropping_pressed)
-    {
       TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
-      SnapField(player, 0, 0);                 // stop snapping
-    }
   }
 
   CheckSaveEngineSnapshot(player);
@@ -11094,7 +11279,7 @@ static byte PlayerActions(struct PlayerInfo *player, byte player_action)
 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
                                         byte *tape_action)
 {
-  if (!tape.use_mouse)
+  if (!tape.use_mouse_actions)
     return;
 
   mouse_action->lx     = tape_action[TAPE_ACTION_LX];
@@ -11105,7 +11290,7 @@ static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
 static void SetTapeActionFromMouseAction(byte *tape_action,
                                         struct MouseActionInfo *mouse_action)
 {
-  if (!tape.use_mouse)
+  if (!tape.use_mouse_actions)
     return;
 
   tape_action[TAPE_ACTION_LX]     = mouse_action->lx;
@@ -11124,11 +11309,11 @@ static void CheckLevelSolved(void)
 
       game_em.game_over = TRUE;
 
-      AllPlayersGone = TRUE;
+      game.all_players_gone = TRUE;
     }
 
     if (game_em.game_over)                             // game lost
-      AllPlayersGone = TRUE;
+      game.all_players_gone = TRUE;
   }
   else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
   {
@@ -11139,11 +11324,11 @@ static void CheckLevelSolved(void)
 
       game_sp.game_over = TRUE;
 
-      AllPlayersGone = TRUE;
+      game.all_players_gone = TRUE;
     }
 
     if (game_sp.game_over)                             // game lost
-      AllPlayersGone = TRUE;
+      game.all_players_gone = TRUE;
   }
   else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
   {
@@ -11154,11 +11339,11 @@ static void CheckLevelSolved(void)
 
       game_mm.game_over = TRUE;
 
-      AllPlayersGone = TRUE;
+      game.all_players_gone = TRUE;
     }
 
     if (game_mm.game_over)                             // game lost
-      AllPlayersGone = TRUE;
+      game.all_players_gone = TRUE;
   }
 }
 
@@ -11203,19 +11388,18 @@ static void CheckLevelTime(void)
        if (!TimeLeft && setup.time_limit)
        {
          if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
-           level.native_em_level->lev->killed_out_of_time = TRUE;
+           game_em.lev->killed_out_of_time = TRUE;
          else
            for (i = 0; i < MAX_PLAYERS; i++)
              KillPlayer(&stored_player[i]);
        }
       }
-      else if (game.no_time_limit && !AllPlayersGone) // level w/o time limit
+      else if (game.no_time_limit && !game.all_players_gone)
       {
        game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
       }
 
-      level.native_em_level->lev->time =
-       (game.no_time_limit ? TimePlayed : TimeLeft);
+      game_em.lev->time = (game.no_time_limit ? TimePlayed : TimeLeft);
     }
 
     if (tape.recording || tape.playing)
@@ -11304,7 +11488,7 @@ static void GameActionsExt(void)
   unsigned int game_frame_delay_value;
   byte *recorded_player_action;
   byte summarized_player_action = 0;
-  byte tape_action[MAX_PLAYERS];
+  byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
   int i;
 
   // detect endless loops, caused by custom element programming
@@ -11334,7 +11518,7 @@ static void GameActionsExt(void)
   if (game.LevelSolved && !game.LevelSolved_GameEnd)
     GameWon();
 
-  if (AllPlayersGone && !TAPE_IS_STOPPED(tape))
+  if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
     TapeStop();
 
   if (game_status != GAME_MODE_PLAYING)                // status might have changed
@@ -11348,6 +11532,15 @@ static void GameActionsExt(void)
 
   SetVideoFrameDelay(game_frame_delay_value);
 
+  // (de)activate virtual buttons depending on current game status
+  if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
+  {
+    if (game.all_players_gone) // if no players there to be controlled anymore
+      SetOverlayActive(FALSE);
+    else if (!tape.playing)    // if game continues after tape stopped playing
+      SetOverlayActive(TRUE);
+  }
+
 #if 0
 #if 0
   // ---------- main game synchronization point ----------
@@ -11424,13 +11617,14 @@ static void GameActionsExt(void)
     stored_player[map_player_action[local_player->index_nr]].effective_action =
       summarized_player_action;
 
+  // summarize all actions at centered player in local team mode
   if (tape.recording &&
-      setup.team_mode &&
+      setup.team_mode && !network.enabled &&
       setup.input_on_focus &&
       game.centered_player_nr != -1)
   {
     for (i = 0; i < MAX_PLAYERS; i++)
-      stored_player[i].effective_action =
+      stored_player[map_player_action[i]].effective_action =
        (i == game.centered_player_nr ? summarized_player_action : 0);
   }
 
@@ -11458,6 +11652,10 @@ static void GameActionsExt(void)
   if (tape.recording)
     TapeRecordAction(tape_action);
 
+  // remember if game was played (especially after tape stopped playing)
+  if (!tape.playing && summarized_player_action)
+    game.GamePlayed = TRUE;
+
 #if USE_NEW_PLAYER_ASSIGNMENTS
   // !!! also map player actions in single player mode !!!
   // if (game.team_mode)
@@ -11623,6 +11821,8 @@ void GameActions_RND_Main(void)
 
 void GameActions_RND(void)
 {
+  static struct MouseActionInfo mouse_action_last = { 0 };
+  struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
   int magic_wall_x = 0, magic_wall_y = 0;
   int i, x, y, element, graphic, last_gfx_frame;
 
@@ -11809,6 +12009,24 @@ void GameActions_RND(void)
 #endif
   }
 
+  if (mouse_action.button)
+  {
+    int new_button = (mouse_action.button && mouse_action_last.button == 0);
+
+    x = mouse_action.lx;
+    y = mouse_action.ly;
+    element = Feld[x][y];
+
+    if (new_button)
+    {
+      CheckElementChange(x, y, element, EL_UNDEFINED, CE_CLICKED_BY_MOUSE);
+      CheckTriggeredElementChange(x, y, element, CE_MOUSE_CLICKED_ON_X);
+    }
+
+    CheckElementChange(x, y, element, EL_UNDEFINED, CE_PRESSED_BY_MOUSE);
+    CheckTriggeredElementChange(x, y, element, CE_MOUSE_PRESSED_ON_X);
+  }
+
   SCAN_PLAYFIELD(x, y)
   {
     element = Feld[x][y];
@@ -11951,7 +12169,8 @@ void GameActions_RND(void)
           element == EL_DC_MAGIC_WALL_FULL ||
           element == EL_DC_MAGIC_WALL_ACTIVE ||
           element == EL_DC_MAGIC_WALL_EMPTYING) &&
-         ABS(x-jx) + ABS(y-jy) < ABS(magic_wall_x-jx) + ABS(magic_wall_y-jy))
+         ABS(x - jx) + ABS(y - jy) <
+         ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
       {
        magic_wall_x = x;
        magic_wall_y = y;
@@ -12133,16 +12352,24 @@ void GameActions_RND(void)
   DrawAllPlayers();
   PlayAllPlayersSound();
 
-  if (local_player->show_envelope != 0 && local_player->MovPos == 0)
+  for (i = 0; i < MAX_PLAYERS; i++)
   {
-    ShowEnvelope(local_player->show_envelope - EL_ENVELOPE_1);
+    struct PlayerInfo *player = &stored_player[i];
 
-    local_player->show_envelope = 0;
+    if (player->show_envelope != 0 && (!player->active ||
+                                      player->MovPos == 0))
+    {
+      ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
+
+      player->show_envelope = 0;
+    }
   }
 
   // use random number generator in every frame to make it less predictable
   if (game.engine_version >= VERSION_IDENT(3,1,1,0))
     RND(1);
+
+  mouse_action_last = mouse_action;
 }
 
 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
@@ -12468,7 +12695,6 @@ boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
        game.centered_player_nr == -1))
   {
     int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
-    int offset = game.scroll_delay_value;
 
     if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
     {
@@ -12480,15 +12706,20 @@ boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
     }
     else
     {
+      int offset_raw = game.scroll_delay_value;
+
       if (jx != old_jx)                // player has moved horizontally
       {
-       if ((player->MovDir == MV_LEFT  && scroll_x > jx - MIDPOSX + offset) ||
-           (player->MovDir == MV_RIGHT && scroll_x < jx - MIDPOSX - offset))
-         scroll_x = jx-MIDPOSX + (scroll_x < jx-MIDPOSX ? -offset : +offset);
+       int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
+       int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
+       int new_scroll_x = jx - MIDPOSX + offset_x;
+
+       if ((player->MovDir == MV_LEFT  && scroll_x > new_scroll_x) ||
+           (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
+         scroll_x = new_scroll_x;
 
        // don't scroll over playfield boundaries
-       if (scroll_x < SBX_Left || scroll_x > SBX_Right)
-         scroll_x = (scroll_x < SBX_Left ? SBX_Left : SBX_Right);
+       scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
 
        // don't scroll more than one field at a time
        scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
@@ -12500,13 +12731,16 @@ boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
       }
       else                     // player has moved vertically
       {
-       if ((player->MovDir == MV_UP   && scroll_y > jy - MIDPOSY + offset) ||
-           (player->MovDir == MV_DOWN && scroll_y < jy - MIDPOSY - offset))
-         scroll_y = jy-MIDPOSY + (scroll_y < jy-MIDPOSY ? -offset : +offset);
+       int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
+       int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
+       int new_scroll_y = jy - MIDPOSY + offset_y;
+
+       if ((player->MovDir == MV_UP   && scroll_y > new_scroll_y) ||
+           (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
+         scroll_y = new_scroll_y;
 
        // don't scroll over playfield boundaries
-       if (scroll_y < SBY_Upper || scroll_y > SBY_Lower)
-         scroll_y = (scroll_y < SBY_Upper ? SBY_Upper : SBY_Lower);
+       scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
 
        // don't scroll more than one field at a time
        scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
@@ -12674,9 +12908,9 @@ void ScrollPlayer(struct PlayerInfo *player, int mode)
     {
       ExitPlayer(player);
 
-      if ((game.friends_still_needed == 0 ||
-          IS_SP_ELEMENT(Feld[jx][jy])) &&
-         AllPlayersGone)
+      if (game.players_still_needed == 0 &&
+         (game.friends_still_needed == 0 ||
+          IS_SP_ELEMENT(Feld[jx][jy])))
        LevelSolved();
     }
 
@@ -12746,7 +12980,7 @@ void ScrollPlayer(struct PlayerInfo *player, int mode)
          for (i = 0; i < MAX_PLAYERS; i++)
            KillPlayer(&stored_player[i]);
       }
-      else if (game.no_time_limit && !AllPlayersGone) // level w/o time limit
+      else if (game.no_time_limit && !game.all_players_gone)
       {
        game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
 
@@ -13404,8 +13638,12 @@ void BuryPlayer(struct PlayerInfo *player)
   PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
   PlayLevelSound(jx, jy, SND_GAME_LOSING);
 
-  player->GameOver = TRUE;
   RemovePlayer(player);
+
+  player->buried = TRUE;
+
+  if (game.all_players_gone)
+    game.GameOver = TRUE;
 }
 
 void RemovePlayer(struct PlayerInfo *player)
@@ -13416,6 +13654,9 @@ void RemovePlayer(struct PlayerInfo *player)
   player->present = FALSE;
   player->active = FALSE;
 
+  // required for some CE actions (even if the player is not active anymore)
+  player->MovPos = 0;
+
   if (!ExplodeField[jx][jy])
     StorePlayer[jx][jy] = 0;
 
@@ -13427,10 +13668,13 @@ void RemovePlayer(struct PlayerInfo *player)
       found = TRUE;
 
   if (!found)
-    AllPlayersGone = TRUE;
+  {
+    game.all_players_gone = TRUE;
+    game.GameOver = TRUE;
+  }
 
-  ExitX = ZX = jx;
-  ExitY = ZY = jy;
+  game.exit_x = game.robot_wheel_x = jx;
+  game.exit_y = game.robot_wheel_y = jy;
 }
 
 void ExitPlayer(struct PlayerInfo *player)
@@ -13440,10 +13684,6 @@ void ExitPlayer(struct PlayerInfo *player)
 
   if (game.players_still_needed > 0)
     game.players_still_needed--;
-
-  // also set if some players not yet gone, but not needed to solve level
-  if (game.players_still_needed == 0)
-    AllPlayersGone = TRUE;
 }
 
 static void setFieldForSnapping(int x, int y, int element, int direction)
@@ -14057,9 +14297,9 @@ static int DigField(struct PlayerInfo *player,
     if (element == EL_ROBOT_WHEEL)
     {
       Feld[x][y] = EL_ROBOT_WHEEL_ACTIVE;
-      ZX = x;
-      ZY = y;
 
+      game.robot_wheel_x = x;
+      game.robot_wheel_y = y;
       game.robot_wheel_active = TRUE;
 
       TEST_DrawLevelField(x, y);
@@ -14148,13 +14388,13 @@ static int DigField(struct PlayerInfo *player,
     {
       int xx, yy;
 
-      game.ball_state = !game.ball_state;
+      game.ball_active = !game.ball_active;
 
       SCAN_PLAYFIELD(xx, yy)
       {
        int e = Feld[xx][yy];
 
-       if (game.ball_state)
+       if (game.ball_active)
        {
          if (e == EL_EMC_MAGIC_BALL)
            CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
@@ -14631,78 +14871,78 @@ static void PlayLevelMusic(void)
 
 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
 {
-  int element = (element_em > -1 ? map_element_EM_to_RND(element_em) : 0);
-  int offset = (BorderElement == EL_STEELWALL ? 1 : 0);
-  int x = xx - 1 - offset;
-  int y = yy - 1 - offset;
+  int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
+  int offset = 0;
+  int x = xx - offset;
+  int y = yy - offset;
 
   switch (sample)
   {
-    case SAMPLE_blank:
+    case SOUND_blank:
       PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
       break;
 
-    case SAMPLE_roll:
+    case SOUND_roll:
       PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
       break;
 
-    case SAMPLE_stone:
+    case SOUND_stone:
       PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
       break;
 
-    case SAMPLE_nut:
+    case SOUND_nut:
       PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
       break;
 
-    case SAMPLE_crack:
+    case SOUND_crack:
       PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
       break;
 
-    case SAMPLE_bug:
+    case SOUND_bug:
       PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
       break;
 
-    case SAMPLE_tank:
+    case SOUND_tank:
       PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
       break;
 
-    case SAMPLE_android_clone:
+    case SOUND_android_clone:
       PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
       break;
 
-    case SAMPLE_android_move:
+    case SOUND_android_move:
       PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
       break;
 
-    case SAMPLE_spring:
+    case SOUND_spring:
       PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
       break;
 
-    case SAMPLE_slurp:
+    case SOUND_slurp:
       PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
       break;
 
-    case SAMPLE_eater:
+    case SOUND_eater:
       PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
       break;
 
-    case SAMPLE_eater_eat:
+    case SOUND_eater_eat:
       PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
       break;
 
-    case SAMPLE_alien:
+    case SOUND_alien:
       PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
       break;
 
-    case SAMPLE_collect:
+    case SOUND_collect:
       PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
       break;
 
-    case SAMPLE_diamond:
+    case SOUND_diamond:
       PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
       break;
 
-    case SAMPLE_squash:
+    case SOUND_squash:
       // !!! CHECK THIS !!!
 #if 1
       PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
@@ -14711,75 +14951,75 @@ void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
 #endif
       break;
 
-    case SAMPLE_wonderfall:
+    case SOUND_wonderfall:
       PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
       break;
 
-    case SAMPLE_drip:
+    case SOUND_drip:
       PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
       break;
 
-    case SAMPLE_push:
+    case SOUND_push:
       PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
       break;
 
-    case SAMPLE_dirt:
+    case SOUND_dirt:
       PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
       break;
 
-    case SAMPLE_acid:
+    case SOUND_acid:
       PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
       break;
 
-    case SAMPLE_ball:
+    case SOUND_ball:
       PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
       break;
 
-    case SAMPLE_grow:
+    case SOUND_slide:
       PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
       break;
 
-    case SAMPLE_wonder:
+    case SOUND_wonder:
       PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
       break;
 
-    case SAMPLE_door:
+    case SOUND_door:
       PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
       break;
 
-    case SAMPLE_exit_open:
+    case SOUND_exit_open:
       PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
       break;
 
-    case SAMPLE_exit_leave:
+    case SOUND_exit_leave:
       PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
       break;
 
-    case SAMPLE_dynamite:
+    case SOUND_dynamite:
       PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
       break;
 
-    case SAMPLE_tick:
+    case SOUND_tick:
       PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
       break;
 
-    case SAMPLE_press:
+    case SOUND_press:
       PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
       break;
 
-    case SAMPLE_wheel:
+    case SOUND_wheel:
       PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
       break;
 
-    case SAMPLE_boom:
+    case SOUND_boom:
       PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
       break;
 
-    case SAMPLE_die:
+    case SOUND_die:
       PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
       break;
 
-    case SAMPLE_time:
+    case SOUND_time:
       PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
       break;
 
@@ -14846,9 +15086,9 @@ void StopSound_MM(int sound_mm)
 
 void RaiseScore(int value)
 {
-  local_player->score += value;
+  game.score += value;
 
-  game_panel_controls[GAME_PANEL_SCORE].value = local_player->score;
+  game_panel_controls[GAME_PANEL_SCORE].value = game.score;
 
   DisplayGameControlValues();
 }
@@ -14939,7 +15179,12 @@ void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
   {
     // closing door required in case of envelope style request dialogs
     if (!skip_request)
+    {
+      // prevent short reactivation of overlay buttons while closing door
+      SetOverlayActive(FALSE);
+
       CloseDoor(DOOR_CLOSE_1);
+    }
 
     if (network.enabled)
       SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
@@ -14968,7 +15213,7 @@ void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
 void RequestQuitGame(boolean ask_if_really_quit)
 {
   boolean quick_quit = (!ask_if_really_quit || level_editor_test_game);
-  boolean skip_request = AllPlayersGone || quick_quit;
+  boolean skip_request = game.all_players_gone || quick_quit;
 
   RequestQuitGameExt(skip_request, quick_quit,
                     "Do you really want to quit the game?");
@@ -15004,6 +15249,10 @@ void CheckGameOver(void)
   if (game.request_active)
     return;
 
+  // do not ask to play again if game was never actually played
+  if (!game.GamePlayed)
+    return;
+
   if (!game_over)
   {
     last_game_over = FALSE;
@@ -15035,9 +15284,6 @@ boolean checkGameSolved(void)
 
 boolean checkGameFailed(void)
 {
-  if (!AllPlayersGone)
-    return FALSE;
-
   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)
@@ -15045,7 +15291,7 @@ boolean checkGameFailed(void)
   else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
     return (game_mm.game_over && !game_mm.level_solved);
   else                         // GAME_ENGINE_TYPE_RND
-    return (local_player->GameOver && !game.LevelSolved);
+    return (game.GameOver && !game.LevelSolved);
 }
 
 boolean checkGameEnded(void)
@@ -15229,11 +15475,6 @@ static ListNode *SaveEngineSnapshotBuffers(void)
   SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
   SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
 
-  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ZX));
-  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ZY));
-  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExitX));
-  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExitY));
-
   SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
   SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
   SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
@@ -15246,8 +15487,6 @@ static ListNode *SaveEngineSnapshotBuffers(void)
 
   SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
 
-  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AllPlayersGone));
-
   SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
   SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
 
@@ -15409,93 +15648,104 @@ static struct
   int gadget_id;
   boolean *setup_value;
   boolean allowed_on_tape;
+  boolean is_touch_button;
   char *infotext;
 } gamebutton_info[NUM_GAME_BUTTONS] =
 {
   {
     IMG_GFX_GAME_BUTTON_STOP,                  &game.button.stop,
     GAME_CTRL_ID_STOP,                         NULL,
-    TRUE,                                      "stop game"
+    TRUE, FALSE,                               "stop game"
   },
   {
     IMG_GFX_GAME_BUTTON_PAUSE,                 &game.button.pause,
     GAME_CTRL_ID_PAUSE,                                NULL,
-    TRUE,                                      "pause game"
+    TRUE, FALSE,                               "pause game"
   },
   {
     IMG_GFX_GAME_BUTTON_PLAY,                  &game.button.play,
     GAME_CTRL_ID_PLAY,                         NULL,
-    TRUE,                                      "play game"
+    TRUE, FALSE,                               "play game"
   },
   {
     IMG_GFX_GAME_BUTTON_UNDO,                  &game.button.undo,
     GAME_CTRL_ID_UNDO,                         NULL,
-    TRUE,                                      "undo step"
+    TRUE, FALSE,                               "undo step"
   },
   {
     IMG_GFX_GAME_BUTTON_REDO,                  &game.button.redo,
     GAME_CTRL_ID_REDO,                         NULL,
-    TRUE,                                      "redo step"
+    TRUE, FALSE,                               "redo step"
   },
   {
     IMG_GFX_GAME_BUTTON_SAVE,                  &game.button.save,
     GAME_CTRL_ID_SAVE,                         NULL,
-    TRUE,                                      "save game"
+    TRUE, FALSE,                               "save game"
   },
   {
     IMG_GFX_GAME_BUTTON_PAUSE2,                        &game.button.pause2,
     GAME_CTRL_ID_PAUSE2,                       NULL,
-    TRUE,                                      "pause game"
+    TRUE, FALSE,                               "pause game"
   },
   {
     IMG_GFX_GAME_BUTTON_LOAD,                  &game.button.load,
     GAME_CTRL_ID_LOAD,                         NULL,
-    TRUE,                                      "load game"
+    TRUE, FALSE,                               "load game"
   },
   {
     IMG_GFX_GAME_BUTTON_PANEL_STOP,            &game.button.panel_stop,
     GAME_CTRL_ID_PANEL_STOP,                   NULL,
-    FALSE,                                     "stop game"
+    FALSE, FALSE,                              "stop game"
   },
   {
     IMG_GFX_GAME_BUTTON_PANEL_PAUSE,           &game.button.panel_pause,
     GAME_CTRL_ID_PANEL_PAUSE,                  NULL,
-    FALSE,                                     "pause game"
+    FALSE, FALSE,                              "pause game"
   },
   {
     IMG_GFX_GAME_BUTTON_PANEL_PLAY,            &game.button.panel_play,
     GAME_CTRL_ID_PANEL_PLAY,                   NULL,
-    FALSE,                                     "play game"
+    FALSE, FALSE,                              "play game"
+  },
+  {
+    IMG_GFX_GAME_BUTTON_TOUCH_STOP,            &game.button.touch_stop,
+    GAME_CTRL_ID_TOUCH_STOP,                   NULL,
+    FALSE, TRUE,                               "stop game"
+  },
+  {
+    IMG_GFX_GAME_BUTTON_TOUCH_PAUSE,           &game.button.touch_pause,
+    GAME_CTRL_ID_TOUCH_PAUSE,                  NULL,
+    FALSE, TRUE,                               "pause game"
   },
   {
     IMG_GFX_GAME_BUTTON_SOUND_MUSIC,           &game.button.sound_music,
     SOUND_CTRL_ID_MUSIC,                       &setup.sound_music,
-    TRUE,                                      "background music on/off"
+    TRUE, FALSE,                               "background music on/off"
   },
   {
     IMG_GFX_GAME_BUTTON_SOUND_LOOPS,           &game.button.sound_loops,
     SOUND_CTRL_ID_LOOPS,                       &setup.sound_loops,
-    TRUE,                                      "sound loops on/off"
+    TRUE, FALSE,                               "sound loops on/off"
   },
   {
     IMG_GFX_GAME_BUTTON_SOUND_SIMPLE,          &game.button.sound_simple,
     SOUND_CTRL_ID_SIMPLE,                      &setup.sound_simple,
-    TRUE,                                      "normal sounds on/off"
+    TRUE, FALSE,                               "normal sounds on/off"
   },
   {
     IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC,     &game.button.panel_sound_music,
     SOUND_CTRL_ID_PANEL_MUSIC,                 &setup.sound_music,
-    FALSE,                                     "background music on/off"
+    FALSE, FALSE,                              "background music on/off"
   },
   {
     IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS,     &game.button.panel_sound_loops,
     SOUND_CTRL_ID_PANEL_LOOPS,                 &setup.sound_loops,
-    FALSE,                                     "sound loops on/off"
+    FALSE, FALSE,                              "sound loops on/off"
   },
   {
     IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE,    &game.button.panel_sound_simple,
     SOUND_CTRL_ID_PANEL_SIMPLE,                        &setup.sound_simple,
-    FALSE,                                     "normal sounds on/off"
+    FALSE, FALSE,                              "normal sounds on/off"
   }
 };
 
@@ -15512,10 +15762,11 @@ void CreateGameButtons(void)
     int button_type;
     boolean checked;
     unsigned int event_mask;
+    boolean is_touch_button = gamebutton_info[i].is_touch_button;
     boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
     boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
-    int base_x = (on_tape ? VX : DX);
-    int base_y = (on_tape ? VY : DY);
+    int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
+    int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
     int gd_x   = gfx->src_x;
     int gd_y   = gfx->src_y;
     int gd_xp  = gfx->src_x + gfx->pressed_xoffset;
@@ -15524,6 +15775,8 @@ void CreateGameButtons(void)
     int gd_ya  = gfx->src_y + gfx->active_yoffset;
     int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
     int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
+    int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
+    int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
     int id = i;
 
     if (gfx->bitmap == NULL)
@@ -15535,6 +15788,7 @@ void CreateGameButtons(void)
 
     if (id == GAME_CTRL_ID_STOP ||
        id == GAME_CTRL_ID_PANEL_STOP ||
+       id == GAME_CTRL_ID_TOUCH_STOP ||
        id == GAME_CTRL_ID_PLAY ||
        id == GAME_CTRL_ID_PANEL_PLAY ||
        id == GAME_CTRL_ID_SAVE ||
@@ -15562,8 +15816,8 @@ void CreateGameButtons(void)
     gi = CreateGadget(GDI_CUSTOM_ID, id,
                      GDI_IMAGE_ID, graphic,
                      GDI_INFO_TEXT, gamebutton_info[i].infotext,
-                     GDI_X, base_x + GDI_ACTIVE_POS(pos->x),
-                     GDI_Y, base_y + GDI_ACTIVE_POS(pos->y),
+                     GDI_X, base_x + x,
+                     GDI_Y, base_y + y,
                      GDI_WIDTH, gfx->width,
                      GDI_HEIGHT, gfx->height,
                      GDI_TYPE, button_type,
@@ -15574,6 +15828,7 @@ void CreateGameButtons(void)
                      GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
                      GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
                      GDI_DIRECT_DRAW, FALSE,
+                     GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
                      GDI_EVENT_MASK, event_mask,
                      GDI_CALLBACK_ACTION, HandleGameButtons,
                      GDI_END);
@@ -15644,8 +15899,6 @@ void MapUndoRedoButtons(void)
 
   MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
   MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
-
-  ModifyGadget(game_gadget[GAME_CTRL_ID_PAUSE2], GDI_CHECKED, TRUE, GDI_END);
 }
 
 void UnmapUndoRedoButtons(void)
@@ -15655,8 +15908,22 @@ void UnmapUndoRedoButtons(void)
 
   MapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
   MapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
+}
 
-  ModifyGadget(game_gadget[GAME_CTRL_ID_PAUSE2], GDI_CHECKED, FALSE, GDI_END);
+void ModifyPauseButtons(void)
+{
+  static int ids[] =
+  {
+    GAME_CTRL_ID_PAUSE,
+    GAME_CTRL_ID_PAUSE2,
+    GAME_CTRL_ID_PANEL_PAUSE,
+    GAME_CTRL_ID_TOUCH_PAUSE,
+    -1
+  };
+  int i;
+
+  for (i = 0; ids[i] > -1; i++)
+    ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
 }
 
 static void MapGameButtonsExt(boolean on_tape)
@@ -15690,9 +15957,6 @@ static void RedrawGameButtonsExt(boolean on_tape)
   for (i = 0; i < NUM_GAME_BUTTONS; i++)
     if (!on_tape || gamebutton_info[i].allowed_on_tape)
       RedrawGadget(game_gadget[i]);
-
-  // RedrawGadget() may have set REDRAW_ALL if buttons are defined off-area
-  redraw_mask &= ~REDRAW_ALL;
 }
 
 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
@@ -15799,6 +16063,7 @@ static void HandleGameButtonsExt(int id, int button)
   {
     case GAME_CTRL_ID_STOP:
     case GAME_CTRL_ID_PANEL_STOP:
+    case GAME_CTRL_ID_TOUCH_STOP:
       if (game_status == GAME_MODE_MAIN)
        break;
 
@@ -15812,6 +16077,7 @@ static void HandleGameButtonsExt(int id, int button)
     case GAME_CTRL_ID_PAUSE:
     case GAME_CTRL_ID_PAUSE2:
     case GAME_CTRL_ID_PANEL_PAUSE:
+    case GAME_CTRL_ID_TOUCH_PAUSE:
       if (network.enabled && game_status == GAME_MODE_PLAYING)
       {
        if (tape.pausing)