added step-based engine snapshots to undo/redo game steps
[rocksndiamonds.git] / src / tools.c
index 335d983a96e8700a5cbf801553cd6cc095d72ad2..e98ac35b9476491b9b6192a282f16c8c447d99e0 100644 (file)
@@ -292,7 +292,6 @@ void DrawMaskedBorder_Rect(int x, int y, int width, int height)
 {
   Bitmap *bitmap = graphic_info[IMG_GLOBAL_BORDER].bitmap;
 
-  SetClipOrigin(bitmap, bitmap->stored_clip_gc, 0, 0);
   BlitBitmapMasked(bitmap, backbuffer, x, y, width, height, x, y);
 }
 
@@ -531,7 +530,7 @@ void BackToFront()
      this could mean that we have to wait for the graphics to complete,
      although we could go on doing calculations for the next frame */
 
-  SyncDisplay();
+  /* SyncDisplay(); */
 
   /* never draw masked border to backbuffer when using playfield buffer */
   if (game_status != GAME_MODE_PLAYING ||
@@ -664,8 +663,6 @@ void BackToFront()
     DrawTextExt(window, SX + SXSIZE + SX, 0, text, FONT_TEXT_2, BLIT_OPAQUE);
   }
 
-  FlushDisplay();
-
   for (x = 0; x < MAX_BUF_XSIZE; x++)
     for (y = 0; y < MAX_BUF_YSIZE; y++)
       redraw[x][y] = 0;
@@ -1045,94 +1042,52 @@ inline int getGraphicAnimationFrame(int graphic, int sync_frame)
                           sync_frame);
 }
 
