added support for x/y/width/height parameters for border/background images
[rocksndiamonds.git] / src / tools.c
index c8070fa2365f3349e0e68b3a9785f3c9c218f564..4ebf19bb15cc49e9e0a5f6a37937202d4ddc58dc 100644 (file)
@@ -165,6 +165,14 @@ static struct DoorPartControlInfo door_part_controls[] =
   }
 };
 
+static struct XY xy_topdown[] =
+{
+  {  0, -1 },
+  { -1,  0 },
+  { +1,  0 },
+  {  0, +1 }
+};
+
 
 // forward declaration for internal use
 static void UnmapToolButtons(void);
@@ -767,7 +775,7 @@ void BackToFront(void)
     DrawFramesPerSecond();
 
   // remove playfield redraw before potentially merging with doors redraw
-  if (DrawingDeactivated(REAL_SX, REAL_SY, FULL_SXSIZE, FULL_SYSIZE))
+  if (DrawingDeactivated(REAL_SX, REAL_SY))
     redraw_mask &= ~REDRAW_FIELD;
 
   // redraw complete window if both playfield and (some) doors need redraw
@@ -1095,82 +1103,103 @@ void FadeSkipNextFadeOut(void)
   FadeExt(0, FADE_MODE_SKIP_FADE_OUT, FADE_TYPE_SKIP);
 }
 
-static Bitmap *getBitmapFromGraphicOrDefault(int graphic, int default_graphic)
+static int getGlobalGameStatus(int status)
+{
+  return (status == GAME_MODE_PSEUDO_TYPENAME ? GAME_MODE_MAIN :
+         status == GAME_MODE_SCOREINFO       ? GAME_MODE_SCORES :
+         status);
+}
+
+int getImageFromGraphicOrDefault(int graphic, int default_graphic)
 {
   if (graphic == IMG_UNDEFINED)
-    return NULL;
+    return IMG_UNDEFINED;
 
   boolean redefined = getImageListEntryFromImageID(graphic)->redefined;
 
   return (graphic_info[graphic].bitmap != NULL || redefined ?
-         graphic_info[graphic].bitmap :
-         graphic_info[default_graphic].bitmap);
+         graphic : default_graphic);
 }
 
-static Bitmap *getBackgroundBitmap(int graphic)
+static int getBackgroundImage(int graphic)
 {
-  return getBitmapFromGraphicOrDefault(graphic, IMG_BACKGROUND);
+  return getImageFromGraphicOrDefault(graphic, IMG_BACKGROUND);
 }
 
-static Bitmap *getGlobalBorderBitmap(int graphic)
+static int getGlobalBorderImage(int graphic)
 {
-  return getBitmapFromGraphicOrDefault(graphic, IMG_GLOBAL_BORDER);
+  return getImageFromGraphicOrDefault(graphic, IMG_GLOBAL_BORDER);
 }
 
-Bitmap *getGlobalBorderBitmapFromStatus(int status)
+Bitmap *getGlobalBorderBitmapFromStatus(int status_raw)
 {
+  int status = getGlobalGameStatus(status_raw);
   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 :
+    (status == GAME_MODE_MAIN    ? 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);
+  int graphic_final = getGlobalBorderImage(graphic);
+
+  return graphic_info[graphic_final].bitmap;
+}
 
