fixed bug with not updating game panel when leaving invisible warp mode
[rocksndiamonds.git] / src / game.c
index db613cc769d44a8dbf8683582e45f2b2daf2ef37..d8a3d16270af01b54ed8dcff09072d13f79aff4a 100644 (file)
@@ -1119,6 +1119,8 @@ void ExitPlayer(struct PlayerInfo *);
 static int getInvisibleActiveFromInvisibleElement(int);
 static int getInvisibleFromInvisibleActiveElement(int);
 
+static void TestFieldAfterSnapping(int, int, int, int, int);
+
 static struct GadgetInfo *game_gadget[NUM_GAME_BUTTONS];
 
 // for detection of endless loops, caused by custom element programming
@@ -1769,13 +1771,11 @@ static void InitPlayerField(int x, int y, int element, boolean init_game)
       StorePlayer[x][y] = Tile[x][y];
 
 #if DEBUG_INIT_PLAYER
-      if (options.debug)
-      {
-       printf("- player element %d activated", player->element_nr);
-       printf(" (local player is %d and currently %s)\n",
-              local_player->element_nr,
-              local_player->active ? "active" : "not active");
-      }
+      Debug("game:init:player", "- player element %d activated",
+           player->element_nr);
+      Debug("game:init:player", "  (local player is %d and currently %s)",
+           local_player->element_nr,
+           local_player->active ? "active" : "not active");
     }
 #endif
 
@@ -2149,8 +2149,9 @@ static void InitGameControlValues(void)
 
     if (nr != i)
     {
-      Error(ERR_INFO, "'game_panel_controls' structure corrupted at %d", i);
-      Error(ERR_EXIT, "this should not happen -- please debug");
+      Error("'game_panel_controls' structure corrupted at %d", i);
+
+      Fail("this should not happen -- please debug");
     }
 
     // force update of game controls after initialization
@@ -2252,6 +2253,7 @@ static void UpdateGameControlValues(void)
                level.game_engine_type == GAME_ENGINE_TYPE_MM ?
                MM_HEALTH(game_mm.laser_overload_value) :
                game.health);
+  int sync_random_frame = INIT_GFX_RANDOM();   // random, but synchronized
 
   UpdatePlayfieldElementCount();
 
@@ -2326,6 +2328,63 @@ static void UpdateGameControlValues(void)
       stored_player[player_nr].num_white_keys;
   }
 
+  // re-arrange keys on game panel, if needed or if defined by style settings
+  for (i = 0; i < MAX_NUM_KEYS + 1; i++)       // all normal keys + white key
+  {
+    int nr = GAME_PANEL_KEY_1 + i;
+    struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
+    struct TextPosInfo *pos = gpc->pos;
+
+    // skip check if key is not in the player's inventory
+    if (gpc->value == EL_EMPTY)
+      continue;
+
+    // check if keys should be arranged on panel from left to right
+    if (pos->style == STYLE_LEFTMOST_POSITION)
+    {
+      // check previous key positions (left from current key)
+      for (k = 0; k < i; k++)
+      {
+       int nr_new = GAME_PANEL_KEY_1 + k;
+
+       if (game_panel_controls[nr_new].value == EL_EMPTY)
+       {
+         game_panel_controls[nr_new].value = gpc->value;
+         gpc->value = EL_EMPTY;
+
+         break;
+       }
+      }
+    }
+
+    // check if "undefined" keys can be placed at some other position
+    if (pos->x == -1 && pos->y == -1)
+    {
+      int nr_new = GAME_PANEL_KEY_1 + i % STD_NUM_KEYS;
+
+      // 1st try: display key at the same position as normal or EM keys
+      if (game_panel_controls[nr_new].value == EL_EMPTY)
+      {
+       game_panel_controls[nr_new].value = gpc->value;
+      }
+      else
+      {
+       // 2nd try: display key at the next free position in the key panel
+       for (k = 0; k < STD_NUM_KEYS; k++)
+       {
+         nr_new = GAME_PANEL_KEY_1 + k;
+
+         if (game_panel_controls[nr_new].value == EL_EMPTY)
+         {
+           game_panel_controls[nr_new].value = gpc->value;
+
+           break;
+         }
+       }
+      }
+    }
+  }
+
   for (i = 0; i < NUM_PANEL_INVENTORY; i++)
   {
     game_panel_controls[GAME_PANEL_INVENTORY_FIRST_1 + i].value =
@@ -2479,11 +2538,13 @@ static void UpdateGameControlValues(void)
        int last_anim_random_frame = gfx.anim_random_frame;
        int element = gpc->value;
        int graphic = el2panelimg(element);
+       int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
+                              sync_random_frame : INIT_GFX_RANDOM());
 
        if (gpc->value != gpc->last_value)
        {
          gpc->gfx_frame = 0;
-         gpc->gfx_random = INIT_GFX_RANDOM();
+         gpc->gfx_random = init_gfx_random;
        }
        else
        {
@@ -2491,7 +2552,7 @@ static void UpdateGameControlValues(void)
 
          if (ANIM_MODE(graphic) == ANIM_RANDOM &&
              IS_NEXT_FRAME(gpc->gfx_frame, graphic))
-           gpc->gfx_random = INIT_GFX_RANDOM();
+           gpc->gfx_random = init_gfx_random;
        }
 
        if (ANIM_MODE(graphic) == ANIM_RANDOM)
