fixed bug with envelopes only displayed for the first player
[rocksndiamonds.git] / src / game.c
index 0d8c032e84028895eb27bfae62a633ce15c52251..c7589cf005057b7a46c2fd1ec22ad7ba36d59a08 100644 (file)
@@ -1683,6 +1683,30 @@ int GetElementFromGroupElement(int element)
   return element;
 }
 
+static void IncrementSokobanFieldsNeeded(void)
+{
+  if (level.sb_fields_needed)
+    game.sokoban_fields_still_needed++;
+}
+
+static void IncrementSokobanObjectsNeeded(void)
+{
+  if (level.sb_objects_needed)
+    game.sokoban_objects_still_needed++;
+}
+
+static void DecrementSokobanFieldsNeeded(void)
+{
+  if (game.sokoban_fields_still_needed > 0)
+    game.sokoban_fields_still_needed--;
+}
+
+static void DecrementSokobanObjectsNeeded(void)
+{
+  if (game.sokoban_objects_still_needed > 0)
+    game.sokoban_objects_still_needed--;
+}
+
 static void InitPlayerField(int x, int y, int element, boolean init_game)
 {
   if (element == EL_SP_MURPHY)
@@ -1792,7 +1816,11 @@ static void InitField(int x, int y, boolean init_game)
       break;
 
     case EL_SOKOBAN_FIELD_EMPTY:
-      local_player->sokobanfields_still_needed++;
+      IncrementSokobanFieldsNeeded();
+      break;
+
+    case EL_SOKOBAN_OBJECT:
+      IncrementSokobanObjectsNeeded();
       break;
 
     case EL_STONEBLOCK:
@@ -1877,11 +1905,11 @@ static void InitField(int x, int y, boolean init_game)
       break;
 
     case EL_LAMP:
-      local_player->lights_still_needed++;
+      game.lights_still_needed++;
       break;
 
     case EL_PENGUIN:
-      local_player->friends_still_needed++;
+      game.friends_still_needed++;
       break;
 
     case EL_PIG:
@@ -2046,8 +2074,8 @@ static int get_next_dropped_element(struct PlayerInfo *player)
 
 static int get_inventory_element_from_pos(struct PlayerInfo *player, int pos)
 {
-  /* pos >= 0: get element from bottom of the stack;
-     pos <  0: get element from top of the stack */
+  // pos >= 0: get element from bottom of the stack;
+  // pos <  0: get element from top of the stack
 
   if (pos < 0)
   {
@@ -2179,8 +2207,8 @@ static void UpdatePlayfieldElementCount(void)
 static void UpdateGameControlValues(void)
 {
   int i, k;
-  int time = (local_player->LevelSolved ?
-             local_player->LevelSolved_CountingTime :
+  int time = (game.LevelSolved ?
+             game.LevelSolved_CountingTime :
              level.game_engine_type == GAME_ENGINE_TYPE_EM ?
              level.native_em_level->lev->time :
              level.game_engine_type == GAME_ENGINE_TYPE_SP ?
@@ -2188,22 +2216,22 @@ static void UpdateGameControlValues(void)
              level.game_engine_type == GAME_ENGINE_TYPE_MM ?
              game_mm.energy_left :
              game.no_time_limit ? TimePlayed : TimeLeft);
-  int score = (local_player->LevelSolved ?
-              local_player->LevelSolved_CountingScore :
+  int score = (game.LevelSolved ?
+              game.LevelSolved_CountingScore :
               level.game_engine_type == GAME_ENGINE_TYPE_EM ?
               level.native_em_level->lev->score :
               level.game_engine_type == GAME_ENGINE_TYPE_SP ?
               level.native_sp_level->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 :
              level.game_engine_type == GAME_ENGINE_TYPE_SP ?
              level.native_sp_level->game_sp->infotrons_still_needed :
              level.game_engine_type == GAME_ENGINE_TYPE_MM ?
              game_mm.kettles_still_needed :
-             local_player->gems_still_needed);
+             game.gems_still_needed);
   int exit_closed = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
                     level.native_em_level->lev->required > 0 :
                     level.game_engine_type == GAME_ENGINE_TYPE_SP ?
@@ -2211,14 +2239,15 @@ static void UpdateGameControlValues(void)
                     level.game_engine_type == GAME_ENGINE_TYPE_MM ?
                     game_mm.kettles_still_needed > 0 ||
                     game_mm.lights_still_needed > 0 :
-                    local_player->gems_still_needed > 0 ||
-                    local_player->sokobanfields_still_needed > 0 ||
-                    local_player->lights_still_needed > 0);
-  int health = (local_player->LevelSolved ?
-               local_player->LevelSolved_CountingHealth :
+                    game.gems_still_needed > 0 ||
+                    game.sokoban_fields_still_needed > 0 ||
+                    game.sokoban_objects_still_needed > 0 ||
+                    game.lights_still_needed > 0);
+  int health = (game.LevelSolved ?
+               game.LevelSolved_CountingHealth :
                level.game_engine_type == GAME_ENGINE_TYPE_MM ?
                MM_HEALTH(game_mm.laser_overload_value) :
-               local_player->health);
+               game.health);
 
   UpdatePlayfieldElementCount();
 
@@ -2378,12 +2407,12 @@ static void UpdateGameControlValues(void)
     (local_player->dynabomb_xl ? EL_DYNABOMB_INCREASE_POWER : EL_EMPTY);
 
   game_panel_controls[GAME_PANEL_PENGUINS].value =
-    local_player->friends_still_needed;
+    game.friends_still_needed;
 
   game_panel_controls[GAME_PANEL_SOKOBAN_OBJECTS].value =
-    local_player->sokobanfields_still_needed;
+    game.sokoban_objects_still_needed;
   game_panel_controls[GAME_PANEL_SOKOBAN_FIELDS].value =
-    local_player->sokobanfields_still_needed;
+    game.sokoban_fields_still_needed;
 
   game_panel_controls[GAME_PANEL_ROBOT_WHEEL].value =
     (game.robot_wheel_active ? EL_ROBOT_WHEEL_ACTIVE : EL_ROBOT_WHEEL);
@@ -2780,13 +2809,11 @@ void DrawGameDoorValues(void)
 }
 
 
-/*
-  =============================================================================
-  InitGameEngine()
-  -----------------------------------------------------------------------------
-  initialize game engine due to level / tape version number
-  =============================================================================
-*/
+// ============================================================================
+// InitGameEngine()
+// ----------------------------------------------------------------------------
+// initialize game engine due to level / tape version number
+// ============================================================================
 
 static void InitGameEngine(void)
 {
@@ -3270,13 +3297,11 @@ static int get_num_special_action(int element, int action_first,
 }
 
 
-/*
-  =============================================================================
-  InitGame()
-  -----------------------------------------------------------------------------
-  initialize and start new game
-  =============================================================================
-*/
+// ============================================================================
+// InitGame()
+// ----------------------------------------------------------------------------
+// initialize and start new game
+// ============================================================================
 
 #if DEBUG_INIT_PLAYER
 static void DebugPrintPlayerStatus(char *message)
@@ -3333,14 +3358,15 @@ void InitGame(void)
   else
     FadeSetEnterScreen();
 
-  if (CheckIfGlobalBorderOrPlayfieldViewportHasChanged())
+  if (CheckFadeAll())
     fade_mask = REDRAW_ALL;
 
   FadeLevelSoundsAndMusic();
 
   ExpireSoundLoops(TRUE);
 
-  FadeOut(fade_mask);
+  if (!level_editor_test_game)
+    FadeOut(fade_mask);
 
   // needed if different viewport properties defined for playing
   ChangeViewportPropertiesIfNeeded();
@@ -3371,10 +3397,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;
@@ -3386,18 +3414,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;
-
-    player->gems_still_needed = level.gems_needed;
-    player->sokobanfields_still_needed = 0;
-    player->lights_still_needed = 0;
-    player->players_still_needed = 0;
-    player->friends_still_needed = 0;
-
     for (j = 0; j < MAX_NUM_KEYS; j++)
       player->key[j] = FALSE;
 
@@ -3520,18 +3536,6 @@ void InitGame(void)
     DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
     SnapField(player, 0, 0);
 
-    player->LevelSolved = FALSE;
-    player->GameOver = FALSE;
-
-    player->LevelSolved_GameWon = FALSE;
-    player->LevelSolved_GameEnd = FALSE;
-    player->LevelSolved_SaveTape = FALSE;
-    player->LevelSolved_SaveScore = FALSE;
-
-    player->LevelSolved_CountingTime = 0;
-    player->LevelSolved_CountingScore = 0;
-    player->LevelSolved_CountingHealth = 0;
-
     map_player_action[i] = i;
   }
 
@@ -3541,9 +3545,6 @@ void InitGame(void)
   if (network_playing)
     SendToServer_MovePlayer(MV_NONE);
 
-  ZX = ZY = -1;
-  ExitX = ExitY = -1;
-
   FrameCounter = 0;
   TimeFrames = 0;
   TimePlayed = 0;
@@ -3556,7 +3557,27 @@ 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;
+  game.LevelSolved_SaveTape = FALSE;
+  game.LevelSolved_SaveScore = FALSE;
+
+  game.LevelSolved_CountingTime = 0;
+  game.LevelSolved_CountingScore = 0;
+  game.LevelSolved_CountingHealth = 0;
 
   game.panel.active = TRUE;
 
@@ -3571,6 +3592,19 @@ 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;
+  game.lights_still_needed = 0;
+  game.players_still_needed = 0;
+  game.friends_still_needed = 0;
+
   game.lenses_time_left = 0;
   game.magnify_time_left = 0;
 
@@ -3705,6 +3739,19 @@ 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 old_index_nr = local_player->index_nr;
+    int new_index_nr = setup.network_player_nr;
+
+    if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
+    {
+      stored_player[old_index_nr].connected_locally = FALSE;
+      stored_player[new_index_nr].connected_locally = TRUE;
+    }
+  }
+
   for (i = 0; i < MAX_PLAYERS; i++)
   {
     stored_player[i].connected = FALSE;
@@ -3724,8 +3771,8 @@ void InitGame(void)
   }
   else if (network.enabled)
   {
-    /* add team mode players connected over the network (needed for correct
-       assignment of player figures from level to locally playing players) */
+    // add team mode players connected over the network (needed for correct
+    // assignment of player figures from level to locally playing players)
 
     for (i = 0; i < MAX_PLAYERS; i++)
       if (stored_player[i].connected_network)
@@ -3733,8 +3780,8 @@ void InitGame(void)
   }
   else if (game.team_mode)
   {
-    /* try to guess locally connected team mode players (needed for correct
-       assignment of player figures from level to locally playing players) */
+    // try to guess locally connected team mode players (needed for correct
+    // assignment of player figures from level to locally playing players)
 
     for (i = 0; i < MAX_PLAYERS; i++)
       if (setup.input[i].use_joystick ||
@@ -3958,10 +4005,10 @@ void InitGame(void)
 
   for (i = 0; i < MAX_PLAYERS; i++)
     if (stored_player[i].active)
-      local_player->players_still_needed++;
+      game.players_still_needed++;
 
   if (level.solved_by_one_player)
-    local_player->players_still_needed = 1;
+    game.players_still_needed = 1;
 
   // when recording the game, store which players take part in the game
   if (tape.recording)
@@ -4006,8 +4053,8 @@ void InitGame(void)
   if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
     SBY_Upper--;
 
-  /* if local player not found, look for custom element that might create
-     the player (make some assumptions about the right custom element) */
+  // if local player not found, look for custom element that might create
+  // the player (make some assumptions about the right custom element)
   if (!local_player->present)
   {
     int start_x = 0, start_y = 0;
@@ -4430,28 +4477,27 @@ void InitAmoebaNr(int x, int y)
   AmoebaCnt2[group_nr]++;
 }
 
-static void PlayerWins(struct PlayerInfo *player)
+static void LevelSolved(void)
 {
   if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
-      local_player->players_still_needed > 0)
+      game.players_still_needed > 0)
     return;
 
-  player->LevelSolved = TRUE;
-  player->GameOver = TRUE;
+  game.LevelSolved = TRUE;
+  game.GameOver = TRUE;
 
-  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 :
-                        player->score);
-  player->health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
-                         MM_HEALTH(game_mm.laser_overload_value) :
-                         player->health);
+  game.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 :
+                     game.score);
+  game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
+                      MM_HEALTH(game_mm.laser_overload_value) :
+                      game.health);
 
