added functions to get current player position for all game engines
[rocksndiamonds.git] / src / game.c
index 2be5985f4466cde783be13950cba59ce4a7c79b0..3d2f055f27c88415dccb499d83d65932ad8a5f1e 100644 (file)
@@ -19,6 +19,7 @@
 #include "files.h"
 #include "tape.h"
 #include "network.h"
+#include "anim.h"
 
 
 /* DEBUG SETTINGS */
@@ -974,12 +975,13 @@ static struct GamePanelControlInfo game_panel_controls[] =
 #define GAME_CTRL_ID_UNDO              3
 #define GAME_CTRL_ID_REDO              4
 #define GAME_CTRL_ID_SAVE              5
-#define GAME_CTRL_ID_LOAD              6
-#define SOUND_CTRL_ID_MUSIC            7
-#define SOUND_CTRL_ID_LOOPS            8
-#define SOUND_CTRL_ID_SIMPLE           9
+#define GAME_CTRL_ID_PAUSE2            6
+#define GAME_CTRL_ID_LOAD              7
+#define SOUND_CTRL_ID_MUSIC            8
+#define SOUND_CTRL_ID_LOOPS            9
+#define SOUND_CTRL_ID_SIMPLE           10
 
-#define NUM_GAME_BUTTONS               10
+#define NUM_GAME_BUTTONS               11
 
 
 /* forward declaration for internal use */
@@ -1948,7 +1950,7 @@ static void InitField(int x, int y, boolean init_game)
     CheckTriggeredElementChange(x, y, element, CE_CREATION_OF_X);
 }
 
-static inline void InitField_WithBug1(int x, int y, boolean init_game)
+inline static void InitField_WithBug1(int x, int y, boolean init_game)
 {
   InitField(x, y, init_game);
 
@@ -1958,7 +1960,7 @@ static inline void InitField_WithBug1(int x, int y, boolean init_game)
     InitMovDir(x, y);
 }
 
-static inline void InitField_WithBug2(int x, int y, boolean init_game)
+inline static void InitField_WithBug2(int x, int y, boolean init_game)
 {
   int old_element = Feld[x][y];
 
@@ -2445,7 +2447,7 @@ void DisplayGameControlValues()
   /* redraw game control buttons */
   RedrawGameButtons();
 
-  game_status = GAME_MODE_PSEUDO_PANEL;
+  SetGameStatus(GAME_MODE_PSEUDO_PANEL);
 
   for (i = 0; i < NUM_GAME_PANEL_CONTROLS; i++)
   {
@@ -2573,7 +2575,7 @@ void DisplayGameControlValues()
     redraw_mask |= REDRAW_DOOR_1;
   }
 
-  game_status = GAME_MODE_PLAYING;
+  SetGameStatus(GAME_MODE_PLAYING);
 }
 
 void UpdateAndDisplayGameControlValues()
@@ -3039,11 +3041,15 @@ static void InitGameEngine()
   for (i = 0; i < MAX_PLAYERS; i++)
     game.snapshot.last_action[i] = 0;
   game.snapshot.changed_action = FALSE;
+  game.snapshot.collected_item = FALSE;
   game.snapshot.mode =
     (strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_STEP) ?
      SNAPSHOT_MODE_EVERY_STEP :
      strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_MOVE) ?
-     SNAPSHOT_MODE_EVERY_MOVE : SNAPSHOT_MODE_OFF);
+     SNAPSHOT_MODE_EVERY_MOVE :
+     strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_COLLECT) ?
+     SNAPSHOT_MODE_EVERY_COLLECT : SNAPSHOT_MODE_OFF);
+  game.snapshot.save_snapshot = FALSE;
 }
 
 int get_num_special_action(int element, int action_first, int action_last)