@@ -2500,8 +2561,7 @@ static void UpdateGameControlValues(void)
        if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
          gpc->gfx_frame = element_info[element].collect_score;
 
-       gpc->frame = getGraphicAnimationFrame(el2panelimg(gpc->value),
-                                             gpc->gfx_frame);
+       gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
 
        if (ANIM_MODE(graphic) == ANIM_RANDOM)
          gfx.anim_random_frame = last_anim_random_frame;
@@ -2513,11 +2573,13 @@ static void UpdateGameControlValues(void)
       {
        int last_anim_random_frame = gfx.anim_random_frame;
        int graphic = gpc->graphic;
+       int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
+                              sync_random_frame : INIT_GFX_RANDOM());
 
        if (gpc->value != gpc->last_value)
        {
          gpc->gfx_frame = 0;
-         gpc->gfx_random = INIT_GFX_RANDOM();
+         gpc->gfx_random = init_gfx_random;
        }
        else
        {
@@ -2525,7 +2587,7 @@ static void UpdateGameControlValues(void)
 
          if (ANIM_MODE(graphic) == ANIM_RANDOM &&
              IS_NEXT_FRAME(gpc->gfx_frame, graphic))
-           gpc->gfx_random = INIT_GFX_RANDOM();
+           gpc->gfx_random = init_gfx_random;
        }
 
        if (ANIM_MODE(graphic) == ANIM_RANDOM)
@@ -2590,6 +2652,10 @@ static void DisplayGameControlValues(void)
     if (PANEL_DEACTIVATED(pos))
       continue;
 
+    if (pos->class == get_hash_from_key("extra_panel_items") &&
+       !setup.prefer_extra_panel_items)
+      continue;
+
     gpc->last_value = value;
     gpc->last_frame = frame;
 
@@ -2637,7 +2703,10 @@ static void DisplayGameControlValues(void)
        element = value;
        graphic = el2panelimg(value);
 
-       // printf("::: %d, '%s' [%d]\n", element, EL_NAME(element), size);
+#if 0
+       Debug("game:DisplayGameControlValues", "%d, '%s' [%d]",
+             element, EL_NAME(element), size);
+#endif
 
        if (element >= EL_GRAPHIC_1 && element <= EL_GRAPHIC_8 && size == 0)
          size = TILESIZE;
@@ -2800,12 +2869,10 @@ void UpdateAndDisplayGameControlValues(void)
   DisplayGameControlValues();
 }
 
-#if 0
-static void UpdateGameDoorValues(void)
+void UpdateGameDoorValues(void)
 {
   UpdateGameControlValues();
 }
-#endif
 
 void DrawGameDoorValues(void)
 {
@@ -2843,16 +2910,16 @@ static void InitGameEngine(void)
   }
 
 #if 0
