moved game values for things still needed from player to game structure
[rocksndiamonds.git] / src / game.c
index 0d8c032e84028895eb27bfae62a633ce15c52251..acba22e9b2f67838bbde1ba74c84b6bedf5fb9ab 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,8 +2216,8 @@ 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 ?
@@ -2203,7 +2231,7 @@ static void UpdateGameControlValues(void)
              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,11 +2239,12 @@ 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);
@@ -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)
@@ -3392,12 +3417,6 @@ void InitGame(void)
     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 +3539,8 @@ 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;
   }
 
@@ -3558,6 +3567,17 @@ void InitGame(void)
 
   AllPlayersGone = FALSE;
 
+  game.LevelSolved = FALSE;
+
+  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;
 
   game.no_time_limit = (level.time == 0);
@@ -3571,6 +3591,13 @@ void InitGame(void)
   game.switchgate_pos = 0;
   game.wind_direction = level.wind_direction_initial;
 
+  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;
 
@@ -3724,8 +3751,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 +3760,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 +3985,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 +4033,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 +4457,28 @@ 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;
+
+  local_player->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);
+  local_player->score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
+                              level.native_em_level->lev->score :
+                              level.game_engine_type == GAME_ENGINE_TYPE_MM ?
+                              game_mm.score :
+                              local_player->score);
+  local_player->health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
+                               MM_HEALTH(game_mm.laser_overload_value) :
+                               local_player->health);
 
-  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 = local_player->score_final;
+  game.LevelSolved_CountingHealth = local_player->health_final;
 }
 
 void GameWon(void)
@@ -4467,7 +4494,7 @@ 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;
 
@@ -4475,9 +4502,9 @@ void GameWon(void)
     if (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)
     {
@@ -4533,8 +4560,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;
@@ -4605,8 +4632,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 +4664,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 +4700,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 +4723,7 @@ void GameEnd(void)
     return;
   }
 
-  if (!local_player->LevelSolved_SaveScore)
+  if (!game.LevelSolved_SaveScore)
   {
     SetGameStatus(GAME_MODE_MAIN);
 
@@ -4893,9 +4920,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 +4997,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 +5053,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);
 
@@ -5493,8 +5520,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 +5529,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 +5607,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;
   }
@@ -7781,8 +7808,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 +7949,10 @@ 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 &&
+       game.friends_still_needed--;
+       if (!game.friends_still_needed &&
            !local_player->GameOver && AllPlayersGone)
-         PlayerWins(local_player);
+         LevelSolved();
 
        return;
       }
@@ -8729,8 +8756,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;
     }
   }
@@ -9056,9 +9083,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);
@@ -9079,9 +9107,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);
@@ -9102,9 +9131,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);
@@ -9125,9 +9155,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);
@@ -9148,7 +9179,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);
@@ -9675,7 +9706,7 @@ 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_GEMS ? game.gems_still_needed :
      action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? local_player->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:
@@ -9689,7 +9720,7 @@ 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_CE_VALUE ? CustomValue[x][y] :
@@ -9772,12 +9803,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();
 
@@ -9818,7 +9848,7 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
          ExitPlayer(&stored_player[i]);
 
       if (AllPlayersGone)
-       PlayerWins(local_player);
+       LevelSolved();
 
       break;
     }
@@ -10217,8 +10247,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 +10283,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 +10537,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 +10659,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 +10674,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)
@@ -11087,34 +11117,32 @@ 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;
+      AllPlayersGone = 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
+    if (game_em.game_over)                             // game lost
       AllPlayersGone = 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;
     }
 
-    if (game_sp.GameOver)                              // game lost
+    if (game_sp.game_over)                             // game lost
       AllPlayersGone = TRUE;
   }
   else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
@@ -11122,7 +11150,7 @@ static void CheckLevelSolved(void)
     if (game_mm.level_solved &&
        !game_mm.game_over)                             // game won
     {
-      PlayerWins(local_player);
+      LevelSolved();
 
       game_mm.game_over = TRUE;
 
@@ -11156,7 +11184,7 @@ static void CheckLevelTime(void)
       }
     }
 
-    if (!local_player->LevelSolved && !level.use_step_counter)
+    if (!game.LevelSolved && !level.use_step_counter)
     {
       TimePlayed++;
 
@@ -11303,7 +11331,7 @@ 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))
@@ -12646,10 +12674,10 @@ void ScrollPlayer(struct PlayerInfo *player, int mode)
     {
       ExitPlayer(player);
 
-      if ((local_player->friends_still_needed == 0 ||
+      if ((game.friends_still_needed == 0 ||
           IS_SP_ELEMENT(Feld[jx][jy])) &&
          AllPlayersGone)
-       PlayerWins(local_player);
+       LevelSolved();
     }
 
     // this breaks one level: "machine", level 000
@@ -12697,7 +12725,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;
 
@@ -13410,11 +13438,11 @@ 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--;
+  if (game.players_still_needed > 0)
+    game.players_still_needed--;
 
   // also set if some players not yet gone, but not needed to solve level
-  if (local_player->players_still_needed == 0)
+  if (game.players_still_needed == 0)
     AllPlayersGone = TRUE;
 }
 
@@ -13825,13 +13853,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 +13965,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 +13998,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);
       }
@@ -14081,7 +14121,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);
@@ -14990,7 +15030,7 @@ 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)
@@ -14999,13 +15039,13 @@ boolean checkGameFailed(void)
     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 (local_player->GameOver && !game.LevelSolved);
 }
 
 boolean checkGameEnded(void)