@@ -3082,6 +3088,7 @@ void InitGame()
 {
   int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
   int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
+  int fade_mask = REDRAW_FIELD;
 
   boolean emulate_bd = TRUE;   /* unless non-BOULDERDASH elements found */
   boolean emulate_sb = TRUE;   /* unless non-SOKOBAN     elements found */
@@ -3089,23 +3096,35 @@ void InitGame()
   int initial_move_dir = MV_DOWN;
   int i, j, x, y;
 
-  game_status = GAME_MODE_PLAYING;
-
-  StopAnimation();
+  // required here to update video display before fading (FIX THIS)
+  DrawMaskedBorder(REDRAW_DOOR_2);
 
   if (!game.restart_level)
     CloseDoor(DOOR_CLOSE_1);
 
+  SetGameStatus(GAME_MODE_PLAYING);
+
   if (level_editor_test_game)
     FadeSkipNextFadeIn();
   else
     FadeSetEnterScreen();
 
-  FadeOut(REDRAW_FIELD);
+  if (CheckIfGlobalBorderHasChanged())
+    fade_mask = REDRAW_ALL;
+
+  FadeSoundsAndMusic();
+
+  ExpireSoundLoops(TRUE);
+
+  FadeOut(fade_mask);
 
   /* needed if different viewport properties defined for playing */
   ChangeViewportPropertiesIfNeeded();
 
+  ClearField();
+
+  OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
+
   DrawCompleteVideoDisplay();
 
   InitGameEngine();
@@ -3194,6 +3213,11 @@ void InitGame()
     player->is_bored = FALSE;
     player->is_sleeping = FALSE;
 
+    player->was_waiting = TRUE;
+    player->was_moving = FALSE;
+    player->was_snapping = FALSE;
+    player->was_dropping = FALSE;
+
     player->frame_counter_bored = -1;
     player->frame_counter_sleeping = -1;
 
@@ -3916,11 +3940,11 @@ void InitGame()
 
   /* blit playfield from scroll buffer to normal back buffer for fading in */
   BlitScreenToBitmap(backbuffer);
-
-  redraw_mask |= REDRAW_FROM_BACKBUFFER;
   /* !!! FIX THIS (END) !!! */
 
-  FadeIn(REDRAW_FIELD);
+  DrawMaskedBorder(fade_mask);
+
+  FadeIn(fade_mask);
 
 #if 1
   // full screen redraw is required at this point in the following cases:
@@ -3949,9 +3973,14 @@ void InitGame()
   {
     UnmapGameButtons();
     UnmapTapeButtons();
+
+    FreeGameButtons();
+    CreateGameButtons();
+
     game_gadget[SOUND_CTRL_ID_MUSIC]->checked = setup.sound_music;
     game_gadget[SOUND_CTRL_ID_LOOPS]->checked = setup.sound_loops;
     game_gadget[SOUND_CTRL_ID_SIMPLE]->checked = setup.sound_simple;
+
     MapGameButtons();
     MapTapeButtons();
 
@@ -4008,13 +4037,24 @@ void InitGame()
   SaveEngineSnapshotToListInitial();
 }
 
-void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y)
+void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
+                       int actual_player_x, int actual_player_y)
 {
   /* this is used for non-R'n'D game engines to update certain engine values */
 
+  if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+  {
+    actual_player_x = correctLevelPosX_EM(actual_player_x);
+    actual_player_y = correctLevelPosY_EM(actual_player_y);
+  }
+
   /* needed to determine if sounds are played within the visible screen area */
   scroll_x = actual_scroll_x;
   scroll_y = actual_scroll_y;
+
+  /* needed to get player position for "follow finger" playing input method */
+  local_player->jx = actual_player_x;
+  local_player->jy = actual_player_y;
 }
 
 void InitMovDir(int x, int y)
@@ -4404,20 +4444,18 @@ void GameEnd()
 
   if (level_editor_test_game)
   {
-    game_status = GAME_MODE_MAIN;
+    SetGameStatus(GAME_MODE_MAIN);
 
-    DrawAndFadeInMainMenu(REDRAW_FIELD);
+    DrawMainMenu();
 
     return;
   }
 
   if (!local_player->LevelSolved_SaveScore)
   {
-    FadeOut(REDRAW_FIELD);
+    SetGameStatus(GAME_MODE_MAIN);
 
-    game_status = GAME_MODE_MAIN;
-
-    DrawAndFadeInMainMenu(REDRAW_FIELD);
+    DrawMainMenu();
 
     return;
   }
@@ -4434,10 +4472,7 @@ void GameEnd()
 
   if ((hi_pos = NewHiScore()) >= 0) 
   {
-    game_status = GAME_MODE_SCORES;
-
-    /* needed if different viewport properties defined for scores */
-    ChangeViewportPropertiesIfNeeded();
+    SetGameStatus(GAME_MODE_SCORES);
 
     DrawHallOfFame(hi_pos);
 
@@ -4449,9 +4484,7 @@ void GameEnd()
   }
   else
   {
-    FadeOut(REDRAW_FIELD);
-
-    game_status = GAME_MODE_MAIN;
+    SetGameStatus(GAME_MODE_MAIN);
 
     if (raise_level)
     {
@@ -4459,7 +4492,7 @@ void GameEnd()
       TapeErase();
     }
 
-    DrawAndFadeInMainMenu(REDRAW_FIELD);
+    DrawMainMenu();
   }
 }
 