-  player->LevelSolved_CountingTime = (game.no_time_limit ? TimePlayed :
-                                     TimeLeft);
-  player->LevelSolved_CountingScore = player->score_final;
-  player->LevelSolved_CountingHealth = player->health_final;
+  game.LevelSolved_CountingTime = (game.no_time_limit ? TimePlayed : TimeLeft);
+  game.LevelSolved_CountingScore = game.score_final;
+  game.LevelSolved_CountingHealth = game.health_final;
 }
 
 void GameWon(void)
@@ -4467,17 +4513,17 @@ void GameWon(void)
   int game_over_delay_value_2 = 25;
   int game_over_delay_value_3 = 50;
 
-  if (!local_player->LevelSolved_GameWon)
+  if (!game.LevelSolved_GameWon)
   {
     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;
 
-    local_player->LevelSolved_GameWon = TRUE;
-    local_player->LevelSolved_SaveTape = tape.recording;
-    local_player->LevelSolved_SaveScore = !tape.playing;
+    game.LevelSolved_GameWon = TRUE;
+    game.LevelSolved_SaveTape = tape.recording;
+    game.LevelSolved_SaveScore = !tape.playing;
 
     if (!tape.playing)
     {
@@ -4496,8 +4542,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)
     {
@@ -4524,8 +4570,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)
@@ -4533,8 +4579,8 @@ void GameWon(void)
       time = time_final;
       score = score_final;
 
-      local_player->LevelSolved_CountingTime = time;
-      local_player->LevelSolved_CountingScore = score;
+      game.LevelSolved_CountingTime = time;
+      game.LevelSolved_CountingScore = score;
 
       game_panel_controls[GAME_PANEL_TIME].value = time;
       game_panel_controls[GAME_PANEL_SCORE].value = score;
@@ -4544,30 +4590,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++)
@@ -4605,8 +4656,8 @@ void GameWon(void)
     time  += time_count_steps * time_count_dir;
     score += time_count_steps * level.score[SC_TIME_BONUS];
 
