fixed saving game engine snapshots (save after game actions are finished)
[rocksndiamonds.git] / src / tools.c
index e2ce4712db41208c121239ecaf39204270c6d0c2..e534cc29c3e79c465cd50d0ee79420ed0bd5bc17 100644 (file)
 #include "init.h"
 #include "game.h"
 #include "events.h"
-#include "cartoons.h"
+#include "anim.h"
 #include "network.h"
 #include "tape.h"
 #include "screens.h"
 
 
 /* select level set with EMC X11 graphics before activating EM GFX debugging */
-#define DEBUG_EM_GFX   0
+#define DEBUG_EM_GFX           FALSE
+#define DEBUG_FRAME_TIME       FALSE
 
 /* tool button identifiers */
 #define TOOL_CTRL_ID_YES       0
@@ -65,83 +66,83 @@ static struct DoorPartControlInfo door_part_controls[] =
 {
   {
     DOOR_1,
-    IMG_DOOR_1_GFX_PART_1,
+    IMG_GFX_DOOR_1_PART_1,
     &door_1.part_1
   },
   {
     DOOR_1,
-    IMG_DOOR_1_GFX_PART_2,
+    IMG_GFX_DOOR_1_PART_2,
     &door_1.part_2
   },
   {
     DOOR_1,
-    IMG_DOOR_1_GFX_PART_3,
+    IMG_GFX_DOOR_1_PART_3,
     &door_1.part_3
   },
   {
     DOOR_1,
-    IMG_DOOR_1_GFX_PART_4,
+    IMG_GFX_DOOR_1_PART_4,
     &door_1.part_4
   },
   {
     DOOR_1,
-    IMG_DOOR_1_GFX_PART_5,
+    IMG_GFX_DOOR_1_PART_5,
     &door_1.part_5
   },
   {
     DOOR_1,
-    IMG_DOOR_1_GFX_PART_6,
+    IMG_GFX_DOOR_1_PART_6,
     &door_1.part_6
   },
   {
     DOOR_1,
-    IMG_DOOR_1_GFX_PART_7,
+    IMG_GFX_DOOR_1_PART_7,
     &door_1.part_7
   },
   {
     DOOR_1,
-    IMG_DOOR_1_GFX_PART_8,
+    IMG_GFX_DOOR_1_PART_8,
     &door_1.part_8
   },
 
   {
     DOOR_2,
-    IMG_DOOR_2_GFX_PART_1,
+    IMG_GFX_DOOR_2_PART_1,
     &door_2.part_1
   },
   {
     DOOR_2,
-    IMG_DOOR_2_GFX_PART_2,
+    IMG_GFX_DOOR_2_PART_2,
     &door_2.part_2
   },
   {
     DOOR_2,
-    IMG_DOOR_2_GFX_PART_3,
+    IMG_GFX_DOOR_2_PART_3,
     &door_2.part_3
   },
   {
     DOOR_2,
-    IMG_DOOR_2_GFX_PART_4,
+    IMG_GFX_DOOR_2_PART_4,
     &door_2.part_4
   },
   {
     DOOR_2,
-    IMG_DOOR_2_GFX_PART_5,
+    IMG_GFX_DOOR_2_PART_5,
     &door_2.part_5
   },
   {
     DOOR_2,
-    IMG_DOOR_2_GFX_PART_6,
+    IMG_GFX_DOOR_2_PART_6,
     &door_2.part_6
   },
   {
     DOOR_2,
-    IMG_DOOR_2_GFX_PART_7,
+    IMG_GFX_DOOR_2_PART_7,
     &door_2.part_7
   },
   {
     DOOR_2,
-    IMG_DOOR_2_GFX_PART_8,
+    IMG_GFX_DOOR_2_PART_8,
     &door_2.part_8
   },
 
@@ -227,13 +228,13 @@ void DumpTile(int x, int y)
   printf("  CustomValue: %d\n", CustomValue[x][y]);
   printf("  GfxElement:  %d\n", GfxElement[x][y]);
   printf("  GfxAction:   %d\n", GfxAction[x][y]);
-  printf("  GfxFrame:    %d\n", GfxFrame[x][y]);
+  printf("  GfxFrame:    %d [%d]\n", GfxFrame[x][y], FrameCounter);
   printf("\n");
 }
 
 void SetDrawtoField(int mode)
 {
-  if (mode == DRAW_BUFFERED && setup.soft_scrolling)
+  if (mode == DRAW_TO_FIELDBUFFER)
   {
     FX = 2 * TILEX_VAR;
     FY = 2 * TILEY_VAR;
@@ -241,12 +242,10 @@ void SetDrawtoField(int mode)
     BY1 = -2;
     BX2 = SCR_FIELDX + 1;
     BY2 = SCR_FIELDY + 1;
-    redraw_x1 = 2;
-    redraw_y1 = 2;
 
     drawto_field = fieldbuffer;
   }
-  else /* DRAW_BACKBUFFER */
+  else /* DRAW_TO_BACKBUFFER */
   {
     FX = SX;
     FY = SY;
@@ -254,8 +253,6 @@ void SetDrawtoField(int mode)
     BY1 = 0;
     BX2 = SCR_FIELDX - 1;
     BY2 = SCR_FIELDY - 1;
-    redraw_x1 = 0;
-    redraw_y1 = 0;
 
     drawto_field = backbuffer;
   }
@@ -288,79 +285,131 @@ void RedrawPlayfield()
             gfx.sx, gfx.sy);
 }
 
-void DrawMaskedBorder_Rect(int x, int y, int width, int height)
+static void DrawMaskedBorderExt_Rect(int x, int y, int width, int height,
+                                    int draw_target)
 {
-  Bitmap *bitmap = graphic_info[IMG_GLOBAL_BORDER].bitmap;
+  Bitmap *src_bitmap = getGlobalBorderBitmapFromStatus(global.border_status);
+  Bitmap *dst_bitmap = gfx.masked_border_bitmap_ptr;
 
-  BlitBitmapMasked(bitmap, backbuffer, x, y, width, height, x, y);
+  if (x == -1 && y == -1)
+    return;
+
+  if (draw_target == DRAW_TO_SCREEN)
+    BlitToScreenMasked(src_bitmap, x, y, width, height, x, y);
+  else
+    BlitBitmapMasked(src_bitmap, dst_bitmap, x, y, width, height, x, y);
 }
 
-void DrawMaskedBorder_FIELD()
+static void DrawMaskedBorderExt_FIELD(int draw_target)
 {
-  if (global.border_status >= GAME_MODE_TITLE &&
+  if (global.border_status >= GAME_MODE_MAIN &&
       global.border_status <= GAME_MODE_PLAYING &&
       border.draw_masked[global.border_status])
-    DrawMaskedBorder_Rect(REAL_SX, REAL_SY, FULL_SXSIZE, FULL_SYSIZE);
+    DrawMaskedBorderExt_Rect(REAL_SX, REAL_SY, FULL_SXSIZE, FULL_SYSIZE,
+                            draw_target);
 }
 
-void DrawMaskedBorder_DOOR_1()
+static void DrawMaskedBorderExt_DOOR_1(int draw_target)
 {
+  // when drawing to backbuffer, never draw border over open doors
+  if (draw_target == DRAW_TO_BACKBUFFER &&
+      (GetDoorState() & DOOR_OPEN_1))
+    return;
+
   if (border.draw_masked[GFX_SPECIAL_ARG_DOOR] &&
       (global.border_status != GAME_MODE_EDITOR ||
        border.draw_masked[GFX_SPECIAL_ARG_EDITOR]))
-    DrawMaskedBorder_Rect(DX, DY, DXSIZE, DYSIZE);
+    DrawMaskedBorderExt_Rect(DX, DY, DXSIZE, DYSIZE, draw_target);
 }
 
-void DrawMaskedBorder_DOOR_2()
+static void DrawMaskedBorderExt_DOOR_2(int draw_target)
 {
+  // when drawing to backbuffer, never draw border over open doors
+  if (draw_target == DRAW_TO_BACKBUFFER &&
+      (GetDoorState() & DOOR_OPEN_2))
+    return;
+
   if (border.draw_masked[GFX_SPECIAL_ARG_DOOR] &&
       global.border_status != GAME_MODE_EDITOR)
-    DrawMaskedBorder_Rect(VX, VY, VXSIZE, VYSIZE);
+    DrawMaskedBorderExt_Rect(VX, VY, VXSIZE, VYSIZE, draw_target);
 }
 
-void DrawMaskedBorder_DOOR_3()
+static void DrawMaskedBorderExt_DOOR_3(int draw_target)
 {
   /* currently not available */
 }
 
-void DrawMaskedBorder_ALL()
+static void DrawMaskedBorderExt_ALL(int draw_target)
 {
-  DrawMaskedBorder_FIELD();
-  DrawMaskedBorder_DOOR_1();
-  DrawMaskedBorder_DOOR_2();
-  DrawMaskedBorder_DOOR_3();
+  DrawMaskedBorderExt_FIELD(draw_target);
+  DrawMaskedBorderExt_DOOR_1(draw_target);
+  DrawMaskedBorderExt_DOOR_2(draw_target);
+  DrawMaskedBorderExt_DOOR_3(draw_target);
 }
 
-void DrawMaskedBorder(int redraw_mask)
+static void DrawMaskedBorderExt(int redraw_mask, int draw_target)
 {
   /* never draw masked screen borders on borderless screens */
-  if (effectiveGameStatus() == GAME_MODE_LOADING ||
-      effectiveGameStatus() == GAME_MODE_TITLE)
-    return;
-
-  /* never draw masked screen borders when displaying request outside door */
-  if (effectiveGameStatus() == GAME_MODE_PSEUDO_DOOR &&
-      global.use_envelope_request)
+  if (global.border_status == GAME_MODE_LOADING ||
+      global.border_status == GAME_MODE_TITLE)
     return;
 
   if (redraw_mask & REDRAW_ALL)
-    DrawMaskedBorder_ALL();
+    DrawMaskedBorderExt_ALL(draw_target);
   else
   {
     if (redraw_mask & REDRAW_FIELD)
-      DrawMaskedBorder_FIELD();
+      DrawMaskedBorderExt_FIELD(draw_target);
     if (redraw_mask & REDRAW_DOOR_1)
-      DrawMaskedBorder_DOOR_1();
+      DrawMaskedBorderExt_DOOR_1(draw_target);
     if (redraw_mask & REDRAW_DOOR_2)
-      DrawMaskedBorder_DOOR_2();
+      DrawMaskedBorderExt_DOOR_2(draw_target);
     if (redraw_mask & REDRAW_DOOR_3)
-      DrawMaskedBorder_DOOR_3();
+      DrawMaskedBorderExt_DOOR_3(draw_target);
+  }
+}
+
+void DrawMaskedBorder_FIELD()
+{
+  DrawMaskedBorderExt_FIELD(DRAW_TO_BACKBUFFER);
+}
+
+void DrawMaskedBorder(int redraw_mask)
+{
+  DrawMaskedBorderExt(redraw_mask, DRAW_TO_BACKBUFFER);
+}
+
+void DrawMaskedBorderToTarget(int draw_target)
+{
+  if (draw_target == DRAW_TO_BACKBUFFER ||
+      draw_target == DRAW_TO_SCREEN)
+  {
+    DrawMaskedBorderExt(REDRAW_ALL, draw_target);
+  }
+  else
+  {
+    int last_border_status = global.border_status;
+
+    if (draw_target == DRAW_TO_FADE_SOURCE)
+    {
+      global.border_status = gfx.fade_border_source_status;
+      gfx.masked_border_bitmap_ptr = gfx.fade_bitmap_source;
+    }
+    else if (draw_target == DRAW_TO_FADE_TARGET)
+    {
+      global.border_status = gfx.fade_border_target_status;
+      gfx.masked_border_bitmap_ptr = gfx.fade_bitmap_target;
+    }
+
+    DrawMaskedBorderExt(REDRAW_ALL, draw_target);
+
+    global.border_status = last_border_status;
+    gfx.masked_border_bitmap_ptr = backbuffer;
   }
 }
 
-static void BlitScreenToBitmap_RND(Bitmap *target_bitmap)
+void BlitScreenToBitmap_RND(Bitmap *target_bitmap)
 {
-  DrawBuffer *buffer = (drawto_field == window ? backbuffer : drawto_field);
   int fx = FX, fy = FY;
   int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
   int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
@@ -414,23 +463,7 @@ static void BlitScreenToBitmap_RND(Bitmap *target_bitmap)
       fy = 2 * TILEY_VAR - (EVEN(lev_fieldy) ? TILEY_VAR / 2 : 0);
   }
 
-  if (border.draw_masked[GAME_MODE_PLAYING])
-  {
-    if (buffer != backbuffer)
-    {
-      /* copy playfield buffer to backbuffer to add masked border */
-      BlitBitmap(buffer, backbuffer, fx, fy, SXSIZE, SYSIZE, SX, SY);
-      DrawMaskedBorder(REDRAW_FIELD);
-    }
-
-    BlitBitmap(backbuffer, target_bitmap,
-              REAL_SX, REAL_SY, FULL_SXSIZE, FULL_SYSIZE,
-              REAL_SX, REAL_SY);
-  }
-  else
-  {
-    BlitBitmap(buffer, target_bitmap, fx, fy, SXSIZE, SYSIZE, SX, SY);
-  }
+  BlitBitmap(drawto_field, target_bitmap, fx, fy, SXSIZE, SYSIZE, SX, SY);
 }
 
 void BlitScreenToBitmap(Bitmap *target_bitmap)