-  return getGlobalBorderBitmap(graphic);
+void SetBackgroundImage(int graphic, int redraw_mask)
+{
+  struct GraphicInfo *g = &graphic_info[graphic];
+  struct GraphicInfo g_undefined = { 0 };
+
+  if (graphic == IMG_UNDEFINED)
+    g = &g_undefined;
+
+  // remove every mask before setting mask for window, and
+  // remove window area mask before setting mask for main or door area
+  int remove_mask = (redraw_mask == REDRAW_ALL ? 0xffff : REDRAW_ALL);
+
+  // (!!! TO BE FIXED: The whole REDRAW_* system really sucks! !!!)
+  SetBackgroundBitmap(NULL, remove_mask, 0, 0, 0, 0);  // !!! FIX THIS !!!
+  SetBackgroundBitmap(g->bitmap, redraw_mask,
+                     g->src_x, g->src_y,
+                     g->width, g->height);
 }
 
 void SetWindowBackgroundImageIfDefined(int graphic)
 {
   if (graphic_info[graphic].bitmap)
-    SetWindowBackgroundBitmap(graphic_info[graphic].bitmap);
+    SetBackgroundImage(graphic, REDRAW_ALL);
 }
 
 void SetMainBackgroundImageIfDefined(int graphic)
 {
   if (graphic_info[graphic].bitmap)
-    SetMainBackgroundBitmap(graphic_info[graphic].bitmap);
+    SetBackgroundImage(graphic, REDRAW_FIELD);
 }
 
 void SetDoorBackgroundImageIfDefined(int graphic)
 {
   if (graphic_info[graphic].bitmap)
-    SetDoorBackgroundBitmap(graphic_info[graphic].bitmap);
+    SetBackgroundImage(graphic, REDRAW_DOOR_1);
 }
 
 void SetWindowBackgroundImage(int graphic)
 {
-  SetWindowBackgroundBitmap(getBackgroundBitmap(graphic));
+  SetBackgroundImage(getBackgroundImage(graphic), REDRAW_ALL);
 }
 
 void SetMainBackgroundImage(int graphic)
 {
-  SetMainBackgroundBitmap(getBackgroundBitmap(graphic));
+  SetBackgroundImage(getBackgroundImage(graphic), REDRAW_FIELD);
 }
 
 void SetDoorBackgroundImage(int graphic)
 {
-  SetDoorBackgroundBitmap(getBackgroundBitmap(graphic));
+  SetBackgroundImage(getBackgroundImage(graphic), REDRAW_DOOR_1);
 }
 
 void SetPanelBackground(void)
 {
-  struct GraphicInfo *gfx = &graphic_info[IMG_BACKGROUND_PANEL];
-
-  BlitBitmapTiled(gfx->bitmap, bitmap_db_panel, gfx->src_x, gfx->src_y,
-                 gfx->width, gfx->height, 0, 0, DXSIZE, DYSIZE);
-
-  SetDoorBackgroundBitmap(bitmap_db_panel);
+  SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
 }
 
 void DrawBackground(int x, int y, int width, int height)
@@ -1426,7 +1455,7 @@ void FloodFillLevelExt(int start_x, int start_y, int fill_element,
                       int max_fieldx, int max_fieldy)
 {
   static struct XY stack_buffer[MAX_LEV_FIELDX * MAX_LEV_FIELDY];
-  static struct XY check[4] = { { -1, 0 }, { 0, -1 }, { 1, 0 }, { 0, 1 } };
+  struct XY *check = xy_topdown;
   int old_element = field[start_x][start_y];
   int stack_pos = 0;
 
@@ -1477,6 +1506,8 @@ 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)
     sync_frame = FrameCounter;