-    local_player->LevelSolved_CountingTime = time;
-    local_player->LevelSolved_CountingScore = score;
+    game.LevelSolved_CountingTime = time;
+    game.LevelSolved_CountingScore = score;
 
     game_panel_controls[GAME_PANEL_TIME].value = time;
     game_panel_controls[GAME_PANEL_SCORE].value = score;
@@ -4637,8 +4688,8 @@ void GameWon(void)
     health += health_count_dir;
     score  += level.score[SC_TIME_BONUS];
 
-    local_player->LevelSolved_CountingHealth = health;
-    local_player->LevelSolved_CountingScore = score;
+    game.LevelSolved_CountingHealth = health;
+    game.LevelSolved_CountingScore = score;
 
     game_panel_controls[GAME_PANEL_HEALTH].value = health;
     game_panel_controls[GAME_PANEL_SCORE].value = score;
@@ -4673,9 +4724,9 @@ void GameEnd(void)
   int last_level_nr = levelset.level_nr;
   int hi_pos;
 
-  local_player->LevelSolved_GameEnd = TRUE;
+  game.LevelSolved_GameEnd = TRUE;
 
-  if (local_player->LevelSolved_SaveTape)
+  if (game.LevelSolved_SaveTape)
   {
     // make sure that request dialog to save tape does not open door again
     if (!global.use_envelope_request)
@@ -4696,7 +4747,7 @@ void GameEnd(void)
     return;
   }
 