@@ -441,245 +474,178 @@ void BlitScreenToBitmap(Bitmap *target_bitmap)
     BlitScreenToBitmap_SP(target_bitmap);
   else if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
     BlitScreenToBitmap_RND(target_bitmap);
+
+  redraw_mask |= REDRAW_FIELD;
 }
 
-void BackToFront()
+void DrawFramesPerSecond()
 {
-  int x, y;
-  DrawBuffer *buffer = (drawto_field == window ? backbuffer : drawto_field);
+  char text[100];
+  int font_nr = FONT_TEXT_2;
+  int font_width = getFontWidth(font_nr);
 
-  if (redraw_mask & REDRAW_TILES && redraw_tiles > REDRAWTILES_THRESHOLD)
-    redraw_mask |= REDRAW_FIELD;
+  sprintf(text, "%04.1f fps", global.frames_per_second);
 
-#if 0
-  // never redraw single tiles, always redraw the whole field
-  // (redrawing single tiles up to a certain threshold was faster on old,
-  // now legacy graphics, but slows things down on modern graphics now)
-  // UPDATE: this is now globally defined by value of REDRAWTILES_THRESHOLD
-  if (redraw_mask & REDRAW_TILES)
-    redraw_mask |= REDRAW_FIELD;
-#endif
+  DrawTextExt(backbuffer, WIN_XSIZE - font_width * strlen(text), 0, text,
+             font_nr, BLIT_OPAQUE);
+}
 
-#if 0
-  /* !!! TEST ONLY !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */
-  /* (force full redraw) */
-  if (game_status == GAME_MODE_PLAYING)
-    redraw_mask |= REDRAW_FIELD;
-#endif
+#if DEBUG_FRAME_TIME
+static void PrintFrameTimeDebugging()
+{
+  static unsigned int last_counter = 0;
+  unsigned int counter = Counter();
+  int diff_1 = counter - last_counter;
+  int diff_2 = diff_1 - GAME_FRAME_DELAY;
+  int diff_2_max = 20;
+  int diff_2_cut = MIN(ABS(diff_2), diff_2_max);
+  char diff_bar[2 * diff_2_max + 5];
+  int pos = 0;
+  int i;
 
-  if (redraw_mask & REDRAW_FIELD)
-    redraw_mask &= ~REDRAW_TILES;
+  diff_bar[pos++] = (diff_2 < -diff_2_max ? '<' : ' ');
 
-  if (redraw_mask == REDRAW_NONE)
-    return;
+  for (i = 0; i < diff_2_max; i++)
+    diff_bar[pos++] = (diff_2 >= 0 ? ' ' :
+                      i >= diff_2_max - diff_2_cut ? '-' : ' ');
 
-#if 0
-  printf("::: ");
-  if (redraw_mask & REDRAW_ALL)
-    printf("[REDRAW_ALL]");
-  if (redraw_mask & REDRAW_FIELD)
-    printf("[REDRAW_FIELD]");
-  if (redraw_mask & REDRAW_TILES)
-    printf("[REDRAW_TILES]");
-  if (redraw_mask & REDRAW_DOOR_1)
-    printf("[REDRAW_DOOR_1]");
-  if (redraw_mask & REDRAW_DOOR_2)
-    printf("[REDRAW_DOOR_2]");
-  if (redraw_mask & REDRAW_FROM_BACKBUFFER)
-    printf("[REDRAW_FROM_BACKBUFFER]");
-  printf(" [%d]\n", FrameCounter);
-#endif
+  diff_bar[pos++] = '|';
 
-  if (redraw_mask & REDRAW_TILES &&
-      game_status == GAME_MODE_PLAYING &&
-      border.draw_masked[GAME_MODE_PLAYING])
-    redraw_mask |= REDRAW_FIELD;
+  for (i = 0; i < diff_2_max; i++)
+    diff_bar[pos++] = (diff_2 <= 0 ? ' ' : i < diff_2_cut ? '+' : ' ');
 
-  if (global.fps_slowdown && game_status == GAME_MODE_PLAYING)
-  {
-    static boolean last_frame_skipped = FALSE;
-    boolean skip_even_when_not_scrolling = TRUE;
-    boolean just_scrolling = (ScreenMovDir != 0);
-    boolean verbose = FALSE;
+  diff_bar[pos++] = (diff_2 > diff_2_max ? '>' : ' ');
 
-    if (global.fps_slowdown_factor > 1 &&
-       (FrameCounter % global.fps_slowdown_factor) &&
-       (just_scrolling || skip_even_when_not_scrolling))
-    {
-      redraw_mask &= ~REDRAW_MAIN;
+  diff_bar[pos++] = '\0';
 
-      last_frame_skipped = TRUE;
+  Error(ERR_INFO, "%06d [%02d] [%c%02d] %s",
+       counter,
+       diff_1,
+       (diff_2 < 0 ? '-' : diff_2 > 0 ? '+' : ' '), ABS(diff_2),
+       diff_bar);
 
-      if (verbose)
-       printf("FRAME SKIPPED\n");
-    }
-    else
-    {
-      if (last_frame_skipped)
-       redraw_mask |= REDRAW_FIELD;
+  last_counter = counter;
+}
+#endif
 
-      last_frame_skipped = FALSE;
+static int unifiedRedrawMask(int mask)
+{
+  if (mask & REDRAW_ALL)
+    return REDRAW_ALL;
 
-      if (verbose)
-       printf("frame not skipped\n");
-    }
-  }
+  if (mask & REDRAW_FIELD && mask & REDRAW_DOORS)
+    return REDRAW_ALL;
 
-  /* synchronize X11 graphics at this point; if we would synchronize the
-     display immediately after the buffer switching (after the XFlush),
-     this could mean that we have to wait for the graphics to complete,
-     although we could go on doing calculations for the next frame */
+  return mask;
+}
 
-  /* SyncDisplay(); */
+static boolean equalRedrawMasks(int mask_1, int mask_2)
+{
+  return unifiedRedrawMask(mask_1) == unifiedRedrawMask(mask_2);
+}
 
-  /* never draw masked border to backbuffer when using playfield buffer */
-  if (game_status != GAME_MODE_PLAYING ||
-      redraw_mask & REDRAW_FROM_BACKBUFFER ||
-      buffer == backbuffer)
-    DrawMaskedBorder(redraw_mask);
-  else
-    DrawMaskedBorder(redraw_mask & REDRAW_DOORS);
+void BackToFront()
+{
+  static int last_redraw_mask = REDRAW_NONE;
 
-  if (redraw_mask & REDRAW_ALL)
-  {
-    BlitBitmap(backbuffer, window, 0, 0, WIN_XSIZE, WIN_YSIZE, 0, 0);
+  // force screen redraw in every frame to continue drawing global animations
+  // (but always use the last redraw mask to prevent unwanted side effects)
+  if (redraw_mask == REDRAW_NONE)
+    redraw_mask = last_redraw_mask;
 
-    redraw_mask = REDRAW_NONE;
-  }
+  last_redraw_mask = redraw_mask;
 
-  if (redraw_mask & REDRAW_FIELD)
-  {
-    if (game_status != GAME_MODE_PLAYING ||
-       redraw_mask & REDRAW_FROM_BACKBUFFER)
-    {
-      BlitBitmap(backbuffer, window,
-                REAL_SX, REAL_SY, FULL_SXSIZE, FULL_SYSIZE, REAL_SX, REAL_SY);
-    }
-    else
-    {
-      BlitScreenToBitmap_RND(window);
-    }
+#if 1
+  // masked border now drawn immediately when blitting backbuffer to window
+#else
+  // draw masked border to all viewports, if defined
+  DrawMaskedBorder(redraw_mask);
+#endif
 
-    redraw_mask &= ~REDRAW_MAIN;
-  }
+  // draw frames per second (only if debug mode is enabled)
+  if (redraw_mask & REDRAW_FPS)
+    DrawFramesPerSecond();
 
-  if (redraw_mask & REDRAW_DOORS)
-  {
-    if (redraw_mask & REDRAW_DOOR_1)
-      BlitBitmap(backbuffer, window, DX, DY, DXSIZE, DYSIZE, DX, DY);
+  // remove playfield redraw before potentially merging with doors redraw
+  if (DrawingDeactivated(REAL_SX, REAL_SY, FULL_SXSIZE, FULL_SYSIZE))
+    redraw_mask &= ~REDRAW_FIELD;
 
-    if (redraw_mask & REDRAW_DOOR_2)
-      BlitBitmap(backbuffer, window, VX, VY, VXSIZE, VYSIZE, VX, VY);
+  // redraw complete window if both playfield and (some) doors need redraw
+  if (redraw_mask & REDRAW_FIELD && redraw_mask & REDRAW_DOORS)
+    redraw_mask = REDRAW_ALL;
 
-    if (redraw_mask & REDRAW_DOOR_3)
-      BlitBitmap(backbuffer, window, EX, EY, EXSIZE, EYSIZE, EX, EY);
+  /* although redrawing the whole window would be fine for normal gameplay,
+     being able to only redraw the playfield is required for deactivating
+     certain drawing areas (mainly playfield) to work, which is needed for
+     warp-forward to be fast enough (by skipping redraw of most frames) */
 
-    redraw_mask &= ~REDRAW_DOORS;
+  if (redraw_mask & REDRAW_ALL)
+  {
+    BlitBitmap(backbuffer, window, 0, 0, WIN_XSIZE, WIN_YSIZE, 0, 0);
   }
-
-  if (redraw_mask & REDRAW_MICROLEVEL)
+  else if (redraw_mask & REDRAW_FIELD)
   {
-    BlitBitmap(backbuffer, window, SX, SY + 10 * TILEY, SXSIZE, 7 * TILEY,
-              SX, SY + 10 * TILEY);
-
-    redraw_mask &= ~REDRAW_MICROLEVEL;
+    BlitBitmap(backbuffer, window,
+              REAL_SX, REAL_SY, FULL_SXSIZE, FULL_SYSIZE, REAL_SX, REAL_SY);
   }
-
-  if (redraw_mask & REDRAW_TILES)
+  else if (redraw_mask & REDRAW_DOORS)
   {
-    int sx = SX;
-    int sy = SY;
-
-    int dx = 0, dy = 0;
-    int dx_var = dx * TILESIZE_VAR / TILESIZE;
-    int dy_var = dy * TILESIZE_VAR / TILESIZE;
-    int ffx, ffy;
-    int fx = FX, fy = FY;
-
-    int scr_fieldx = SCR_FIELDX + (EVEN(SCR_FIELDX) ? 2 : 0);
-    int scr_fieldy = SCR_FIELDY + (EVEN(SCR_FIELDY) ? 2 : 0);
-
-    InitGfxClipRegion(TRUE, SX, SY, SXSIZE, SYSIZE);
-
-    ffx = (scroll_x - SBX_Left)  * TILEX_VAR + dx_var;
-    ffy = (scroll_y - SBY_Upper) * TILEY_VAR + dy_var;
+    // merge door areas to prevent calling screen redraw more than once
+    int x1 = WIN_XSIZE;
+    int y1 = WIN_YSIZE;
+    int x2 = 0;
+    int y2 = 0;
 
-    if (EVEN(SCR_FIELDX))
+    if (redraw_mask & REDRAW_DOOR_1)
     {
-      if (ffx < SBX_Right * TILEX_VAR + TILEX_VAR / 2 + TILEX_VAR)
-      {
-       fx += dx_var - MIN(ffx, TILEX_VAR / 2) + TILEX_VAR;
-
-       if (fx % TILEX_VAR)
-         sx -= TILEX_VAR / 2;
-       else
-         sx -= TILEX_VAR;
-      }
-      else
-      {
-       fx += (dx_var > 0 ? TILEX_VAR : 0);
-      }
+      x1 = MIN(x1, DX);
+      y1 = MIN(y1, DY);
+      x2 = MAX(x2, DX + DXSIZE);
+      y2 = MAX(y2, DY + DYSIZE);
     }
 
-    if (EVEN(SCR_FIELDY))
+    if (redraw_mask & REDRAW_DOOR_2)
     {
-      if (ffy < SBY_Lower * TILEY_VAR + TILEY_VAR / 2 + TILEY_VAR)
-      {
-       fy += dy_var - MIN(ffy, TILEY_VAR / 2) + TILEY_VAR;
-
-       if (fy % TILEY_VAR)
-         sy -= TILEY_VAR / 2;
-       else
-         sy -= TILEY_VAR;
-      }
-      else
-      {
-       fy += (dy_var > 0 ? TILEY_VAR : 0);
-      }
+      x1 = MIN(x1, VX);
+      y1 = MIN(y1, VY);
+      x2 = MAX(x2, VX + VXSIZE);
+      y2 = MAX(y2, VY + VYSIZE);
     }
 
-    for (x = 0; x < scr_fieldx; x++)
-      for (y = 0 ; y < scr_fieldy; y++)
-       if (redraw[redraw_x1 + x][redraw_y1 + y])
-         BlitBitmap(buffer, window,
-                    FX + x * TILEX_VAR, FY + y * TILEY_VAR,
-                    TILEX_VAR, TILEY_VAR,
-                    sx + x * TILEX_VAR, sy + y * TILEY_VAR);
+    if (redraw_mask & REDRAW_DOOR_3)
+    {
+      x1 = MIN(x1, EX);
+      y1 = MIN(y1, EY);
+      x2 = MAX(x2, EX + EXSIZE);
+      y2 = MAX(y2, EY + EYSIZE);
+    }
 
-    InitGfxClipRegion(FALSE, -1, -1, -1, -1);
+    BlitBitmap(backbuffer, window, x1, y1, x2 - x1, y2 - y1, x1, y1);
   }
 
-  if (redraw_mask & REDRAW_FPS)                /* display frames per second */
-  {
-    char text[100];
-    char info1[100];
+  redraw_mask = REDRAW_NONE;
 
-    sprintf(info1, " (only every %d. frame)", global.fps_slowdown_factor);
-    if (!global.fps_slowdown)
-      info1[0] = '\0';
+#if DEBUG_FRAME_TIME
+  PrintFrameTimeDebugging();
+#endif
+}
 
-    sprintf(text, "%04.1f fps%s", global.frames_per_second, info1);
+void BackToFront_WithFrameDelay(unsigned int frame_delay_value)
+{
+  unsigned int frame_delay_value_old = GetVideoFrameDelay();
 
-    DrawTextExt(window, SX + SXSIZE + SX, 0, text, FONT_TEXT_2, BLIT_OPAQUE);
-  }
+  SetVideoFrameDelay(frame_delay_value);
 
-  for (x = 0; x < MAX_BUF_XSIZE; x++)
-    for (y = 0; y < MAX_BUF_YSIZE; y++)
-      redraw[x][y] = 0;
-  redraw_tiles = 0;
-  redraw_mask = REDRAW_NONE;
-}
+  BackToFront();
 
-static void FadeCrossSaveBackbuffer()
-{
-  BlitBitmap(backbuffer, bitmap_db_cross, 0, 0, WIN_XSIZE, WIN_YSIZE, 0, 0);
+  SetVideoFrameDelay(frame_delay_value_old);
 }
 
+static int fade_type_skip = FADE_TYPE_NONE;
+
 static void FadeExt(int fade_mask, int fade_mode, int fade_type)
 {
-  static int fade_type_skip = FADE_TYPE_NONE;
   void (*draw_border_function)(void) = NULL;
-  Bitmap *bitmap = (fade_mode & FADE_TYPE_TRANSFORM ? bitmap_db_cross : NULL);
   int x, y, width, height;
   int fade_delay, post_delay;
 
@@ -695,11 +661,7 @@ static void FadeExt(int fade_mask, int fade_mode, int fade_type)
     }
 
     if (fading.fade_mode & FADE_TYPE_TRANSFORM)
-    {
-      FadeCrossSaveBackbuffer();
-
       return;
-    }
   }
 
   redraw_mask |= fade_mask;