-  printf("level %d: level.game_version  == %06d\n", level_nr,
-        level.game_version);
-  printf("          tape.file_version   == %06d\n",
-        tape.file_version);
-  printf("          tape.game_version   == %06d\n",
-        tape.game_version);
-  printf("          tape.engine_version == %06d\n",
-        tape.engine_version);
-  printf("       => game.engine_version == %06d [tape mode: %s]\n",
-        game.engine_version, (tape.playing ? "PLAYING" : "RECORDING"));
+  Debug("game:init:level", "level %d: level.game_version  == %06d", level_nr,
+       level.game_version);
+  Debug("game:init:level", "          tape.file_version   == %06d",
+       tape.file_version);
+  Debug("game:init:level", "          tape.game_version   == %06d",
+       tape.game_version);
+  Debug("game:init:level", "          tape.engine_version == %06d",
+       tape.engine_version);
+  Debug("game:init:level", "       => game.engine_version == %06d [tape mode: %s]",
+       game.engine_version, (tape.playing ? "PLAYING" : "RECORDING"));
 #endif
 
   // --------------------------------------------------------------------------
@@ -3441,24 +3508,21 @@ static void DebugPrintPlayerStatus(char *message)
   if (!options.debug)
     return;
 
-  printf("%s:\n", message);
+  Debug("game:init:player", "%s:", message);
 
   for (i = 0; i < MAX_PLAYERS; i++)
   {
     struct PlayerInfo *player = &stored_player[i];
 
-    printf("- player %d: present == %d, connected == %d [%d/%d], active == %d",
-          i + 1,
-          player->present,
-          player->connected,
-          player->connected_locally,
-          player->connected_network,
-          player->active);
-
-    if (local_player == player)
-      printf(" (local player)");
-
-    printf("\n");
+    Debug("game:init:player",
+         "- player %d: present == %d, connected == %d [%d/%d], active == %d%s",
+         i + 1,
+         player->present,
+         player->connected,
+         player->connected_locally,
+         player->connected_network,
+         player->active,
+         (local_player == player ? " (local player)" : ""));
   }
 }
 #endif
@@ -3512,11 +3576,15 @@ void InitGame(void)
   InitGameEngine();
   InitGameControlValues();
 
-  // initialize tape actions from game when recording tape
   if (tape.recording)
   {
+    // initialize tape actions from game when recording tape
     tape.use_key_actions   = game.use_key_actions;
     tape.use_mouse_actions = game.use_mouse_actions;
+
+    // initialize visible playfield size when recording tape (for team mode)
+    tape.scr_fieldx = SCR_FIELDX;
+    tape.scr_fieldy = SCR_FIELDY;
   }
 
   // don't play tapes over network
@@ -3649,6 +3717,8 @@ void InitGame(void)
     player->shield_normal_time_left = 0;
     player->shield_deadly_time_left = 0;
 
+    player->last_removed_element = EL_UNDEFINED;
+
     player->inventory_infinite_element = EL_UNDEFINED;
     player->inventory_size = 0;
 
@@ -3934,8 +4004,7 @@ void InitGame(void)
 #endif
 
 #if DEBUG_INIT_PLAYER
-  if (options.debug)
-    printf("Reassigning players ...\n");
+  Debug("game:init:player", "Reassigning players ...");
 #endif
 
   // check if any connected player was not found in playfield
@@ -3948,8 +4017,8 @@ void InitGame(void)
       struct PlayerInfo *field_player = NULL;
 
 #if DEBUG_INIT_PLAYER
-      if (options.debug)
-       printf("- looking for field player for player %d ...\n", i + 1);
+      Debug("game:init:player",
+           "- looking for field player for player %d ...", i + 1);
 #endif
 
       // assign first free player found that is present in the playfield
@@ -3974,8 +4043,8 @@ void InitGame(void)
        int jx = field_player->jx, jy = field_player->jy;
 
 #if DEBUG_INIT_PLAYER
-       if (options.debug)
-         printf("- found player %d\n", field_player->index_nr + 1);
+       Debug("game:init:player", "- found player %d",
+             field_player->index_nr + 1);
 #endif
 
        player->present = FALSE;
@@ -4005,9 +4074,8 @@ void InitGame(void)
        field_player->mapped = TRUE;
 
 #if DEBUG_INIT_PLAYER
-       if (options.debug)
-         printf("- map_player_action[%d] == %d\n",
-                field_player->index_nr + 1, i + 1);
+       Debug("game:init:player", "- map_player_action[%d] == %d",
+             field_player->index_nr + 1, i + 1);
 #endif
       }
     }