-  if (!local_player->LevelSolved_SaveScore)
+  if (!game.LevelSolved_SaveScore)
   {
     SetGameStatus(GAME_MODE_MAIN);
 
@@ -4758,12 +4809,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
 
@@ -4792,7 +4843,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;
@@ -4893,9 +4944,9 @@ static void InitMovingField(int x, int y, int direction)
   is_moving_before = (WasJustMoving[x][y] != 0);
   is_moving_after  = (getElementMoveStepsizeExt(x, y, direction)    != 0);
 
-  /* reset animation only for moving elements which change direction of moving
-     or which just started or stopped moving
-     (else CEs with property "can move" / "not moving" are reset each frame) */
+  // reset animation only for moving elements which change direction of moving
+  // or which just started or stopped moving
+  // (else CEs with property "can move" / "not moving" are reset each frame)
   if (is_moving_before != is_moving_after ||
       direction != MovDir[x][y])
     ResetGfxAnimation(x, y);
@@ -4970,9 +5021,9 @@ static int MovingOrBlocked2Element(int x, int y)
 
 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
 {
-  /* like MovingOrBlocked2Element(), but if element is moving
-     and (x,y) is the field the moving element is just leaving,
-     return EL_BLOCKED instead of the element value */
+  // like MovingOrBlocked2Element(), but if element is moving
+  // and (x,y) is the field the moving element is just leaving,
+  // return EL_BLOCKED instead of the element value
   int element = Feld[x][y];
 
   if (IS_MOVING(x, y))
@@ -5026,9 +5077,9 @@ static void RemoveMovingField(int x, int y)
 
     if (Feld[newx][newy] != EL_BLOCKED)
     {
-      /* element is moving, but target field is not free (blocked), but
-        already occupied by something different (example: acid pool);
-        in this case, only remove the moving field, but not the target */
+      // element is moving, but target field is not free (blocked), but
+      // already occupied by something different (example: acid pool);
+      // in this case, only remove the moving field, but not the target
 
       RemoveField(oldx, oldy);
 
@@ -5222,11 +5273,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;
@@ -5234,14 +5282,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);
@@ -5277,7 +5330,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
@@ -5493,8 +5546,8 @@ static void Explode(int ex, int ey, int phase, int mode)
          Store[x][y] = EL_EMPTY;
       }
 
-      /* !!! check this case -- currently needed for rnd_rado_negundo_v,
-        !!! levels 015 018 019 020 021 022 023 026 027 028 !!! */
+      // !!! check this case -- currently needed for rnd_rado_negundo_v,
+      // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
       else if (ELEM_IS_PLAYER(center_element))
        Store[x][y] = EL_EMPTY;
       else if (center_element == EL_YAMYAM)
@@ -5502,9 +5555,9 @@ static void Explode(int ex, int ey, int phase, int mode)
       else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
        Store[x][y] = element_info[center_element].content.e[xx][yy];
 #if 1
-      /* needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
-        (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
-        otherwise) -- FIX THIS !!! */
+      // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
+      // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
+      // otherwise) -- FIX THIS !!!
       else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
        Store[x][y] = element_info[element].content.e[1][1];
 #else
@@ -5580,9 +5633,9 @@ static void Explode(int ex, int ey, int phase, int mode)
       border_explosion = TRUE;
     }
 