@@ -730,10 +692,10 @@ static void FadeExt(int fade_mask, int fade_mode, int fade_type)
 
   if (fade_mask == REDRAW_FIELD)
   {
-    x = REAL_SX;
-    y = REAL_SY;
-    width  = FULL_SXSIZE;
-    height = FULL_SYSIZE;
+    x = FADE_SX;
+    y = FADE_SY;
+    width  = FADE_SXSIZE;
+    height = FADE_SYSIZE;
 
     if (border.draw_masked_when_fading)
       draw_border_function = DrawMaskedBorder_FIELD;   /* update when fading */
@@ -762,28 +724,94 @@ static void FadeExt(int fade_mask, int fade_mode, int fade_type)
     return;
   }
 
-  FadeRectangle(bitmap, x, y, width, height, fade_mode, fade_delay, post_delay,
+  FadeRectangle(x, y, width, height, fade_mode, fade_delay, post_delay,
                draw_border_function);
 
   redraw_mask &= ~fade_mask;
 }
 
+static void SetScreenStates_BeforeFadingIn()
+{
+  // temporarily set screen mode for animations to screen after fading in
+  global.anim_status = global.anim_status_next;
+
+  // store backbuffer with all animations that will be started after fading in
+  if (fade_type_skip != FADE_MODE_SKIP_FADE_IN)
+    PrepareFadeBitmap(DRAW_TO_FADE_TARGET);
+
+  // set screen mode for animations back to fading
+  global.anim_status = GAME_MODE_PSEUDO_FADING;
+}
+
+static void SetScreenStates_AfterFadingIn()
+{
+  // store new source screen (to use correct masked border for fading)
+  gfx.fade_border_source_status = global.border_status;
+
+  global.anim_status = global.anim_status_next;
+}
+
+static void SetScreenStates_BeforeFadingOut()
+{
+  // store new target screen (to use correct masked border for fading)
+  gfx.fade_border_target_status = game_status;
+
+  // set screen mode for animations to fading
+  global.anim_status = GAME_MODE_PSEUDO_FADING;
+
+  // store backbuffer with all animations that will be stopped for fading out
+  if (fade_type_skip != FADE_MODE_SKIP_FADE_OUT)
+    PrepareFadeBitmap(DRAW_TO_FADE_SOURCE);
+}
+
+static void SetScreenStates_AfterFadingOut()
+{
+  global.border_status = game_status;
+}
+
 void FadeIn(int fade_mask)
 {
+  SetScreenStates_BeforeFadingIn();
+
+#if 1
+  DrawMaskedBorder(REDRAW_ALL);
+#endif
+
   if (fading.fade_mode & FADE_TYPE_TRANSFORM)
     FadeExt(fade_mask, fading.fade_mode, FADE_TYPE_FADE_IN);
   else
     FadeExt(fade_mask, FADE_MODE_FADE_IN, FADE_TYPE_FADE_IN);
+
+  FADE_SX = REAL_SX;
+  FADE_SY = REAL_SY;
+  FADE_SXSIZE = FULL_SXSIZE;
+  FADE_SYSIZE = FULL_SYSIZE;
+
+  SetScreenStates_AfterFadingIn();
+
+  // force update of global animation status in case of rapid screen changes
+  redraw_mask = REDRAW_ALL;
+  BackToFront();
 }
 
 void FadeOut(int fade_mask)
 {
+  // update screen if areas covered by "fade_mask" and "redraw_mask" differ
+  if (!equalRedrawMasks(fade_mask, redraw_mask))
+    BackToFront();
+
+  SetScreenStates_BeforeFadingOut();
+
+#if 0
+  DrawMaskedBorder(REDRAW_ALL);
+#endif
+
   if (fading.fade_mode & FADE_TYPE_TRANSFORM)
     FadeExt(fade_mask, fading.fade_mode, FADE_TYPE_FADE_OUT);
   else
     FadeExt(fade_mask, FADE_MODE_FADE_OUT, FADE_TYPE_FADE_OUT);
 
-  global.border_status = game_status;
+  SetScreenStates_AfterFadingOut();
 }
 
 static void FadeSetLeaveNext(struct TitleFadingInfo fading_leave, boolean set)
@@ -819,7 +847,7 @@ void FadeSetEnterScreen()
 
 void FadeSetNextScreen()
 {
-  fading = menu.next_screen;
+  fading = menu.next_screen[game_status];
 
   // (do not overwrite fade mode set by FadeSetEnterScreen)
   // FadeSetLeaveNext(fading, TRUE);   /* (keep same fade mode) */
@@ -857,6 +885,39 @@ void FadeSkipNextFadeOut()
   FadeExt(0, FADE_MODE_SKIP_FADE_OUT, FADE_TYPE_SKIP);
 }
 
+Bitmap *getBitmapFromGraphicOrDefault(int graphic, int default_graphic)
+{
+  boolean redefined = getImageListEntryFromImageID(graphic)->redefined;
+
+  return (graphic == IMG_UNDEFINED ? NULL :
+         graphic_info[graphic].bitmap != NULL || redefined ?
+         graphic_info[graphic].bitmap :
+         graphic_info[default_graphic].bitmap);
+}
+
+Bitmap *getBackgroundBitmap(int graphic)
+{
+  return getBitmapFromGraphicOrDefault(graphic, IMG_BACKGROUND);
+}
+
+Bitmap *getGlobalBorderBitmap(int graphic)
+{
+  return getBitmapFromGraphicOrDefault(graphic, IMG_GLOBAL_BORDER);
+}
+
+Bitmap *getGlobalBorderBitmapFromStatus(int status)
+{
+  int graphic =
+    (status == GAME_MODE_MAIN ||
+     status == GAME_MODE_PSEUDO_TYPENAME       ? IMG_GLOBAL_BORDER_MAIN :
+     status == GAME_MODE_SCORES                        ? IMG_GLOBAL_BORDER_SCORES :
+     status == GAME_MODE_EDITOR                        ? IMG_GLOBAL_BORDER_EDITOR :
+     status == GAME_MODE_PLAYING               ? IMG_GLOBAL_BORDER_PLAYING :
+     IMG_GLOBAL_BORDER);
+
+  return getGlobalBorderBitmap(graphic);
+}
+
 void SetWindowBackgroundImageIfDefined(int graphic)
 {
   if (graphic_info[graphic].bitmap)
@@ -877,26 +938,17 @@ void SetDoorBackgroundImageIfDefined(int graphic)
 
 void SetWindowBackgroundImage(int graphic)
 {
-  SetWindowBackgroundBitmap(graphic == IMG_UNDEFINED ? NULL :
-                           graphic_info[graphic].bitmap ?
-                           graphic_info[graphic].bitmap :
-                           graphic_info[IMG_BACKGROUND].bitmap);
+  SetWindowBackgroundBitmap(getBackgroundBitmap(graphic));
 }
 
 void SetMainBackgroundImage(int graphic)
 {
-  SetMainBackgroundBitmap(graphic == IMG_UNDEFINED ? NULL :
-                         graphic_info[graphic].bitmap ?
-                         graphic_info[graphic].bitmap :
-                         graphic_info[IMG_BACKGROUND].bitmap);
+  SetMainBackgroundBitmap(getBackgroundBitmap(graphic));
 }
 
 void SetDoorBackgroundImage(int graphic)
 {
-  SetDoorBackgroundBitmap(graphic == IMG_UNDEFINED ? NULL :
-                         graphic_info[graphic].bitmap ?
-                         graphic_info[graphic].bitmap :
-                         graphic_info[IMG_BACKGROUND].bitmap);
+  SetDoorBackgroundBitmap(getBackgroundBitmap(graphic));
 }
 
 void SetPanelBackground()
@@ -944,32 +996,157 @@ void DrawBackgroundForGraphic(int x, int y, int width, int height, int graphic)
   DrawBackground(x, y, width, height);
 }
 