@@ -4879,168 +4912,107 @@ static void setScreenCenteredToAllPlayers(int *sx, int *sy)
 void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
                        boolean center_screen, boolean quick_relocation)
 {
+  unsigned int frame_delay_value_old = GetVideoFrameDelay();
   boolean ffwd_delay = (tape.playing && tape.fast_forward);
   boolean no_delay = (tape.warp_forward);
   int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
   int wait_delay_value = (no_delay ? 0 : frame_delay_value);
+  int new_scroll_x, new_scroll_y;
 
-  if (quick_relocation)
+  if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
   {
-    if (!IN_VIS_FIELD(SCREENX(x), SCREENY(y)) || center_screen)
-    {
-      if (!level.shifted_relocation || center_screen)
-      {
-       /* quick relocation (without scrolling), with centering of screen */
-
-       scroll_x = (x < SBX_Left  + MIDPOSX ? SBX_Left :
-                   x > SBX_Right + MIDPOSX ? SBX_Right :
-                   x - MIDPOSX);
-
-       scroll_y = (y < SBY_Upper + MIDPOSY ? SBY_Upper :
-                   y > SBY_Lower + MIDPOSY ? SBY_Lower :
-                   y - MIDPOSY);
-      }
-      else
-      {
-       /* quick relocation (without scrolling), but do not center screen */
-
-       int center_scroll_x = (old_x < SBX_Left  + MIDPOSX ? SBX_Left :
-                              old_x > SBX_Right + MIDPOSX ? SBX_Right :
-                              old_x - MIDPOSX);
-
-       int center_scroll_y = (old_y < SBY_Upper + MIDPOSY ? SBY_Upper :
-                              old_y > SBY_Lower + MIDPOSY ? SBY_Lower :
-                              old_y - MIDPOSY);
+    /* case 1: quick relocation inside visible screen (without scrolling) */
 
-       int offset_x = x + (scroll_x - center_scroll_x);
-       int offset_y = y + (scroll_y - center_scroll_y);
+    RedrawPlayfield();
 
-       scroll_x = (offset_x < SBX_Left  + MIDPOSX ? SBX_Left :
-                   offset_x > SBX_Right + MIDPOSX ? SBX_Right :
-                   offset_x - MIDPOSX);
+    return;
+  }
 
-       scroll_y = (offset_y < SBY_Upper + MIDPOSY ? SBY_Upper :
-                   offset_y > SBY_Lower + MIDPOSY ? SBY_Lower :
-                   offset_y - MIDPOSY);
-      }
-    }
-    else
-    {
-      if (!level.shifted_relocation || center_screen)
-      {
-       /* quick relocation (without scrolling), with centering of screen */
+  if (!level.shifted_relocation || center_screen)
+  {
+    /* relocation _with_ centering of screen */
 
-       scroll_x = (x < SBX_Left  + MIDPOSX ? SBX_Left :
+    new_scroll_x = (x < SBX_Left  + MIDPOSX ? SBX_Left :
                    x > SBX_Right + MIDPOSX ? SBX_Right :
                    x - MIDPOSX);
 
-       scroll_y = (y < SBY_Upper + MIDPOSY ? SBY_Upper :
+    new_scroll_y = (y < SBY_Upper + MIDPOSY ? SBY_Upper :
                    y > SBY_Lower + MIDPOSY ? SBY_Lower :
                    y - MIDPOSY);
-      }
-      else
-      {
-       /* quick relocation (without scrolling), but do not center screen */
+  }
+  else
+  {
+    /* relocation _without_ centering of screen */
 
-       int center_scroll_x = (old_x < SBX_Left  + MIDPOSX ? SBX_Left :
-                              old_x > SBX_Right + MIDPOSX ? SBX_Right :
-                              old_x - MIDPOSX);
+    int center_scroll_x = (old_x < SBX_Left  + MIDPOSX ? SBX_Left :
+                          old_x > SBX_Right + MIDPOSX ? SBX_Right :
+                          old_x - MIDPOSX);
 
-       int center_scroll_y = (old_y < SBY_Upper + MIDPOSY ? SBY_Upper :
-                              old_y > SBY_Lower + MIDPOSY ? SBY_Lower :
-                              old_y - MIDPOSY);
+    int center_scroll_y = (old_y < SBY_Upper + MIDPOSY ? SBY_Upper :
+                          old_y > SBY_Lower + MIDPOSY ? SBY_Lower :
+                          old_y - MIDPOSY);
 
-       int offset_x = x + (scroll_x - center_scroll_x);
-       int offset_y = y + (scroll_y - center_scroll_y);
+    int offset_x = x + (scroll_x - center_scroll_x);
+    int offset_y = y + (scroll_y - center_scroll_y);
 
-       scroll_x = (offset_x < SBX_Left  + MIDPOSX ? SBX_Left :
+    new_scroll_x = (offset_x < SBX_Left  + MIDPOSX ? SBX_Left :
                    offset_x > SBX_Right + MIDPOSX ? SBX_Right :
                    offset_x - MIDPOSX);
 
-       scroll_y = (offset_y < SBY_Upper + MIDPOSY ? SBY_Upper :
+    new_scroll_y = (offset_y < SBY_Upper + MIDPOSY ? SBY_Upper :
                    offset_y > SBY_Lower + MIDPOSY ? SBY_Lower :
                    offset_y - MIDPOSY);
-      }
-    }
-
-    RedrawPlayfield(TRUE, 0,0,0,0);
   }
-  else
-  {
-    int scroll_xx, scroll_yy;
 
-    if (!level.shifted_relocation || center_screen)
-    {
-      /* visible relocation (with scrolling), with centering of screen */
-
-      scroll_xx = (x < SBX_Left  + MIDPOSX ? SBX_Left :
-                  x > SBX_Right + MIDPOSX ? SBX_Right :
-                  x - MIDPOSX);
-
-      scroll_yy = (y < SBY_Upper + MIDPOSY ? SBY_Upper :
-                  y > SBY_Lower + MIDPOSY ? SBY_Lower :
-                  y - MIDPOSY);
-    }
-    else
-    {
-      /* visible relocation (with scrolling), but do not center screen */
+  if (quick_relocation)
+  {
+    /* case 2: quick relocation (redraw without visible scrolling) */
 
-      int center_scroll_x = (old_x < SBX_Left  + MIDPOSX ? SBX_Left :
-                            old_x > SBX_Right + MIDPOSX ? SBX_Right :
-                            old_x - MIDPOSX);
+    scroll_x = new_scroll_x;
+    scroll_y = new_scroll_y;
 
-      int center_scroll_y = (old_y < SBY_Upper + MIDPOSY ? SBY_Upper :
-                            old_y > SBY_Lower + MIDPOSY ? SBY_Lower :
-                            old_y - MIDPOSY);
+    RedrawPlayfield();
 
-      int offset_x = x + (scroll_x - center_scroll_x);
-      int offset_y = y + (scroll_y - center_scroll_y);
+    return;
+  }
 
-      scroll_xx = (offset_x < SBX_Left  + MIDPOSX ? SBX_Left :
-                  offset_x > SBX_Right + MIDPOSX ? SBX_Right :
-                  offset_x - MIDPOSX);
+  /* case 3: visible relocation (with scrolling to new position) */
 
-      scroll_yy = (offset_y < SBY_Upper + MIDPOSY ? SBY_Upper :
-                  offset_y > SBY_Lower + MIDPOSY ? SBY_Lower :
-                  offset_y - MIDPOSY);
-    }
+  ScrollScreen(NULL, SCROLL_GO_ON);    /* scroll last frame to full tile */
 
+  SetVideoFrameDelay(wait_delay_value);
 
-    ScrollScreen(NULL, SCROLL_GO_ON);  /* scroll last frame to full tile */
+  while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
+  {
+    int dx = 0, dy = 0;
+    int fx = FX, fy = FY;
 
-    while (scroll_x != scroll_xx || scroll_y != scroll_yy)
-    {
-      int dx = 0, dy = 0;
-      int fx = FX, fy = FY;
+    dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
+    dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
 
-      dx = (scroll_xx < scroll_x ? +1 : scroll_xx > scroll_x ? -1 : 0);
-      dy = (scroll_yy < scroll_y ? +1 : scroll_yy > scroll_y ? -1 : 0);
+    if (dx == 0 && dy == 0)            /* no scrolling needed at all */
+      break;
 
-      if (dx == 0 && dy == 0)          /* no scrolling needed at all */
-       break;
+    scroll_x -= dx;
+    scroll_y -= dy;
 
-      scroll_x -= dx;
-      scroll_y -= dy;
+    fx += dx * TILEX / 2;
+    fy += dy * TILEY / 2;
 
-      fx += dx * TILEX / 2;
-      fy += dy * TILEY / 2;
+    ScrollLevel(dx, dy);
+    DrawAllPlayers();
 
-      ScrollLevel(dx, dy);
-      DrawAllPlayers();
+    /* scroll in two steps of half tile size to make things smoother */
+    BlitBitmap(drawto_field, window, fx, fy, SXSIZE, SYSIZE, SX, SY);
 
-      /* scroll in two steps of half tile size to make things smoother */
-      BlitBitmap(drawto_field, window, fx, fy, SXSIZE, SYSIZE, SX, SY);
-      Delay(wait_delay_value);
+    /* scroll second step to align at full tile size */
+    BlitScreenToBitmap(window);
+  }
 
-      /* scroll second step to align at full tile size */
-      BackToFront();
-      Delay(wait_delay_value);
-    }
+  DrawAllPlayers();
+  BackToFront();
 
-    DrawAllPlayers();
-    BackToFront();
-    Delay(wait_delay_value);
-  }
+  SetVideoFrameDelay(frame_delay_value_old);
 }
 
 void RelocatePlayer(int jx, int jy, int el_player_raw)
@@ -5090,8 +5062,7 @@ void RelocatePlayer(int jx, int jy, int el_player_raw)
 
       DrawPlayer(player);
 
-      BackToFront();
-      Delay(wait_delay_value);
+      BackToFront_WithFrameDelay(wait_delay_value);
     }
 
     DrawPlayer(player);                /* needed here only to cleanup last field */
@@ -9549,6 +9520,8 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
     {
       local_player->gems_still_needed = action_arg_number_new;
 
+      game.snapshot.collected_item = TRUE;
+
       game_panel_controls[GAME_PANEL_GEMS].value =
        local_player->gems_still_needed;
 
@@ -10237,9 +10210,50 @@ static void HandleElementChange(int x, int y, int page)
     {
       /* !!! not clear why graphic animation should be reset at all here !!! */
       /* !!! UPDATE: but is needed for correct Snake Bite tail animation !!! */
+      /* !!! SOLUTION: do not reset if graphics engine set to 4 or above !!! */
+
+      /*
+       GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
+
+       When using an animation frame delay of 1 (this only happens with
+       "sp_zonk.moving.left/right" in the classic graphics), the default
+       (non-moving) animation shows wrong animation frames (while the
+       moving animation, like "sp_zonk.moving.left/right", is correct,
+       so this graphical bug never shows up with the classic graphics).
+       For an animation with 4 frames, this causes wrong frames 0,0,1,2
+       be drawn instead of the correct frames 0,1,2,3. This is caused by
+       "GfxFrame[][]" being reset *twice* (in two successive frames) after
+       an element change: First when the change delay ("ChangeDelay[][]")
+       counter has reached zero after decrementing, then a second time in
+       the next frame (after "GfxFrame[][]" was already incremented) when
+       "ChangeDelay[][]" is reset to the initial delay value again.
+
+       This causes frame 0 to be drawn twice, while the last frame won't
+       be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
+
+       As some animations may already be cleverly designed around this bug
+       (at least the "Snake Bite" snake tail animation does this), it cannot
+       simply be fixed here without breaking such existing animations.
+       Unfortunately, it cannot easily be detected if a graphics set was
+       designed "before" or "after" the bug was fixed. As a workaround,
+       a new graphics set option "game.graphics_engine_version" was added
+       to be able to specify the game's major release version for which the
+       graphics set was designed, which can then be used to decide if the
+       bugfix should be used (version 4 and above) or not (version 3 or
+       below, or if no version was specified at all, as with old sets).
+
+       (The wrong/fixed animation frames can be tested with the test level set
+       "test_gfxframe" and level "000", which contains a specially prepared
+       custom element at level position (x/y) == (11/9) which uses the zonk
+       animation mentioned above. Using "game.graphics_engine_version: 4"
+       fixes the wrong animation frames, showing the correct frames 0,1,2,3.
+       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 */
-      if (!IS_MOVING(x, y))
+      if (game.graphics_engine_version < 4 &&
+         !IS_MOVING(x, y))
       {
        ResetGfxAnimation(x, y);
        ResetRandomAnimationValue(x, y);
@@ -10691,32 +10705,28 @@ static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
 
 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
 {
-  static boolean player_was_moving = FALSE;
-  static boolean player_was_snapping = FALSE;
-  static boolean player_was_dropping = FALSE;
-
-  if ((!player->is_moving  && player_was_moving) ||
-      (player->MovPos == 0 && player_was_moving) ||
-      (player->is_snapping && !player_was_snapping) ||
-      (player->is_dropping && !player_was_dropping))
+  if ((!player->is_moving  && player->was_moving) ||
+      (player->MovPos == 0 && player->was_moving) ||
+      (player->is_snapping && !player->was_snapping) ||
+      (player->is_dropping && !player->was_dropping))
   {
-    if (!SaveEngineSnapshotToList())
+    if (!CheckSaveEngineSnapshotToList())
       return;
 
-    player_was_moving = FALSE;
-    player_was_snapping = TRUE;
-    player_was_dropping = TRUE;
+    player->was_moving = FALSE;
+    player->was_snapping = TRUE;
+    player->was_dropping = TRUE;
   }
   else
   {
     if (player->is_moving)
-      player_was_moving = TRUE;
+      player->was_moving = TRUE;
 
     if (!player->is_snapping)
-      player_was_snapping = FALSE;
+      player->was_snapping = FALSE;
 
     if (!player->is_dropping)
-      player_was_dropping = FALSE;
+      player->was_dropping = FALSE;
   }
 }
 
@@ -10961,32 +10971,17 @@ void StartGameActions(boolean init_network_game, boolean record_tape,
   InitGame();
 }
 
-void GameActions()
+void GameActionsExt()
 {
+#if 0
   static unsigned int game_frame_delay = 0;
+#endif
   unsigned int game_frame_delay_value;
   byte *recorded_player_action;
   byte summarized_player_action = 0;
   byte tape_action[MAX_PLAYERS];
   int i;
 
-  for (i = 0; i < MAX_PLAYERS; i++)
-  {
-    struct PlayerInfo *player = &stored_player[i];
-
-    // allow engine snapshot if movement attempt was stopped
-    if ((game.snapshot.last_action[i] & KEY_MOTION) != 0 &&
-       (player->action & KEY_MOTION) == 0)
-      game.snapshot.changed_action = TRUE;
-
-    // allow engine snapshot in case of snapping/dropping attempt
-    if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
-       (player->action & KEY_BUTTON) != 0)
-      game.snapshot.changed_action = TRUE;
-
-    game.snapshot.last_action[i] = player->action;
-  }
-
   /* detect endless loops, caused by custom element programming */
   if (recursion_loop_detected && recursion_loop_depth == 0)
   {
@@ -11058,9 +11053,22 @@ void GameActions()
   if (tape.playing && tape.warp_forward && !tape.pausing)
     game_frame_delay_value = 0;
 
+  SetVideoFrameDelay(game_frame_delay_value);
+
+#if 0
+#if 0
+  /* ---------- main game synchronization point ---------- */
+
+  int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
+
+  printf("::: skip == %d\n", skip);
+
+#else
   /* ---------- main game synchronization point ---------- */
 
   WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
+#endif
+#endif
 
   if (network_playing && !network_player_action_received)
   {
@@ -11114,8 +11122,11 @@ void GameActions()
     SendToServer_MovePlayer(summarized_player_action);
 #endif
 
+  // summarize all actions at local players mapped input device position
+  // (this allows using different input devices in single player mode)
   if (!options.network && !game.team_mode)
-    local_player->effective_action = summarized_player_action;
+    stored_player[map_player_action[local_player->index_nr]].effective_action =
+      summarized_player_action;
 
   if (tape.recording &&
       setup.team_mode &&
@@ -11151,6 +11162,7 @@ void GameActions()
 #if USE_NEW_PLAYER_ASSIGNMENTS
   // !!! also map player actions in single player mode !!!
   // if (game.team_mode)
+  if (1)
   {
     byte mapped_action[MAX_PLAYERS];
 
@@ -11184,6 +11196,21 @@ void GameActions()
 #endif
 #endif
 
+  for (i = 0; i < MAX_PLAYERS; i++)
+  {
+    // allow engine snapshot in case of changed movement attempt
+    if ((game.snapshot.last_action[i] & KEY_MOTION) !=
+       (stored_player[i].effective_action & KEY_MOTION))
+      game.snapshot.changed_action = TRUE;
+
+    // allow engine snapshot in case of snapping/dropping attempt
+    if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
+       (stored_player[i].effective_action & KEY_BUTTON) != 0)
+      game.snapshot.changed_action = TRUE;
+
+    game.snapshot.last_action[i] = stored_player[i].effective_action;
+  }
+
   if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
   {
     GameActions_EM_Main();
@@ -11223,6 +11250,24 @@ void GameActions()
   }
 }
 
+static void GameActions_CheckSaveEngineSnapshot()
+{
+  if (!game.snapshot.save_snapshot)
+    return;
+
+  // clear flag for saving snapshot _before_ saving snapshot
+  game.snapshot.save_snapshot = FALSE;
+
+  SaveEngineSnapshotToList();
+}
+
+void GameActions()
+{
+  GameActionsExt();
+
+  GameActions_CheckSaveEngineSnapshot();
+}
+
 void GameActions_EM_Main()
 {
   byte effective_action[MAX_PLAYERS];
@@ -12053,7 +12098,7 @@ boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
       AdvanceFrameAndPlayerCounters(player->index_nr);
 
       DrawAllPlayers();
-      BackToFront();
+      BackToFront_WithFrameDelay(0);
     }
 
     player->move_delay_value = original_move_delay_value;
@@ -13465,6 +13510,8 @@ static int DigField(struct PlayerInfo *player,
       if (local_player->gems_still_needed < 0)
        local_player->gems_still_needed = 0;
 
+      game.snapshot.collected_item = TRUE;
+
       game_panel_controls[GAME_PANEL_GEMS].value = local_player->gems_still_needed;
 
       DisplayGameControlValues();
@@ -13663,9 +13710,16 @@ static int DigField(struct PlayerInfo *player,
       SCAN_PLAYFIELD(xx, yy)
       {
        if (Feld[xx][yy] == EL_SP_DISK_YELLOW)
+       {
          Bang(xx, yy);
+       }
        else if (Feld[xx][yy] == EL_SP_TERMINAL)
+       {
          Feld[xx][yy] = EL_SP_TERMINAL_ACTIVE;
+
+         ResetGfxAnimation(xx, yy);
+         TEST_DrawLevelField(xx, yy);
+       }
       }
     }
     else if (IS_BELT_SWITCH(element))
@@ -14457,19 +14511,11 @@ void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
 #endif
     {
       if (quick_quit)
-      {
        FadeSkipNextFadeIn();
 
-       game_status = GAME_MODE_MAIN;
-
-       DrawAndFadeInMainMenu(REDRAW_FIELD);
-      }
-      else
-      {
-       game_status = GAME_MODE_MAIN;
+      SetGameStatus(GAME_MODE_MAIN);
 
-       DrawAndFadeInMainMenu(REDRAW_FIELD);
-      }
+      DrawMainMenu();
     }
   }
   else         /* continue playing the game */
@@ -14754,39 +14800,39 @@ void SaveEngineSnapshotSingle()
   snapshot_level_nr = level_nr;
 }
 
-static boolean SaveEngineSnapshotToListExt(boolean initial_snapshot)
+boolean CheckSaveEngineSnapshotToList()
 {
   boolean save_snapshot =
-    (initial_snapshot ||
-     (game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
+    ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
      (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
-      game.snapshot.changed_action));
+      game.snapshot.changed_action) ||
+     (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
+      game.snapshot.collected_item));
 
   game.snapshot.changed_action = FALSE;
+  game.snapshot.collected_item = FALSE;
+  game.snapshot.save_snapshot = save_snapshot;
+
+  return save_snapshot;
+}
 
+void SaveEngineSnapshotToList()
+{
   if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
-      tape.quick_resume ||
-      !save_snapshot)
-    return FALSE;
+      tape.quick_resume)
+    return;
 
   ListNode *buffers = SaveEngineSnapshotBuffers();
 
   /* finally save all snapshot buffers to snapshot list */
   SaveSnapshotToList(buffers);
-
-  return TRUE;
-}
-
-boolean SaveEngineSnapshotToList()
-{
-  return SaveEngineSnapshotToListExt(FALSE);
 }
 
 void SaveEngineSnapshotToListInitial()
 {
   FreeEngineSnapshotList();
 
-  SaveEngineSnapshotToListExt(TRUE);
+  SaveEngineSnapshotToList();
 }
 
 void LoadEngineSnapshotValues()
@@ -14845,43 +14891,47 @@ static struct
 } gamebutton_info[NUM_GAME_BUTTONS] =
 {
   {
-    IMG_GAME_BUTTON_GFX_STOP,          &game.button.stop,
+    IMG_GFX_GAME_BUTTON_STOP,          &game.button.stop,
     GAME_CTRL_ID_STOP,                 "stop game"
   },
   {
-    IMG_GAME_BUTTON_GFX_PAUSE,         &game.button.pause,
+    IMG_GFX_GAME_BUTTON_PAUSE,         &game.button.pause,
     GAME_CTRL_ID_PAUSE,                        "pause game"
   },
   {
-    IMG_GAME_BUTTON_GFX_PLAY,          &game.button.play,
+    IMG_GFX_GAME_BUTTON_PLAY,          &game.button.play,
     GAME_CTRL_ID_PLAY,                 "play game"
   },
   {
-    IMG_GAME_BUTTON_GFX_UNDO,          &game.button.undo,
+    IMG_GFX_GAME_BUTTON_UNDO,          &game.button.undo,
     GAME_CTRL_ID_UNDO,                 "undo step"
   },
   {
-    IMG_GAME_BUTTON_GFX_REDO,          &game.button.redo,
+    IMG_GFX_GAME_BUTTON_REDO,          &game.button.redo,
     GAME_CTRL_ID_REDO,                 "redo step"
   },
   {
-    IMG_GAME_BUTTON_GFX_SAVE,          &game.button.save,
+    IMG_GFX_GAME_BUTTON_SAVE,          &game.button.save,
     GAME_CTRL_ID_SAVE,                 "save game"
   },
   {
-    IMG_GAME_BUTTON_GFX_LOAD,          &game.button.load,
+    IMG_GFX_GAME_BUTTON_PAUSE2,                &game.button.pause2,
+    GAME_CTRL_ID_PAUSE2,               "pause game"
+  },
+  {
+    IMG_GFX_GAME_BUTTON_LOAD,          &game.button.load,
     GAME_CTRL_ID_LOAD,                 "load game"
   },
   {
-    IMG_GAME_BUTTON_GFX_SOUND_MUSIC,   &game.button.sound_music,
+    IMG_GFX_GAME_BUTTON_SOUND_MUSIC,   &game.button.sound_music,
     SOUND_CTRL_ID_MUSIC,               "background music on/off"
   },
   {
-    IMG_GAME_BUTTON_GFX_SOUND_LOOPS,   &game.button.sound_loops,
+    IMG_GFX_GAME_BUTTON_SOUND_LOOPS,   &game.button.sound_loops,
     SOUND_CTRL_ID_LOOPS,               "sound loops on/off"
   },
   {
-    IMG_GAME_BUTTON_GFX_SOUND_SIMPLE,  &game.button.sound_simple,
+    IMG_GFX_GAME_BUTTON_SOUND_SIMPLE,  &game.button.sound_simple,
     SOUND_CTRL_ID_SIMPLE,              "normal sounds on/off"
   }
 };
@@ -14918,7 +14968,6 @@ void CreateGameButtons()
     }
 
     if (id == GAME_CTRL_ID_STOP ||
-       id == GAME_CTRL_ID_PAUSE ||
        id == GAME_CTRL_ID_PLAY ||
        id == GAME_CTRL_ID_SAVE ||
        id == GAME_CTRL_ID_LOAD)
@@ -14977,7 +15026,7 @@ void FreeGameButtons()
     FreeGadget(game_gadget[i]);
 }
 
-static void MapGameButtonsAtSamePosition(int id)
+static void UnmapGameButtonsAtSamePosition(int id)
 {
   int i;
 
@@ -14985,10 +15034,26 @@ static void MapGameButtonsAtSamePosition(int id)
     if (i != id &&
        gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
        gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
-      MapGadget(game_gadget[i]);
+      UnmapGadget(game_gadget[i]);
 }
 
-static void UnmapGameButtonsAtSamePosition(int id)
+static void UnmapGameButtonsAtSamePosition_All()
+{
+  if (setup.show_snapshot_buttons)
+  {
+    UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
+    UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
+    UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
+  }
+  else
+  {
+    UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
+    UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
+    UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
+  }
+}
+
+static void MapGameButtonsAtSamePosition(int id)
 {
   int i;
 
@@ -14996,29 +15061,31 @@ static void UnmapGameButtonsAtSamePosition(int id)
     if (i != id &&
        gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
        gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
-      UnmapGadget(game_gadget[i]);
+      MapGadget(game_gadget[i]);
+
+  UnmapGameButtonsAtSamePosition_All();
 }
 
 void MapUndoRedoButtons()
 {
   UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
   UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
-  UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
 
   MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
   MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
-  MapGadget(game_gadget[GAME_CTRL_ID_PLAY]);
+
+  ModifyGadget(game_gadget[GAME_CTRL_ID_PAUSE2], GDI_CHECKED, TRUE, GDI_END);
 }
 
 void UnmapUndoRedoButtons()
 {
   UnmapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
   UnmapGadget(game_gadget[GAME_CTRL_ID_REDO]);
-  UnmapGadget(game_gadget[GAME_CTRL_ID_PLAY]);
 
   MapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
   MapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
-  MapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
+
+  ModifyGadget(game_gadget[GAME_CTRL_ID_PAUSE2], GDI_CHECKED, FALSE, GDI_END);
 }
 
 void MapGameButtons()
@@ -15027,9 +15094,12 @@ void MapGameButtons()
 
   for (i = 0; i < NUM_GAME_BUTTONS; i++)
     if (i != GAME_CTRL_ID_UNDO &&
-       i != GAME_CTRL_ID_REDO &&
-       i != GAME_CTRL_ID_PLAY)
+       i != GAME_CTRL_ID_REDO)
       MapGadget(game_gadget[i]);
+
+  UnmapGameButtonsAtSamePosition_All();
+
+  RedrawGameButtons();
 }
 
 void UnmapGameButtons()
@@ -15063,8 +15133,7 @@ void GameUndoRedoExt()
   DrawCompleteVideoDisplay();
   DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
   DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
-  DrawVideoDisplay((tape.single_step ? VIDEO_STATE_1STEP_ON :
-                   VIDEO_STATE_1STEP_OFF), 0);
+  DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
 
   BackToFront();
 }