-    /* if an element just explodes due to another explosion (chain-reaction),
-       do not immediately end the new explosion when it was the last frame of
-       the explosion (as it would be done in the following "if"-statement!) */
+    // if an element just explodes due to another explosion (chain-reaction),
+    // do not immediately end the new explosion when it was the last frame of
+    // the explosion (as it would be done in the following "if"-statement!)
     if (border_explosion && phase == last_phase)
       return;
   }
@@ -6787,10 +6840,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
     {
@@ -6813,12 +6866,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)
@@ -6851,13 +6906,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)
     {
@@ -7156,10 +7211,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
     {
@@ -7781,8 +7836,8 @@ static void StartMoving(int x, int y)
 
     if (!MovDelay[x][y])       // start new movement phase
     {
-      /* all objects that can change their move direction after each step
-        (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall */
+      // all objects that can change their move direction after each step
+      // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
 
       if (element != EL_YAMYAM &&
          element != EL_DARK_YAMYAM &&
@@ -7922,10 +7977,11 @@ static void StartMoving(int x, int y)
        if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
          DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0);
 
-       local_player->friends_still_needed--;
-       if (!local_player->friends_still_needed &&
-           !local_player->GameOver && AllPlayersGone)
-         PlayerWins(local_player);
+       game.friends_still_needed--;
+       if (!game.friends_still_needed &&
+           !game.GameOver &&
+           game.all_players_gone)
+         LevelSolved();
 
        return;
       }
@@ -8729,8 +8785,8 @@ static void AmoebaDisappearing(int x, int y)
       Feld[x][y] = EL_EMPTY;
       TEST_DrawLevelField(x, y);
 
-      /* don't let mole enter this field in this cycle;
-        (give priority to objects falling to this field from above) */
+      // don't let mole enter this field in this cycle;
+      // (give priority to objects falling to this field from above)
       Stop[x][y] = TRUE;
     }
   }
@@ -8999,10 +9055,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;
   }
 }