-void getSizedGraphicSourceExt(int graphic, int frame, int tilesize_raw,
+void getSizedGraphicSourceExt(int graphic, int frame, int tilesize,
                              Bitmap **bitmap, int *x, int *y,
                              boolean get_backside)
 {
-  struct
-  {
-    int width_mult, width_div;
-    int height_mult, height_div;
-  }
-  offset_calc[6] =
-  {
-    { 15, 16,  2, 3    },      /* 1 x 1 */
-    { 7, 8,    2, 3    },      /* 2 x 2 */
-    { 3, 4,    2, 3    },      /* 4 x 4 */
-    { 1, 2,    2, 3    },      /* 8 x 8 */
-    { 0, 1,    2, 3    },      /* 16 x 16 */
-    { 0, 1,    0, 1    },      /* 32 x 32 */
-  };
   struct GraphicInfo *g = &graphic_info[graphic];
   Bitmap *src_bitmap = g->bitmap;
-  int tilesize = MIN(MAX(1, tilesize_raw), TILESIZE);
-  int offset_calc_pos = log_2(tilesize);
-  int bitmap_width  = src_bitmap->width;
-  int bitmap_height = src_bitmap->height;
-  int width_mult  = offset_calc[offset_calc_pos].width_mult;
-  int width_div   = offset_calc[offset_calc_pos].width_div;
-  int height_mult = offset_calc[offset_calc_pos].height_mult;
-  int height_div  = offset_calc[offset_calc_pos].height_div;
-  int startx = bitmap_width * width_mult / width_div;
-  int starty = bitmap_height * height_mult / height_div;
-  int src_x = (g->src_x + (get_backside ? g->offset2_x : 0)) *
-    tilesize_raw / TILESIZE;
-  int src_y = (g->src_y + (get_backside ? g->offset2_y : 0)) *
-    tilesize_raw / TILESIZE;
-  int width = g->width * tilesize_raw / TILESIZE;
-  int height = g->height * tilesize_raw / TILESIZE;
-  int offset_x = g->offset_x * tilesize_raw / TILESIZE;
-  int offset_y = g->offset_y * tilesize_raw / TILESIZE;
-
-  if (game.tile_size != TILESIZE)
-  {
-    int bitmap_width_std =
-      bitmap_width * TILESIZE / (TILESIZE + game.tile_size);
-    int bitmap_height_std =
-      bitmap_height * TILESIZE / game.tile_size * 3 / 2;
-
-    if (tilesize_raw == game.tile_size)
-    {
-      startx = bitmap_width_std;
-      starty = 0;
-    }
-    else
-    {
-      bitmap_width = bitmap_width_std;
+  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 (game.tile_size > TILESIZE * 3 / 2)
-       bitmap_height = bitmap_height_std;
+  // if no in-game graphics defined, always use standard graphic size
+  if (g->bitmaps[IMG_BITMAP_GAME] == NULL)
+    tilesize = TILESIZE;
 
-      startx = bitmap_width * width_mult / width_div;
-      starty = bitmap_height * height_mult / height_div;
-    }
-  }
+  if (tilesize == gfx.standard_tile_size)
+    src_bitmap = g->bitmaps[IMG_BITMAP_STANDARD];
+  else if (tilesize == game.tile_size)
+    src_bitmap = g->bitmaps[IMG_BITMAP_GAME];
+  else
+    src_bitmap = g->bitmaps[IMG_BITMAP_1x1 - log_2(tilesize_capped)];
 
   if (g->offset_y == 0)                /* frames are ordered horizontally */
   {
-    int max_width = g->anim_frames_per_line * width;
-    int pos = (src_y / height) * max_width + src_x + frame * offset_x;
+    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 % height + pos / max_width * height;
+    src_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 * height;
-    int pos = (src_x / width) * max_height + src_y + frame * offset_y;
+    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 % width + pos / max_height * width;
+    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 * offset_x;
-    src_y = src_y + frame * offset_y;
+    src_x = src_x + frame * g->offset_x;
+    src_y = src_y + frame * g->offset_y;
   }
 
   *bitmap = src_bitmap;
-  *x = startx + src_x;
-  *y = starty + src_y;
+  *x = src_x * tilesize / TILESIZE;
+  *y = src_y * tilesize / TILESIZE;
 }
 
 void getFixedGraphicSourceExt(int graphic, int frame, Bitmap **bitmap,
@@ -1142,10 +1097,10 @@ void getFixedGraphicSourceExt(int graphic, int frame, Bitmap **bitmap,
                           get_backside);
 }
 
-void getSizedGraphicSource(int graphic, int frame, int tilesize_raw,
+void getSizedGraphicSource(int graphic, int frame, int tilesize,
                           Bitmap **bitmap, int *x, int *y)
 {
-  getSizedGraphicSourceExt(graphic, frame, tilesize_raw, bitmap, x, y, FALSE);
+  getSizedGraphicSourceExt(graphic, frame, tilesize, bitmap, x, y, FALSE);
 }
 
 void getFixedGraphicSource(int graphic, int frame,
@@ -1295,9 +1250,6 @@ void DrawGraphicThruMaskExt(DrawBuffer *d, int dst_x, int dst_y, int graphic,
 
   getGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
 
-  SetClipOrigin(src_bitmap, src_bitmap->stored_clip_gc,
-               dst_x - src_x, dst_y - src_y);
-
   BlitBitmapMasked(src_bitmap, d, src_x, src_y, TILEX_VAR, TILEY_VAR,
                   dst_x, dst_y);
 }
@@ -1305,14 +1257,14 @@ void DrawGraphicThruMaskExt(DrawBuffer *d, int dst_x, int dst_y, int graphic,
 void DrawFixedGraphicThruMaskExt(DrawBuffer *d, int dst_x, int dst_y,
                                 int graphic, int frame)
 {
+  struct GraphicInfo *g = &graphic_info[graphic];
   Bitmap *src_bitmap;
   int src_x, src_y;
 
   getFixedGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
 
-  SetClipOrigin(src_bitmap, src_bitmap->stored_clip_gc,
-               dst_x - src_x, dst_y - src_y);
-  BlitBitmapMasked(src_bitmap, d, src_x, src_y, TILEX, TILEY, dst_x, dst_y);
+  BlitBitmapMasked(src_bitmap, d, src_x, src_y, g->width, g->height,
+                  dst_x, dst_y);
 }
 
 void DrawSizedGraphic(int x, int y, int graphic, int frame, int tilesize)
@@ -1448,12 +1400,8 @@ inline static void DrawGraphicShiftedNormal(int x, int y, int dx, int dy,
     dst_y = FY + y * TILEY_VAR + dy;
 
     if (mask_mode == USE_MASKING)
-    {
-      SetClipOrigin(src_bitmap, src_bitmap->stored_clip_gc,
-                   dst_x - src_x, dst_y - src_y);
       BlitBitmapMasked(src_bitmap, drawto_field, src_x, src_y, width, height,
                       dst_x, dst_y);
-    }
     else
       BlitBitmap(src_bitmap, drawto_field, src_x, src_y, width, height,
                 dst_x, dst_y);
@@ -1499,12 +1447,8 @@ inline static void DrawGraphicShiftedDouble(int x, int y, int dx, int dy,
     dst_y = FY + y1 * TILEY_VAR;
 
     if (mask_mode == USE_MASKING)
-    {
-      SetClipOrigin(src_bitmap, src_bitmap->stored_clip_gc,
-                   dst_x - src_x, dst_y - src_y);
       BlitBitmapMasked(src_bitmap, drawto_field, src_x, src_y, width, height,
                       dst_x, dst_y);
-    }
     else
       BlitBitmap(src_bitmap, drawto_field, src_x, src_y, width, height,
                 dst_x, dst_y);
@@ -1521,12 +1465,8 @@ inline static void DrawGraphicShiftedDouble(int x, int y, int dx, int dy,
     dst_y = FY + y2 * TILEY_VAR;
 
     if (mask_mode == USE_MASKING)
-    {
-      SetClipOrigin(src_bitmap, src_bitmap->stored_clip_gc,
-                   dst_x - src_x, dst_y - src_y);
       BlitBitmapMasked(src_bitmap, drawto_field, src_x, src_y, width, height,
                       dst_x, dst_y);
-    }
     else
       BlitBitmap(src_bitmap, drawto_field, src_x, src_y, width, height,
                 dst_x, dst_y);
@@ -2088,6 +2028,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;
@@ -2096,6 +2044,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;
@@ -2140,12 +2101,8 @@ void DrawEnvelopeBackgroundTiles(int graphic, int startx, int starty,
            inner_sy + (y - 1) * tile_height % inner_height);
 
   if (draw_masked)
-  {
-    SetClipOrigin(src_bitmap, src_bitmap->stored_clip_gc,
-                 dst_x - src_x, dst_y - src_y);
     BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, tile_width, tile_height,
                     dst_x, dst_y);
-  }
   else
     BlitBitmap(src_bitmap, drawto, src_x, src_y, tile_width, tile_height,
               dst_x, dst_y);
@@ -2565,6 +2522,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;
@@ -3420,109 +3389,110 @@ static int RequestHandleEvents(unsigned int req_state)
     {
       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 */
-
-           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;
+           if (event.type == EVENT_MOTIONNOTIFY)
+           {
+             if (!PointerInWindow(window))
+               continue;       /* window and pointer on different screens */
+
+             if (!button_status)
+               continue;
+
+             motion_status = TRUE;
+             mx = ((MotionEvent *) &event)->x;
+             my = ((MotionEvent *) &event)->y;
+           }
            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;
+           {
+             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;
          }
 
-         break;
-       }
-
-       case EVENT_KEYPRESS:
-         switch (GetEventKey((KeyEvent *)&event, TRUE))
-         {
-           case KSYM_space:
-             if (req_state & REQ_CONFIRM)
-               result = 1;
-             break;
+         case EVENT_KEYPRESS:
+           switch (GetEventKey((KeyEvent *)&event, TRUE))
+           {
+             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;
+               result = 0;
+               break;
 
-           default:
-             break;
-         }
+             default:
+               break;
+           }
 
-         if (req_state & REQ_PLAYER)
-           result = 0;
-         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)
@@ -4000,11 +3970,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;
        }
       }
@@ -4105,15 +4077,6 @@ unsigned int MoveDoor(unsigned int door_state)
   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);
 
@@ -4159,6 +4122,8 @@ unsigned int MoveDoor(unsigned int door_state)
     int max_move_delay = 0;    // delay for complete animations of all doors
     int max_step_delay = 0;    // delay (ms) between two animation frames
     int num_move_steps = 0;    // number of animation steps for all doors
+    int max_move_delay_doors_only = 0; // delay for doors only (no panel)
+    int num_move_steps_doors_only = 0; // steps for doors only (no panel)
     int current_move_delay = 0;
     int start = 0;
     int k;
@@ -4207,16 +4172,23 @@ unsigned int MoveDoor(unsigned int door_state)
       if (door_part_skip[nr])
        continue;
 
-      if (!is_panel)
-       panel_has_doors[door_index] = TRUE;
-
       max_move_delay = MAX(max_move_delay, move_delay);
       max_step_delay = (max_step_delay == 0 ? step_delay :
                        euclid(max_step_delay, step_delay));
       num_steps[nr] = move_steps;
+
+      if (!is_panel)
+      {
+       max_move_delay_doors_only = MAX(max_move_delay_doors_only, move_delay);
+
+       panel_has_doors[door_index] = TRUE;
+      }
     }
 
+    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;
 
     door_delay_value = max_step_delay;
 
@@ -4249,6 +4221,7 @@ unsigned int MoveDoor(unsigned int door_state)
        int door_token = dpc->door_token;
        int door_index = DOOR_INDEX_FROM_TOKEN(door_token);
        boolean is_panel = DOOR_PART_IS_PANEL(nr);
+       boolean is_panel_and_door_has_closed = FALSE;
        struct Rect *door_rect = &door_rect_list[door_index];
        Bitmap *bitmap_db_door = (door_token == DOOR_1 ? bitmap_db_door_1 :
                                  bitmap_db_door_2);
@@ -4264,7 +4237,9 @@ unsigned int MoveDoor(unsigned int door_state)
        int step_factor = step_delay / max_step_delay;
        int k1 = (step_factor ? k / step_factor + 1 : k);
        int k2 = (part_opening ? k1 + start_step : num_steps[nr] - k1);
-       int kk = (k2 < 0 ? 0 : k2);
+       int kk = MAX(0, k2);
+       int g_src_x = 0;
+       int g_src_y = 0;
        int src_x, src_y, src_xx, src_yy;
        int dst_x, dst_y, dst_xx, dst_yy;
        int width, height;
@@ -4278,6 +4253,16 @@ unsigned int MoveDoor(unsigned int door_state)
        if (!g->bitmap)
          continue;
 
+       if (!is_panel)
+       {
+         int k2_door = (door_opening ? k : num_move_steps_doors_only - k - 1);
+         int kk_door = MAX(0, k2_door);
+         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);
+       }
+
        // draw door panel
 
        if (!door_panel_drawn[door_index])