@@ -4062,7 +4130,8 @@ void InitGame(void)
 #endif
 
 #if 0
-  printf("::: local_player->present == %d\n", local_player->present);
+  Debug("game:init:player", "local_player->present == %d",
+       local_player->present);
 #endif
 
   // set focus to local player for network games, else to all players
@@ -4095,8 +4164,8 @@ void InitGame(void)
          int jx = player->jx, jy = player->jy;
 
 #if DEBUG_INIT_PLAYER
-         if (options.debug)
-           printf("Removing player %d at (%d, %d)\n", i + 1, jx, jy);
+         Debug("game:init:player", "Removing player %d at (%d, %d)",
+               i + 1, jx, jy);
 #endif
 
          player->active = FALSE;
@@ -4384,7 +4453,9 @@ void InitGame(void)
 
   game.restart_level = FALSE;
   game.restart_game_message = NULL;
+
   game.request_active = FALSE;
+  game.request_active_or_moving = FALSE;
 
   if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
     InitGameActions_MM();
@@ -4645,7 +4716,7 @@ void GameWon(void)
 {
   static int time_count_steps;
   static int time, time_final;
-  static int score, score_final;
+  static float score, score_final; // needed for time score < 10 for 10 seconds
   static int health, health_final;
   static int game_over_delay_1 = 0;
   static int game_over_delay_2 = 0;
@@ -4653,6 +4724,8 @@ void GameWon(void)
   int game_over_delay_value_1 = 50;
   int game_over_delay_value_2 = 25;
   int game_over_delay_value_3 = 50;
+  int time_score_base = MIN(MAX(1, level.time_score_base), 10);
+  float time_score = (float)level.score[SC_TIME_BONUS] / time_score_base;
 
   if (!game.LevelSolved_GameWon)
   {
@@ -4686,19 +4759,23 @@ void GameWon(void)
     score = score_final = game.score_final;
     health = health_final = game.health_final;
 
-    if (level.score[SC_TIME_BONUS] > 0)
+    if (time_score > 0)
     {
+      int time_frames = 0;
+
       if (TimeLeft > 0)
       {
        time_final = 0;
-       score_final += TimeLeft * level.score[SC_TIME_BONUS];
+       time_frames = TimeLeft * FRAMES_PER_SECOND - TimeFrames;
       }
       else if (game.no_time_limit && TimePlayed < 999)
       {
        time_final = 999;
-       score_final += (999 - TimePlayed) * level.score[SC_TIME_BONUS];
+       time_frames = (999 - TimePlayed) * FRAMES_PER_SECOND - TimeFrames;
       }
 
+      score_final += time_score * time_frames / FRAMES_PER_SECOND + 0.5;
+
       time_count_steps = MAX(1, ABS(time_final - time) / 100);
 
       game_over_delay_1 = game_over_delay_value_1;
@@ -4706,7 +4783,7 @@ void GameWon(void)
       if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
       {
        health_final = 0;
-       score_final += health * level.score[SC_TIME_BONUS];
+       score_final += health * time_score;
 
        game_over_delay_2 = game_over_delay_value_2;
       }
@@ -4795,7 +4872,11 @@ void GameWon(void)
       time_count_steps = 1;
 
     time  += time_count_steps * time_count_dir;
-    score += time_count_steps * level.score[SC_TIME_BONUS];
+    score += time_count_steps * time_score;
+
+    // set final score to correct rounding differences after counting score
+    if (time == time_final)
+      score = score_final;
 
     game.LevelSolved_CountingTime = time;
     game.LevelSolved_CountingScore = score;
@@ -4827,7 +4908,7 @@ void GameWon(void)
     int health_count_dir = (health < health_final ? +1 : -1);
 
     health += health_count_dir;
-    score  += level.score[SC_TIME_BONUS];
+    score  += time_score;
 
     game.LevelSolved_CountingHealth = health;
     game.LevelSolved_CountingScore = score;
@@ -8776,8 +8857,9 @@ void AmoebaToDiamond(int ax, int ay)
 #ifdef DEBUG
     if (group_nr == 0)
     {
-      printf("AmoebaToDiamond(): ax = %d, ay = %d\n", ax, ay);
-      printf("AmoebaToDiamond(): This should never happen!\n");
+      Debug("game:playing:AmoebaToDiamond", "ax = %d, ay = %d", ax, ay);
+      Debug("game:playing:AmoebaToDiamond", "This should never happen!");
+
       return;
     }
 #endif
@@ -8834,8 +8916,9 @@ static void AmoebaToDiamondBD(int ax, int ay, int new_element)
 #ifdef DEBUG
   if (group_nr == 0)
   {
-    printf("AmoebaToDiamondBD(): ax = %d, ay = %d\n", ax, ay);
-    printf("AmoebaToDiamondBD(): This should never happen!\n");
+    Debug("game:playing:AmoebaToDiamondBD", "ax = %d, ay = %d", ax, ay);
+    Debug("game:playing:AmoebaToDiamondBD", "This should never happen!");
+
     return;
   }
 #endif
@@ -9043,8 +9126,10 @@ static void AmoebaReproduce(int ax, int ay)
 #ifdef DEBUG
   if (new_group_nr == 0)
   {
-    printf("AmoebaReproduce(): newax = %d, neway = %d\n", newax, neway);
-    printf("AmoebaReproduce(): This should never happen!\n");
+    Debug("game:playing:AmoebaReproduce", "newax = %d, neway = %d",
+         newax, neway);
+    Debug("game:playing:AmoebaReproduce", "This should never happen!");
+
     return;
   }
 #endif
@@ -10660,11 +10745,9 @@ static void HandleElementChange(int x, int y, int page)
   if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
       !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
   {
-    printf("\n\n");
-    printf("HandleElementChange(): %d,%d: element = %d ('%s')\n",
-          x, y, element, element_info[element].token_name);
-    printf("HandleElementChange(): This should never happen!\n");
-    printf("\n\n");
+    Debug("game:playing:HandleElementChange", "%d,%d: element = %d ('%s')",
+         x, y, element, element_info[element].token_name);
+    Debug("game:playing:HandleElementChange", "This should never happen!");
   }
 #endif
 
@@ -11200,18 +11283,29 @@ static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
     if (!player->is_dropping)
       player->was_dropping = FALSE;
   }
+
+  static struct MouseActionInfo mouse_action_last = { 0 };
+  struct MouseActionInfo mouse_action = player->effective_mouse_action;
+  boolean new_released = (!mouse_action.button && mouse_action_last.button);
+
+  if (new_released)
+    CheckSaveEngineSnapshotToList();
+
+  mouse_action_last = mouse_action;
 }
 
 static void CheckSingleStepMode(struct PlayerInfo *player)
 {
   if (tape.single_step && tape.recording && !tape.pausing)
   {
-    /* as it is called "single step mode", just return to pause mode when the
-       player stopped moving after one tile (or never starts moving at all) */
-    if (!player->is_moving &&
-       !player->is_pushing &&
-       !player->is_dropping_pressed)
-      TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
+    // as it is called "single step mode", just return to pause mode when the
+    // player stopped moving after one tile (or never starts moving at all)
+    // (reverse logic needed here in case single step mode used in team mode)
+    if (player->is_moving ||
+       player->is_pushing ||
+       player->is_dropping_pressed ||
+       player->effective_mouse_action.button)
+      game.enter_single_step_mode = FALSE;
   }
 
   CheckSaveEngineSnapshot(player);
@@ -11495,8 +11589,8 @@ static void GameActionsExt(void)
                                  EL_NAME(recursion_loop_element),
                                  " caused endless loop! Quit the game?");
 
-    Error(ERR_WARN, "element '%s' caused endless loop in game engine",
-         EL_NAME(recursion_loop_element));
+    Warn("element '%s' caused endless loop in game engine",
+        EL_NAME(recursion_loop_element));
 
     RequestQuitGameExt(FALSE, level_editor_test_game, message);
 
@@ -11544,7 +11638,7 @@ static void GameActionsExt(void)
 
   int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
 
-  printf("::: skip == %d\n", skip);
+  Debug("game:playing:skip", "skip == %d", skip);
 
 #else
   // ---------- main game synchronization point ----------
@@ -11661,9 +11755,8 @@ static void GameActionsExt(void)
     byte mapped_action[MAX_PLAYERS];
 
 #if DEBUG_PLAYER_ACTIONS
-    printf(":::");
     for (i = 0; i < MAX_PLAYERS; i++)
-      printf(" %d, ", stored_player[i].effective_action);
+      DebugContinued("", "%d, ", stored_player[i].effective_action);
 #endif
 
     for (i = 0; i < MAX_PLAYERS; i++)
@@ -11673,19 +11766,18 @@ static void GameActionsExt(void)
       stored_player[i].effective_action = mapped_action[i];
 
 #if DEBUG_PLAYER_ACTIONS
-    printf(" =>");
+    DebugContinued("", "=> ");
     for (i = 0; i < MAX_PLAYERS; i++)
-      printf(" %d, ", stored_player[i].effective_action);
-    printf("\n");
+      DebugContinued("", "%d, ", stored_player[i].effective_action);
+    DebugContinued("game:playing:player", "\n");
 #endif
   }
 #if DEBUG_PLAYER_ACTIONS
   else
   {
-    printf(":::");
     for (i = 0; i < MAX_PLAYERS; i++)
-      printf(" %d, ", stored_player[i].effective_action);
-    printf("\n");
+      DebugContinued("", "%d, ", stored_player[i].effective_action);
+    DebugContinued("game:playing:player", "\n");
   }
 #endif
 #endif
