added new level option to only redraw off-screen player relocation
[rocksndiamonds.git] / src / game.c
index 59a77329b4b5f83dbffb7fe84bd41036a90a46b9..cea1b769be4fd43ad9ece0c2710720b63434caa9 100644 (file)
@@ -15,6 +15,7 @@
 #include "init.h"
 #include "tools.h"
 #include "screens.h"
+#include "events.h"
 #include "files.h"
 #include "tape.h"
 #include "network.h"
@@ -970,13 +971,16 @@ static struct GamePanelControlInfo game_panel_controls[] =
 #define GAME_CTRL_ID_STOP              0
 #define GAME_CTRL_ID_PAUSE             1
 #define GAME_CTRL_ID_PLAY              2
-#define SOUND_CTRL_ID_MUSIC            3
-#define SOUND_CTRL_ID_LOOPS            4
-#define SOUND_CTRL_ID_SIMPLE           5
-#define GAME_CTRL_ID_SAVE              6
+#define GAME_CTRL_ID_UNDO              3
+#define GAME_CTRL_ID_REDO              4
+#define GAME_CTRL_ID_SAVE              5
+#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               8
+#define NUM_GAME_BUTTONS               11
 
 
 /* forward declaration for internal use */
@@ -1945,7 +1949,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);
 
@@ -1955,7 +1959,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];
 
@@ -2575,7 +2579,7 @@ void DisplayGameControlValues()
 
 void UpdateAndDisplayGameControlValues()
 {
-  if (tape.warp_forward)
+  if (tape.deactivate_display)
     return;
 
   UpdateGameControlValues();
@@ -3031,6 +3035,16 @@ static void InitGameEngine()
      setup.scroll_delay                   ? setup.scroll_delay_value       : 0);
   game.scroll_delay_value =
     MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
+
+  /* ---------- initialize game engine snapshots ---------------------------- */
+  for (i = 0; i < MAX_PLAYERS; i++)
+    game.snapshot.last_action[i] = 0;
+  game.snapshot.changed_action = 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);
 }
 
 int get_num_special_action(int element, int action_first, int action_last)
@@ -3069,6 +3083,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 */
@@ -3076,22 +3091,30 @@ void InitGame()
   int initial_move_dir = MV_DOWN;
   int i, j, x, y;
 
-  game_status = GAME_MODE_PLAYING;
+  // required here to update video display before fading (FIX THIS)
+  DrawMaskedBorder(REDRAW_DOOR_2);
 
-  StopAnimation();
+  game_status = GAME_MODE_PLAYING;
 
   if (!game.restart_level)
     CloseDoor(DOOR_CLOSE_1);
 
+  /* needed if different viewport properties defined for playing */
+  ChangeViewportPropertiesIfNeeded();
+
   if (level_editor_test_game)
     FadeSkipNextFadeIn();
   else
     FadeSetEnterScreen();
 
-  FadeOut(REDRAW_FIELD);
+  if (CheckIfGlobalBorderHasChanged())
+    fade_mask = REDRAW_ALL;
 
-  /* needed if different viewport properties defined for playing */
-  ChangeViewportPropertiesIfNeeded();
+  FadeOut(fade_mask);
+
+  OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
+
+  ClearField();
 
   DrawCompleteVideoDisplay();
 
@@ -3903,11 +3926,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:
@@ -3936,9 +3959,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();
 
@@ -3991,6 +4019,8 @@ void InitGame()
   }
 
   game.restart_level = FALSE;
+
+  SaveEngineSnapshotToListInitial();
 }
 
 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y)
@@ -4377,18 +4407,21 @@ void GameEnd()
 
   local_player->LevelSolved_GameEnd = TRUE;
 
-  CloseDoor(DOOR_CLOSE_1);
+  if (!global.use_envelope_request)
+    CloseDoor(DOOR_CLOSE_1);
 
   if (local_player->LevelSolved_SaveTape)
   {
     SaveTapeChecked(tape.level_nr);    /* ask to save tape */
   }
 