+  else if (graphic_info[graphic].anim_global_anim_sync)
+    sync_frame = getGlobalAnimSyncFrame();
 
   return getAnimationFrame(graphic_info[graphic].anim_frames,
                           graphic_info[graphic].anim_delay,
@@ -2286,13 +2317,7 @@ static void DrawLevelFieldCrumbledExt(int x, int y, int graphic, int frame)
   int sx = SCREENX(x), sy = SCREENY(y);
   int element;
   int i;
-  static int xy[4][2] =
-  {
-    { 0, -1 },
-    { -1, 0 },
-    { +1, 0 },
-    { 0, +1 }
-  };
+  struct XY *xy = xy_topdown;
 
   if (!IN_LEV_FIELD(x, y))
     return;
@@ -2307,8 +2332,8 @@ static void DrawLevelFieldCrumbledExt(int x, int y, int graphic, int frame)
     // crumble field borders towards direct neighbour fields
     for (i = 0; i < 4; i++)
     {
-      int xx = x + xy[i][0];
-      int yy = y + xy[i][1];
+      int xx = x + xy[i].x;
+      int yy = y + xy[i].y;
 
       element = (IN_LEV_FIELD(xx, yy) ? TILE_GFX_ELEMENT(xx, yy) :
                 BorderElement);
@@ -2342,10 +2367,10 @@ static void DrawLevelFieldCrumbledExt(int x, int y, int graphic, int frame)
     // crumble field borders of direct neighbour fields
     for (i = 0; i < 4; i++)
     {
-      int xx = x + xy[i][0];
-      int yy = y + xy[i][1];
-      int sxx = sx + xy[i][0];
-      int syy = sy + xy[i][1];
+      int xx = x + xy[i].x;
+      int yy = y + xy[i].y;
+      int sxx = sx + xy[i].x;
+      int syy = sy + xy[i].y;
 
       if (!IN_LEV_FIELD(xx, yy) ||
          !IN_SCR_FIELD(sxx, syy))
@@ -2438,22 +2463,16 @@ void DrawLevelFieldCrumbledDigging(int x, int y, int direction,
 void DrawLevelFieldCrumbledNeighbours(int x, int y)
 {
   int sx = SCREENX(x), sy = SCREENY(y);
-  static int xy[4][2] =
-  {
-    { 0, -1 },
-    { -1, 0 },
-    { +1, 0 },
-    { 0, +1 }
-  };
+  struct XY *xy = xy_topdown;
   int i;
 
   // crumble direct neighbour fields (required for field borders)
   for (i = 0; i < 4; i++)
   {
-    int xx = x + xy[i][0];
-    int yy = y + xy[i][1];
-    int sxx = sx + xy[i][0];
-    int syy = sy + xy[i][1];
+    int xx = x + xy[i].x;
+    int yy = y + xy[i].y;
+    int sxx = sx + xy[i].x;
+    int syy = sy + xy[i].y;
 
     if (!IN_LEV_FIELD(xx, yy) ||
        !IN_SCR_FIELD(sxx, syy) ||
@@ -2527,6 +2546,11 @@ void DrawScreenGraphic(int x, int y, int graphic, int frame)
   }
 }
 
+void DrawLevelGraphic(int x, int y, int graphic, int frame)
+{
+  DrawScreenGraphic(SCREENX(x), SCREENY(y), graphic, frame);
+}
+
 void DrawScreenElement(int x, int y, int element)
 {
   int mask_mode = NO_MASKING;
@@ -2874,9 +2898,9 @@ static void AnimateEnvelope(int envelope_nr, int anim_mode, int action)
   int mask_mode = (src_bitmap != NULL ? BLIT_MASKED : BLIT_ON_BACKGROUND);
   boolean ffwd_delay = (tape.playing && tape.fast_forward);
   boolean no_delay = (tape.warp_forward);
-  unsigned int anim_delay = 0;
   int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
   int anim_delay_value = MAX(1, (no_delay ? 0 : frame_delay_value) / 2);
+  DelayCounter anim_delay = { anim_delay_value };
   int font_nr = FONT_ENVELOPE_1 + envelope_nr;
   int font_width = getFontWidth(font_nr);
   int font_height = getFontHeight(font_nr);
@@ -2913,16 +2937,16 @@ static void AnimateEnvelope(int envelope_nr, int anim_mode, int action)
       for (xx = 0; xx < xsize; xx++)
        DrawEnvelopeBackground(graphic, sx, sy, xx, yy, xsize, ysize, font_nr);
 
-    DrawTextBuffer(sx + font_width, sy + font_height,
-                  level.envelope[envelope_nr].text, font_nr, max_xsize,
-                  xsize - 2, ysize - 2, 0, mask_mode,
-                  level.envelope[envelope_nr].autowrap,
-                  level.envelope[envelope_nr].centered, FALSE);
+    DrawTextArea(sx + font_width, sy + font_height,
+                level.envelope[envelope_nr].text, font_nr, max_xsize,
+                xsize - 2, ysize - 2, 0, mask_mode,
+                level.envelope[envelope_nr].autowrap,
+                level.envelope[envelope_nr].centered, FALSE);
 
     redraw_mask |= REDRAW_FIELD;
     BackToFront();
 
-    SkipUntilDelayReached(&anim_delay, anim_delay_value, &i, last_frame);
+    SkipUntilDelayReached(&anim_delay, &i, last_frame);
   }
 
   ClearAutoRepeatKeyEvents();
@@ -2979,6 +3003,16 @@ void ShowEnvelope(int envelope_nr)
   BackToFront();
 }
 
+void ShowEnvelope_MM(int envelope_nr)
+{
+  BlitBitmap(backbuffer, bitmap_db_field, REAL_SX, REAL_SY,
+            FULL_SXSIZE, FULL_SYSIZE, REAL_SX, REAL_SY);
+
+  ShowEnvelope(envelope_nr);
+
+  SetDrawtoField(DRAW_TO_BACKBUFFER);
+}
+
 static void PrepareEnvelopeRequestToScreen(Bitmap *bitmap, int sx, int sy,
                                           int xsize, int ysize)
 {
@@ -3192,7 +3226,7 @@ static void AnimateEnvelopeRequest(int anim_mode, int action)
   boolean no_delay = (tape.warp_forward);
   int delay_value = (ffwd_delay ? delay_value_fast : delay_value_normal);
   int anim_delay_value = MAX(1, (no_delay ? 0 : delay_value + 500 * 0) / 2);
-  unsigned int anim_delay = 0;
+  DelayCounter anim_delay = { anim_delay_value };
 
   int tile_size = MAX(request.step_offset, 1);
   int max_xsize = request.width  / tile_size;
@@ -3265,7 +3299,7 @@ static void AnimateEnvelopeRequest(int anim_mode, int action)
 
     BackToFront();
 
-    SkipUntilDelayReached(&anim_delay, anim_delay_value, &i, last_frame);
+    SkipUntilDelayReached(&anim_delay, &i, last_frame);
   }
 
   ClearAutoRepeatKeyEvents();
@@ -3536,15 +3570,17 @@ static void DrawPreviewLevelInfo(int mode)
 
 static void DrawPreviewLevelExt(boolean restart)
 {
-  static unsigned int scroll_delay = 0;
-  static unsigned int label_delay = 0;
+  static DelayCounter scroll_delay = { 0 };
+  static DelayCounter label_delay = { 0 };
   static int from_x, from_y, scroll_direction;
   static int label_state, label_counter;
-  unsigned int scroll_delay_value = preview.step_delay;
   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);
 
+  scroll_delay.value = preview.step_delay;
+  label_delay.value = MICROLEVEL_LABEL_DELAY;
+
   if (restart)
   {
     from_x = 0;
@@ -3598,7 +3634,7 @@ static void DrawPreviewLevelExt(boolean restart)
   // scroll preview level, if needed
   if (preview.anim_mode != ANIM_NONE &&
       (level_xsize > preview.xsize || level_ysize > preview.ysize) &&
-      DelayReached(&scroll_delay, scroll_delay_value))
+      DelayReached(&scroll_delay))
   {
     switch (scroll_direction)
     {
@@ -3656,7 +3692,7 @@ static void DrawPreviewLevelExt(boolean restart)
   if (!strEqual(level.name, NAMELESS_LEVEL_NAME) &&
       !strEqual(level.author, ANONYMOUS_NAME) &&
       !strEqual(level.author, leveldir_current->name) &&
-      DelayReached(&label_delay, MICROLEVEL_LABEL_DELAY))
+      DelayReached(&label_delay))
   {
     int max_label_counter = 23;
 
@@ -4179,9 +4215,6 @@ static void DrawPlayerExt(struct PlayerInfo *player, int drawing_stage)
       DrawDynamite(last_jx, last_jy);
     else
       DrawLevelField(last_jx, last_jy);
-
-    if (player->is_pushing && IN_SCR_FIELD(SCREENX(next_jx), SCREENY(next_jy)))
-      DrawLevelElement(next_jx, next_jy, EL_EMPTY);
   }
   else if (drawing_stage == DRAW_PLAYER_STAGE_FIELD_UNDER_PLAYER)
   {
@@ -4239,6 +4272,9 @@ static void DrawPlayerExt(struct PlayerInfo *player, int drawing_stage)
     if (!player->is_pushing || !player->is_moving)
       return;
 
+    if (Tile[next_jx][next_jy] == EL_EXPLOSION)
+      return;
+
     int gfx_frame = GfxFrame[jx][jy];
 
     if (!IS_MOVING(jx, jy))            // push movement already finished
@@ -4260,14 +4296,12 @@ static void DrawPlayerExt(struct PlayerInfo *player, int drawing_stage)
        DrawLevelElement(jx, jy, Back[jx][jy]);
       else
        DrawLevelElement(jx, jy, EL_EMPTY);
-
-      if (Back[next_jx][next_jy])
-       DrawLevelElement(next_jx, next_jy, Back[next_jx][next_jy]);
-      else
-       DrawLevelElement(next_jx, next_jy, EL_EMPTY);
     }
-    else if (Back[next_jx][next_jy])
+
+    if (Back[next_jx][next_jy])
       DrawLevelElement(next_jx, next_jy, Back[next_jx][next_jy]);
+    else
+      DrawLevelElement(next_jx, next_jy, EL_EMPTY);
 
     int px = SCREENX(jx), py = SCREENY(jy);
     int pxx = (TILEX - ABS(sxx)) * dx;
@@ -4593,7 +4627,7 @@ static int RequestHandleEvents(unsigned int req_state, int draw_buffer_game)
 
          case EVENT_KEYPRESS:
          {
-           Key key = GetEventKey((KeyEvent *)&event, TRUE);
+           Key key = GetEventKey((KeyEvent *)&event);
 
            switch (key)
            {
@@ -5340,8 +5374,7 @@ unsigned int MoveDoor(unsigned int door_state)
   };
   static int door1 = DOOR_CLOSE_1;
   static int door2 = DOOR_CLOSE_2;
-  unsigned int door_delay = 0;
-  unsigned int door_delay_value;
+  DelayCounter door_delay = { 0 };
   int i;
 
   if (door_state == DOOR_GET_STATE)
@@ -5456,7 +5489,7 @@ unsigned int MoveDoor(unsigned int door_state)
     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;
+    door_delay.value = max_step_delay;
 
     if ((door_state & DOOR_NO_DELAY) || setup.quick_doors)
     {
@@ -5539,7 +5572,7 @@ unsigned int MoveDoor(unsigned int door_state)
        {
          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 sync_frame = kk_door * door_delay.value;
          int frame = getGraphicAnimationFrame(dpc->graphic, sync_frame);
 
          getFixedGraphicSource(dpc->graphic, frame, &bitmap,
@@ -5648,7 +5681,7 @@ unsigned int MoveDoor(unsigned int door_state)
       {
        BackToFront();
 
-       SkipUntilDelayReached(&door_delay, door_delay_value, &k, last_frame);
+       SkipUntilDelayReached(&door_delay, &k, last_frame);
 
        // prevent OS (Windows) from complaining about program not responding
        CheckQuitEvent();
@@ -5662,13 +5695,13 @@ unsigned int MoveDoor(unsigned int door_state)
     {
       // wait for specified door action post delay
       if (door_state & DOOR_ACTION_1 && door_state & DOOR_ACTION_2)
-       door_delay_value = MAX(door_1.post_delay, door_2.post_delay);
+       door_delay.value = MAX(door_1.post_delay, door_2.post_delay);
       else if (door_state & DOOR_ACTION_1)
-       door_delay_value = door_1.post_delay;
+       door_delay.value = door_1.post_delay;
       else if (door_state & DOOR_ACTION_2)
-       door_delay_value = door_2.post_delay;
+       door_delay.value = door_2.post_delay;
 
-      while (!DelayReached(&door_delay, door_delay_value))
+      while (!DelayReached(&door_delay))
        BackToFront();
     }
   }
@@ -5845,6 +5878,10 @@ void CreateToolButtons(void)
     int y = pos->y;
     int id = i;
 
+    // do not use touch buttons if overlay touch buttons are disabled
+    if (is_touch_button && !setup.touch.overlay_buttons)
+      continue;
+
     if (global.use_envelope_request && !is_touch_button)
     {
       setRequestPosition(&base_x, &base_y, TRUE);
@@ -5881,7 +5918,8 @@ void CreateToolButtons(void)
       }
     }
 
-    if (id >= TOOL_CTRL_ID_PLAYER_1 && id <= TOOL_CTRL_ID_PLAYER_4)
+    if (id >= TOOL_CTRL_ID_PLAYER_1 && id <= TOOL_CTRL_ID_PLAYER_4 &&
+       pos->draw_player)
     {
       int player_nr = id - TOOL_CTRL_ID_PLAYER_1;
 
@@ -8190,10 +8228,6 @@ int map_element_RND_to_MM(int element_rnd)
          element_rnd <= EL_MM_RUNTIME_END ?
          EL_MM_RUNTIME_START_NATIVE + element_rnd - EL_MM_RUNTIME_START :
 
-         element_rnd >= EL_MM_DUMMY_START &&
-         element_rnd <= EL_MM_DUMMY_END ?
-         EL_MM_DUMMY_START_NATIVE + element_rnd - EL_MM_DUMMY_START :
-
          EL_MM_EMPTY_NATIVE);
 }
 
@@ -8219,10 +8253,6 @@ int map_element_MM_to_RND(int element_mm)
          element_mm <= EL_MM_RUNTIME_END_NATIVE ?
          EL_MM_RUNTIME_START + element_mm - EL_MM_RUNTIME_START_NATIVE :
 
-         element_mm >= EL_MM_DUMMY_START_NATIVE &&
-         element_mm <= EL_MM_DUMMY_END_NATIVE ?
-         EL_MM_DUMMY_START + element_mm - EL_MM_DUMMY_START_NATIVE :
-
          EL_EMPTY);
 }
 
@@ -8316,6 +8346,11 @@ int el2img_mm(int element_mm)
   return el2img(map_element_MM_to_RND(element_mm));
 }
 
+int el_act2img_mm(int element_mm, int action)
+{
+  return el_act2img(map_element_MM_to_RND(element_mm), action);
+}
+
 int el_act_dir2img(int element, int action, int direction)
 {
   element = GFX_ELEMENT(element);
@@ -8773,6 +8808,8 @@ void SetGfxAnimation_EM(struct GraphicInfo_EM *g_em,
 
   if (graphic_info[graphic].anim_global_sync)
     sync_frame = FrameCounter;
+  else if (graphic_info[graphic].anim_global_anim_sync)
+    sync_frame = getGlobalAnimSyncFrame();
   else if (IN_FIELD(x, y, MAX_LEV_FIELDX, MAX_LEV_FIELDY))
     sync_frame = GfxFrame[x][y];
   else
@@ -8832,6 +8869,8 @@ void getGraphicSourceObjectExt_EM(struct GraphicInfo_EM *g_em,
 
   if (graphic_info[graphic].anim_global_sync)
     sync_frame = FrameCounter;
+  else if (graphic_info[graphic].anim_global_anim_sync)
+    sync_frame = getGlobalAnimSyncFrame();
   else if (IN_FIELD(x, y, MAX_LEV_FIELDX, MAX_LEV_FIELDY))
     sync_frame = GfxFrame[x][y];
   else
@@ -9295,7 +9334,7 @@ void InitGraphicInfo_EM(void)
   }
 }
 
-static void CheckSaveEngineSnapshot_EM(byte action[MAX_PLAYERS], int frame,
+static void CheckSaveEngineSnapshot_EM(int frame,
                                       boolean any_player_moving,
                                       boolean any_player_snapping,
                                       boolean any_player_dropping)
@@ -9352,7 +9391,7 @@ static void CheckSaveEngineSnapshot_MM(boolean element_clicked,
   }
 }
 
-boolean CheckSingleStepMode_EM(byte action[MAX_PLAYERS], int frame,
+boolean CheckSingleStepMode_EM(int frame,
                               boolean any_player_moving,
                               boolean any_player_snapping,
                               boolean any_player_dropping)
@@ -9361,7 +9400,7 @@ boolean CheckSingleStepMode_EM(byte action[MAX_PLAYERS], int frame,
     if (frame == 7 && !any_player_dropping && FrameCounter > 6)
       TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
 
-  CheckSaveEngineSnapshot_EM(action, frame, any_player_moving,
+  CheckSaveEngineSnapshot_EM(frame, any_player_moving,
                             any_player_snapping, any_player_dropping);
 
   return tape.pausing;
@@ -9395,7 +9434,7 @@ void CheckSingleStepMode_MM(boolean element_clicked,
 }
 
 void getGraphicSource_SP(struct GraphicInfo_SP *g_sp,
-                        int graphic, int sync_frame, int x, int y)
+                        int graphic, int sync_frame)
 {
   int frame = getGraphicAnimationFrame(graphic, sync_frame);
 
@@ -9412,6 +9451,17 @@ int getGraphicInfo_Delay(int graphic)
   return graphic_info[graphic].anim_delay;
 }
 
+boolean getGraphicInfo_NewFrame(int x, int y, int graphic)
+{
+  if (!IS_NEW_FRAME(GfxFrame[x][y], graphic))
+    return FALSE;
+
+  if (ANIM_MODE(graphic) & (ANIM_TILED | ANIM_RANDOM_STATIC))
+    return FALSE;
+
+  return TRUE;
+}
+
 void PlayMenuSoundExt(int sound)
 {
   if (sound == SND_UNDEFINED)
@@ -9734,9 +9784,9 @@ void ChangeViewportPropertiesIfNeeded(void)
 {
   boolean use_mini_tilesize = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
                               FALSE : setup.small_game_graphics);
-  int gfx_game_mode = game_status;
-  int gfx_game_mode2 = (game_status == GAME_MODE_EDITOR ? GAME_MODE_DEFAULT :
-                       game_status);
+  int gfx_game_mode = getGlobalGameStatus(game_status);
+  int gfx_game_mode2 = (gfx_game_mode == GAME_MODE_EDITOR ? GAME_MODE_DEFAULT :
+                       gfx_game_mode);
   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];
@@ -9949,12 +9999,28 @@ void ChangeViewportPropertiesIfNeeded(void)
   }
 }
 
+void OpenURL(char *url)
+{
+#if SDL_VERSION_ATLEAST(2,0,14)
+  SDL_OpenURL(url);
+#else
+  Warn("SDL_OpenURL(\"%s\") not supported by SDL %d.%d.%d!",
+       url, SDL_MAJOR_VERSION, SDL_MINOR_VERSION, SDL_PATCHLEVEL);
+  Warn("Please upgrade to at least SDL 2.0.14 for URL support!");
+#endif
+}
+
+void OpenURLFromHash(SetupFileHash *hash, int hash_key)
+{
+  OpenURL(getHashEntry(hash, int2str(hash_key, 0)));
+}
+
 
 // ============================================================================
 // tests
 // ============================================================================
 
-#if defined(PLATFORM_WIN32)
+#if defined(PLATFORM_WINDOWS)
 /* FILETIME of Jan 1 1970 00:00:00. */
 static const unsigned __int64 epoch = ((unsigned __int64) 116444736000000000ULL);
 
@@ -10008,7 +10074,7 @@ static char *test_init_uuid_random_function_better(void)
   return seed_text;
 }
 
-#if defined(PLATFORM_WIN32)
+#if defined(PLATFORM_WINDOWS)
 static char *test_init_uuid_random_function_better_windows(void)
 {
   static char seed_text[100];
@@ -10036,7 +10102,7 @@ static unsigned int test_uuid_random_function_better(int max)
   return (max > 0 ? prng_get_uint() % max : 0);
 }
 
-#if defined(PLATFORM_WIN32)
+#if defined(PLATFORM_WINDOWS)
 #define NUM_UUID_TESTS                 3
 #else
 #define NUM_UUID_TESTS                 2
@@ -10063,7 +10129,7 @@ static void TestGeneratingUUIDs_RunTest(int nr, int always_seed, int num_uuids)
      test_uuid_random_function_better);
   int xpos = 40;
 
-#if defined(PLATFORM_WIN32)
+#if defined(PLATFORM_WINDOWS)
   if (nr == 2)
   {
     random_name = "windows";