+static int game_status_last = -1;
+static Bitmap *global_border_bitmap_last = NULL;
+static Bitmap *global_border_bitmap = NULL;
+static int real_sx_last = -1, real_sy_last = -1;
+static int full_sxsize_last = -1, full_sysize_last = -1;
+static int dx_last = -1, dy_last = -1;
+static int dxsize_last = -1, dysize_last = -1;
+static int vx_last = -1, vy_last = -1;
+static int vxsize_last = -1, vysize_last = -1;
+
+boolean CheckIfGlobalBorderHasChanged()
+{
+  // if game status has not changed, global border has not changed either
+  if (game_status == game_status_last)
+    return FALSE;
+
+  // determine and store new global border bitmap for current game status
+  global_border_bitmap = getGlobalBorderBitmapFromStatus(game_status);
+
+  return (global_border_bitmap_last != global_border_bitmap);
+}
+
+boolean CheckIfGlobalBorderRedrawIsNeeded()
+{
+  // if game status has not changed, nothing has to be redrawn
+  if (game_status == game_status_last)
+    return FALSE;
+
+  // redraw if last screen was title screen
+  if (game_status_last == GAME_MODE_TITLE)
+    return TRUE;
+
+  // redraw if global screen border has changed
+  if (CheckIfGlobalBorderHasChanged())
+    return TRUE;
+
+  // redraw if position or size of playfield area has changed
+  if (real_sx_last != REAL_SX || real_sy_last != REAL_SY ||
+      full_sxsize_last != FULL_SXSIZE || full_sysize_last != FULL_SYSIZE)
+    return TRUE;
+
+  // redraw if position or size of door area has changed
+  if (dx_last != DX || dy_last != DY ||
+      dxsize_last != DXSIZE || dysize_last != DYSIZE)
+    return TRUE;
+
+  // redraw if position or size of tape area has changed
+  if (vx_last != VX || vy_last != VY ||
+      vxsize_last != VXSIZE || vysize_last != VYSIZE)
+    return TRUE;
+
+  return FALSE;
+}
+
+void RedrawGlobalBorderFromBitmap(Bitmap *bitmap)
+{
+  if (bitmap)
+    BlitBitmap(bitmap, backbuffer, 0, 0, WIN_XSIZE, WIN_YSIZE, 0, 0);
+  else
+    ClearRectangle(backbuffer, 0, 0, WIN_XSIZE, WIN_YSIZE);
+}
+
+void RedrawGlobalBorder()
+{
+  Bitmap *bitmap = getGlobalBorderBitmapFromStatus(game_status);
+
+  RedrawGlobalBorderFromBitmap(bitmap);
+
+  redraw_mask = REDRAW_ALL;
+}
+
+static void RedrawGlobalBorderIfNeeded()
+{
+  if (game_status == game_status_last)
+    return;
+
+  // copy current draw buffer to later copy back areas that have not changed
+  if (game_status_last != GAME_MODE_TITLE)
+    BlitBitmap(backbuffer, bitmap_db_store_1, 0, 0, WIN_XSIZE, WIN_YSIZE, 0, 0);
+
+  if (CheckIfGlobalBorderRedrawIsNeeded())
+  {
+    // redraw global screen border (or clear, if defined to be empty)
+    RedrawGlobalBorderFromBitmap(global_border_bitmap);
+
+    // copy previous playfield and door areas, if they are defined on both
+    // previous and current screen and if they still have the same size
+
+    if (real_sx_last != -1 && real_sy_last != -1 &&
+       REAL_SX != -1 && REAL_SY != -1 &&
+       full_sxsize_last == FULL_SXSIZE && full_sysize_last == FULL_SYSIZE)
+      BlitBitmap(bitmap_db_store_1, backbuffer,
+                real_sx_last, real_sy_last, FULL_SXSIZE, FULL_SYSIZE,
+                REAL_SX, REAL_SY);
+
+    if (dx_last != -1 && dy_last != -1 &&
+       DX != -1 && DY != -1 &&
+       dxsize_last == DXSIZE && dysize_last == DYSIZE)
+      BlitBitmap(bitmap_db_store_1, backbuffer,
+                dx_last, dy_last, DXSIZE, DYSIZE, DX, DY);
+
+    if (vx_last != -1 && vy_last != -1 &&
+       VX != -1 && VY != -1 &&
+       vxsize_last == VXSIZE && vysize_last == VYSIZE)
+      BlitBitmap(bitmap_db_store_1, backbuffer,
+                vx_last, vy_last, VXSIZE, VYSIZE, VX, VY);
+
+    redraw_mask = REDRAW_ALL;
+  }
+
+  game_status_last = game_status;
+
+  global_border_bitmap_last = global_border_bitmap;
+
+  real_sx_last = REAL_SX;
+  real_sy_last = REAL_SY;
+  full_sxsize_last = FULL_SXSIZE;
+  full_sysize_last = FULL_SYSIZE;
+  dx_last = DX;
+  dy_last = DY;
+  dxsize_last = DXSIZE;
+  dysize_last = DYSIZE;
+  vx_last = VX;
+  vy_last = VY;
+  vxsize_last = VXSIZE;
+  vysize_last = VYSIZE;
+}
+
 void ClearField()
 {
+  RedrawGlobalBorderIfNeeded();
+
   /* !!! "drawto" might still point to playfield buffer here (see above) !!! */
   /* (when entering hall of fame after playing) */
   DrawBackground(REAL_SX, REAL_SY, FULL_SXSIZE, FULL_SYSIZE);
 
   /* !!! maybe this should be done before clearing the background !!! */
-  if (setup.soft_scrolling && game_status == GAME_MODE_PLAYING)
+  if (game_status == GAME_MODE_PLAYING)
   {
     ClearRectangle(fieldbuffer, 0, 0, FXSIZE, FYSIZE);
-    SetDrawtoField(DRAW_BUFFERED);
+    SetDrawtoField(DRAW_TO_FIELDBUFFER);
   }
   else
-    SetDrawtoField(DRAW_BACKBUFFER);
+  {
+    SetDrawtoField(DRAW_TO_BACKBUFFER);
+  }
 }
 
 void MarkTileDirty(int x, int y)
 {
-  int xx = redraw_x1 + x;
-  int yy = redraw_y1 + y;
-
-  if (!redraw[xx][yy])
-    redraw_tiles++;
-
-  redraw[xx][yy] = TRUE;
-  redraw_mask |= REDRAW_TILES;
+  redraw_mask |= REDRAW_FIELD;
 }
 
 void SetBorderElement()
@@ -1029,7 +1206,7 @@ void SetRandomAnimationValue(int x, int y)
   gfx.anim_random_frame = GfxRandom[x][y];
 }
 
-inline int getGraphicAnimationFrame(int graphic, int sync_frame)
+int getGraphicAnimationFrame(int graphic, int sync_frame)
 {
   /* animation synchronized with global frame counter, not move position */
   if (graphic_info[graphic].anim_global_sync || sync_frame < 0)
@@ -1042,48 +1219,64 @@ inline int getGraphicAnimationFrame(int graphic, int sync_frame)
                           sync_frame);
 }
 
-void getSizedGraphicSourceExt(int graphic, int frame, int tilesize,
-                             Bitmap **bitmap, int *x, int *y,
-                             boolean get_backside)
+void getGraphicSourceBitmap(int graphic, int tilesize, Bitmap **bitmap)
 {
   struct GraphicInfo *g = &graphic_info[graphic];
-  Bitmap *src_bitmap = g->bitmap;
-  int src_x = g->src_x + (get_backside ? g->offset2_x : 0);
-  int src_y = g->src_y + (get_backside ? g->offset2_y : 0);
   int tilesize_capped = MIN(MAX(1, tilesize), TILESIZE);
 
   if (tilesize == gfx.standard_tile_size)
-    src_bitmap = g->bitmaps[IMG_BITMAP_STANDARD];
+    *bitmap = g->bitmaps[IMG_BITMAP_STANDARD];
   else if (tilesize == game.tile_size)
-    src_bitmap = g->bitmaps[IMG_BITMAP_GAME];
+    *bitmap = g->bitmaps[IMG_BITMAP_GAME];
   else
-    src_bitmap = g->bitmaps[IMG_BITMAP_1x1 - log_2(tilesize_capped)];
+    *bitmap = g->bitmaps[IMG_BITMAP_1x1 - log_2(tilesize_capped)];
+}
+
+void getGraphicSourceXY(int graphic, int frame, int *x, int *y,
+                       boolean get_backside)
+{
+  struct GraphicInfo *g = &graphic_info[graphic];
+  int src_x = g->src_x + (get_backside ? g->offset2_x : 0);
+  int src_y = g->src_y + (get_backside ? g->offset2_y : 0);
 
   if (g->offset_y == 0)                /* frames are ordered horizontally */
   {
     int max_width = g->anim_frames_per_line * g->width;
     int pos = (src_y / g->height) * max_width + src_x + frame * g->offset_x;
 
-    src_x = pos % max_width;
-    src_y = src_y % g->height + pos / max_width * g->height;
+    *x = pos % max_width;
+    *y = src_y % g->height + pos / max_width * g->height;
   }
   else if (g->offset_x == 0)   /* frames are ordered vertically */
   {
     int max_height = g->anim_frames_per_line * g->height;
     int pos = (src_x / g->width) * max_height + src_y + frame * g->offset_y;
 
-    src_x = src_x % g->width + pos / max_height * g->width;
-    src_y = pos % max_height;
-  }
-  else                         /* frames are ordered diagonally */
-  {
-    src_x = src_x + frame * g->offset_x;
-    src_y = src_y + frame * g->offset_y;
-  }
+    *x = src_x % g->width + pos / max_height * g->width;
+    *y = pos % max_height;
+  }
+  else                         /* frames are ordered diagonally */
+  {
+    *x = src_x + frame * g->offset_x;
+    *y = src_y + frame * g->offset_y;
+  }
+}
+
+void getSizedGraphicSourceExt(int graphic, int frame, int tilesize,
+                             Bitmap **bitmap, int *x, int *y,
+                             boolean get_backside)
+{
+  struct GraphicInfo *g = &graphic_info[graphic];
+
+  // if no in-game graphics defined, always use standard graphic size
+  if (g->bitmaps[IMG_BITMAP_GAME] == NULL)
+    tilesize = TILESIZE;
+
+  getGraphicSourceBitmap(graphic, tilesize, bitmap);
+  getGraphicSourceXY(graphic, frame, x, y, get_backside);
 
-  *bitmap = src_bitmap;
-  *x = src_x * tilesize / TILESIZE;
-  *y = src_y * tilesize / TILESIZE;
+  *x = *x * tilesize / g->tile_size;
+  *y = *y * tilesize / g->tile_size;
 }
 
 void getFixedGraphicSourceExt(int graphic, int frame, Bitmap **bitmap,
@@ -1110,40 +1303,11 @@ void getMiniGraphicSource(int graphic, Bitmap **bitmap, int *x, int *y)
   getSizedGraphicSource(graphic, 0, MINI_TILESIZE, bitmap, x, y);
 }
 
-inline void getGraphicSourceExt(int graphic, int frame, Bitmap **bitmap,
-                               int *x, int *y, boolean get_backside)
+inline static void getGraphicSourceExt(int graphic, int frame, Bitmap **bitmap,
+                                      int *x, int *y, boolean get_backside)
 {
-  struct GraphicInfo *g = &graphic_info[graphic];
-  int src_x = g->src_x + (get_backside ? g->offset2_x : 0);
-  int src_y = g->src_y + (get_backside ? g->offset2_y : 0);
-
-  if (TILESIZE_VAR != TILESIZE)
-    return getSizedGraphicSourceExt(graphic, frame, TILESIZE_VAR, bitmap, x, y,
-                                   get_backside);
-
-  *bitmap = g->bitmap;
-
-  if (g->offset_y == 0)                /* frames are ordered horizontally */
-  {
-    int max_width = g->anim_frames_per_line * g->width;
-    int pos = (src_y / g->height) * max_width + src_x + frame * g->offset_x;
-
-    *x = pos % max_width;
-    *y = src_y % g->height + pos / max_width * g->height;
-  }
-  else if (g->offset_x == 0)   /* frames are ordered vertically */
-  {
-    int max_height = g->anim_frames_per_line * g->height;
-    int pos = (src_x / g->width) * max_height + src_y + frame * g->offset_y;
-
-    *x = src_x % g->width + pos / max_height * g->width;
-    *y = pos % max_height;
-  }
-  else                         /* frames are ordered diagonally */
-  {
-    *x = src_x + frame * g->offset_x;
-    *y = src_y + frame * g->offset_y;
-  }
+  getSizedGraphicSourceExt(graphic, frame, TILESIZE_VAR, bitmap, x, y,
+                          get_backside);
 }
 
 void getGraphicSource(int graphic, int frame, Bitmap **bitmap, int *x, int *y)
@@ -1258,7 +1422,8 @@ void DrawFixedGraphicThruMaskExt(DrawBuffer *d, int dst_x, int dst_y,
 
   getFixedGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
 
-  BlitBitmapMasked(src_bitmap, d, src_x, src_y, TILEX, TILEY, dst_x, dst_y);
+  BlitBitmapMasked(src_bitmap, d, src_x, src_y, TILEX, TILEY,
+                  dst_x, dst_y);
 }
 
 void DrawSizedGraphic(int x, int y, int graphic, int frame, int tilesize)
@@ -1318,20 +1483,20 @@ inline static void DrawGraphicShiftedNormal(int x, int y, int dx, int dy,
       width = -dx;
       dx = TILEX + dx;
     }
-    else if (x==BX1 && dx < 0) /* object leaves playfield to the left */
+    else if (x == BX1 && dx < 0) /* object leaves playfield to the left */
     {
       width += dx;
       cx = -dx;
       dx = 0;
     }
-    else if (x==BX2 && dx > 0) /* object leaves playfield to the right */
+    else if (x == BX2 && dx > 0) /* object leaves playfield to the right */
       width -= dx;
     else if (dx)               /* general horizontal movement */
       MarkTileDirty(x + SIGN(dx), y);
 
     if (y < BY1)               /* object enters playfield from the top */
     {
-      if (cut_mode==CUT_BELOW) /* object completely above top border */
+      if (cut_mode == CUT_BELOW) /* object completely above top border */
        return;
 
       y = BY1;
@@ -1345,7 +1510,7 @@ inline static void DrawGraphicShiftedNormal(int x, int y, int dx, int dy,
       height = -dy;
       dy = TILEY + dy;
     }
-    else if (y==BY1 && dy < 0) /* object leaves playfield to the top */
+    else if (y == BY1 && dy < 0) /* object leaves playfield to the top */
     {
       height += dy;
       cy = -dy;
@@ -2022,6 +2187,14 @@ void DrawLevelField(int x, int y)
   }
 }
 
+void DrawSizedElement(int x, int y, int element, int tilesize)
+{
+  int graphic;
+
+  graphic = el2edimg(element);
+  DrawSizedGraphic(x, y, graphic, 0, tilesize);
+}
+
 void DrawMiniElement(int x, int y, int element)
 {
   int graphic;
@@ -2030,6 +2203,19 @@ void DrawMiniElement(int x, int y, int element)
   DrawMiniGraphic(x, y, graphic);
 }
 