+  CloseDoor(DOOR_CLOSE_ALL);
+
   if (level_editor_test_game)
   {
     game_status = GAME_MODE_MAIN;
 
-    DrawAndFadeInMainMenu(REDRAW_FIELD);
+    DrawMainMenu();
 
     return;
   }
@@ -4399,7 +4432,7 @@ void GameEnd()
 
     game_status = GAME_MODE_MAIN;
 
-    DrawAndFadeInMainMenu(REDRAW_FIELD);
+    DrawMainMenu();
 
     return;
   }
@@ -4438,7 +4471,7 @@ void GameEnd()
       TapeErase();
     }
 
-    DrawAndFadeInMainMenu(REDRAW_FIELD);
+    DrawMainMenu();
   }
 }
 
@@ -4863,7 +4896,11 @@ void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
   int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
   int wait_delay_value = (no_delay ? 0 : frame_delay_value);
 
-  if (quick_relocation)
+  if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
+  {
+    RedrawPlayfield();
+  }
+  else if (quick_relocation)
   {
     if (!IN_VIS_FIELD(SCREENX(x), SCREENY(y)) || center_screen)
     {
@@ -4942,7 +4979,7 @@ void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
       }
     }
 
-    RedrawPlayfield(TRUE, 0,0,0,0);
+    RedrawPlayfield();
   }
   else
   {
@@ -4984,7 +5021,6 @@ void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
                   offset_y - MIDPOSY);
     }
 
-
     ScrollScreen(NULL, SCROLL_GO_ON);  /* scroll last frame to full tile */
 
     while (scroll_x != scroll_xx || scroll_y != scroll_yy)
@@ -10668,6 +10704,37 @@ 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 (!SaveEngineSnapshotToList())
+      return;
+
+    player_was_moving = FALSE;
+    player_was_snapping = TRUE;
+    player_was_dropping = TRUE;
+  }
+  else
+  {
+    if (player->is_moving)
+      player_was_moving = TRUE;
+
+    if (!player->is_snapping)
+      player_was_snapping = FALSE;
+
+    if (!player->is_dropping)
+      player_was_dropping = FALSE;
+  }
+}
+
 static void CheckSingleStepMode(struct PlayerInfo *player)
 {
   if (tape.single_step && tape.recording && !tape.pausing)
@@ -10680,6 +10747,8 @@ static void CheckSingleStepMode(struct PlayerInfo *player)
       SnapField(player, 0, 0);                 /* stop snapping */
     }
   }
+
+  CheckSaveEngineSnapshot(player);
 }
 
 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
@@ -10987,9 +11056,18 @@ void GameActions()
   if (tape.playing && tape.warp_forward && !tape.pausing)
     game_frame_delay_value = 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
 
   if (network_playing && !network_player_action_received)
   {
@@ -11113,6 +11191,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();
@@ -11123,7 +11216,32 @@ void GameActions()
   }
   else
   {
-    GameActions_RND();
+    GameActions_RND_Main();
+  }
+
+  BlitScreenToBitmap(backbuffer);
+
+  CheckLevelTime();
+
+  AdvanceFrameAndPlayerCounters(-1);   /* advance counters for all players */
+
+  if (options.debug)                   /* calculate frames per second */
+  {
+    static unsigned int fps_counter = 0;
+    static int fps_frames = 0;
+    unsigned int fps_delay_ms = Counter() - fps_counter;
+
+    fps_frames++;
+
+    if (fps_delay_ms >= 500)   /* calculate fps every 0.5 seconds */
+    {
+      global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
+
+      fps_frames = 0;
+      fps_counter = Counter();
+    }
+
+    redraw_mask |= REDRAW_FPS;
   }
 }
 
@@ -11137,10 +11255,6 @@ void GameActions_EM_Main()
     effective_action[i] = stored_player[i].effective_action;
 
   GameActions_EM(effective_action, warp_mode);