@@ -9056,9 +9113,10 @@ static void ActivateMagicBall(int bx, int by)
 
 static void CheckExit(int x, int y)
 {
-  if (local_player->gems_still_needed > 0 ||
-      local_player->sokobanfields_still_needed > 0 ||
-      local_player->lights_still_needed > 0)
+  if (game.gems_still_needed > 0 ||
+      game.sokoban_fields_still_needed > 0 ||
+      game.sokoban_objects_still_needed > 0 ||
+      game.lights_still_needed > 0)
   {
     int element = Feld[x][y];
     int graphic = el2img(element);
@@ -9069,7 +9127,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;
@@ -9079,9 +9138,10 @@ static void CheckExit(int x, int y)
 
 static void CheckExitEM(int x, int y)
 {
-  if (local_player->gems_still_needed > 0 ||
-      local_player->sokobanfields_still_needed > 0 ||
-      local_player->lights_still_needed > 0)
+  if (game.gems_still_needed > 0 ||
+      game.sokoban_fields_still_needed > 0 ||
+      game.sokoban_objects_still_needed > 0 ||
+      game.lights_still_needed > 0)
   {
     int element = Feld[x][y];
     int graphic = el2img(element);
@@ -9092,7 +9152,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;
@@ -9102,9 +9163,10 @@ static void CheckExitEM(int x, int y)
 
 static void CheckExitSteel(int x, int y)
 {
-  if (local_player->gems_still_needed > 0 ||
-      local_player->sokobanfields_still_needed > 0 ||
-      local_player->lights_still_needed > 0)
+  if (game.gems_still_needed > 0 ||
+      game.sokoban_fields_still_needed > 0 ||
+      game.sokoban_objects_still_needed > 0 ||
+      game.lights_still_needed > 0)
   {
     int element = Feld[x][y];
     int graphic = el2img(element);
@@ -9115,7 +9177,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;
@@ -9125,9 +9188,10 @@ static void CheckExitSteel(int x, int y)
 
 static void CheckExitSteelEM(int x, int y)
 {
-  if (local_player->gems_still_needed > 0 ||
-      local_player->sokobanfields_still_needed > 0 ||
-      local_player->lights_still_needed > 0)
+  if (game.gems_still_needed > 0 ||
+      game.sokoban_fields_still_needed > 0 ||
+      game.sokoban_objects_still_needed > 0 ||
+      game.lights_still_needed > 0)
   {
     int element = Feld[x][y];
     int graphic = el2img(element);
@@ -9138,7 +9202,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;
@@ -9148,7 +9213,7 @@ static void CheckExitSteelEM(int x, int y)
 
 static void CheckExitSP(int x, int y)
 {
-  if (local_player->gems_still_needed > 0)
+  if (game.gems_still_needed > 0)
   {
     int element = Feld[x][y];
     int graphic = el2img(element);
@@ -9159,7 +9224,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;
@@ -9675,8 +9741,8 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
      action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
      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 ? local_player->gems_still_needed :
-     action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? local_player->score :
+     action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
+     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):
@@ -9689,9 +9755,9 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
      -1);
 
   int action_arg_number_old =
-    (action_type == CA_SET_LEVEL_GEMS ? local_player->gems_still_needed :
+    (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);
@@ -9761,9 +9827,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();
 
@@ -9772,12 +9838,11 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
 
     case CA_SET_LEVEL_GEMS:
     {
-      local_player->gems_still_needed = action_arg_number_new;
+      game.gems_still_needed = action_arg_number_new;
 
       game.snapshot.collected_item = TRUE;
 
-      game_panel_controls[GAME_PANEL_GEMS].value =
-       local_player->gems_still_needed;
+      game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
 
       DisplayGameControlValues();
 
@@ -9817,8 +9882,8 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
        if (action_arg_player_bits & (1 << i))
          ExitPlayer(&stored_player[i]);
 
-      if (AllPlayersGone)
-       PlayerWins(local_player);
+      if (game.players_still_needed == 0)
+       LevelSolved();
 
       break;
     }
@@ -10217,8 +10282,8 @@ static void CreateFieldExt(int x, int y, int element, boolean is_change)
       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) */
+  // 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))
@@ -10253,8 +10318,8 @@ static void CreateElementFromChange(int x, int y, int element)
   {
     int old_element = Feld[x][y];
 
-    /* prevent changed element from moving in same engine frame
-       unless both old and new element can either fall or move */
+    // prevent changed element from moving in same engine frame
+    // unless both old and new element can either fall or move
     if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
        (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
       Stop[x][y] = TRUE;
@@ -10507,8 +10572,8 @@ static void HandleElementChange(int x, int y, int page)
        This can also be seen from the debug output for this test element.)
       */
 
-      /* when a custom element is about to change (for example by change delay),
-        do not reset graphic animation when the custom element is moving */
+      // when a custom element is about to change (for example by change delay),
+      // do not reset graphic animation when the custom element is moving
       if (game.graphics_engine_version < 4 &&
          !IS_MOVING(x, y))
       {
@@ -10629,9 +10694,9 @@ static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
            {
              if (change->can_change && !change_done)
              {
-               /* if element already changed in this frame, not only prevent
-                  another element change (checked in ChangeElement()), but
-                  also prevent additional element actions for this element */
+               // if element already changed in this frame, not only prevent
+               // another element change (checked in ChangeElement()), but
+               // also prevent additional element actions for this element
 
                if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
                    !level.use_action_after_change_bug)
@@ -10644,9 +10709,9 @@ static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
              }
              else if (change->has_action)
              {
-               /* if element already changed in this frame, not only prevent
-                  another element change (checked in ChangeElement()), but
-                  also prevent additional element actions for this element */
+               // if element already changed in this frame, not only prevent
+               // another element change (checked in ChangeElement()), but
+               // also prevent additional element actions for this element
 
                if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
                    !level.use_action_after_change_bug)
@@ -10996,10 +11061,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);
@@ -11087,50 +11149,48 @@ static void CheckLevelSolved(void)
 {
   if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
   {
-    if (level.native_em_level->lev->home == 0) // all players at home
+    if (game_em.level_solved &&
+       !game_em.game_over)                             // game won
     {
-      PlayerWins(local_player);
+      LevelSolved();
 
-      AllPlayersGone = TRUE;
+      game_em.game_over = TRUE;
 
-      level.native_em_level->lev->home = -1;
+      game.all_players_gone = TRUE;
     }
 
-    if (level.native_em_level->ply[0]->alive == 0 &&
-       level.native_em_level->ply[1]->alive == 0 &&
-       level.native_em_level->ply[2]->alive == 0 &&
-       level.native_em_level->ply[3]->alive == 0)      // all dead
-      AllPlayersGone = TRUE;
+    if (game_em.game_over)                             // game lost
+      game.all_players_gone = TRUE;
   }
   else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
   {
-    if (game_sp.LevelSolved &&
-       !game_sp.GameOver)                              // game won
+    if (game_sp.level_solved &&
+       !game_sp.game_over)                             // game won
     {
-      PlayerWins(local_player);
+      LevelSolved();
 
-      game_sp.GameOver = TRUE;
+      game_sp.game_over = TRUE;
 
-      AllPlayersGone = TRUE;
+      game.all_players_gone = TRUE;
     }
 
-    if (game_sp.GameOver)                              // game lost
-      AllPlayersGone = TRUE;
+    if (game_sp.game_over)                             // game lost
+      game.all_players_gone = TRUE;
   }
   else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
   {
     if (game_mm.level_solved &&
        !game_mm.game_over)                             // game won
     {
-      PlayerWins(local_player);
+      LevelSolved();
 
       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;
   }
 }
 
@@ -11156,7 +11216,7 @@ static void CheckLevelTime(void)
       }
     }
 
-    if (!local_player->LevelSolved && !level.use_step_counter)
+    if (!game.LevelSolved && !level.use_step_counter)
     {
       TimePlayed++;
 
@@ -11181,7 +11241,7 @@ static void CheckLevelTime(void)
              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;
       }
@@ -11303,10 +11363,10 @@ static void GameActionsExt(void)
 
   CheckLevelSolved();
 
-  if (local_player->LevelSolved && !local_player->LevelSolved_GameEnd)
+  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
@@ -11320,6 +11380,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 ----------
@@ -11396,13 +11465,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);
   }
 
@@ -11430,6 +11500,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)
@@ -11923,7 +11997,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;
@@ -12105,11 +12180,17 @@ 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
@@ -12440,7 +12521,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)))
     {
@@ -12452,15 +12532,19 @@ boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
     }
     else
     {
+      int offset = 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_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);
@@ -12472,13 +12556,15 @@ 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_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);
@@ -12646,10 +12732,10 @@ void ScrollPlayer(struct PlayerInfo *player, int mode)
     {
       ExitPlayer(player);
 
-      if ((local_player->friends_still_needed == 0 ||
-          IS_SP_ELEMENT(Feld[jx][jy])) &&
-         AllPlayersGone)
-       PlayerWins(local_player);
+      if (game.players_still_needed == 0 &&
+         (game.friends_still_needed == 0 ||
+          IS_SP_ELEMENT(Feld[jx][jy])))
+       LevelSolved();
     }
 
     // this breaks one level: "machine", level 000
@@ -12697,7 +12783,7 @@ void ScrollPlayer(struct PlayerInfo *player, int mode)
        RemovePlayer(player);
     }
 