+void DrawSizedElementOrWall(int sx, int sy, int scroll_x, int scroll_y,
+                           int tilesize)
+{
+  int x = sx + scroll_x, y = sy + scroll_y;
+
+  if (x < -1 || x > lev_fieldx || y < -1 || y > lev_fieldy)
+    DrawSizedElement(sx, sy, EL_EMPTY, tilesize);
+  else if (x > -1 && x < lev_fieldx && y > -1 && y < lev_fieldy)
+    DrawSizedElement(sx, sy, Feld[x][y], tilesize);
+  else
+    DrawSizedGraphic(sx, sy, el2edimg(getBorderElement(x, y)), 0, tilesize);
+}
+
 void DrawMiniElementOrWall(int sx, int sy, int scroll_x, int scroll_y)
 {
   int x = sx + scroll_x, y = sy + scroll_y;
@@ -2100,7 +2286,7 @@ void AnimateEnvelope(int envelope_nr, int anim_mode, int action)
   boolean no_delay = (tape.warp_forward);
   unsigned int anim_delay = 0;
   int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
-  int anim_delay_value = (no_delay ? 0 : frame_delay_value);
+  int anim_delay_value = (no_delay ? 0 : frame_delay_value) / 2;
   int font_nr = FONT_ENVELOPE_1 + envelope_nr;
   int font_width = getFontWidth(font_nr);
   int font_height = getFontHeight(font_nr);
@@ -2112,21 +2298,26 @@ void AnimateEnvelope(int envelope_nr, int anim_mode, int action)
   int yend = (anim_mode != ANIM_DEFAULT ? max_ysize : 0);
   int xstep = (xstart < xend ? 1 : 0);
   int ystep = (ystart < yend || xstep == 0 ? 1 : 0);
-  int x, y;
+  int start = 0;
+  int end = MAX(xend - xstart, yend - ystart);
+  int i;
 
-  for (x = xstart, y = ystart; x <= xend && y <= yend; x += xstep, y += ystep)
+  for (i = start; i <= end; i++)
   {
+    int last_frame = end;      // last frame of this "for" loop
+    int x = xstart + i * xstep;
+    int y = ystart + i * ystep;
     int xsize = (action == ACTION_CLOSING ? xend - (x - xstart) : x) + 2;
     int ysize = (action == ACTION_CLOSING ? yend - (y - ystart) : y) + 2;
     int sx = SX + (SXSIZE - xsize * font_width)  / 2;
     int sy = SY + (SYSIZE - ysize * font_height) / 2;
     int xx, yy;
 
-    SetDrawtoField(DRAW_BUFFERED);
+    SetDrawtoField(DRAW_TO_FIELDBUFFER);
 
     BlitScreenToBitmap(backbuffer);
 
-    SetDrawtoField(DRAW_BACKBUFFER);
+    SetDrawtoField(DRAW_TO_BACKBUFFER);
 
     for (yy = 0; yy < ysize; yy++)
       for (xx = 0; xx < xsize; xx++)
@@ -2138,10 +2329,10 @@ void AnimateEnvelope(int envelope_nr, int anim_mode, int action)
                   level.envelope[envelope_nr].autowrap,
                   level.envelope[envelope_nr].centered, FALSE);
 
-    redraw_mask |= REDRAW_FIELD | REDRAW_FROM_BACKBUFFER;
+    redraw_mask |= REDRAW_FIELD;
     BackToFront();
 
-    WaitUntilDelayReached(&anim_delay, anim_delay_value / 2);
+    SkipUntilDelayReached(&anim_delay, anim_delay_value, &i, last_frame);
   }
 }
 
@@ -2183,19 +2374,63 @@ void ShowEnvelope(int envelope_nr)
 
   game.envelope_active = FALSE;
 
-  SetDrawtoField(DRAW_BUFFERED);
+  SetDrawtoField(DRAW_TO_FIELDBUFFER);
 
   redraw_mask |= REDRAW_FIELD;
   BackToFront();
 }
 
-static void setRequestPosition(int *x, int *y, boolean add_border_size)
+static void setRequestBasePosition(int *x, int *y)
+{
+  int sx_base, sy_base;
+
+  if (request.x != -1)
+    sx_base = request.x;
+  else if (request.align == ALIGN_LEFT)
+    sx_base = SX;
+  else if (request.align == ALIGN_RIGHT)
+    sx_base = SX + SXSIZE;
+  else
+    sx_base = SX + SXSIZE / 2;
+
+  if (request.y != -1)
+    sy_base = request.y;
+  else if (request.valign == VALIGN_TOP)
+    sy_base = SY;
+  else if (request.valign == VALIGN_BOTTOM)
+    sy_base = SY + SYSIZE;
+  else
+    sy_base = SY + SYSIZE / 2;
+
+  *x = sx_base;
+  *y = sy_base;
+}
+
+static void setRequestPositionExt(int *x, int *y, int width, int height,
+                                 boolean add_border_size)
 {
   int border_size = request.border_size;
-  int sx_center = (request.x != -1 ? request.x : SX + SXSIZE / 2);
-  int sy_center = (request.y != -1 ? request.y : SY + SYSIZE / 2);
-  int sx = sx_center - request.width  / 2;
-  int sy = sy_center - request.height / 2;
+  int sx_base, sy_base;
+  int sx, sy;
+
+  setRequestBasePosition(&sx_base, &sy_base);
+
+  if (request.align == ALIGN_LEFT)
+    sx = sx_base;
+  else if (request.align == ALIGN_RIGHT)
+    sx = sx_base - width;
+  else
+    sx = sx_base - width  / 2;
+
+  if (request.valign == VALIGN_TOP)
+    sy = sy_base;
+  else if (request.valign == VALIGN_BOTTOM)
+    sy = sy_base - height;
+  else
+    sy = sy_base - height / 2;
+
+  sx = MAX(0, MIN(sx, WIN_XSIZE - width));
+  sy = MAX(0, MIN(sy, WIN_YSIZE - height));
 
   if (add_border_size)
   {
@@ -2207,6 +2442,11 @@ static void setRequestPosition(int *x, int *y, boolean add_border_size)
   *y = sy;
 }
 
+static void setRequestPosition(int *x, int *y, boolean add_border_size)
+{
+  setRequestPositionExt(x, y, request.width, request.height, add_border_size);
+}
+
 void DrawEnvelopeRequest(char *text)
 {
   char *text_final = text;
@@ -2220,19 +2460,25 @@ void DrawEnvelopeRequest(char *text)
   int border_size = request.border_size;
   int line_spacing = request.line_spacing;
   int line_height = font_height + line_spacing;
-  int text_width = request.width - 2 * border_size;
-  int text_height = request.height - 2 * border_size;
-  int line_length = text_width / font_width;
-  int max_lines = text_height / line_height;
+  int max_text_width  = request.width  - 2 * border_size;
+  int max_text_height = request.height - 2 * border_size;
+  int line_length = max_text_width  / font_width;
+  int max_lines   = max_text_height / line_height;
+  int text_width = line_length * font_width;
   int width = request.width;
   int height = request.height;
-  int tile_size = request.step_offset;
+  int tile_size = MAX(request.step_offset, 1);
   int x_steps = width  / tile_size;
   int y_steps = height / tile_size;
+  int sx_offset = border_size;
+  int sy_offset = border_size;
   int sx, sy;
   int i, x, y;
 
-  if (request.wrap_single_words)
+  if (request.centered)
+    sx_offset = (request.width - text_width) / 2;
+
+  if (request.wrap_single_words && !request.autowrap)
   {
     char *src_text_ptr, *dst_text_ptr;
 
@@ -2269,15 +2515,20 @@ void DrawEnvelopeRequest(char *text)
                                  x, y, x_steps, y_steps,
                                  tile_size, tile_size);
 
-  DrawTextBuffer(sx + border_size, sy + border_size, text_final, font_nr,
+  /* force DOOR font inside door area */
+  SetFontStatus(GAME_MODE_PSEUDO_DOOR);
+
+  DrawTextBuffer(sx + sx_offset, sy + sy_offset, text_final, font_nr,
                 line_length, -1, max_lines, line_spacing, mask_mode,
                 request.autowrap, request.centered, FALSE);
 
+  ResetFontStatus();
+
   for (i = 0; i < NUM_TOOL_BUTTONS; i++)
     RedrawGadget(tool_gadget[i]);
 
   // store readily prepared envelope request for later use when animating
-  BlitBitmap(backbuffer, bitmap_db_cross, 0, 0, WIN_XSIZE, WIN_YSIZE, 0, 0);
+  BlitBitmap(backbuffer, bitmap_db_store_2, 0, 0, WIN_XSIZE, WIN_YSIZE, 0, 0);
 
   if (text_door_style)
     free(text_door_style);
@@ -2292,14 +2543,12 @@ void AnimateEnvelopeRequest(int anim_mode, int action)
   boolean ffwd_delay = (tape.playing && tape.fast_forward);
   boolean no_delay = (tape.warp_forward);
   int delay_value = (ffwd_delay ? delay_value_fast : delay_value_normal);
-  int anim_delay_value = (no_delay ? 0 : delay_value + 500 * 0);
+  int anim_delay_value = (no_delay ? 0 : delay_value + 500 * 0) / 2;
   unsigned int anim_delay = 0;
 
-  int width = request.width;
-  int height = request.height;
-  int tile_size = request.step_offset;
-  int max_xsize = width  / tile_size;
-  int max_ysize = height / tile_size;
+  int tile_size = MAX(request.step_offset, 1);
+  int max_xsize = request.width  / tile_size;
+  int max_ysize = request.height / tile_size;
   int max_xsize_inner = max_xsize - 2;
   int max_ysize_inner = max_ysize - 2;
 
@@ -2309,38 +2558,38 @@ void AnimateEnvelopeRequest(int anim_mode, int action)
   int yend = (anim_mode != ANIM_DEFAULT ? max_ysize_inner : 0);
   int xstep = (xstart < xend ? 1 : 0);
   int ystep = (ystart < yend || xstep == 0 ? 1 : 0);
-  int x, y;
+  int start = 0;
+  int end = MAX(xend - xstart, yend - ystart);
+  int i;
 
   if (setup.quick_doors)
   {
     xstart = xend;
     ystart = yend;
-  }
-  else
-  {
-    if (action == ACTION_OPENING)
-      PlayMenuSoundStereo(SND_DOOR_OPENING, SOUND_MIDDLE);
-    else if (action == ACTION_CLOSING)
-      PlayMenuSoundStereo(SND_DOOR_CLOSING, SOUND_MIDDLE);
+    end = 0;
   }
 
-  for (x = xstart, y = ystart; x <= xend && y <= yend; x += xstep, y += ystep)
+  for (i = start; i <= end; i++)
   {
+    int last_frame = end;      // last frame of this "for" loop
+    int x = xstart + i * xstep;
+    int y = ystart + i * ystep;
     int xsize = (action == ACTION_CLOSING ? xend - (x - xstart) : x) + 2;
     int ysize = (action == ACTION_CLOSING ? yend - (y - ystart) : y) + 2;
-    int sx_center = (request.x != -1 ? request.x : SX + SXSIZE / 2);
-    int sy_center = (request.y != -1 ? request.y : SY + SYSIZE / 2);
-    int src_x = sx_center - width  / 2;
-    int src_y = sy_center - height / 2;
-    int dst_x = sx_center - xsize * tile_size / 2;
-    int dst_y = sy_center - ysize * tile_size / 2;
     int xsize_size_left = (xsize - 1) * tile_size;
     int ysize_size_top  = (ysize - 1) * tile_size;
     int max_xsize_pos = (max_xsize - 1) * tile_size;
     int max_ysize_pos = (max_ysize - 1) * tile_size;
+    int width  = xsize * tile_size;
+    int height = ysize * tile_size;
+    int src_x, src_y;
+    int dst_x, dst_y;
     int xx, yy;
 
-    BlitBitmap(bitmap_db_store, backbuffer, 0, 0, WIN_XSIZE, WIN_YSIZE, 0, 0);
+    setRequestPosition(&src_x, &src_y, FALSE);
+    setRequestPositionExt(&dst_x, &dst_y, width, height, FALSE);
+
+    BlitBitmap(bitmap_db_store_1, backbuffer, 0, 0, WIN_XSIZE, WIN_YSIZE, 0, 0);
 
     for (yy = 0; yy < 2; yy++)
     {
@@ -2354,44 +2603,43 @@ void AnimateEnvelopeRequest(int anim_mode, int action)
        int yy_size = (yy ? tile_size : ysize_size_top);
 
        if (draw_masked)
-         BlitBitmapMasked(bitmap_db_cross, backbuffer,
+         BlitBitmapMasked(bitmap_db_store_2, backbuffer,
                           src_xx, src_yy, xx_size, yy_size, dst_xx, dst_yy);
        else
-         BlitBitmap(bitmap_db_cross, backbuffer,
+         BlitBitmap(bitmap_db_store_2, backbuffer,
                     src_xx, src_yy, xx_size, yy_size, dst_xx, dst_yy);
       }
     }
 
-    redraw_mask |= REDRAW_FIELD | REDRAW_FROM_BACKBUFFER;
+    redraw_mask |= REDRAW_FIELD;
 
-    DoAnimation();
     BackToFront();
 
-    WaitUntilDelayReached(&anim_delay, anim_delay_value / 2);
+    SkipUntilDelayReached(&anim_delay, anim_delay_value, &i, last_frame);
   }
 }
 
-
 void ShowEnvelopeRequest(char *text, unsigned int req_state, int action)
 {
-  int last_game_status = game_status;  /* save current game status */
   int graphic = IMG_BACKGROUND_REQUEST;
   int sound_opening = SND_REQUEST_OPENING;
   int sound_closing = SND_REQUEST_CLOSING;
-  int anim_mode = graphic_info[graphic].anim_mode;
+  int anim_mode_1 = request.anim_mode;                 /* (higher priority) */
+  int anim_mode_2 = graphic_info[graphic].anim_mode;   /* (lower priority) */
+  int anim_mode = (anim_mode_1 != ANIM_DEFAULT ? anim_mode_1 : anim_mode_2);
   int main_anim_mode = (anim_mode == ANIM_NONE ? ANIM_VERTICAL|ANIM_HORIZONTAL:
                        anim_mode == ANIM_DEFAULT ? ANIM_VERTICAL : anim_mode);
 
   if (game_status == GAME_MODE_PLAYING)
     BlitScreenToBitmap(backbuffer);
 
-  SetDrawtoField(DRAW_BACKBUFFER);
+  SetDrawtoField(DRAW_TO_BACKBUFFER);
 
   // SetDrawBackgroundMask(REDRAW_NONE);
 
   if (action == ACTION_OPENING)
   {
-    BlitBitmap(backbuffer, bitmap_db_store, 0, 0, WIN_XSIZE, WIN_YSIZE, 0, 0);
+    BlitBitmap(backbuffer, bitmap_db_store_1, 0, 0, WIN_XSIZE, WIN_YSIZE, 0, 0);
 
     if (req_state & REQ_ASK)
     {
@@ -2411,14 +2659,8 @@ void ShowEnvelopeRequest(char *text, unsigned int req_state, int action)
     }
 
     DrawEnvelopeRequest(text);
-
-    if (game_status != GAME_MODE_MAIN)
-      InitAnimation();
   }
 
-  /* force DOOR font inside door area */
-  game_status = GAME_MODE_PSEUDO_DOOR;
-
   game.envelope_active = TRUE; /* needed for RedrawPlayfield() events */
 
   if (action == ACTION_OPENING)
@@ -2429,7 +2671,6 @@ void ShowEnvelopeRequest(char *text, unsigned int req_state, int action)
       AnimateEnvelopeRequest(ANIM_DEFAULT, ACTION_OPENING);
 
     AnimateEnvelopeRequest(main_anim_mode, ACTION_OPENING);
-
   }
   else
   {
@@ -2444,29 +2685,19 @@ void ShowEnvelopeRequest(char *text, unsigned int req_state, int action)
 
   game.envelope_active = FALSE;
 
-  game_status = last_game_status;      /* restore current game status */
-
   if (action == ACTION_CLOSING)
-  {
-    if (game_status != GAME_MODE_MAIN)
-      StopAnimation();
-
-    BlitBitmap(bitmap_db_store, backbuffer, 0, 0, WIN_XSIZE, WIN_YSIZE, 0, 0);
-  }
+    BlitBitmap(bitmap_db_store_1, backbuffer, 0, 0, WIN_XSIZE, WIN_YSIZE, 0, 0);
 
   // SetDrawBackgroundMask(last_draw_background_mask);
 
   redraw_mask |= REDRAW_FIELD;
 
-  if (game_status == GAME_MODE_MAIN)
-    DoAnimation();
-
   BackToFront();
 
   if (action == ACTION_CLOSING &&
       game_status == GAME_MODE_PLAYING &&
       level.game_engine_type == GAME_ENGINE_TYPE_RND)
-    SetDrawtoField(DRAW_BUFFERED);
+    SetDrawtoField(DRAW_TO_FIELDBUFFER);
 }
 
 void DrawPreviewElement(int dst_x, int dst_y, int element, int tilesize)
@@ -2495,6 +2726,18 @@ void DrawLevel(int draw_background_mask)
   redraw_mask |= REDRAW_FIELD;
 }
 
+void DrawSizedLevel(int size_x, int size_y, int scroll_x, int scroll_y,
+                   int tilesize)
+{
+  int x,y;
+
+  for (x = 0; x < size_x; x++)
+    for (y = 0; y < size_y; y++)
+      DrawSizedElementOrWall(x, y, scroll_x, scroll_y, tilesize);
+
+  redraw_mask |= REDRAW_FIELD;
+}
+
 void DrawMiniLevel(int size_x, int size_y, int scroll_x, int scroll_y)
 {
   int x,y;
@@ -2544,7 +2787,7 @@ static void DrawPreviewLevelPlayfieldExt(int from_x, int from_y)
     }
   }
 
-  redraw_mask |= REDRAW_MICROLEVEL;
+  redraw_mask |= REDRAW_FIELD;
 }
 
 #define MICROLABEL_EMPTY               0