-
-  CheckLevelTime();
-
-  AdvanceFrameAndPlayerCounters(-1);   /* advance counters for all players */
 }
 
 void GameActions_SP_Main()
@@ -11153,10 +11267,11 @@ void GameActions_SP_Main()
     effective_action[i] = stored_player[i].effective_action;
 
   GameActions_SP(effective_action, warp_mode);
+}
 
-  CheckLevelTime();
-
-  AdvanceFrameAndPlayerCounters(-1);   /* advance counters for all players */
+void GameActions_RND_Main()
+{
+  GameActions_RND();
 }
 
 void GameActions_RND()
@@ -11658,32 +11773,9 @@ void GameActions_RND()
   }
 #endif
 
-  CheckLevelTime();
-
   DrawAllPlayers();
   PlayAllPlayersSound();
 
-  if (options.debug)                   /* calculate frames per second */
-  {
-    static unsigned int fps_counter = 0;
-    static int fps_frames = 0;
-    unsigned int fps_delay_ms = Counter() - fps_counter;
-
-    fps_frames++;
-
-    if (fps_delay_ms >= 500)   /* calculate fps every 0.5 seconds */
-    {
-      global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
-
-      fps_frames = 0;
-      fps_counter = Counter();
-    }
-
-    redraw_mask |= REDRAW_FPS;
-  }
-
-  AdvanceFrameAndPlayerCounters(-1);   /* advance counters for all players */
-
   if (local_player->show_envelope != 0 && local_player->MovPos == 0)
   {
     ShowEnvelope(local_player->show_envelope - EL_ENVELOPE_1);
@@ -11737,16 +11829,16 @@ static boolean AllPlayersInVisibleScreen()
 
 void ScrollLevel(int dx, int dy)
 {
-  int softscroll_offset = (setup.soft_scrolling ? 2 * TILEX_VAR : 0);
+  int scroll_offset = 2 * TILEX_VAR;
   int x, y;
 
   BlitBitmap(drawto_field, drawto_field,
-            FX + TILEX_VAR * (dx == -1) - softscroll_offset,
-            FY + TILEY_VAR * (dy == -1) - softscroll_offset,
-            SXSIZE - TILEX_VAR * (dx != 0) + 2 * softscroll_offset,
-            SYSIZE - TILEY_VAR * (dy != 0) + 2 * softscroll_offset,
-            FX + TILEX_VAR * (dx == 1) - softscroll_offset,
-            FY + TILEY_VAR * (dy == 1) - softscroll_offset);
+            FX + TILEX_VAR * (dx == -1) - scroll_offset,
+            FY + TILEY_VAR * (dy == -1) - scroll_offset,
+            SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
+            SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
+            FX + TILEX_VAR * (dx == 1) - scroll_offset,
+            FY + TILEY_VAR * (dy == 1) - scroll_offset);
 
   if (dx != 0)
   {
@@ -12308,6 +12400,9 @@ void ScrollPlayer(struct PlayerInfo *player, int mode)
     if (tape.single_step && tape.recording && !tape.pausing &&
        !player->programmed_action)
       TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
+
+    if (!player->programmed_action)
+      CheckSaveEngineSnapshot(player);
   }
 }
 
@@ -14389,13 +14484,13 @@ void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
 
        game_status = GAME_MODE_MAIN;
 
-       DrawAndFadeInMainMenu(REDRAW_FIELD);
+       DrawMainMenu();
       }
       else
       {
        game_status = GAME_MODE_MAIN;
 
-       DrawAndFadeInMainMenu(REDRAW_FIELD);
+       DrawMainMenu();
       }
     }
   }
@@ -14551,22 +14646,22 @@ static void LoadEngineSnapshotValues_RND()
   }
 }
 
-void FreeEngineSnapshot()
+void FreeEngineSnapshotSingle()
 {
-  FreeEngineSnapshotBuffers();
+  FreeSnapshotSingle();
 
   setString(&snapshot_level_identifier, NULL);
   snapshot_level_nr = -1;
 }
 
