fixed playfield flashing black when game started from level editor
[rocksndiamonds.git] / src / game.c
index 4a3d1eef4d4e63bbe088da3b3209f31ce03088ad..99540b18d9622ef055a0066d41c7e32f8dbb53d6 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,11 +1816,11 @@ static void InitField(int x, int y, boolean init_game)
       break;
 
     case EL_SOKOBAN_FIELD_EMPTY:
-      local_player->sokoban_fields_still_needed++;
+      IncrementSokobanFieldsNeeded();
       break;
 
     case EL_SOKOBAN_OBJECT:
-      local_player->sokoban_objects_still_needed++;
+      IncrementSokobanObjectsNeeded();
       break;
 
     case EL_STONEBLOCK:
@@ -1881,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:
@@ -2183,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 ?
@@ -2192,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 ?
@@ -2215,15 +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->sokoban_fields_still_needed > 0 ||
-                    local_player->sokoban_objects_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();
 
@@ -2383,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->sokoban_objects_still_needed;
+    game.sokoban_objects_still_needed;
   game_panel_controls[GAME_PANEL_SOKOBAN_FIELDS].value =
-    local_player->sokoban_fields_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);
@@ -3334,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();
@@ -3372,6 +3397,7 @@ void InitGame(void)
 
     player->killed = FALSE;
     player->reanimated = FALSE;
+    player->buried = FALSE;
 
     player->action = 0;
     player->effective_action = 0;
@@ -3387,19 +3413,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->sokoban_fields_still_needed = 0;
-    player->sokoban_objects_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;
 
@@ -3522,18 +3535,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;
   }
 
@@ -3543,9 +3544,6 @@ void InitGame(void)
   if (network_playing)
     SendToServer_MovePlayer(MV_NONE);
 
-  ZX = ZY = -1;
-  ExitX = ExitY = -1;
-
   FrameCounter = 0;
   TimeFrames = 0;
   TimePlayed = 0;
@@ -3558,7 +3556,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;
 
@@ -3573,6 +3591,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;
 
@@ -3960,10 +3991,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)
@@ -4432,28 +4463,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)
@@ -4469,17 +4499,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)
     {
@@ -4498,8 +4528,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)
     {
@@ -4526,8 +4556,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)
@@ -4535,8 +4565,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;
@@ -4546,30 +4576,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++)
@@ -4607,8 +4642,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;
@@ -4639,8 +4674,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;
@@ -4675,9 +4710,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)
@@ -4698,7 +4733,7 @@ void GameEnd(void)
     return;
   }
 
-  if (!local_player->LevelSolved_SaveScore)
+  if (!game.LevelSolved_SaveScore)
   {
     SetGameStatus(GAME_MODE_MAIN);
 
@@ -4760,12 +4795,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
 
@@ -4794,7 +4829,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;
@@ -5279,7 +5314,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
@@ -6789,10 +6824,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
     {
@@ -6815,12 +6850,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)
@@ -6853,13 +6890,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)
     {
@@ -7158,10 +7195,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
     {
@@ -7924,10 +7961,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;
       }
@@ -9001,10 +9039,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;
   }
 }
@@ -9058,10 +9097,10 @@ static void ActivateMagicBall(int bx, int by)
 
 static void CheckExit(int x, int y)
 {
-  if (local_player->gems_still_needed > 0 ||
-      local_player->sokoban_fields_still_needed > 0 ||
-      local_player->sokoban_objects_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);
@@ -9072,7 +9111,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;
@@ -9082,10 +9122,10 @@ static void CheckExit(int x, int y)
 
 static void CheckExitEM(int x, int y)
 {
-  if (local_player->gems_still_needed > 0 ||
-      local_player->sokoban_fields_still_needed > 0 ||
-      local_player->sokoban_objects_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);
@@ -9096,7 +9136,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;
@@ -9106,10 +9147,10 @@ static void CheckExitEM(int x, int y)
 
 static void CheckExitSteel(int x, int y)
 {
-  if (local_player->gems_still_needed > 0 ||
-      local_player->sokoban_fields_still_needed > 0 ||
-      local_player->sokoban_objects_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);
@@ -9120,7 +9161,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;
@@ -9130,10 +9172,10 @@ static void CheckExitSteel(int x, int y)
 
 static void CheckExitSteelEM(int x, int y)
 {
-  if (local_player->gems_still_needed > 0 ||
-      local_player->sokoban_fields_still_needed > 0 ||
-      local_player->sokoban_objects_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);
@@ -9144,7 +9186,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;
@@ -9154,7 +9197,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);
@@ -9165,7 +9208,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;
@@ -9681,8 +9725,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):
@@ -9695,9 +9739,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);
@@ -9767,9 +9811,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();
 
@@ -9778,12 +9822,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();
 
@@ -9823,8 +9866,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;
     }
@@ -11093,50 +11136,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;
   }
 }
 
@@ -11162,7 +11203,7 @@ static void CheckLevelTime(void)
       }
     }
 
-    if (!local_player->LevelSolved && !level.use_step_counter)
+    if (!game.LevelSolved && !level.use_step_counter)
     {
       TimePlayed++;
 
@@ -11187,7 +11228,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;
       }
@@ -11309,10 +11350,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
@@ -11326,6 +11367,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 ----------
@@ -11436,6 +11486,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)
@@ -12111,7 +12165,8 @@ void GameActions_RND(void)
   DrawAllPlayers();
   PlayAllPlayersSound();
 
-  if (local_player->show_envelope != 0 && local_player->MovPos == 0)
+  if (local_player->show_envelope != 0 && (!local_player->active ||
+                                          local_player->MovPos == 0))
   {
     ShowEnvelope(local_player->show_envelope - EL_ENVELOPE_1);
 
@@ -12652,10 +12707,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
@@ -12703,7 +12758,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;
 
@@ -12724,7 +12779,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;
 
@@ -13382,8 +13437,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)
@@ -13394,6 +13453,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;
 
@@ -13405,10 +13467,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)
@@ -13416,12 +13481,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)
@@ -13831,13 +13892,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();
     }
@@ -13948,15 +14009,17 @@ static int DigField(struct PlayerInfo *player,
       if (element == EL_SOKOBAN_FIELD_FULL)
       {
        Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
-       local_player->sokoban_fields_still_needed++;
-       local_player->sokoban_objects_still_needed++;
+
+       IncrementSokobanFieldsNeeded();
+       IncrementSokobanObjectsNeeded();
       }
 
       if (Feld[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
       {
        Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
-       local_player->sokoban_fields_still_needed--;
-       local_player->sokoban_objects_still_needed--;
+
+       DecrementSokobanFieldsNeeded();
+       DecrementSokobanObjectsNeeded();
 
        // sokoban object was pushed from empty field to sokoban field
        if (Back[x][y] == EL_EMPTY)
@@ -13975,13 +14038,13 @@ static int DigField(struct PlayerInfo *player,
                                    ACTION_FILLING);
 
       if (sokoban_task_solved &&
-         local_player->sokoban_fields_still_needed == 0 &&
-         local_player->sokoban_objects_still_needed == 0 &&
+         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(local_player);
+       LevelSolved();
 
        PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
       }
@@ -14033,9 +14096,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);
@@ -14097,7 +14160,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);
@@ -14822,9 +14885,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();
 }
@@ -14915,7 +14978,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);
@@ -14944,7 +15012,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?");
@@ -14980,6 +15048,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;
@@ -15006,22 +15078,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)
@@ -15205,11 +15274,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));
@@ -15222,8 +15286,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));