@@ -2613,7 +2856,7 @@ static void DrawPreviewLevelLabelExt(int mode)
   if (strlen(label_text) > 0)
     DrawTextSAligned(pos->x, pos->y, label_text, font_nr, pos->align);
 
-  redraw_mask |= REDRAW_MICROLEVEL;
+  redraw_mask |= REDRAW_FIELD;
 }
 
 static void DrawPreviewLevelExt(boolean restart)
@@ -2626,7 +2869,6 @@ static void DrawPreviewLevelExt(boolean restart)
   boolean show_level_border = (BorderElement != EL_EMPTY);
   int level_xsize = lev_fieldx + (show_level_border ? 2 : 0);
   int level_ysize = lev_fieldy + (show_level_border ? 2 : 0);
-  int last_game_status = game_status;          /* save current game status */
 
   if (restart)
   {
@@ -2672,8 +2914,6 @@ static void DrawPreviewLevelExt(boolean restart)
        DrawTextSAligned(pos->x, pos->y, label_text, font_nr, pos->align);
     }
 
-    game_status = last_game_status;    /* restore current game status */
-
     return;
   }
 
@@ -2773,8 +3013,6 @@ static void DrawPreviewLevelExt(boolean restart)
 
     DrawPreviewLevelLabelExt(label_state);
   }
-
-  game_status = last_game_status;      /* restore current game status */
 }
 
 void DrawPreviewLevelInitial()
@@ -2787,8 +3025,9 @@ void DrawPreviewLevelAnimation()
   DrawPreviewLevelExt(FALSE);
 }
 
-inline void DrawGraphicAnimationExt(DrawBuffer *dst_bitmap, int x, int y,
-                                   int graphic, int sync_frame, int mask_mode)
+inline static void DrawGraphicAnimationExt(DrawBuffer *dst_bitmap, int x, int y,
+                                          int graphic, int sync_frame,
+                                          int mask_mode)
 {
   int frame = getGraphicAnimationFrame(graphic, sync_frame);
 
@@ -2798,9 +3037,8 @@ inline void DrawGraphicAnimationExt(DrawBuffer *dst_bitmap, int x, int y,
     DrawGraphicExt(dst_bitmap, x, y, graphic, frame);
 }
 
-inline void DrawFixedGraphicAnimationExt(DrawBuffer *dst_bitmap, int x, int y,
-                                        int graphic, int sync_frame,
-                                        int mask_mode)
+void DrawFixedGraphicAnimationExt(DrawBuffer *dst_bitmap, int x, int y,
+                                 int graphic, int sync_frame, int mask_mode)
 {
   int frame = getGraphicAnimationFrame(graphic, sync_frame);
 
@@ -2810,7 +3048,7 @@ inline void DrawFixedGraphicAnimationExt(DrawBuffer *dst_bitmap, int x, int y,
     DrawFixedGraphicExt(dst_bitmap, x, y, graphic, frame);
 }
 
-inline void DrawGraphicAnimation(int x, int y, int graphic)
+inline static void DrawGraphicAnimation(int x, int y, int graphic)
 {
   int lx = LEVELX(x), ly = LEVELY(y);
 
@@ -2823,7 +3061,7 @@ inline void DrawGraphicAnimation(int x, int y, int graphic)
   MarkTileDirty(x, y);
 }
 
-inline void DrawFixedGraphicAnimation(int x, int y, int graphic)
+void DrawFixedGraphicAnimation(int x, int y, int graphic)
 {
   int lx = LEVELX(x), ly = LEVELY(y);
 
@@ -2847,7 +3085,7 @@ void DrawLevelElementAnimation(int x, int y, int element)
   DrawGraphicAnimation(SCREENX(x), SCREENY(y), graphic);
 }
 
-inline void DrawLevelGraphicAnimationIfNeeded(int x, int y, int graphic)
+void DrawLevelGraphicAnimationIfNeeded(int x, int y, int graphic)
 {
   int sx = SCREENX(x), sy = SCREENY(y);
 
@@ -3099,9 +3337,6 @@ void DrawPlayer(struct PlayerInfo *player)
       syy = player->GfxPos;
   }
 
-  if (!setup.soft_scrolling && ScreenMovPos)
-    sxx = syy = 0;
-
   if (player_is_opaque)
     DrawGraphicShifted(sx, sy, sxx, syy, graphic, frame,NO_CUTTING,NO_MASKING);
   else
@@ -3207,9 +3442,6 @@ void DrawPlayer(struct PlayerInfo *player)
       syy = player->GfxPos;
   }
 
-  if (!setup.soft_scrolling && ScreenMovPos)
-    sxx = syy = 0;
-
   if (player_is_opaque)
     DrawGraphicShifted(sx, sy, sxx, syy, graphic, frame,NO_CUTTING,NO_MASKING);
   else
@@ -3322,10 +3554,7 @@ void WaitForEventToContinue()
       still_wait = FALSE;
     }
 
-    DoAnimation();
-
-    /* don't eat all CPU time */
-    Delay(10);
+    BackToFront();
   }
 }
 
@@ -3335,9 +3564,14 @@ void WaitForEventToContinue()
 
 static int RequestHandleEvents(unsigned int req_state)
 {
-  int last_game_status = game_status;  /* save current game status */
+  boolean level_solved = (game_status == GAME_MODE_PLAYING &&
+                         local_player->LevelSolved_GameEnd);
+  int width  = request.width;
+  int height = request.height;
+  int sx, sy;
   int result;
-  int mx, my;
+
+  setRequestPosition(&sx, &sy, FALSE);
 
   button_status = MB_RELEASED;
 
@@ -3346,113 +3580,134 @@ static int RequestHandleEvents(unsigned int req_state)
 
   while (result < 0)
   {
+    if (level_solved)
+    {
+      SetDrawtoField(DRAW_TO_FIELDBUFFER);
+
+      HandleGameActions();
+
+      SetDrawtoField(DRAW_TO_BACKBUFFER);
+
+      if (global.use_envelope_request)
+      {
+       /* copy current state of request area to middle of playfield area */
+       BlitBitmap(bitmap_db_store_2, drawto, sx, sy, width, height, sx, sy);
+      }
+    }
+
     if (PendingEvent())
     {
       Event event;
 
-      NextEvent(&event);
-
-      switch (event.type)
+      while (NextValidEvent(&event))
       {
-       case EVENT_BUTTONPRESS:
-       case EVENT_BUTTONRELEASE:
-       case EVENT_MOTIONNOTIFY:
+       switch (event.type)
        {
-         if (event.type == EVENT_MOTIONNOTIFY)
+         case EVENT_BUTTONPRESS:
+         case EVENT_BUTTONRELEASE:
+         case EVENT_MOTIONNOTIFY:
          {
-           if (!PointerInWindow(window))
-             continue; /* window and pointer are on different screens */
+           int mx, my;
 
-           if (!button_status)
-             continue;
+           if (event.type == EVENT_MOTIONNOTIFY)
+           {
+             if (!button_status)
+               continue;
 
-           motion_status = TRUE;
-           mx = ((MotionEvent *) &event)->x;
-           my = ((MotionEvent *) &event)->y;
-         }
-         else
-         {
-           motion_status = FALSE;
-           mx = ((ButtonEvent *) &event)->x;
-           my = ((ButtonEvent *) &event)->y;
-           if (event.type == EVENT_BUTTONPRESS)
-             button_status = ((ButtonEvent *) &event)->button;
+             motion_status = TRUE;
+             mx = ((MotionEvent *) &event)->x;
+             my = ((MotionEvent *) &event)->y;
+           }
            else
-             button_status = MB_RELEASED;
+           {
+             motion_status = FALSE;
+             mx = ((ButtonEvent *) &event)->x;
+             my = ((ButtonEvent *) &event)->y;
+             if (event.type == EVENT_BUTTONPRESS)
+               button_status = ((ButtonEvent *) &event)->button;
+             else
+               button_status = MB_RELEASED;
+           }
+
+           /* this sets 'request_gadget_id' */
+           HandleGadgets(mx, my, button_status);
+
+           switch (request_gadget_id)
+           {
+             case TOOL_CTRL_ID_YES:
+               result = TRUE;
+               break;
+             case TOOL_CTRL_ID_NO:
+               result = FALSE;
+               break;
+             case TOOL_CTRL_ID_CONFIRM:
+               result = TRUE | FALSE;
+               break;
+
+             case TOOL_CTRL_ID_PLAYER_1:
+               result = 1;
+               break;
+             case TOOL_CTRL_ID_PLAYER_2:
+               result = 2;
+               break;
+             case TOOL_CTRL_ID_PLAYER_3:
+               result = 3;
+               break;
+             case TOOL_CTRL_ID_PLAYER_4:
+               result = 4;
+               break;
+
+             default:
+               break;
+           }
+
+           break;
          }
 
-         /* this sets 'request_gadget_id' */
-         HandleGadgets(mx, my, button_status);
-
-         switch (request_gadget_id)
+         case EVENT_KEYPRESS:
          {
-           case TOOL_CTRL_ID_YES:
-             result = TRUE;
-             break;
-           case TOOL_CTRL_ID_NO:
-             result = FALSE;
-             break;
-           case TOOL_CTRL_ID_CONFIRM:
-             result = TRUE | FALSE;
-             break;
-
-           case TOOL_CTRL_ID_PLAYER_1:
-             result = 1;
-             break;
-           case TOOL_CTRL_ID_PLAYER_2:
-             result = 2;
-             break;
-           case TOOL_CTRL_ID_PLAYER_3:
-             result = 3;
-             break;
-           case TOOL_CTRL_ID_PLAYER_4:
-             result = 4;
-             break;
-
-           default:
-             break;
-         }
-
-         break;
-       }
+           Key key = GetEventKey((KeyEvent *)&event, TRUE);
 
-       case EVENT_KEYPRESS:
-         switch (GetEventKey((KeyEvent *)&event, TRUE))
-         {
-           case KSYM_space:
-             if (req_state & REQ_CONFIRM)
-               result = 1;
-             break;
+           switch (key)
+           {
+             case KSYM_space:
+               if (req_state & REQ_CONFIRM)
+                 result = 1;
+               break;
 
-           case KSYM_Return:
+             case KSYM_Return:
 #if defined(TARGET_SDL2)
-           case KSYM_Menu:
+             case KSYM_Menu:
 #endif
-             result = 1;
-             break;
+               result = 1;
+               break;
 
-           case KSYM_Escape:
+             case KSYM_Escape:
 #if defined(TARGET_SDL2)
-           case KSYM_Back:
+             case KSYM_Back:
 #endif
+               result = 0;
+               break;
+
+             default:
+               HandleKeysDebug(key);
+               break;
+           }
+
+           if (req_state & REQ_PLAYER)
              result = 0;
-             break;
 
-           default:
-             break;
+           break;
          }
 
-         if (req_state & REQ_PLAYER)
-           result = 0;
-         break;
-
-       case EVENT_KEYRELEASE:
-         ClearPlayerAction();
-         break;
+         case EVENT_KEYRELEASE:
+           ClearPlayerAction();
+           break;
 
-       default:
-         HandleOtherEvents(&event);
-         break;
+         default:
+           HandleOtherEvents(&event);
+           break;
+       }
       }
     }
     else if (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED)
@@ -3465,23 +3720,16 @@ static int RequestHandleEvents(unsigned int req_state)
        result = 0;
     }
 