-void SaveEngineSnapshot()
+void FreeEngineSnapshotList()
 {
-  /* do not save snapshots from editor */
-  if (level_editor_test_game)
-    return;
+  FreeSnapshotList();
+}
 
-  /* free previous snapshot buffers, if needed */
-  FreeEngineSnapshotBuffers();
+ListNode *SaveEngineSnapshotBuffers()
+{
+  ListNode *buffers = NULL;
 
   /* copy some special values to a structure better suited for the snapshot */
 
@@ -14575,87 +14670,82 @@ void SaveEngineSnapshot()
   if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
     SaveEngineSnapshotValues_EM();
   if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
-    SaveEngineSnapshotValues_SP();
+    SaveEngineSnapshotValues_SP(&buffers);
 
   /* save values stored in special snapshot structure */
 
   if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
-    SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
+    SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
   if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
-    SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
+    SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
   if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
-    SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
+    SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
 
   /* save further RND engine values */
 
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(stored_player));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(game));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(tape));
-
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ZX));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ZY));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ExitX));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ExitY));
-
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(TapeTime));
-
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
-
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
-
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(AllPlayersGone));
-
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
-
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(Feld));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(MovPos));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(MovDir));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(MovDelay));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ChangePage));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(CustomValue));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(Store));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(Store2));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(Back));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(Stop));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(Pushed));
-
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
-
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
-
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
-
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(GfxElement));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(GfxAction));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(GfxDir));
-
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(scroll_x));
-  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(scroll_y));
-
-  /* save level identification information */
-
-  setString(&snapshot_level_identifier, leveldir_current->identifier);
-  snapshot_level_nr = level_nr;
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
+
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ZX));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ZY));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExitX));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExitY));
+
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
+
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
+
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
+
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AllPlayersGone));
+
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
+
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Feld));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
+
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
+
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
+
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
+
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
+
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
+  SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
 
 #if 0
   ListNode *node = engine_snapshot_list_rnd;
@@ -14670,14 +14760,59 @@ void SaveEngineSnapshot()
 
   printf("::: size of engine snapshot: %d bytes\n", num_bytes);
 #endif
+
+  return buffers;
 }
 
-void LoadEngineSnapshot()
+void SaveEngineSnapshotSingle()
 {
-  /* restore generically stored snapshot buffers */
+  ListNode *buffers = SaveEngineSnapshotBuffers();
+
+  /* finally save all snapshot buffers to single snapshot */
+  SaveSnapshotSingle(buffers);
 
-  LoadEngineSnapshotBuffers();
+  /* save level identification information */
+  setString(&snapshot_level_identifier, leveldir_current->identifier);
+  snapshot_level_nr = level_nr;
+}
+
+static boolean SaveEngineSnapshotToListExt(boolean initial_snapshot)
+{
+  boolean save_snapshot =
+    (initial_snapshot ||
+     (game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
+     (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
+      game.snapshot.changed_action));
+
+  game.snapshot.changed_action = FALSE;
+
+  if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
+      tape.quick_resume ||
+      !save_snapshot)
+    return FALSE;
 
+  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);
+}
+
+void LoadEngineSnapshotValues()
+{
   /* restore special values from snapshot structure */
 
   if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
@@ -14688,19 +14823,45 @@ void LoadEngineSnapshot()
     LoadEngineSnapshotValues_SP();
 }
 
-boolean CheckEngineSnapshot()
+void LoadEngineSnapshotSingle()
+{
+  LoadSnapshotSingle();
+
+  LoadEngineSnapshotValues();
+}
+
+void LoadEngineSnapshot_Undo(int steps)
+{
+  LoadSnapshotFromList_Older(steps);
+
+  LoadEngineSnapshotValues();
+}
+
+void LoadEngineSnapshot_Redo(int steps)
+{
+  LoadSnapshotFromList_Newer(steps);
+
+  LoadEngineSnapshotValues();
+}
+
+boolean CheckEngineSnapshotSingle()
 {
   return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
          snapshot_level_nr == level_nr);
 }
 