-    if (!local_player->LevelSolved && level.use_step_counter)
+    if (!game.LevelSolved && level.use_step_counter)
     {
       int i;
 
@@ -12718,7 +12804,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;
 
@@ -13376,8 +13462,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)
@@ -13388,6 +13478,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;
 
@@ -13399,10 +13492,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)
@@ -13410,12 +13506,8 @@ void ExitPlayer(struct PlayerInfo *player)
   DrawPlayer(player);  // needed here only to cleanup last field
   RemovePlayer(player);
 
-  if (local_player->players_still_needed > 0)
-    local_player->players_still_needed--;
-
-  // also set if some players not yet gone, but not needed to solve level
-  if (local_player->players_still_needed == 0)
-    AllPlayersGone = TRUE;
+  if (game.players_still_needed > 0)
+    game.players_still_needed--;
 }
 
 static void setFieldForSnapping(int x, int y, int element, int direction)
@@ -13825,13 +13917,13 @@ static int DigField(struct PlayerInfo *player,
     }
     else if (collect_count > 0)
     {
-      local_player->gems_still_needed -= collect_count;
-      if (local_player->gems_still_needed < 0)
-       local_player->gems_still_needed = 0;
+      game.gems_still_needed -= collect_count;
+      if (game.gems_still_needed < 0)
+       game.gems_still_needed = 0;
 
       game.snapshot.collected_item = TRUE;
 
-      game_panel_controls[GAME_PANEL_GEMS].value = local_player->gems_still_needed;
+      game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
 
       DisplayGameControlValues();
     }