-    if (game_status == GAME_MODE_PLAYING && local_player->LevelSolved_GameEnd)
-    {
-      HandleGameActions();
-    }
-    else
+    if (level_solved)
     {
-      DoAnimation();
-
-      if (!PendingEvent())     /* delay only if no pending events */
-       Delay(10);
+      if (global.use_envelope_request)
+      {
+       /* copy back current state of pressed buttons inside request area */
+       BlitBitmap(drawto, bitmap_db_store_2, sx, sy, width, height, sx, sy);
+      }
     }
 
-    game_status = GAME_MODE_PSEUDO_DOOR;
-
     BackToFront();
-
-    game_status = last_game_status;    /* restore current game status */
   }
 
   return result;
@@ -3490,7 +3738,6 @@ static int RequestHandleEvents(unsigned int req_state)
 static boolean RequestDoor(char *text, unsigned int req_state)
 {
   unsigned int old_door_state;
-  int last_game_status = game_status;  /* save current game status */
   int max_request_line_len = MAX_REQUEST_LINE_FONT1_LEN;
   int font_nr = FONT_TEXT_2;
   char *text_ptr;
@@ -3547,7 +3794,7 @@ static boolean RequestDoor(char *text, unsigned int req_state)
   DrawBackground(DX, DY, DXSIZE, DYSIZE);
 
   /* force DOOR font inside door area */
-  game_status = GAME_MODE_PSEUDO_DOOR;
+  SetFontStatus(GAME_MODE_PSEUDO_DOOR);
 
   /* write text for request */
   for (text_ptr = text, ty = 0; ty < MAX_REQUEST_LINES; ty++)
@@ -3587,7 +3834,7 @@ static boolean RequestDoor(char *text, unsigned int req_state)
     // text_ptr += tl + (tc == ' ' || tc == '?' || tc == '!' ? 1 : 0);
   }
 
-  game_status = last_game_status;      /* restore current game status */
+  ResetFontStatus();
 
   if (req_state & REQ_ASK)
   {
@@ -3626,17 +3873,11 @@ static boolean RequestDoor(char *text, unsigned int req_state)
     return FALSE;
   }
 
-  if (game_status != GAME_MODE_MAIN)
-    InitAnimation();
-
   SetDrawBackgroundMask(REDRAW_FIELD | REDRAW_DOOR_1);
 
   // ---------- handle request buttons ----------
   result = RequestHandleEvents(req_state);
 
-  if (game_status != GAME_MODE_MAIN)
-    StopAnimation();
-
   UnmapToolButtons();
 
   if (!(req_state & REQ_STAY_OPEN))
@@ -3731,9 +3972,6 @@ static boolean RequestEnvelope(char *text, unsigned int req_state)
   // ---------- handle request buttons ----------
   result = RequestHandleEvents(req_state);
 
-  if (game_status != GAME_MODE_MAIN)
-    StopAnimation();
-
   UnmapToolButtons();
 
   ShowEnvelopeRequest(text, req_state, ACTION_CLOSING);
@@ -3797,8 +4035,8 @@ void InitGraphicCompatibilityInfo_Doors()
   }
   doors[] =
   {
-    { DOOR_1,  IMG_DOOR_1_GFX_PART_1,  IMG_DOOR_1_GFX_PART_8,  &door_1 },
-    { DOOR_2,  IMG_DOOR_2_GFX_PART_1,  IMG_DOOR_2_GFX_PART_8,  &door_2 },
+    { DOOR_1,  IMG_GFX_DOOR_1_PART_1,  IMG_GFX_DOOR_1_PART_8,  &door_1 },
+    { DOOR_2,  IMG_GFX_DOOR_2_PART_1,  IMG_GFX_DOOR_2_PART_8,  &door_2 },
 
     { -1,      -1,                     -1,                     NULL    }
   };
@@ -3930,11 +4168,13 @@ void InitGraphicCompatibilityInfo_Doors()
        {
          num_panel_steps = 2 * door_rect->height / door->step_offset;
          door->panel.start_step = num_panel_steps - num_door_steps;
+         door->panel.start_step_closing = door->panel.start_step;
        }
        else
        {
          num_panel_steps = door_rect->height / door->step_offset;
          door->panel.start_step = num_panel_steps - num_door_steps / 2;
+         door->panel.start_step_closing = door->panel.start_step;
          door->panel.step_delay *= 2;
        }
       }
@@ -4029,21 +4269,12 @@ unsigned int MoveDoor(unsigned int door_state)
     { DX, DY, DXSIZE, DYSIZE },
     { VX, VY, VXSIZE, VYSIZE }
   };
-  static int door1 = DOOR_OPEN_1;
+  static int door1 = DOOR_CLOSE_1;
   static int door2 = DOOR_CLOSE_2;
   unsigned int door_delay = 0;
   unsigned int door_delay_value;
   int i;
 
-  if (door_1.width < 0 || door_1.width > DXSIZE)
-    door_1.width = DXSIZE;
-  if (door_1.height < 0 || door_1.height > DYSIZE)
-    door_1.height = DYSIZE;
-  if (door_2.width < 0 || door_2.width > VXSIZE)
-    door_2.width = VXSIZE;
-  if (door_2.height < 0 || door_2.height > VYSIZE)
-    door_2.height = VYSIZE;
-
   if (door_state == DOOR_GET_STATE)
     return (door1 | door2);
 
@@ -4152,6 +4383,8 @@ unsigned int MoveDoor(unsigned int door_state)
       }
     }
 
+    max_step_delay = MAX(1, max_step_delay);   // prevent division by zero
+
     num_move_steps = max_move_delay / max_step_delay;
     num_move_steps_doors_only = max_move_delay_doors_only / max_step_delay;
 
@@ -4172,6 +4405,8 @@ unsigned int MoveDoor(unsigned int door_state)
 
     for (k = start; k < num_move_steps; k++)
     {
+      int last_frame = num_move_steps - 1;     // last frame of this "for" loop
+
       door_part_done_all = TRUE;
 
       for (i = 0; i < NUM_DOORS; i++)
@@ -4225,7 +4460,8 @@ unsigned int MoveDoor(unsigned int door_state)
          int sync_frame = kk_door * door_delay_value;
          int frame = getGraphicAnimationFrame(dpc->graphic, sync_frame);
 
-         getGraphicSource(dpc->graphic, frame, &bitmap, &g_src_x, &g_src_y);
+         getFixedGraphicSource(dpc->graphic, frame, &bitmap,
+                               &g_src_x, &g_src_y);
        }
 
        // draw door panel
@@ -4262,6 +4498,9 @@ unsigned int MoveDoor(unsigned int door_state)
 
          width = g->width - src_xx;
 
+         if (width > door_rect->width)
+           width = door_rect->width;
+
          // printf("::: k == %d [%d] \n", k, start_step);
        }
 
@@ -4327,10 +4566,7 @@ unsigned int MoveDoor(unsigned int door_state)
       {
        BackToFront();
 
-       if (game_status == GAME_MODE_MAIN)
-         DoAnimation();
-
-       WaitUntilDelayReached(&door_delay, door_delay_value);
+       SkipUntilDelayReached(&door_delay, door_delay_value, &k, last_frame);
 
        current_move_delay += max_step_delay;
       }
@@ -4345,9 +4581,36 @@ unsigned int MoveDoor(unsigned int door_state)
   if (door_state & DOOR_ACTION_2)
     door2 = door_state & DOOR_ACTION_2;
 
+  // draw masked border over door area
+  DrawMaskedBorder(REDRAW_DOOR_1);
+  DrawMaskedBorder(REDRAW_DOOR_2);
+
   return (door1 | door2);
 }
 