+boolean CheckEngineSnapshotList()
+{
+  return CheckSnapshotList();
+}
+
 
 /* ---------- new game button stuff ---------------------------------------- */
 
 static struct
 {
   int graphic;
-  struct Rect *pos;
+  struct XY *pos;
   int gadget_id;
   char *infotext;
 } gamebutton_info[NUM_GAME_BUTTONS] =
@@ -14718,24 +14879,36 @@ static struct
     GAME_CTRL_ID_PLAY,                 "play game"
   },
   {
-    IMG_GAME_BUTTON_GFX_SOUND_MUSIC,   &game.button.sound_music,
-    SOUND_CTRL_ID_MUSIC,               "background music on/off"
+    IMG_GAME_BUTTON_GFX_UNDO,          &game.button.undo,
+    GAME_CTRL_ID_UNDO,                 "undo step"
   },
   {
-    IMG_GAME_BUTTON_GFX_SOUND_LOOPS,   &game.button.sound_loops,
-    SOUND_CTRL_ID_LOOPS,               "sound loops on/off"
-  },
-  {
-    IMG_GAME_BUTTON_GFX_SOUND_SIMPLE,  &game.button.sound_simple,
-    SOUND_CTRL_ID_SIMPLE,              "normal sounds on/off"
+    IMG_GAME_BUTTON_GFX_REDO,          &game.button.redo,
+    GAME_CTRL_ID_REDO,                 "redo step"
   },
   {
     IMG_GAME_BUTTON_GFX_SAVE,          &game.button.save,
     GAME_CTRL_ID_SAVE,                 "save game"
   },
+  {
+    IMG_GAME_BUTTON_GFX_PAUSE2,                &game.button.pause2,
+    GAME_CTRL_ID_PAUSE2,               "pause game"
+  },
   {
     IMG_GAME_BUTTON_GFX_LOAD,          &game.button.load,
     GAME_CTRL_ID_LOAD,                 "load game"
+  },
+  {
+    IMG_GAME_BUTTON_GFX_SOUND_MUSIC,   &game.button.sound_music,
+    SOUND_CTRL_ID_MUSIC,               "background music on/off"
+  },
+  {
+    IMG_GAME_BUTTON_GFX_SOUND_LOOPS,   &game.button.sound_loops,
+    SOUND_CTRL_ID_LOOPS,               "sound loops on/off"
+  },
+  {
+    IMG_GAME_BUTTON_GFX_SOUND_SIMPLE,  &game.button.sound_simple,
+    SOUND_CTRL_ID_SIMPLE,              "normal sounds on/off"
   }
 };
 
@@ -14746,7 +14919,7 @@ void CreateGameButtons()
   for (i = 0; i < NUM_GAME_BUTTONS; i++)
   {
     struct GraphicInfo *gfx = &graphic_info[gamebutton_info[i].graphic];
-    struct Rect *pos = gamebutton_info[i].pos;
+    struct XY *pos = gamebutton_info[i].pos;
     struct GadgetInfo *gi;
     int button_type;
     boolean checked;
@@ -14771,7 +14944,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)
@@ -14780,6 +14952,13 @@ void CreateGameButtons()
       checked = FALSE;
       event_mask = GD_EVENT_RELEASED;
     }
+    else if (id == GAME_CTRL_ID_UNDO ||
+            id == GAME_CTRL_ID_REDO)
+    {
+      button_type = GD_TYPE_NORMAL_BUTTON;
+      checked = FALSE;
+      event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
+    }
     else
     {
       button_type = GD_TYPE_CHECK_BUTTON;
@@ -14823,12 +15002,69 @@ void FreeGameButtons()
     FreeGadget(game_gadget[i]);
 }
 