@@ -11876,6 +11968,10 @@ void GameActions_RND(void)
     DrawGameDoorValues();
   }
 
+  // check single step mode (set flag and clear again if any player is active)
+  game.enter_single_step_mode =
+    (tape.single_step && tape.recording && !tape.pausing);
+
   for (i = 0; i < MAX_PLAYERS; i++)
   {
     int actual_player_action = stored_player[i].effective_action;
@@ -11900,6 +11996,10 @@ void GameActions_RND(void)
     ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
   }
 
+  // single step pause mode may already have been toggled by "ScrollPlayer()"
+  if (game.enter_single_step_mode && !tape.pausing)
+    TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
+
   ScrollScreen(NULL, SCROLL_GO_ON);
 
   /* for backwards compatibility, the following code emulates a fixed bug that
@@ -11952,18 +12052,26 @@ void GameActions_RND(void)
       MovDelay[x][y]--;
       if (MovDelay[x][y] <= 0)
       {
+       int element = Store[x][y];
+       int move_direction = MovDir[x][y];
+       int player_index_bit = Store2[x][y];
+
+       Store[x][y] = 0;
+       Store2[x][y] = 0;
+
        RemoveField(x, y);
        TEST_DrawLevelField(x, y);
 
-       TestIfElementTouchesCustomElement(x, y);        // for empty space
+       TestFieldAfterSnapping(x, y, element, move_direction, player_index_bit);
       }
     }
 
 #if DEBUG
     if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
     {
-      printf("GameActions(): x = %d, y = %d: ChangePage != -1\n", x, y);
-      printf("GameActions(): This should never happen!\n");
+      Debug("game:playing:GameActions_RND", "x = %d, y = %d: ChangePage != -1",
+           x, y);
+      Debug("game:playing:GameActions_RND", "This should never happen!");
 
       ChangePage[x][y] = -1;
     }
@@ -11997,10 +12105,10 @@ void GameActions_RND(void)
       Blocked2Moving(x, y, &oldx, &oldy);
       if (!IS_MOVING(oldx, oldy))
       {
-       printf("GameActions(): (BLOCKED => MOVING) context corrupted!\n");
-       printf("GameActions(): BLOCKED: x = %d, y = %d\n", x, y);
-       printf("GameActions(): !MOVING: oldx = %d, oldy = %d\n", oldx, oldy);
-       printf("GameActions(): This should never happen!\n");
+       Debug("game:playing:GameActions_RND", "(BLOCKED => MOVING) context corrupted!");
+       Debug("game:playing:GameActions_RND", "BLOCKED: x = %d, y = %d", x, y);
+       Debug("game:playing:GameActions_RND", "!MOVING: oldx = %d, oldy = %d", oldx, oldy);
+       Debug("game:playing:GameActions_RND", "This should never happen!");
       }
     }
 #endif
@@ -12372,6 +12480,8 @@ void GameActions_RND(void)
 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
 {
   int min_x = x, min_y = y, max_x = x, max_y = y;
+  int scr_fieldx = getScreenFieldSizeX();
+  int scr_fieldy = getScreenFieldSizeY();
   int i;
 
   for (i = 0; i < MAX_PLAYERS; i++)
@@ -12387,7 +12497,7 @@ static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
     max_y = MAX(max_y, jy);
   }
 
-  return (max_x - min_x < SCR_FIELDX && max_y - min_y < SCR_FIELDY);
+  return (max_x - min_x < scr_fieldx && max_y - min_y < scr_fieldy);
 }
 
 static boolean AllPlayersInVisibleScreen(void)
@@ -12641,8 +12751,9 @@ boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
     int original_move_delay_value = player->move_delay_value;
 
 #if DEBUG
-    printf("THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]\n",
-          tape.counter);
+    Debug("game:playing:MovePlayer",
+         "THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]",
+         tape.counter);
 #endif
 
     // scroll remaining steps with finest movement resolution
@@ -12952,6 +13063,21 @@ void ScrollPlayer(struct PlayerInfo *player, int mode)
       if (!player->is_pushing)
        TestIfElementTouchesCustomElement(jx, jy);      // for empty space
 
+      if (level.finish_dig_collect &&
+         (player->is_digging || player->is_collecting))
+      {
+       int last_element = player->last_removed_element;
+       int move_direction = player->MovDir;
+       int enter_side = MV_DIR_OPPOSITE(move_direction);
+       int change_event = (player->is_digging ? CE_PLAYER_DIGS_X :
+                           CE_PLAYER_COLLECTS_X);
+
+       CheckTriggeredElementChangeByPlayer(jx, jy, last_element, change_event,
+                                           player->index_bit, enter_side);
+
+       player->last_removed_element = EL_UNDEFINED;
+      }
+
       if (!player->active)
        RemovePlayer(player);
     }
@@ -13567,8 +13693,9 @@ void KillPlayer(struct PlayerInfo *player)
     return;
 
 #if 0
-  printf("::: 0: killed == %d, active == %d, reanimated == %d\n",
-        player->killed, player->active, player->reanimated);
+  Debug("game:playing:KillPlayer",
+       "0: killed == %d, active == %d, reanimated == %d",
+       player->killed, player->active, player->reanimated);
 #endif
 
   /* the following code was introduced to prevent an infinite loop when calling
@@ -13596,15 +13723,17 @@ void KillPlayer(struct PlayerInfo *player)
   player->shield_deadly_time_left = 0;
 
 #if 0
-  printf("::: 1: killed == %d, active == %d, reanimated == %d\n",
-        player->killed, player->active, player->reanimated);
+  Debug("game:playing:KillPlayer",
+       "1: killed == %d, active == %d, reanimated == %d",
+       player->killed, player->active, player->reanimated);
 #endif
 
   Bang(jx, jy);
 
 #if 0
-  printf("::: 2: killed == %d, active == %d, reanimated == %d\n",
-        player->killed, player->active, player->reanimated);
+  Debug("game:playing:KillPlayer",
+       "2: killed == %d, active == %d, reanimated == %d",
+       player->killed, player->active, player->reanimated);
 #endif
 
   if (player->reanimated)      // killed player may have been reanimated
@@ -13683,7 +13812,8 @@ void ExitPlayer(struct PlayerInfo *player)
     game.players_still_needed--;
 }
 
-static void setFieldForSnapping(int x, int y, int element, int direction)
+static void SetFieldForSnapping(int x, int y, int element, int direction,
+                               int player_index_bit)
 {
   struct ElementInfo *ei = &element_info[element];
   int direction_bit = MV_DIR_TO_BIT(direction);
@@ -13693,6 +13823,9 @@ static void setFieldForSnapping(int x, int y, int element, int direction)
 
   Tile[x][y] = EL_ELEMENT_SNAPPING;
   MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
+  MovDir[x][y] = direction;
+  Store[x][y] = element;
+  Store2[x][y] = player_index_bit;
 
   ResetGfxAnimation(x, y);
 
@@ -13702,6 +13835,20 @@ static void setFieldForSnapping(int x, int y, int element, int direction)
   GfxFrame[x][y] = -1;
 }
 
+static void TestFieldAfterSnapping(int x, int y, int element, int direction,
+                                  int player_index_bit)
+{
+  TestIfElementTouchesCustomElement(x, y);     // for empty space
+
+  if (level.finish_dig_collect)
+  {
+    int dig_side = MV_DIR_OPPOSITE(direction);
+
+    CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
+                                       player_index_bit, dig_side);
+  }
+}
+
 /*
   =============================================================================
   checkDiagonalPushing()
@@ -13981,18 +14128,28 @@ static int DigField(struct PlayerInfo *player,
 
     PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
 
-    CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
-                                       player->index_bit, dig_side);
+    // use old behaviour for old levels (digging)
+    if (!level.finish_dig_collect)
+    {
+      CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
+                                         player->index_bit, dig_side);
+
+      // if digging triggered player relocation, finish digging tile
+      if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
+       SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
+    }
 
     if (mode == DF_SNAP)
     {
       if (level.block_snap_field)
-       setFieldForSnapping(x, y, element, move_direction);
+       SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
       else
-       TestIfElementTouchesCustomElement(x, y);        // for empty space
+       TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
 
-      CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
-                                         player->index_bit, dig_side);
+      // use old behaviour for old levels (snapping)
+      if (!level.finish_dig_collect)
+       CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
+                                           player->index_bit, dig_side);
     }
   }
   else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
@@ -14104,19 +14261,28 @@ static int DigField(struct PlayerInfo *player,
     RaiseScoreElement(element);
     PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
 
-    if (is_player)
+    // use old behaviour for old levels (collecting)
+    if (!level.finish_dig_collect && is_player)
+    {
       CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
                                          player->index_bit, dig_side);
 
+      // if collecting triggered player relocation, finish collecting tile
+      if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
+       SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
+    }
+
     if (mode == DF_SNAP)
     {
       if (level.block_snap_field)
-       setFieldForSnapping(x, y, element, move_direction);
+       SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
       else
-       TestIfElementTouchesCustomElement(x, y);        // for empty space
+       TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
 
-      CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
-                                         player->index_bit, dig_side);
+      // use old behaviour for old levels (snapping)
+      if (!level.finish_dig_collect)
+       CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
+                                           player->index_bit, dig_side);
     }
   }
   else if (player_can_move_or_snap && IS_PUSHABLE(element))
@@ -14454,6 +14620,8 @@ static int DigField(struct PlayerInfo *player,
     {
       player->is_collecting = !player->is_digging;
       player->is_active = TRUE;
+
+      player->last_removed_element = element;
     }
   }
 
@@ -15229,6 +15397,9 @@ void RequestRestartGame(char *message)
   }
   else
   {
+    // needed in case of envelope request to close game panel
+    CloseDoor(DOOR_CLOSE_1);
+
     SetGameStatus(GAME_MODE_MAIN);
 
     DrawMainMenu();
@@ -15420,10 +15591,11 @@ static void LoadEngineSnapshotValues_RND(void)
 
   if (game.num_random_calls != num_random_calls)
   {
-    Error(ERR_INFO, "number of random calls out of sync");
-    Error(ERR_INFO, "number of random calls should be %d", num_random_calls);
-    Error(ERR_INFO, "number of random calls is %d", game.num_random_calls);
-    Error(ERR_EXIT, "this should not happen -- please debug");
+    Error("number of random calls out of sync");
+    Error("number of random calls should be %d", num_random_calls);
+    Error("number of random calls is %d", game.num_random_calls);
+
+    Fail("this should not happen -- please debug");
   }
 }
 
@@ -15536,7 +15708,8 @@ static ListNode *SaveEngineSnapshotBuffers(void)
     node = node->next;
   }
 
-  printf("::: size of engine snapshot: %d bytes\n", num_bytes);
+  Debug("game:playing:SaveEngineSnapshotBuffers",
+       "size of engine snapshot: %d bytes", num_bytes);
 #endif
 
   return buffers;
@@ -15831,7 +16004,7 @@ void CreateGameButtons(void)
                      GDI_END);
 
     if (gi == NULL)
-      Error(ERR_EXIT, "cannot create gadget");
+      Fail("cannot create gadget");
 
     game_gadget[id] = gi;
   }