@@ -15091,6 +15160,7 @@ void GameRedo(int steps)
 
 static void HandleGameButtonsExt(int id, int button)
 {
+  static boolean game_undo_executed = FALSE;
   int steps = BUTTON_STEPSIZE(button);
   boolean handle_game_buttons =
     (game_status == GAME_MODE_PLAYING ||
@@ -15113,6 +15183,7 @@ static void HandleGameButtonsExt(int id, int button)
       break;
 
     case GAME_CTRL_ID_PAUSE:
+    case GAME_CTRL_ID_PAUSE2:
       if (options.network && game_status == GAME_MODE_PLAYING)
       {
 #if defined(NETWORK_AVALIABLE)
@@ -15124,6 +15195,9 @@ static void HandleGameButtonsExt(int id, int button)
       }
       else
        TapeTogglePause(TAPE_TOGGLE_MANUAL);
+
+      game_undo_executed = FALSE;
+
       break;
 
     case GAME_CTRL_ID_PLAY:
@@ -15138,11 +15212,22 @@ static void HandleGameButtonsExt(int id, int button)
          SendToServer_ContinuePlaying();
        else
 #endif
-         TapeTogglePause(TAPE_TOGGLE_MANUAL);
+         TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
       }
       break;
 
     case GAME_CTRL_ID_UNDO:
+      // Important: When using "save snapshot when collecting an item" mode,
+      // load last (current) snapshot for first "undo" after pressing "pause"
+      // (else the last-but-one snapshot would be loaded, because the snapshot
+      // pointer already points to the last snapshot when pressing "pause",
+      // which is fine for "every step/move" mode, but not for "every collect")
+      if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
+         !game_undo_executed)
+       steps--;
+
+      game_undo_executed = TRUE;
+
       GameUndo(steps);
       break;