+static void MapGameButtonsAtSamePosition(int id)
+{
+  int i;
+
+  for (i = 0; i < NUM_GAME_BUTTONS; i++)
+    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]);
+}
+
+static void UnmapGameButtonsAtSamePosition(int id)
+{
+  int i;
+
+  for (i = 0; i < NUM_GAME_BUTTONS; i++)
+    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]);
+}
+
+void MapUndoRedoButtons()
+{
+  UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
+  UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
+
+  MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
+  MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
+}
+
+void UnmapUndoRedoButtons()
+{
+  UnmapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
+  UnmapGadget(game_gadget[GAME_CTRL_ID_REDO]);
+
+  MapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
+  MapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
+}
+
 void MapGameButtons()
 {
   int i;
 
   for (i = 0; i < NUM_GAME_BUTTONS; i++)
-    MapGadget(game_gadget[i]);
+    if (i != GAME_CTRL_ID_UNDO &&
+       i != GAME_CTRL_ID_REDO)
+      MapGadget(game_gadget[i]);
+
+  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);
+  }
+
+  RedrawGameButtons();
 }
 
 void UnmapGameButtons()
@@ -14850,8 +15086,47 @@ void RedrawGameButtons()
   redraw_mask &= ~REDRAW_ALL;
 }
 
-static void HandleGameButtonsExt(int id)
+void GameUndoRedoExt()
+{
+  ClearPlayerAction();
+
+  tape.pausing = TRUE;
+
+  RedrawPlayfield();
+  UpdateAndDisplayGameControlValues();
+
+  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);
+
+  BackToFront();
+}
+
+void GameUndo(int steps)
 {
+  if (!CheckEngineSnapshotList())
+    return;
+
+  LoadEngineSnapshot_Undo(steps);
+
+  GameUndoRedoExt();
+}
+
+void GameRedo(int steps)
+{
+  if (!CheckEngineSnapshotList())
+    return;
+
+  LoadEngineSnapshot_Redo(steps);
+
+  GameUndoRedoExt();
+}
+
+static void HandleGameButtonsExt(int id, int button)
+{
+  int steps = BUTTON_STEPSIZE(button);
   boolean handle_game_buttons =
     (game_status == GAME_MODE_PLAYING ||
      (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
@@ -14873,6 +15148,7 @@ static void HandleGameButtonsExt(int id)
       break;
 
     case GAME_CTRL_ID_PAUSE:
+    case GAME_CTRL_ID_PAUSE2:
       if (options.network && game_status == GAME_MODE_PLAYING)
       {
 #if defined(NETWORK_AVALIABLE)
@@ -14898,13 +15174,26 @@ static void HandleGameButtonsExt(int id)
          SendToServer_ContinuePlaying();
        else
 #endif
-       {
-         tape.pausing = FALSE;
-         DrawVideoDisplay(VIDEO_STATE_PAUSE_OFF, 0);
-       }
+         TapeTogglePause(TAPE_TOGGLE_MANUAL);
       }
       break;
 
+    case GAME_CTRL_ID_UNDO:
+      GameUndo(steps);
+      break;
+
+    case GAME_CTRL_ID_REDO:
+      GameRedo(steps);
+      break;
+
+    case GAME_CTRL_ID_SAVE:
+      TapeQuickSave();
+      break;
+
+    case GAME_CTRL_ID_LOAD:
+      TapeQuickLoad();
+      break;
+
     case SOUND_CTRL_ID_MUSIC:
       if (setup.sound_music)
       { 
@@ -14944,14 +15233,6 @@ static void HandleGameButtonsExt(int id)
       }
       break;
 
-    case GAME_CTRL_ID_SAVE:
-      TapeQuickSave();
-      break;
-
-    case GAME_CTRL_ID_LOAD:
-      TapeQuickLoad();
-      break;
-
     default:
       break;
   }
@@ -14959,7 +15240,7 @@ static void HandleGameButtonsExt(int id)
 
 static void HandleGameButtons(struct GadgetInfo *gi)
 {
-  HandleGameButtonsExt(gi->custom_id);
+  HandleGameButtonsExt(gi->custom_id, gi->event.button);
 }
 
 void HandleSoundButtonKeys(Key key)