@@ -4338,22 +4323,21 @@ unsigned int MoveDoor(unsigned int door_state)
          height = g->height - src_yy;
        }
 
-       if (is_panel)
-       {
-         src_x = src_xx;
-         src_y = src_yy;
-       }
-       else
-       {
-         src_x = g->src_x + src_xx;
-         src_y = g->src_y + src_yy;
-       }
+       src_x = g_src_x + src_xx;
+       src_y = g_src_y + src_yy;
 
        dst_x = door_rect->x + dst_xx;
        dst_y = door_rect->y + dst_yy;
 
+       is_panel_and_door_has_closed =
+         (is_panel &&
+          door_closing &&
+          panel_has_doors[door_index] &&
+          k >= num_move_steps_doors_only - 1);
+
        if (width  >= 0 && width  <= g->width &&
-           height >= 0 && height <= g->height)
+           height >= 0 && height <= g->height &&
+           !is_panel_and_door_has_closed)
        {
          if (is_panel || !pos->draw_masked)
            BlitBitmap(bitmap, drawto, src_x, src_y, width, height,
@@ -4370,8 +4354,7 @@ unsigned int MoveDoor(unsigned int door_state)
          door_part_done[nr] = TRUE;
 
        // continue door part animations, but not panel after door has closed
-       if (!door_part_done[nr] &&
-           !(is_panel && door_closing && panel_has_doors[door_index]))
+       if (!door_part_done[nr] && !is_panel_and_door_has_closed)
          door_part_done_all = FALSE;
       }
 
@@ -4411,8 +4394,6 @@ void DrawSpecialEditorDoor()
   int vy = VY - outer_border;
   int exsize = EXSIZE + 2 * outer_border;
 
-  CloseDoor(DOOR_CLOSE_2);
-
   /* draw bigger level editor toolbox window */
   BlitBitmap(gfx1->bitmap, drawto, gfx1->src_x, gfx1->src_y,
             top_border_width, top_border_height, ex, ey - top_border_height);
@@ -6220,7 +6201,7 @@ em_object_mapping_list[] =
   },
   {
     Xalpha_copyr,                      TRUE,   FALSE,
-    EL_CHAR('©'),                      -1, -1
+    EL_CHAR(CHAR_BYTE_COPYRIGHT),      -1, -1
   },
 
   {
@@ -7813,13 +7794,65 @@ 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)
+{
+  static boolean player_was_waiting = TRUE;
+
+  if (!tape.recording)
+    return;
+
+  if (frame == 0 && !any_player_dropping)
+  {
+    if (!player_was_waiting)
+    {
+      SaveEngineSnapshotToList();
+
+      player_was_waiting = TRUE;
+    }
+  }
+  else if (any_player_moving || any_player_snapping || any_player_dropping)
+  {
+    player_was_waiting = FALSE;
+  }
+}
+
+void CheckSaveEngineSnapshot_SP(boolean murphy_is_waiting,
+                               boolean murphy_is_dropping)
+{
+  static boolean player_was_waiting = FALSE;
+
+  if (!tape.recording)
+    return;
+
+  if (murphy_is_waiting)
+  {
+    if (!player_was_waiting)
+    {
+      SaveEngineSnapshotToList();
+
+      player_was_waiting = TRUE;
+    }
+  }
+  else
+  {
+    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,
@@ -7828,6 +7861,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,
@@ -8046,6 +8081,7 @@ void ChangeViewportPropertiesIfNeeded()
   boolean init_video_buffer = FALSE;
   boolean init_gadgets_and_toons = FALSE;
   boolean init_em_graphics = FALSE;
+  boolean drawing_area_changed = FALSE;
 
   if (viewport.window.width  != WIN_XSIZE ||
       viewport.window.height != WIN_YSIZE)
@@ -8098,12 +8134,25 @@ void ChangeViewportPropertiesIfNeeded()
       // 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;
     }
 
+    if (new_sx != SX ||
+       new_sy != SY ||
+       new_sxsize != SXSIZE ||
+       new_sysize != SYSIZE ||
+       new_real_sx != REAL_SX ||
+       new_real_sy != REAL_SY ||
+       new_full_sxsize != FULL_SXSIZE ||
+       new_full_sysize != FULL_SYSIZE)
+    {
+      if (!init_video_buffer)
+       drawing_area_changed = TRUE;
+    }
+
     SX = new_sx;
     SY = new_sy;
     DX = new_dx;
@@ -8144,6 +8193,11 @@ void ChangeViewportPropertiesIfNeeded()
 
     SCR_FIELDX = new_scr_fieldx;
     SCR_FIELDY = new_scr_fieldy;
+
+    gfx.drawing_area_changed = drawing_area_changed;
+
+    SetDrawDeactivationMask(REDRAW_NONE);
+    SetDrawBackgroundMask(REDRAW_FIELD);
   }
 
   if (init_video_buffer)
@@ -8151,9 +8205,6 @@ void ChangeViewportPropertiesIfNeeded()
     // printf("::: init_video_buffer\n");
 
     InitVideoBuffer(WIN_XSIZE, WIN_YSIZE, DEFAULT_DEPTH, setup.fullscreen);
-
-    SetDrawDeactivationMask(REDRAW_NONE);
-    SetDrawBackgroundMask(REDRAW_FIELD);
   }
 
   if (init_gadgets_and_toons)