+static boolean useSpecialEditorDoor()
+{
+  int graphic = IMG_GLOBAL_BORDER_EDITOR;
+  boolean redefined = getImageListEntryFromImageID(graphic)->redefined;
+
+  // do not draw special editor door if editor border defined or redefined
+  if (graphic_info[graphic].bitmap != NULL || redefined)
+    return FALSE;
+
+  // do not draw special editor door if global border defined to be empty
+  if (graphic_info[IMG_GLOBAL_BORDER].bitmap == NULL)
+    return FALSE;
+
+  // do not draw special editor door if viewport definitions do not match
+  if (EX != VX ||
+      EY >= VY ||
+      EXSIZE != VXSIZE ||
+      EY + EYSIZE != VY + VYSIZE)
+    return FALSE;
+
+  return TRUE;
+}
+
 void DrawSpecialEditorDoor()
 {
   struct GraphicInfo *gfx1 = &graphic_info[IMG_DOOR_2_TOP_BORDER_CORRECTION];
@@ -4359,7 +4622,8 @@ void DrawSpecialEditorDoor()
   int vy = VY - outer_border;
   int exsize = EXSIZE + 2 * outer_border;
 
-  CloseDoor(DOOR_CLOSE_2);
+  if (!useSpecialEditorDoor())
+    return;
 
   /* draw bigger level editor toolbox window */
   BlitBitmap(gfx1->bitmap, drawto, gfx1->src_x, gfx1->src_y,
@@ -4382,6 +4646,9 @@ void UndrawSpecialEditorDoor()
   int exsize = EXSIZE + 2 * outer_border;
   int eysize = EYSIZE + 2 * outer_border;
 
+  if (!useSpecialEditorDoor())
+    return;
+
   /* draw normal tape recorder window */
   if (graphic_info[IMG_GLOBAL_BORDER].bitmap)
   {
@@ -4413,31 +4680,31 @@ static struct
 } toolbutton_info[NUM_TOOL_BUTTONS] =
 {
   {
-    IMG_REQUEST_BUTTON_GFX_YES,                &request.button.yes,
+    IMG_GFX_REQUEST_BUTTON_YES,                &request.button.yes,
     TOOL_CTRL_ID_YES,                  "yes"
   },
   {
-    IMG_REQUEST_BUTTON_GFX_NO,         &request.button.no,
+    IMG_GFX_REQUEST_BUTTON_NO,         &request.button.no,
     TOOL_CTRL_ID_NO,                   "no"
   },
   {
-    IMG_REQUEST_BUTTON_GFX_CONFIRM,    &request.button.confirm,
+    IMG_GFX_REQUEST_BUTTON_CONFIRM,    &request.button.confirm,
     TOOL_CTRL_ID_CONFIRM,              "confirm"
   },
   {
-    IMG_REQUEST_BUTTON_GFX_PLAYER_1,   &request.button.player_1,
+    IMG_GFX_REQUEST_BUTTON_PLAYER_1,   &request.button.player_1,
     TOOL_CTRL_ID_PLAYER_1,             "player 1"
   },
   {
-    IMG_REQUEST_BUTTON_GFX_PLAYER_2,   &request.button.player_2,
+    IMG_GFX_REQUEST_BUTTON_PLAYER_2,   &request.button.player_2,
     TOOL_CTRL_ID_PLAYER_2,             "player 2"
   },
   {
-    IMG_REQUEST_BUTTON_GFX_PLAYER_3,   &request.button.player_3,
+    IMG_GFX_REQUEST_BUTTON_PLAYER_3,   &request.button.player_3,
     TOOL_CTRL_ID_PLAYER_3,             "player 3"
   },
   {
-    IMG_REQUEST_BUTTON_GFX_PLAYER_4,   &request.button.player_4,
+    IMG_GFX_REQUEST_BUTTON_PLAYER_4,   &request.button.player_4,
     TOOL_CTRL_ID_PLAYER_4,             "player 4"
   }
 };
@@ -7761,13 +8028,57 @@ void InitGraphicInfo_EM(void)
 #endif
 }
 
+void CheckSaveEngineSnapshot_EM(byte action[MAX_PLAYERS], int frame,
+                               boolean any_player_moving,
+                               boolean any_player_snapping,
+                               boolean any_player_dropping)
+{
+  if (frame == 0 && !any_player_dropping)
+  {
+    if (!local_player->was_waiting)
+    {
+      if (!CheckSaveEngineSnapshotToList())
+       return;
+
+      local_player->was_waiting = TRUE;
+    }
+  }
+  else if (any_player_moving || any_player_snapping || any_player_dropping)
+  {
+    local_player->was_waiting = FALSE;
+  }
+}
+
+void CheckSaveEngineSnapshot_SP(boolean murphy_is_waiting,
+                               boolean murphy_is_dropping)
+{
+  if (murphy_is_waiting)
+  {
+    if (!local_player->was_waiting)
+    {
+      if (!CheckSaveEngineSnapshotToList())
+       return;
+
+      local_player->was_waiting = TRUE;
+    }
+  }
+  else
+  {
+    local_player->was_waiting = FALSE;
+  }
+}
+
 void CheckSingleStepMode_EM(byte action[MAX_PLAYERS], int frame,
                            boolean any_player_moving,
-                           boolean player_is_dropping)
+                           boolean any_player_snapping,
+                           boolean any_player_dropping)
 {
   if (tape.single_step && tape.recording && !tape.pausing)
-    if (frame == 0 && !player_is_dropping)
+    if (frame == 0 && !any_player_dropping)
       TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
+
+  CheckSaveEngineSnapshot_EM(action, frame, any_player_moving,
+                            any_player_snapping, any_player_dropping);
 }
 
 void CheckSingleStepMode_SP(boolean murphy_is_waiting,
@@ -7776,6 +8087,8 @@ void CheckSingleStepMode_SP(boolean murphy_is_waiting,
   if (tape.single_step && tape.recording && !tape.pausing)
     if (murphy_is_waiting)
       TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
+
+  CheckSaveEngineSnapshot_SP(murphy_is_waiting, murphy_is_dropping);
 }
 
 void getGraphicSource_SP(struct GraphicInfo_SP *g_sp,
@@ -7883,9 +8196,6 @@ void ToggleFullscreenOrChangeWindowScalingIfNeeded()
 {
   boolean change_fullscreen = (setup.fullscreen !=
                               video.fullscreen_enabled);
-  boolean change_fullscreen_mode = (video.fullscreen_enabled &&
-                                   !strEqual(setup.fullscreen_mode,
-                                             video.fullscreen_mode_current));
   boolean change_window_scaling_percent = (!video.fullscreen_enabled &&
                                           setup.window_scaling_percent !=
                                           video.window_scaling_percent);
@@ -7915,7 +8225,6 @@ void ToggleFullscreenOrChangeWindowScalingIfNeeded()
 #endif
 
   if (change_fullscreen ||
-      change_fullscreen_mode ||
       change_window_scaling_percent)
   {
     Bitmap *tmp_backbuffer = CreateBitmap(WIN_XSIZE, WIN_YSIZE, DEFAULT_DEPTH);
@@ -7923,12 +8232,6 @@ void ToggleFullscreenOrChangeWindowScalingIfNeeded()
     /* save backbuffer content which gets lost when toggling fullscreen mode */
     BlitBitmap(backbuffer, tmp_backbuffer, 0, 0, WIN_XSIZE, WIN_YSIZE, 0, 0);
 
-    if (change_fullscreen_mode)
-    {
-      /* keep fullscreen, but change fullscreen mode (screen resolution) */
-      video.fullscreen_enabled = FALSE;                /* force new fullscreen mode */
-    }
-
     if (change_window_scaling_percent)
     {
       /* keep window mode, but change window scaling */
@@ -7951,15 +8254,75 @@ void ToggleFullscreenOrChangeWindowScalingIfNeeded()
   }
 }
 
+void JoinRectangles(int *x, int *y, int *width, int *height,
+                   int x2, int y2, int width2, int height2)
+{
+  // do not join with "off-screen" rectangle
+  if (x2 == -1 || y2 == -1)
+    return;
+
+  *x = MIN(*x, x2);
+  *y = MIN(*y, y2);
+  *width = MAX(*width, width2);
+  *height = MAX(*height, height2);
+}
+
+void SetAnimStatus(int anim_status_new)
+{
+  if (anim_status_new == GAME_MODE_MAIN)
+    anim_status_new = GAME_MODE_PSEUDO_MAINONLY;
+
+  global.anim_status_next = anim_status_new;
+
+  // directly set screen modes that are entered without fading
+  if ((global.anim_status      == GAME_MODE_PSEUDO_MAINONLY &&
+       global.anim_status_next == GAME_MODE_PSEUDO_TYPENAME) ||
+      (global.anim_status      == GAME_MODE_PSEUDO_TYPENAME &&
+       global.anim_status_next == GAME_MODE_PSEUDO_MAINONLY))
+    global.anim_status = global.anim_status_next;
+}
+
+void SetGameStatus(int game_status_new)
+{
+  game_status = game_status_new;
+
+  SetAnimStatus(game_status_new);
+}
+
+void SetFontStatus(int game_status_new)
+{
+  static int last_game_status = -1;
+
+  if (game_status_new != -1)
+  {
+    // set game status for font use after storing last game status
+    last_game_status = game_status;
+    game_status = game_status_new;
+  }
+  else
+  {
+    // reset game status after font use from last stored game status
+    game_status = last_game_status;
+  }
+}
+
+void ResetFontStatus()
+{
+  SetFontStatus(-1);
+}
+
 void ChangeViewportPropertiesIfNeeded()
 {
   int gfx_game_mode = game_status;
   int gfx_game_mode2 = (game_status == GAME_MODE_EDITOR ? GAME_MODE_DEFAULT :
                        game_status);
+  struct RectWithBorder *vp_window    = &viewport.window[gfx_game_mode];
   struct RectWithBorder *vp_playfield = &viewport.playfield[gfx_game_mode];
-  struct RectWithBorder *vp_door_1 = &viewport.door_1[gfx_game_mode];
-  struct RectWithBorder *vp_door_2 = &viewport.door_2[gfx_game_mode2];
-  struct RectWithBorder *vp_door_3 = &viewport.door_2[GAME_MODE_EDITOR];
+  struct RectWithBorder *vp_door_1    = &viewport.door_1[gfx_game_mode];
+  struct RectWithBorder *vp_door_2    = &viewport.door_2[gfx_game_mode2];
+  struct RectWithBorder *vp_door_3    = &viewport.door_2[GAME_MODE_EDITOR];
+  int new_win_xsize    = vp_window->width;
+  int new_win_ysize    = vp_window->height;
   int border_size      = vp_playfield->border_size;
   int new_sx           = vp_playfield->x + border_size;
   int new_sy           = vp_playfield->y + border_size;
@@ -7992,17 +8355,18 @@ void ChangeViewportPropertiesIfNeeded()
   int new_scr_fieldy_buffers = new_sysize / new_tilesize_var;
   boolean init_gfx_buffers = FALSE;
   boolean init_video_buffer = FALSE;
-  boolean init_gadgets_and_toons = FALSE;
+  boolean init_gadgets_and_anims = FALSE;
   boolean init_em_graphics = FALSE;
 
-  if (viewport.window.width  != WIN_XSIZE ||
-      viewport.window.height != WIN_YSIZE)
+  if (new_win_xsize != WIN_XSIZE ||
+      new_win_ysize != WIN_YSIZE)
   {
-    WIN_XSIZE = viewport.window.width;
-    WIN_YSIZE = viewport.window.height;
+    WIN_XSIZE = new_win_xsize;
+    WIN_YSIZE = new_win_ysize;
 
     init_video_buffer = TRUE;
     init_gfx_buffers = TRUE;
+    init_gadgets_and_anims = TRUE;
 
     // printf("::: video: init_video_buffer, init_gfx_buffers\n");
   }
@@ -8041,12 +8405,54 @@ void ChangeViewportPropertiesIfNeeded()
       new_tilesize_var != TILESIZE_VAR
       )
   {
+    // ------------------------------------------------------------------------
+    // determine next fading area for changed viewport definitions
+    // ------------------------------------------------------------------------
+
+    // start with current playfield area (default fading area)
+    FADE_SX = REAL_SX;
+    FADE_SY = REAL_SY;
+    FADE_SXSIZE = FULL_SXSIZE;
+    FADE_SYSIZE = FULL_SYSIZE;
+
+    // add new playfield area if position or size has changed
+    if (new_real_sx != REAL_SX || new_real_sy != REAL_SY ||
+       new_full_sxsize != FULL_SXSIZE || new_full_sysize != FULL_SYSIZE)
+    {
+      JoinRectangles(&FADE_SX, &FADE_SY, &FADE_SXSIZE, &FADE_SYSIZE,
+                    new_real_sx, new_real_sy, new_full_sxsize,new_full_sysize);
+    }
+
+    // add current and new door 1 area if position or size has changed
+    if (new_dx != DX || new_dy != DY ||
+       new_dxsize != DXSIZE || new_dysize != DYSIZE)
+    {
+      JoinRectangles(&FADE_SX, &FADE_SY, &FADE_SXSIZE, &FADE_SYSIZE,
+                    DX, DY, DXSIZE, DYSIZE);
+      JoinRectangles(&FADE_SX, &FADE_SY, &FADE_SXSIZE, &FADE_SYSIZE,
+                    new_dx, new_dy, new_dxsize, new_dysize);
+    }
+
+    // add current and new door 2 area if position or size has changed
+    if (new_dx != VX || new_dy != VY ||
+       new_dxsize != VXSIZE || new_dysize != VYSIZE)
+    {
+      JoinRectangles(&FADE_SX, &FADE_SY, &FADE_SXSIZE, &FADE_SYSIZE,
+                    VX, VY, VXSIZE, VYSIZE);
+      JoinRectangles(&FADE_SX, &FADE_SY, &FADE_SXSIZE, &FADE_SYSIZE,
+                    new_vx, new_vy, new_vxsize, new_vysize);
+    }
+
+    // ------------------------------------------------------------------------
+    // handle changed tile size
+    // ------------------------------------------------------------------------
+
     if (new_tilesize_var != TILESIZE_VAR)
     {
       // printf("::: new_tilesize_var != TILESIZE_VAR\n");
 
       // changing tile size invalidates scroll values of engine snapshots
-      FreeEngineSnapshot();
+      FreeEngineSnapshotSingle();
 
       // changing tile size requires update of graphic mapping for EM engine
       init_em_graphics = TRUE;
@@ -8075,10 +8481,10 @@ void ChangeViewportPropertiesIfNeeded()
     TILESIZE_VAR = new_tilesize_var;
 
     init_gfx_buffers = TRUE;
-    init_gadgets_and_toons = TRUE;
+    init_gadgets_and_anims = TRUE;
 
     // printf("::: viewports: init_gfx_buffers\n");
-    // printf("::: viewports: init_gadgets_and_toons\n");
+    // printf("::: viewports: init_gadgets_and_anims\n");
   }
 
   if (init_gfx_buffers)
@@ -8092,6 +8498,9 @@ void ChangeViewportPropertiesIfNeeded()
 
     SCR_FIELDX = new_scr_fieldx;
     SCR_FIELDY = new_scr_fieldy;
+
+    SetDrawDeactivationMask(REDRAW_NONE);
+    SetDrawBackgroundMask(REDRAW_FIELD);
   }
 
   if (init_video_buffer)
@@ -8099,17 +8508,15 @@ void ChangeViewportPropertiesIfNeeded()
     // printf("::: init_video_buffer\n");
 
     InitVideoBuffer(WIN_XSIZE, WIN_YSIZE, DEFAULT_DEPTH, setup.fullscreen);
-
-    SetDrawDeactivationMask(REDRAW_NONE);
-    SetDrawBackgroundMask(REDRAW_FIELD);
+    InitImageTextures();
   }
 
-  if (init_gadgets_and_toons)
+  if (init_gadgets_and_anims)
   {
-    // printf("::: init_gadgets_and_toons\n");
+    // printf("::: init_gadgets_and_anims\n");
 
     InitGadgets();
-    InitToons();
+    InitGlobalAnimations();
   }
 
   if (init_em_graphics)