@@ -13937,16 +14029,26 @@ static int DigField(struct PlayerInfo *player,
 
     if (IS_SB_ELEMENT(element))
     {
+      boolean sokoban_task_solved = FALSE;
+
       if (element == EL_SOKOBAN_FIELD_FULL)
       {
        Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
-       local_player->sokobanfields_still_needed++;
+
+       IncrementSokobanFieldsNeeded();
+       IncrementSokobanObjectsNeeded();
       }
 
       if (Feld[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
       {
        Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
-       local_player->sokobanfields_still_needed--;
+
+       DecrementSokobanFieldsNeeded();
+       DecrementSokobanObjectsNeeded();
+
+       // sokoban object was pushed from empty field to sokoban field
+       if (Back[x][y] == EL_EMPTY)
+         sokoban_task_solved = TRUE;
       }
 
       Feld[x][y] = EL_SOKOBAN_OBJECT;
@@ -13960,12 +14062,14 @@ static int DigField(struct PlayerInfo *player,
        PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
                                    ACTION_FILLING);
 
-      if (local_player->sokobanfields_still_needed == 0 &&
+      if (sokoban_task_solved &&
+         game.sokoban_fields_still_needed == 0 &&
+         game.sokoban_objects_still_needed == 0 &&
          (game.emulation == EMU_SOKOBAN || level.auto_exit_sokoban))
       {
-       local_player->players_still_needed = 0;
+       game.players_still_needed = 0;
 
-       PlayerWins(player);
+       LevelSolved();
 
        PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
       }
@@ -14017,9 +14121,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);
@@ -14081,7 +14185,7 @@ static int DigField(struct PlayerInfo *player,
     else if (element == EL_LAMP)
     {
       Feld[x][y] = EL_LAMP_ACTIVE;
-      local_player->lights_still_needed--;
+      game.lights_still_needed--;
 
       ResetGfxAnimation(x, y);
       TEST_DrawLevelField(x, y);
@@ -14806,9 +14910,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();
 }
@@ -14899,7 +15003,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);
@@ -14928,7 +15037,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?");
@@ -14964,6 +15073,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;
@@ -14990,22 +15103,19 @@ void CheckGameOver(void)
 boolean checkGameSolved(void)
 {
   // set for all game engines if level was solved
-  return local_player->LevelSolved_GameEnd;
+  return game.LevelSolved_GameEnd;
 }
 
 boolean checkGameFailed(void)
 {
-  if (!AllPlayersGone)
-    return FALSE;
-
   if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
-    return (level.native_em_level->lev->home > 0);
+    return (game_em.game_over && !game_em.level_solved);
   else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
-    return (game_sp.GameOver && !game_sp.LevelSolved);
+    return (game_sp.game_over && !game_sp.level_solved);
   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 && !local_player->LevelSolved);
+    return (game.GameOver && !game.LevelSolved);
 }
 
 boolean checkGameEnded(void)
@@ -15189,11 +15299,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));
@@ -15206,8 +15311,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));