added optional button to restart game (door, panel and touch variants)
[rocksndiamonds.git] / src / game_mm / mm_game.c
index e3437a7c64cd88faa0f8eaca3b2c39f47ba58ee8..b258e374b7f0bbeb868be9b873e831e331394af0 100644 (file)
@@ -25,9 +25,8 @@
 
 // values for Explode_MM()
 #define EX_PHASE_START         0
-#define EX_NORMAL              0
-#define EX_KETTLE              1
-#define EX_SHORT               2
+#define EX_TYPE_NONE           0
+#define EX_TYPE_NORMAL         (1 << 0)
 
 // special positions in the game control window (relative to control window)
 #define XX_LEVEL               36
@@ -98,7 +97,22 @@ static void RaiseScoreElement_MM(int);
 static void RemoveMovingField_MM(int, int);
 static void InitMovingField_MM(int, int, int);
 static void ContinueMoving_MM(int, int);
-static void Moving2Blocked_MM(int, int, int *, int *);
+
+static void AddLaserEdge(int, int);
+static void ScanLaser(void);
+static void DrawLaser(int, int);
+static boolean HitElement(int, int);
+static boolean HitOnlyAnEdge(int);
+static boolean HitPolarizer(int, int);
+static boolean HitBlock(int, int);
+static boolean HitLaserSource(int, int);
+static boolean HitLaserDestination(int, int);
+static boolean HitReflectingWalls(int, int);
+static boolean HitAbsorbingWalls(int, int);
+static void RotateMirror(int, int, int);
+static boolean ObjHit(int, int, int);
+static void DeletePacMan(int, int);
+static void MovePacMen(void);
 
 // bitmap for laser beam detection
 static Bitmap *laser_bitmap = NULL;
@@ -111,13 +125,31 @@ static int hold_x = -1, hold_y = -1;
 static int pacman_nr = -1;
 
 // various game engine delay counters
-static unsigned int rotate_delay = 0;
-static unsigned int pacman_delay = 0;
-static unsigned int energy_delay = 0;
-static unsigned int overload_delay = 0;
+static DelayCounter rotate_delay = { AUTO_ROTATE_DELAY };
+static DelayCounter pacman_delay = { PACMAN_MOVE_DELAY };
+static DelayCounter energy_delay = { ENERGY_DELAY };
+static DelayCounter overload_delay = { 0 };
+
+// element mask positions for scanning pixels of MM elements
+#define MM_MASK_MCDUFFIN_RIGHT 0
+#define MM_MASK_MCDUFFIN_UP    1
+#define MM_MASK_MCDUFFIN_LEFT  2
+#define MM_MASK_MCDUFFIN_DOWN  3
+#define MM_MASK_GRID_1         4
+#define MM_MASK_GRID_2         5
+#define MM_MASK_GRID_3         6
+#define MM_MASK_GRID_4         7
+#define MM_MASK_SLOPE_1                8
+#define MM_MASK_SLOPE_2                9
+#define MM_MASK_SLOPE_3                10
+#define MM_MASK_SLOPE_4                11
+#define MM_MASK_RECTANGLE      12
+#define MM_MASK_CIRCLE         13
+
+#define NUM_MM_MASKS           14
 
 // element masks for scanning pixels of MM elements
-static const char mm_masks[10][16][16 + 1] =
+static const char mm_masks[NUM_MM_MASKS][16][16 + 1] =
 {
   {
     "                ",
@@ -263,6 +295,78 @@ static const char mm_masks[10][16][16 + 1] =
     "    XXX  XXXX   ",
     "     XX  XXXXX  ",
   },
+  {
+    "               X",
+    "              XX",
+    "             XXX",
+    "            XXXX",
+    "           XXXXX",
+    "          XXXXXX",
+    "         XXXXXXX",
+    "        XXXXXXXX",
+    "       XXXXXXXXX",
+    "      XXXXXXXXXX",
+    "     XXXXXXXXXXX",
+    "    XXXXXXXXXXXX",
+    "   XXXXXXXXXXXXX",
+    "  XXXXXXXXXXXXXX",
+    " XXXXXXXXXXXXXXX",
+    "XXXXXXXXXXXXXXXX",
+  },
+  {
+    "X               ",
+    "XX              ",
+    "XXX             ",
+    "XXXX            ",
+    "XXXXX           ",
+    "XXXXXX          ",
+    "XXXXXXX         ",
+    "XXXXXXXX        ",
+    "XXXXXXXXX       ",
+    "XXXXXXXXXX      ",
+    "XXXXXXXXXXX     ",
+    "XXXXXXXXXXXX    ",
+    "XXXXXXXXXXXXX   ",
+    "XXXXXXXXXXXXXX  ",
+    "XXXXXXXXXXXXXXX ",
+    "XXXXXXXXXXXXXXXX",
+  },
+  {
+    "XXXXXXXXXXXXXXXX",
+    "XXXXXXXXXXXXXXX ",
+    "XXXXXXXXXXXXXX  ",
+    "XXXXXXXXXXXXX   ",
+    "XXXXXXXXXXXX    ",
+    "XXXXXXXXXXX     ",
+    "XXXXXXXXXX      ",
+    "XXXXXXXXX       ",
+    "XXXXXXXX        ",
+    "XXXXXXX         ",
+    "XXXXXX          ",
+    "XXXXX           ",
+    "XXXX            ",
+    "XXX             ",
+    "XX              ",
+    "X               ",
+  },
+  {
+    "XXXXXXXXXXXXXXXX",
+    " XXXXXXXXXXXXXXX",
+    "  XXXXXXXXXXXXXX",
+    "   XXXXXXXXXXXXX",
+    "    XXXXXXXXXXXX",
+    "     XXXXXXXXXXX",
+    "      XXXXXXXXXX",
+    "       XXXXXXXXX",
+    "        XXXXXXXX",
+    "         XXXXXXX",
+    "          XXXXXX",
+    "           XXXXX",
+    "            XXXX",
+    "             XXX",
+    "              XX",
+    "               X",
+  },
   {
     "XXXXXXXXXXXXXXXX",
     "XXXXXXXXXXXXXXXX",
@@ -310,6 +414,8 @@ static int get_element_angle(int element)
       IS_LASER(element) ||
       IS_RECEIVER(element))
     return 4 * element_phase;
+  else if (IS_DF_SLOPE(element))
+    return 4 + (element_phase % 2) * 8;
   else
     return element_phase;
 }
@@ -335,7 +441,7 @@ static void DrawLaserLines(struct XY *points, int num_points, int mode)
   Pixel pixel_drawto = (mode == DL_LASER_ENABLED ? pen_ray     : pen_bg);
   Pixel pixel_buffer = (mode == DL_LASER_ENABLED ? WHITE_PIXEL : BLACK_PIXEL);
 
-  DrawLines(drawto, points, num_points, pixel_drawto);
+  DrawLines(drawto_mm, points, num_points, pixel_drawto);
 
   BEGIN_NO_HEADLESS
   {
@@ -411,6 +517,20 @@ static void CheckExitMM(void)
     PlayLevelSound_MM(exit_x, exit_y, exit_element, MM_ACTION_OPENING);
 }
 
+static void SetLaserColor(int brightness)
+{
+  int color_min = 0x00;
+  int color_max = brightness;          // (0x00 <= brightness <= 0xFF)
+  int color_up   = color_max * laser.overload_value / MAX_LASER_OVERLOAD;
+  int color_down = color_max - color_up;
+
+  pen_ray =
+    GetPixelFromRGB(window,
+                   (game_mm.laser_red   ? color_max  : color_up),
+                   (game_mm.laser_green ? color_down : color_min),
+                   (game_mm.laser_blue  ? color_down : color_min));
+}
+
 static void InitMovDir_MM(int x, int y)
 {
   int element = Tile[x][y];
@@ -448,7 +568,7 @@ static void InitField(int x, int y, boolean init_game)
 
     case EL_KETTLE:
     case EL_CELL:
-      if (native_mm_level.auto_count_kettles)
+      if (init_game && native_mm_level.auto_count_kettles)
        game_mm.kettles_still_needed++;
       break;
 
@@ -508,9 +628,27 @@ static void InitField(int x, int y, boolean init_game)
       }
       else if (IS_MCDUFFIN(element) || IS_LASER(element))
       {
-       laser.start_edge.x = x;
-       laser.start_edge.y = y;
-       laser.start_angle = get_element_angle(element);
+       if (init_game)
+       {
+         laser.start_edge.x = x;
+         laser.start_edge.y = y;
+         laser.start_angle = get_element_angle(element);
+       }
+
+        if (IS_MCDUFFIN(element))
+        {
+          game_mm.laser_red   = native_mm_level.mm_laser_red;
+          game_mm.laser_green = native_mm_level.mm_laser_green;
+          game_mm.laser_blue  = native_mm_level.mm_laser_blue;
+        }
+        else
+        {
+          game_mm.laser_red   = native_mm_level.df_laser_red;
+          game_mm.laser_green = native_mm_level.df_laser_green;
+          game_mm.laser_blue  = native_mm_level.df_laser_blue;
+        }
+
+       game_mm.has_mcduffin = (IS_MCDUFFIN(element));
       }
 
       break;
@@ -537,7 +675,6 @@ static void InitCycleElements_RotateSingleStep(void)
 
     Tile[x][y] = next_element;
 
-    DrawField_MM(x, y);
     game_mm.cycle[i].steps -= step;
   }
 }
@@ -574,10 +711,7 @@ static void InitLaser(void)
 
   AddLaserEdge(LX, LY);                // set laser starting edge
 
-  pen_ray = GetPixelFromRGB(window,
-                           native_mm_level.laser_red   * 0xFF,
-                           native_mm_level.laser_green * 0xFF,
-                           native_mm_level.laser_blue  * 0xFF);
+  SetLaserColor(0xFF);
 }
 
 void InitGameEngine_MM(void)
@@ -587,8 +721,8 @@ void InitGameEngine_MM(void)
   BEGIN_NO_HEADLESS
   {
     // initialize laser bitmap to current playfield (screen) size
-    ReCreateBitmap(&laser_bitmap, drawto->width, drawto->height);
-    ClearRectangle(laser_bitmap, 0, 0, drawto->width, drawto->height);
+    ReCreateBitmap(&laser_bitmap, drawto_mm->width, drawto_mm->height);
+    ClearRectangle(laser_bitmap, 0, 0, drawto_mm->width, drawto_mm->height);
   }
   END_NO_HEADLESS
 
@@ -602,10 +736,17 @@ void InitGameEngine_MM(void)
     (native_mm_level.auto_count_kettles ? 0 : native_mm_level.kettles_needed);
   game_mm.lights_still_needed = 0;
   game_mm.num_keys = 0;
+  game_mm.ball_choice_pos = 0;
+
+  game_mm.laser_red = FALSE;
+  game_mm.laser_green = FALSE;
+  game_mm.laser_blue = TRUE;
+  game_mm.has_mcduffin = TRUE;
 
   game_mm.level_solved = FALSE;
   game_mm.game_over = FALSE;
   game_mm.game_over_cause = 0;
+  game_mm.game_over_message = NULL;
 
   game_mm.laser_overload_value = 0;
   game_mm.laser_enabled = FALSE;
@@ -624,6 +765,9 @@ void InitGameEngine_MM(void)
   laser.fuse_x = laser.fuse_y = -1;
 
   laser.dest_element = EL_EMPTY;
+  laser.dest_element_last = EL_EMPTY;
+  laser.dest_element_last_x = -1;
+  laser.dest_element_last_y = -1;
   laser.wall_mask = 0;
 
   last_LX = 0;
@@ -637,10 +781,10 @@ void InitGameEngine_MM(void)
 
   CT = Ct = 0;
 
-  rotate_delay = 0;
-  pacman_delay = 0;
-  energy_delay = 0;
-  overload_delay = 0;
+  rotate_delay.count = 0;
+  pacman_delay.count = 0;
+  energy_delay.count = 0;
+  overload_delay.count = 0;
 
   ClickElement(-1, -1, -1);
 
@@ -653,7 +797,6 @@ void InitGameEngine_MM(void)
       Angle[x][y] = 0;
       MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
       Store[x][y] = Store2[x][y] = 0;
-      Frame[x][y] = 0;
       Stop[x][y] = FALSE;
 
       InitField(x, y, TRUE);
@@ -691,16 +834,27 @@ void InitGameActions_MM(void)
       cycle_steps_done++;
     }
 
-    BackToFront();
+    AdvanceFrameCounter();
+    AdvanceGfxFrame();
 
-    ColorCycling();
+    if (PendingEscapeKeyEvent())
+      continue;
 
 #ifdef DEBUG
     if (setup.quick_doors)
       continue;
 #endif
+
+    DrawLevel_MM();
+
+    BackToFront_MM();
   }
 
+#ifdef DEBUG
+  if (setup.quick_doors)
+    DrawLevel_MM();
+#endif
+
   ScanLaser();
 
   if (game_mm.kettles_still_needed == 0)
@@ -708,14 +862,64 @@ void InitGameActions_MM(void)
 
   SetTileCursorXY(laser.start_edge.x, laser.start_edge.y);
   SetTileCursorActive(TRUE);
+
+  // restart all delay counters after initially cycling game elements
+  ResetFrameCounter(&rotate_delay);
+  ResetFrameCounter(&pacman_delay);
+  ResetFrameCounter(&energy_delay);
+  ResetFrameCounter(&overload_delay);
+}
+
+static void FadeOutLaser(void)
+{
+  int i;
+
+  for (i = 15; i >= 0; i--)
+  {
+    SetLaserColor(0x11 * i);
+
+    DrawLaser(0, DL_LASER_ENABLED);
+
+    BackToFront_MM();
+    Delay_WithScreenUpdates(50);
+  }
+
+  DrawLaser(0, DL_LASER_DISABLED);
+
+  StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
+}
+
+static void GameOver_MM(int game_over_cause)
+{
+  game_mm.game_over = TRUE;
+  game_mm.game_over_cause = game_over_cause;
+  game_mm.game_over_message = (game_mm.has_mcduffin ?
+                              (game_over_cause == GAME_OVER_BOMB ?
+                               "Bomb killed Mc Duffin!" :
+                               game_over_cause == GAME_OVER_NO_ENERGY ?
+                               "Out of magic energy!" :
+                               game_over_cause == GAME_OVER_OVERLOADED ?
+                               "Magic spell hit Mc Duffin!" :
+                               NULL) :
+                              (game_over_cause == GAME_OVER_BOMB ?
+                               "Bomb destroyed laser cannon!" :
+                               game_over_cause == GAME_OVER_NO_ENERGY ?
+                               "Out of laser energy!" :
+                               game_over_cause == GAME_OVER_OVERLOADED ?
+                               "Laser beam hit laser cannon!" :
+                               NULL));
+
+  SetTileCursorActive(FALSE);
 }
 
-void AddLaserEdge(int lx, int ly)
+static void AddLaserEdge(int lx, int ly)
 {
-  int clx = dSX + lx;
-  int cly = dSY + ly;
+  int full_sxsize = MAX(FULL_SXSIZE, lev_fieldx * TILEX);
+  int full_sysize = MAX(FULL_SYSIZE, lev_fieldy * TILEY);
 
-  if (clx < -2 || cly < -2 || clx >= SXSIZE + 2 || cly >= SYSIZE + 2)
+  // check if laser is still inside visible playfield area (or inside level)
+  if (cSX + lx < REAL_SX || cSX + lx >= REAL_SX + full_sxsize ||
+      cSY + ly < REAL_SY || cSY + ly >= REAL_SY + full_sysize)
   {
     Warn("AddLaserEdge: out of bounds: %d, %d", lx, ly);
 
@@ -729,8 +933,15 @@ void AddLaserEdge(int lx, int ly)
   laser.redraw = TRUE;
 }
 
-void AddDamagedField(int ex, int ey)
+static void AddDamagedField(int ex, int ey)
 {
+  // prevent adding the same field position again
+  if (laser.num_damages > 0 &&
+      laser.damage[laser.num_damages - 1].x == ex &&
+      laser.damage[laser.num_damages - 1].y == ey &&
+      laser.damage[laser.num_damages - 1].edge == laser.num_edges)
+    return;
+
   laser.damage[laser.num_damages].is_mirror = FALSE;
   laser.damage[laser.num_damages].angle = laser.current_angle;
   laser.damage[laser.num_damages].edge = laser.num_edges;
@@ -756,14 +967,33 @@ static boolean StepBehind(void)
 
 static int getMaskFromElement(int element)
 {
-  if (IS_GRID(element))
-    return IMG_MM_MASK_GRID_1 + get_element_phase(element);
-  else if (IS_MCDUFFIN(element))
-    return IMG_MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element);
-  else if (IS_RECTANGLE(element) || IS_DF_GRID(element))
-    return IMG_MM_MASK_RECTANGLE;
+  if (IS_MCDUFFIN(element))
+    return MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element);
+  else if (IS_GRID(element))
+    return MM_MASK_GRID_1 + get_element_phase(element);
+  else if (IS_DF_GRID(element))
+    return MM_MASK_RECTANGLE;
+  else if (IS_DF_SLOPE(element))
+    return MM_MASK_SLOPE_1 + get_element_phase(element);
+  else if (IS_RECTANGLE(element))
+    return MM_MASK_RECTANGLE;
   else
-    return IMG_MM_MASK_CIRCLE;
+    return MM_MASK_CIRCLE;
+}
+
+static int getPixelFromMask(int pos, int dx, int dy)
+{
+  return (mm_masks[pos][dy / 2][dx / 2] == 'X' ? 1 : 0);
+}
+
+static int getLevelFromLaserX(int x)
+{
+  return x / TILEX - (x < 0 ? 1 : 0);          // correct negative values
+}
+
+static int getLevelFromLaserY(int y)
+{
+  return y / TILEY - (y < 0 ? 1 : 0);          // correct negative values
 }
 
 static int ScanPixel(void)
@@ -771,8 +1001,8 @@ static int ScanPixel(void)
   int hit_mask = 0;
 
 #if 0
-  printf("ScanPixel: start scanning at (%d, %d) [%d, %d] [%d, %d]\n",
-        LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
+  Debug("game:mm:ScanPixel", "start scanning at (%d, %d) [%d, %d] [%d, %d]",
+       LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
 #endif
 
   // follow laser beam until it hits something (at least the screen border)
@@ -785,20 +1015,40 @@ static int ScanPixel(void)
     if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
        SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
     {
-      printf("ScanPixel: touched screen border!\n");
+      Debug("game:mm:ScanPixel", "touched screen border!");
 
       return HIT_MASK_ALL;
     }
 #endif
 
+    // check if laser scan has crossed element boundaries (not just mini tiles)
+    boolean cross_x = (LX / TILEX != (LX + 2) / TILEX);
+    boolean cross_y = (LY / TILEY != (LY + 2) / TILEY);
+
+    if (cross_x && cross_y)
+    {
+      int elx1 = (LX - XS) / TILEX;
+      int ely1 = (LY + YS) / TILEY;
+      int elx2 = (LX + XS) / TILEX;
+      int ely2 = (LY - YS) / TILEY;
+
+      // add element corners left and right from the laser beam to damage list
+
+      if (IN_LEV_FIELD(elx1, ely1) && Tile[elx1][ely1] != EL_EMPTY)
+       AddDamagedField(elx1, ely1);
+
+      if (IN_LEV_FIELD(elx2, ely2) && Tile[elx2][ely2] != EL_EMPTY)
+       AddDamagedField(elx2, ely2);
+    }
+
     for (i = 0; i < 4; i++)
     {
       int px = LX + (i % 2) * 2;
       int py = LY + (i / 2) * 2;
       int dx = px % TILEX;
       int dy = py % TILEY;
-      int lx = (px + TILEX) / TILEX - 1;  // ...+TILEX...-1 to get correct
-      int ly = (py + TILEY) / TILEY - 1;  // negative values!
+      int lx = getLevelFromLaserX(px);
+      int ly = getLevelFromLaserY(py);
       Pixel pixel;
 
       if (IN_LEV_FIELD(lx, ly))
@@ -817,13 +1067,14 @@ static int ScanPixel(void)
        }
        else
        {
-         int pos = getMaskFromElement(element) - IMG_MM_MASK_MCDUFFIN_RIGHT;
+         int pos = getMaskFromElement(element);
 
-         pixel = (mm_masks[pos][dy / 2][dx / 2] == 'X' ? 1 : 0);
+         pixel = getPixelFromMask(pos, dx, dy);
        }
       }
       else
       {
+       // check if laser is still inside visible playfield area
        pixel = (cSX + px < REAL_SX || cSX + px >= REAL_SX + FULL_SXSIZE ||
                 cSY + py < REAL_SY || cSY + py >= REAL_SY + FULL_SYSIZE);
       }
@@ -843,23 +1094,55 @@ static int ScanPixel(void)
   return hit_mask;
 }
 
-void ScanLaser(void)
+static void DeactivateLaserTargetElement(void)
 {
-  int element;
+  if (laser.dest_element_last == EL_BOMB_ACTIVE ||
+      laser.dest_element_last == EL_MINE_ACTIVE ||
+      laser.dest_element_last == EL_GRAY_BALL_ACTIVE ||
+      laser.dest_element_last == EL_GRAY_BALL_OPENING)
+  {
+    int x = laser.dest_element_last_x;
+    int y = laser.dest_element_last_y;
+    int element = laser.dest_element_last;
+
+    if (Tile[x][y] == element)
+      Tile[x][y] = (element == EL_BOMB_ACTIVE ? EL_BOMB :
+                   element == EL_MINE_ACTIVE ? EL_MINE : EL_GRAY_BALL);
+
+    if (Tile[x][y] == EL_GRAY_BALL)
+      MovDelay[x][y] = 0;
+
+    laser.dest_element_last = EL_EMPTY;
+    laser.dest_element_last_x = -1;
+    laser.dest_element_last_y = -1;
+  }
+}
+
+static void ScanLaser(void)
+{
+  int element = EL_EMPTY;
+  int last_element = EL_EMPTY;
   int end = 0, rf = laser.num_edges;
 
   // do not scan laser again after the game was lost for whatever reason
   if (game_mm.game_over)
     return;
 
+  // do not scan laser if fuse is off
+  if (laser.fuse_off)
+    return;
+
+  DeactivateLaserTargetElement();
+
   laser.overloaded = FALSE;
   laser.stops_inside_element = FALSE;
 
   DrawLaser(0, DL_LASER_ENABLED);
 
 #if 0
-  printf("Start scanning with LX == %d, LY == %d, XS == %d, YS == %d\n",
-        LX, LY, XS, YS);
+  Debug("game:mm:ScanLaser",
+       "Start scanning with LX == %d, LY == %d, XS == %d, YS == %d",
+       LX, LY, XS, YS);
 #endif
 
   while (1)
@@ -877,71 +1160,133 @@ void ScanLaser(void)
     hit_mask = ScanPixel();
 
 #if 0
-    printf("Hit something at LX == %d, LY == %d, XS == %d, YS == %d\n",
-          LX, LY, XS, YS);
+    Debug("game:mm:ScanLaser",
+         "Hit something at LX == %d, LY == %d, XS == %d, YS == %d",
+         LX, LY, XS, YS);
 #endif
 
-    // hit something -- check out what it was
-    ELX = (LX + XS) / TILEX;
-    ELY = (LY + YS) / TILEY;
+    // check if laser scan has hit two diagonally adjacent element corners
+    boolean diag_1 = ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1);
+    boolean diag_2 = ((hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2);
+
+    // check if laser scan has crossed element boundaries (not just mini tiles)
+    boolean cross_x = (getLevelFromLaserX(LX) != getLevelFromLaserX(LX + 2));
+    boolean cross_y = (getLevelFromLaserY(LY) != getLevelFromLaserY(LY + 2));
+
+    if (cross_x || cross_y)
+    {
+      // hit something at next tile -- check out what it was
+      ELX = getLevelFromLaserX(LX + XS);
+      ELY = getLevelFromLaserY(LY + YS);
+    }
+    else
+    {
+      // hit something at same tile -- check out what it was
+      ELX = getLevelFromLaserX(LX);
+      ELY = getLevelFromLaserY(LY);
+    }
 
 #if 0
-    printf("hit_mask (1) == '%x' (%d, %d) (%d, %d)\n",
-          hit_mask, LX, LY, ELX, ELY);
+    Debug("game:mm:ScanLaser", "hit_mask (1) == '%x' (%d, %d) (%d, %d)",
+         hit_mask, LX, LY, ELX, ELY);
 #endif
 
-    if (!IN_LEV_FIELD(ELX, ELY) || !IN_PIX_FIELD(LX, LY))
+    if (!IN_LEV_FIELD(ELX, ELY))
     {
+      // laser next step position
+      int x = cSX + LX + XS;
+      int y = cSY + LY + YS;
+
+      // check if next step of laser is still inside visible playfield area
+      if (x >= REAL_SX && x < REAL_SX + FULL_SXSIZE &&
+         y >= REAL_SY && y < REAL_SY + FULL_SYSIZE)
+      {
+       // go on with another step
+       LX += XS;
+       LY += YS;
+
+       continue;
+      }
+
       element = EL_EMPTY;
       laser.dest_element = element;
 
       break;
     }
 
-    if (hit_mask == (HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT))
+    // handle special case of laser hitting two diagonally adjacent elements
+    // (with or without a third corner element behind these two elements)
+    if ((diag_1 || diag_2) && cross_x && cross_y)
     {
-      /* we have hit the top-right and bottom-left element --
-        choose the bottom-left one */
-      /* !!! THIS CAN BE DONE MORE INTELLIGENTLY, FOR EXAMPLE, IF ONE
-        ELEMENT WAS STEEL AND THE OTHER ONE WAS ICE => ALWAYS CHOOSE
-        THE ICE AND MELT IT AWAY INSTEAD OF OVERLOADING LASER !!! */
-      ELX = (LX - 2) / TILEX;
-      ELY = (LY + 2) / TILEY;
-    }
+      // compare the two diagonally adjacent elements
+      int xoffset = 2;
+      int yoffset = 2 * (diag_1 ? -1 : +1);
+      int elx1 = (LX - xoffset) / TILEX;
+      int ely1 = (LY + yoffset) / TILEY;
+      int elx2 = (LX + xoffset) / TILEX;
+      int ely2 = (LY - yoffset) / TILEY;
+      int e1 = Tile[elx1][ely1];
+      int e2 = Tile[elx2][ely2];
+      boolean use_element_1 = FALSE;
+
+      if (IS_WALL_ICE(e1) || IS_WALL_ICE(e2))
+      {
+       if (IS_WALL_ICE(e1) && IS_WALL_ICE(e2))
+         use_element_1 = (RND(2) ? TRUE : FALSE);
+       else if (IS_WALL_ICE(e1))
+         use_element_1 = TRUE;
+      }
+      else if (IS_WALL_AMOEBA(e1) || IS_WALL_AMOEBA(e2))
+      {
+       // if both tiles match, we can just select the first one
+       if (IS_WALL_AMOEBA(e1))
+         use_element_1 = TRUE;
+      }
+      else if (IS_ABSORBING_BLOCK(e1) || IS_ABSORBING_BLOCK(e2))
+      {
+       // if both tiles match, we can just select the first one
+       if (IS_ABSORBING_BLOCK(e1))
+         use_element_1 = TRUE;
+      }
 
-    if (hit_mask == (HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT))
-    {
-      /* we have hit the top-left and bottom-right element --
-        choose the top-left one */
-      // !!! SEE ABOVE !!!
-      ELX = (LX - 2) / TILEX;
-      ELY = (LY - 2) / TILEY;
+      ELX = (use_element_1 ? elx1 : elx2);
+      ELY = (use_element_1 ? ely1 : ely2);
     }
 
 #if 0
-    printf("hit_mask (2) == '%x' (%d, %d) (%d, %d)\n",
-          hit_mask, LX, LY, ELX, ELY);
+    Debug("game:mm:ScanLaser", "hit_mask (2) == '%x' (%d, %d) (%d, %d)",
+         hit_mask, LX, LY, ELX, ELY);
 #endif
 
+    last_element = element;
+
     element = Tile[ELX][ELY];
     laser.dest_element = element;
 
 #if 0
-    printf("Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]\n",
-          element, ELX, ELY,
-          LX, LY,
-          LX % TILEX, LY % TILEY,
-          hit_mask);
+    Debug("game:mm:ScanLaser",
+         "Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]",
+         element, ELX, ELY,
+         LX, LY,
+         LX % TILEX, LY % TILEY,
+         hit_mask);
 #endif
 
 #if 0
     if (!IN_LEV_FIELD(ELX, ELY))
-      printf("WARNING! (1) %d, %d (%d)\n", ELX, ELY, element);
+      Debug("game:mm:ScanLaser", "WARNING! (1) %d, %d (%d)",
+           ELX, ELY, element);
 #endif
 
+    // special case: leaving fixed MM steel grid (upwards) with non-90° angle
+    if (element == EL_EMPTY &&
+       IS_GRID_STEEL(last_element) &&
+       laser.current_angle % 4)                // angle is not 90°
+      element = last_element;
+
     if (element == EL_EMPTY)
     {
-      if (!HitOnlyAnEdge(element, hit_mask))
+      if (!HitOnlyAnEdge(hit_mask))
        break;
     }
     else if (element == EL_FUSE_ON)
@@ -997,20 +1342,29 @@ void ScanLaser(void)
     if (rf)
       DrawLaser(rf - 1, DL_LASER_ENABLED);
     rf = laser.num_edges;
+
+    if (!IS_DF_WALL_STEEL(element))
+    {
+      // only used for scanning DF steel walls; reset for all other elements
+      last_LX = 0;
+      last_LY = 0;
+      last_hit_mask = 0;
+    }
   }
 
 #if 0
   if (laser.dest_element != Tile[ELX][ELY])
   {
-    printf("ALARM: laser.dest_element == %d, Tile[ELX][ELY] == %d\n",
-          laser.dest_element, Tile[ELX][ELY]);
+    Debug("game:mm:ScanLaser",
+         "ALARM: laser.dest_element == %d, Tile[ELX][ELY] == %d",
+         laser.dest_element, Tile[ELX][ELY]);
   }
 #endif
 
   if (!end && !laser.stops_inside_element && !StepBehind())
   {
 #if 0
-    printf("ScanLaser: Go one step back\n");
+    Debug("game:mm:ScanLaser", "Go one step back");
 #endif
 
     LX -= XS;
@@ -1026,18 +1380,34 @@ void ScanLaser(void)
 
 #if 0
     if (!IN_LEV_FIELD(ELX, ELY))
-      printf("WARNING! (2) %d, %d\n", ELX, ELY);
+      Debug("game:mm:ScanLaser", "WARNING! (2) %d, %d", ELX, ELY);
 #endif
 }
 
+static void ScanLaser_FromLastMirror(void)
+{
+  int start_pos = (laser.num_damages > 0 ? laser.num_damages - 1 : 0);
+  int i;
+
+  for (i = start_pos; i >= 0; i--)
+    if (laser.damage[i].is_mirror)
+      break;
+
+  int start_edge = (i > 0 ? laser.damage[i].edge - 1 : 0);
+
+  DrawLaser(start_edge, DL_LASER_DISABLED);
+
+  ScanLaser();
+}
+
 static void DrawLaserExt(int start_edge, int num_edges, int mode)
 {
   int element;
   int elx, ely;
 
 #if 0
-  printf("DrawLaserExt: start_edge, num_edges, mode == %d, %d, %d\n",
-        start_edge, num_edges, mode);
+  Debug("game:mm:DrawLaserExt", "start_edge, num_edges, mode == %d, %d, %d",
+       start_edge, num_edges, mode);
 #endif
 
   if (start_edge < 0)
@@ -1057,7 +1427,7 @@ static void DrawLaserExt(int start_edge, int num_edges, int mode)
 #if 0
   if (mode == DL_LASER_DISABLED)
   {
-    printf("DrawLaser: Delete laser from edge %d\n", start_edge);
+    Debug("game:mm:DrawLaserExt", "Delete laser from edge %d", start_edge);
   }
 #endif
 
@@ -1117,11 +1487,12 @@ static void DrawLaserExt(int start_edge, int num_edges, int mode)
       int i;
 
       for (i = 0; i < laser.num_beamers; i++)
-       printf("-> %d\n", laser.beamer_edge[i]);
-      printf("DrawLaserExt: IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]\n",
-            mode, elx, ely, Hit[elx][ely], start_edge);
-      printf("DrawLaserExt: IS_BEAMER: %d / %d\n",
-            get_element_angle(element), laser.damage[damage_start].angle);
+       Debug("game:mm:DrawLaserExt", "-> %d", laser.beamer_edge[i]);
+
+      Debug("game:mm:DrawLaserExt", "IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]",
+           mode, elx, ely, Hit[elx][ely], start_edge);
+      Debug("game:mm:DrawLaserExt", "IS_BEAMER: %d / %d",
+           get_element_angle(element), laser.damage[damage_start].angle);
     }
 #endif
 
@@ -1161,8 +1532,8 @@ static void DrawLaserExt(int start_edge, int num_edges, int mode)
   YS = 2 * Step[laser.current_angle].y;
 
 #if 0
-  printf("DrawLaser: Set (LX, LY) to (%d, %d) [%d]\n",
-        LX, LY, element);
+  Debug("game:mm:DrawLaserExt", "Set (LX, LY) to (%d, %d) [%d]",
+       LX, LY, element);
 #endif
 
   if (start_edge > 0)
@@ -1177,7 +1548,7 @@ static void DrawLaserExt(int start_edge, int num_edges, int mode)
       int step_size;
 
 #if 0
-      printf("element == %d\n", element);
+      Debug("game:mm:DrawLaserExt", "element == %d", element);
 #endif
 
       if (IS_22_5_ANGLE(laser.current_angle))  // neither 90° nor 45° angle
@@ -1197,10 +1568,9 @@ static void DrawLaserExt(int start_edge, int num_edges, int mode)
 
 #if 0
       if (IS_BEAMER(element))
-      {
-       printf("start_edge == %d, laser.beamer_edge == %d\n",
-              start_edge, laser.beamer_edge);
-      }
+       Debug("game:mm:DrawLaserExt",
+             "start_edge == %d, laser.beamer_edge == %d",
+             start_edge, laser.beamer_edge);
 #endif
 
       LX += step_size * XS;
@@ -1215,13 +1585,20 @@ static void DrawLaserExt(int start_edge, int num_edges, int mode)
   }
 
 #if 0
-  printf("DrawLaser: Finally: (LX, LY) to (%d, %d) [%d]\n",
-        LX, LY, element);
+  Debug("game:mm:DrawLaserExt", "Finally: (LX, LY) to (%d, %d) [%d]",
+       LX, LY, element);
 #endif
 }
 
 void DrawLaser(int start_edge, int mode)
 {
+  // do not draw laser if fuse is off
+  if (laser.fuse_off && mode == DL_LASER_ENABLED)
+    return;
+
+  if (mode == DL_LASER_DISABLED)
+    DeactivateLaserTargetElement();
+
   if (laser.num_edges - start_edge < 0)
   {
     Warn("DrawLaser: laser.num_edges - start_edge < 0");
@@ -1247,8 +1624,8 @@ void DrawLaser(int start_edge, int mode)
          continue;
 
 #if 0
-       printf("DrawLaser: DL_LASER_ENABLED: i==%d: %d, %d\n",
-              i, laser.beamer_edge[i], tmp_start_edge);
+       Debug("game:mm:DrawLaser", "DL_LASER_ENABLED: i==%d: %d, %d",
+             i, laser.beamer_edge[i], tmp_start_edge);
 #endif
 
        DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
@@ -1282,8 +1659,8 @@ void DrawLaser(int start_edge, int mode)
 
 #if 0
       if (last_num_edges - start_edge <= 0)
-       printf("DrawLaser: DL_LASER_DISABLED: %d, %d\n",
-              last_num_edges, start_edge);
+       Debug("game:mm:DrawLaser", "DL_LASER_DISABLED: %d, %d",
+             last_num_edges, start_edge);
 #endif
 
       // special case when rotating first beamer: delete laser edge on beamer
@@ -1308,44 +1685,128 @@ void DrawLaser_MM(void)
   DrawLaser(0, game_mm.laser_enabled);
 }
 
-boolean HitElement(int element, int hit_mask)
+static boolean HitElement(int element, int hit_mask)
 {
-  if (HitOnlyAnEdge(element, hit_mask))
-    return FALSE;
+  if (IS_DF_SLOPE(element))
+  {
+    // check if laser scan has crossed element boundaries (not just mini tiles)
+    boolean cross_x = (getLevelFromLaserX(LX) != getLevelFromLaserX(LX + 2));
+    boolean cross_y = (getLevelFromLaserY(LY) != getLevelFromLaserY(LY + 2));
+    int element_angle = get_element_angle(element);
+    int mirrored_angle = get_mirrored_angle(laser.current_angle, element_angle);
+    int opposite_angle = get_opposite_angle(laser.current_angle);
+
+    // check if wall (horizontal or vertical) side of slope was hit
+    if (hit_mask == HIT_MASK_LEFT ||
+       hit_mask == HIT_MASK_RIGHT ||
+       hit_mask == HIT_MASK_TOP ||
+       hit_mask == HIT_MASK_BOTTOM)
+    {
+      boolean hit_slope_corner_in_laser_direction =
+       ((hit_mask == HIT_MASK_LEFT   && (element == EL_DF_SLOPE_01 ||
+                                         element == EL_DF_SLOPE_02)) ||
+        (hit_mask == HIT_MASK_RIGHT  && (element == EL_DF_SLOPE_00 ||
+                                         element == EL_DF_SLOPE_03)) ||
+        (hit_mask == HIT_MASK_TOP    && (element == EL_DF_SLOPE_02 ||
+                                         element == EL_DF_SLOPE_03)) ||
+        (hit_mask == HIT_MASK_BOTTOM && (element == EL_DF_SLOPE_00 ||
+                                         element == EL_DF_SLOPE_01)));
+
+      boolean hit_slope_corner_in_laser_direction_double_checked =
+       (cross_x && cross_y &&
+        laser.current_angle == mirrored_angle &&
+        hit_slope_corner_in_laser_direction);
+
+      // check special case of laser hitting the corner of a slope and another
+      // element (either wall or another slope), following the diagonal side
+      // of the slope which has the same angle as the direction of the laser
+      if (!hit_slope_corner_in_laser_direction_double_checked)
+       return HitReflectingWalls(element, hit_mask);
+    }
+
+    // check if an edge was hit while crossing element borders
+    if (cross_x && cross_y && get_number_of_bits(hit_mask) == 1)
+    {
+      // check both sides of potentially diagonal side of slope
+      int dx1 = (LX + XS) % TILEX;
+      int dy1 = (LY + YS) % TILEY;
+      int dx2 = (LX + XS + 2) % TILEX;
+      int dy2 = (LY + YS + 2) % TILEY;
+      int pos = getMaskFromElement(element);
+
+      // check if we are entering empty space area after hitting edge
+      if (!getPixelFromMask(pos, dx1, dy1) &&
+         !getPixelFromMask(pos, dx2, dy2))
+      {
+       // we already know that we hit an edge, but use this function to go on
+       if (HitOnlyAnEdge(hit_mask))
+         return FALSE;
+      }
+    }
+
+    // check if laser is reflected by slope by 180°
+    if (mirrored_angle == opposite_angle)
+    {
+      AddDamagedField(LX / TILEX, LY / TILEY);
+
+      laser.overloaded = TRUE;
+
+      return TRUE;
+    }
+  }
+  else
+  {
+    if (HitOnlyAnEdge(hit_mask))
+      return FALSE;
+  }
 
   if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
     element = MovingOrBlocked2Element_MM(ELX, ELY);
 
 #if 0
-  printf("HitElement (1): element == %d\n", element);
+  Debug("game:mm:HitElement", "(1): element == %d", element);
 #endif
 
 #if 0
   if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
-    printf("HitElement (%d): EXACT MATCH @ (%d, %d)\n", element, ELX, ELY);
+    Debug("game:mm:HitElement", "(%d): EXACT MATCH @ (%d, %d)",
+         element, ELX, ELY);
   else
-    printf("HitElement (%d): FUZZY MATCH @ (%d, %d)\n", element, ELX, ELY);
+    Debug("game:mm:HitElement", "(%d): FUZZY MATCH @ (%d, %d)",
+         element, ELX, ELY);
 #endif
 
   AddDamagedField(ELX, ELY);
 
+  boolean through_center = ((ELX * TILEX + 14 - LX) * YS ==
+                           (ELY * TILEY + 14 - LY) * XS);
+
   // this is more precise: check if laser would go through the center
-  if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
+  if (!IS_DF_SLOPE(element) && !through_center)
   {
+    int skip_count = 0;
+
+    // prevent cutting through laser emitter with laser beam
+    if (IS_LASER(element))
+      return TRUE;
+
     // skip the whole element before continuing the scan
     do
     {
       LX += XS;
       LY += YS;
+
+      skip_count++;
     }
     while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
 
-    if (LX/TILEX > ELX || LY/TILEY > ELY)
+    if ((LX/TILEX > ELX || LY/TILEY > ELY) && skip_count > 1)
     {
       /* skipping scan positions to the right and down skips one scan
         position too much, because this is only the top left scan position
         of totally four scan positions (plus one to the right, one to the
         bottom and one to the bottom right) */
+      /* ... but only roll back scan position if more than one step done */
 
       LX -= XS;
       LY -= YS;
@@ -1355,7 +1816,7 @@ boolean HitElement(int element, int hit_mask)
   }
 
 #if 0
-  printf("HitElement (2): element == %d\n", element);
+  Debug("game:mm:HitElement", "(2): element == %d", element);
 #endif
 
   if (LX + 5 * XS < 0 ||
@@ -1368,7 +1829,7 @@ boolean HitElement(int element, int hit_mask)
   }
 
 #if 0
-  printf("HitElement (3): element == %d\n", element);
+  Debug("game:mm:HitElement", "(3): element == %d", element);
 #endif
 
   if (IS_POLAR(element) &&
@@ -1392,16 +1853,34 @@ boolean HitElement(int element, int hit_mask)
     return TRUE;
   }
 
-  if (!IS_BEAMER(element) &&
-      !IS_FIBRE_OPTIC(element) &&
-      !IS_GRID_WOOD(element) &&
-      element != EL_FUEL_EMPTY)
+  if (IS_DF_SLOPE(element) && !through_center)
+  {
+    int correction = 2;
+
+    if (hit_mask == HIT_MASK_ALL)
+    {
+      // laser already inside slope -- go back half step
+      LX -= XS / 2;
+      LY -= YS / 2;
+
+      correction = 1;
+    }
+
+    AddLaserEdge(LX, LY);
+
+    LX -= (ABS(XS) < ABS(YS) ? correction * SIGN(XS) : 0);
+    LY -= (ABS(YS) < ABS(XS) ? correction * SIGN(YS) : 0);
+  }
+  else if (!IS_BEAMER(element) &&
+          !IS_FIBRE_OPTIC(element) &&
+          !IS_GRID_WOOD(element) &&
+          element != EL_FUEL_EMPTY)
   {
 #if 0
     if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
-      printf("EXACT MATCH @ (%d, %d)\n", ELX, ELY);
+      Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
     else
-      printf("FUZZY MATCH @ (%d, %d)\n", ELX, ELY);
+      Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
 #endif
 
     LX = ELX * TILEX + 14;
@@ -1416,6 +1895,8 @@ boolean HitElement(int element, int hit_mask)
       IS_POLAR_CROSS(element) ||
       IS_DF_MIRROR(element) ||
       IS_DF_MIRROR_AUTO(element) ||
+      IS_DF_MIRROR_FIXED(element) ||
+      IS_DF_SLOPE(element) ||
       element == EL_PRISM ||
       element == EL_REFRACTOR)
   {
@@ -1434,7 +1915,9 @@ boolean HitElement(int element, int hit_mask)
     if (IS_MIRROR(element) ||
        IS_MIRROR_FIXED(element) ||
        IS_DF_MIRROR(element) ||
-       IS_DF_MIRROR_AUTO(element))
+       IS_DF_MIRROR_AUTO(element) ||
+       IS_DF_MIRROR_FIXED(element) ||
+       IS_DF_SLOPE(element))
       laser.current_angle = get_mirrored_angle(laser.current_angle,
                                               get_element_angle(element));
 
@@ -1444,22 +1927,36 @@ boolean HitElement(int element, int hit_mask)
     XS = 2 * Step[laser.current_angle].x;
     YS = 2 * Step[laser.current_angle].y;
 
-    if (!IS_22_5_ANGLE(laser.current_angle))   // 90° or 45° angle
-      step_size = 8;
-    else
-      step_size = 4;
+    if (through_center)
+    {
+      // start from center position for all game elements but slope
+      if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle
+       step_size = 8;
+      else
+       step_size = 4;
 
-    LX += step_size * XS;
-    LY += step_size * YS;
+      LX += step_size * XS;
+      LY += step_size * YS;
+    }
+    else
+    {
+      // advance laser position until reaching the next tile (slopes)
+      while (LX / TILEX == ELX && (LX + 2) / TILEX == ELX &&
+            LY / TILEY == ELY && (LY + 2) / TILEY == ELY)
+      {
+       LX += XS;
+       LY += YS;
+      }
+    }
 
-#if 0
     // draw sparkles on mirror
-    if ((IS_MIRROR(element) || IS_MIRROR_FIXED(element)) &&
+    if ((IS_MIRROR(element) ||
+        IS_MIRROR_FIXED(element) ||
+        element == EL_PRISM) &&
        current_angle != laser.current_angle)
     {
-      MoveSprite(vp, &Pfeil[2], 4 + 16 * ELX, 5 + 16 * ELY + 1);
+      MovDelay[ELX][ELY] = 11;         // start animation
     }
-#endif
 
     if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
        current_angle != laser.current_angle)
@@ -1469,6 +1966,68 @@ boolean HitElement(int element, int hit_mask)
       (get_opposite_angle(laser.current_angle) ==
        laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
 
+    if (IS_DF_SLOPE(element))
+    {
+      // handle special cases for slope element
+
+      if (IS_45_ANGLE(laser.current_angle))
+      {
+       int elx, ely;
+
+       elx = getLevelFromLaserX(LX + XS);
+       ely = getLevelFromLaserY(LY + YS);
+
+       if (IN_LEV_FIELD(elx, ely))
+       {
+         int element_next = Tile[elx][ely];
+
+         // check if slope is followed by slope with opposite orientation
+         if (IS_DF_SLOPE(element_next) && ABS(element - element_next) == 2)
+           laser.overloaded = TRUE;
+       }
+
+       int nr = element - EL_DF_SLOPE_START;
+       int dx = (nr == 0 ? (XS > 0 ? TILEX - 1 : -1) :
+                 nr == 1 ? (XS > 0 ? TILEX     :  0) :
+                 nr == 2 ? (XS > 0 ? TILEX     :  0) :
+                 nr == 3 ? (XS > 0 ? TILEX - 1 : -1) : 0);
+       int dy = (nr == 0 ? (YS > 0 ? TILEY - 1 : -1) :
+                 nr == 1 ? (YS > 0 ? TILEY - 1 : -1) :
+                 nr == 2 ? (YS > 0 ? TILEY     :  0) :
+                 nr == 3 ? (YS > 0 ? TILEY     :  0) : 0);
+
+       int px = ELX * TILEX + dx;
+       int py = ELY * TILEY + dy;
+
+       dx = px % TILEX;
+       dy = py % TILEY;
+
+       elx = getLevelFromLaserX(px);
+       ely = getLevelFromLaserY(py);
+
+       if (IN_LEV_FIELD(elx, ely))
+       {
+         int element_side = Tile[elx][ely];
+
+         // check if end of slope is blocked by other element
+         if (IS_WALL(element_side) || IS_WALL_CHANGING(element_side))
+         {
+           int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
+
+           if (element & (1 << pos))
+             laser.overloaded = TRUE;
+         }
+         else
+         {
+           int pos = getMaskFromElement(element_side);
+
+           if (getPixelFromMask(pos, dx, dy))
+             laser.overloaded = TRUE;
+         }
+       }
+      }
+    }
+
     return (laser.overloaded ? TRUE : FALSE);
   }
 
@@ -1479,10 +2038,20 @@ boolean HitElement(int element, int hit_mask)
     return TRUE;
   }
 
-  if (element == EL_BOMB || element == EL_MINE)
+  if (element == EL_BOMB || element == EL_MINE || element == EL_GRAY_BALL)
   {
     PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
 
+    Tile[ELX][ELY] = (element == EL_BOMB ? EL_BOMB_ACTIVE :
+                     element == EL_MINE ? EL_MINE_ACTIVE :
+                     EL_GRAY_BALL_ACTIVE);
+
+    GfxFrame[ELX][ELY] = 0;            // restart animation
+
+    laser.dest_element_last = Tile[ELX][ELY];
+    laser.dest_element_last_x = ELX;
+    laser.dest_element_last_y = ELY;
+
     if (element == EL_MINE)
       laser.overloaded = TRUE;
   }
@@ -1492,9 +2061,11 @@ boolean HitElement(int element, int hit_mask)
       element == EL_KEY ||
       element == EL_LIGHTBALL ||
       element == EL_PACMAN ||
-      IS_PACMAN(element))
+      IS_PACMAN(element) ||
+      IS_ENVELOPE(element))
   {
-    if (!IS_PACMAN(element))
+    if (!IS_PACMAN(element) &&
+       !IS_ENVELOPE(element))
       Bang_MM(ELX, ELY);
 
     if (element == EL_PACMAN)
@@ -1522,6 +2093,10 @@ boolean HitElement(int element, int hit_mask)
     {
       DeletePacMan(ELX, ELY);
     }
+    else if (IS_ENVELOPE(element))
+    {
+      Tile[ELX][ELY] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(Tile[ELX][ELY]);
+    }
 
     RaiseScoreElement_MM(element);
 
@@ -1557,7 +2132,7 @@ boolean HitElement(int element, int hit_mask)
   }
 
 #if 0
-  printf("HitElement (4): element == %d\n", element);
+  Debug("game:mm:HitElement", "(4): element == %d", element);
 #endif
 
   if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
@@ -1569,7 +2144,7 @@ boolean HitElement(int element, int hit_mask)
     int step_size;
 
 #if 0
-  printf("HitElement (BEAMER): element == %d\n", element);
+    Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
 #endif
 
     laser.num_damages--;
@@ -1631,12 +2206,13 @@ boolean HitElement(int element, int hit_mask)
   return TRUE;
 }
 
-boolean HitOnlyAnEdge(int element, int hit_mask)
+static boolean HitOnlyAnEdge(int hit_mask)
 {
   // check if the laser hit only the edge of an element and, if so, go on
 
 #if 0
-  printf("LX, LY, hit_mask == %d, %d, %d\n", LX, LY, hit_mask);
+  Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
+       LX, LY, hit_mask);
 #endif
 
   if ((hit_mask == HIT_MASK_TOPLEFT ||
@@ -1674,22 +2250,22 @@ boolean HitOnlyAnEdge(int element, int hit_mask)
     LY += YS;
 
 #if 0
-    printf("[HitOnlyAnEdge() == TRUE]\n");
+    Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
 #endif
 
     return TRUE;
   }
 
 #if 0
-    printf("[HitOnlyAnEdge() == FALSE]\n");
+  Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
 #endif
 
   return FALSE;
 }
 
-boolean HitPolarizer(int element, int hit_mask)
+static boolean HitPolarizer(int element, int hit_mask)
 {
-  if (HitOnlyAnEdge(element, hit_mask))
+  if (HitOnlyAnEdge(hit_mask))
     return FALSE;
 
   if (IS_DF_GRID(element))
@@ -1697,8 +2273,8 @@ boolean HitPolarizer(int element, int hit_mask)
     int grid_angle = get_element_angle(element);
 
 #if 0
-    printf("HitPolarizer: angle: grid == %d, laser == %d\n",
-          grid_angle, laser.current_angle);
+    Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
+         grid_angle, laser.current_angle);
 #endif
 
     AddLaserEdge(LX, LY);
@@ -1735,10 +2311,10 @@ boolean HitPolarizer(int element, int hit_mask)
       LY += YS;
 
 #if 0
-      printf("HitPolarizer: LX, LY == %d, %d [%d, %d] [%d, %d]\n",
-            LX, LY,
-            LX / TILEX, LY / TILEY,
-            LX % TILEX, LY % TILEY);
+      Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
+           LX, LY,
+           LX / TILEX, LY / TILEY,
+           LX % TILEX, LY % TILEY);
 #endif
 
       return FALSE;
@@ -1754,17 +2330,23 @@ boolean HitPolarizer(int element, int hit_mask)
   }
   else if (IS_GRID_STEEL(element))
   {
+    // may be required if graphics for steel grid redefined
+    AddDamagedField(ELX, ELY);
+
     return HitReflectingWalls(element, hit_mask);
   }
   else // IS_GRID_WOOD
   {
+    // may be required if graphics for wooden grid redefined
+    AddDamagedField(ELX, ELY);
+
     return HitAbsorbingWalls(element, hit_mask);
   }
 
   return TRUE;
 }
 
-boolean HitBlock(int element, int hit_mask)
+static boolean HitBlock(int element, int hit_mask)
 {
   boolean check = FALSE;
 
@@ -1807,11 +2389,9 @@ boolean HitBlock(int element, int hit_mask)
   if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
   {
     int xs = XS / 2, ys = YS / 2;
-    int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
-    int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
 
-    if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
-       (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
+    if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 ||
+       (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2)
     {
       laser.overloaded = (element == EL_GATE_STONE);
 
@@ -1850,11 +2430,9 @@ boolean HitBlock(int element, int hit_mask)
   if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
   {
     int xs = XS / 2, ys = YS / 2;
-    int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
-    int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
 
-    if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
-       (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
+    if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 ||
+       (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2)
     {
       laser.overloaded = (element == EL_BLOCK_STONE);
 
@@ -1885,9 +2463,9 @@ boolean HitBlock(int element, int hit_mask)
   return TRUE;
 }
 
-boolean HitLaserSource(int element, int hit_mask)
+static boolean HitLaserSource(int element, int hit_mask)
 {
-  if (HitOnlyAnEdge(element, hit_mask))
+  if (HitOnlyAnEdge(hit_mask))
     return FALSE;
 
   PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
@@ -1897,9 +2475,9 @@ boolean HitLaserSource(int element, int hit_mask)
   return TRUE;
 }
 
-boolean HitLaserDestination(int element, int hit_mask)
+static boolean HitLaserDestination(int element, int hit_mask)
 {
-  if (HitOnlyAnEdge(element, hit_mask))
+  if (HitOnlyAnEdge(hit_mask))
     return FALSE;
 
   if (element != EL_EXIT_OPEN &&
@@ -1943,7 +2521,7 @@ boolean HitLaserDestination(int element, int hit_mask)
   return TRUE;
 }
 
-boolean HitReflectingWalls(int element, int hit_mask)
+static boolean HitReflectingWalls(int element, int hit_mask)
 {
   // check if laser hits side of a wall with an angle that is not 90°
   if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
@@ -2135,7 +2713,7 @@ boolean HitReflectingWalls(int element, int hit_mask)
     }
   }
 
-  if (!HitOnlyAnEdge(element, hit_mask))
+  if (!HitOnlyAnEdge(hit_mask))
   {
     laser.overloaded = TRUE;
 
@@ -2145,9 +2723,9 @@ boolean HitReflectingWalls(int element, int hit_mask)
   return FALSE;
 }
 
-boolean HitAbsorbingWalls(int element, int hit_mask)
+static boolean HitAbsorbingWalls(int element, int hit_mask)
 {
-  if (HitOnlyAnEdge(element, hit_mask))
+  if (HitOnlyAnEdge(hit_mask))
     return FALSE;
 
   if (ABS(XS) == 4 &&
@@ -2184,10 +2762,18 @@ boolean HitAbsorbingWalls(int element, int hit_mask)
 
   if (IS_WALL_ICE(element))
   {
+    int lx = LX + XS;
+    int ly = LY + YS;
     int mask;
 
-    mask = (LX + XS) / MINI_TILEX - ELX * 2 + 1;    // Quadrant (horizontal)
-    mask <<= (((LY + YS) / MINI_TILEY - ELY * 2) > 0) * 2;  // || (vertical)
+    // check if laser hit adjacent edges of two diagonal tiles
+    if (ELX != lx / TILEX)
+      lx = LX - XS;
+    if (ELY != ly / TILEY)
+      ly = LY - YS;
+
+    mask =     lx / MINI_TILEX - ELX * 2 + 1;    // Quadrant (horizontal)
+    mask <<= ((ly / MINI_TILEY - ELY * 2) > 0 ? 2 : 0);  // || (vertical)
 
     // check if laser hits wall with an angle of 90°
     if (IS_90_ANGLE(laser.current_angle))
@@ -2239,7 +2825,7 @@ boolean HitAbsorbingWalls(int element, int hit_mask)
     if (IS_90_ANGLE(laser.current_angle))
       mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
 
-    laser.dest_element = element2 | EL_WALL_AMOEBA;
+    laser.dest_element = element2 | EL_WALL_AMOEBA_BASE;
 
     laser.wall_mask = mask;
   }
@@ -2272,12 +2858,25 @@ static void OpenExit(int x, int y)
   }
 }
 
-static void OpenSurpriseBall(int x, int y)
+static void OpenGrayBall(int x, int y)
 {
   int delay = 2;
 
   if (!MovDelay[x][y])         // next animation frame
+  {
+    if (IS_WALL(Store[x][y]))
+    {
+      DrawWalls_MM(x, y, Store[x][y]);
+
+      // copy wall tile to spare bitmap for "melting" animation
+      BlitBitmap(drawto_mm, bitmap_db_field, cSX + x * TILEX, cSY + y * TILEY,
+                TILEX, TILEY, x * TILEX, y * TILEY);
+
+      DrawElement_MM(x, y, EL_GRAY_BALL);
+    }
+
     MovDelay[x][y] = 50 * delay;
+  }
 
   if (MovDelay[x][y])          // wait some time before next frame
   {
@@ -2286,25 +2885,77 @@ static void OpenSurpriseBall(int x, int y)
     if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
     {
       Bitmap *bitmap;
-      int graphic = el2gfx(Store[x][y]);
       int gx, gy;
       int dx = RND(26), dy = RND(26);
 
-      getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
+      if (IS_WALL(Store[x][y]))
+      {
+       // copy wall tile from spare bitmap for "melting" animation
+       bitmap = bitmap_db_field;
+       gx = x * TILEX;
+       gy = y * TILEY;
+      }
+      else
+      {
+       int graphic = el2gfx(Store[x][y]);
+
+       getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
+      }
 
-      BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
+      BlitBitmap(bitmap, drawto_mm, gx + dx, gy + dy, 6, 6,
                 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
 
+      laser.redraw = TRUE;
+
       MarkTileDirty(x, y);
     }
 
     if (!MovDelay[x][y])
     {
       Tile[x][y] = Store[x][y];
-      Store[x][y] = 0;
+      Store[x][y] = Store2[x][y] = 0;
+      MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
+
+      InitField(x, y, FALSE);
+      DrawField_MM(x, y);
+
+      ScanLaser_FromLastMirror();
+    }
+  }
+}
+
+static void OpenEnvelope(int x, int y)
+{
+  int num_frames = 8;          // seven frames plus final empty space
+
+  if (!MovDelay[x][y])         // next animation frame
+    MovDelay[x][y] = num_frames;
+
+  if (MovDelay[x][y])          // wait some time before next frame
+  {
+    int nr = ENVELOPE_OPENING_NR(Tile[x][y]);
+
+    MovDelay[x][y]--;
+
+    if (MovDelay[x][y] > 0 && IN_SCR_FIELD(x, y))
+    {
+      int graphic = el_act2gfx(EL_ENVELOPE_1 + nr, MM_ACTION_COLLECTING);
+      int frame = num_frames - MovDelay[x][y] - 1;
+
+      DrawGraphicAnimation_MM(x, y, graphic, frame);
+
+      laser.redraw = TRUE;
+    }
+
+    if (MovDelay[x][y] == 0)
+    {
+      Tile[x][y] = EL_EMPTY;
+
       DrawField_MM(x, y);
 
       ScanLaser();
+
+      ShowEnvelope(nr);
     }
   }
 }
@@ -2321,33 +2972,22 @@ static void MeltIce(int x, int y)
   {
     int phase;
     int wall_mask = Store2[x][y];
-    int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_ICE;
+    int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_ICE_BASE;
 
     MovDelay[x][y]--;
     phase = frames - MovDelay[x][y] / delay - 1;
 
     if (!MovDelay[x][y])
     {
-      int i;
-
       Tile[x][y] = real_element & (wall_mask ^ 0xFF);
       Store[x][y] = Store2[x][y] = 0;
 
       DrawWalls_MM(x, y, Tile[x][y]);
 
-      if (Tile[x][y] == EL_WALL_ICE)
+      if (Tile[x][y] == EL_WALL_ICE_BASE)
        Tile[x][y] = EL_EMPTY;
 
-      for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
-       if (laser.damage[i].is_mirror)
-         break;
-
-      if (i > 0)
-       DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
-      else
-       DrawLaser(0, DL_LASER_DISABLED);
-
-      ScanLaser();
+      ScanLaser_FromLastMirror();
     }
     else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
     {
@@ -2370,7 +3010,7 @@ static void GrowAmoeba(int x, int y)
   {
     int phase;
     int wall_mask = Store2[x][y];
-    int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_AMOEBA;
+    int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_AMOEBA_BASE;
 
     MovDelay[x][y]--;
     phase = MovDelay[x][y] / delay;
@@ -2390,17 +3030,58 @@ static void GrowAmoeba(int x, int y)
   }
 }
 
+static void DrawFieldAnimated_MM(int x, int y)
+{
+  DrawField_MM(x, y);
+
+  laser.redraw = TRUE;
+}
+
+static void DrawFieldAnimatedIfNeeded_MM(int x, int y)
+{
+  int element = Tile[x][y];
+  int graphic = el2gfx(element);
+
+  if (!getGraphicInfo_NewFrame(x, y, graphic))
+    return;
+
+  DrawField_MM(x, y);
+
+  laser.redraw = TRUE;
+}
+
+static void DrawFieldTwinkle(int x, int y)
+{
+  if (MovDelay[x][y] != 0)     // wait some time before next frame
+  {
+    MovDelay[x][y]--;
+
+    DrawField_MM(x, y);
+
+    if (MovDelay[x][y] != 0)
+    {
+      int graphic = IMG_TWINKLE_WHITE;
+      int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
+
+      DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
+    }
+
+    laser.redraw = TRUE;
+  }
+}
+
 static void Explode_MM(int x, int y, int phase, int mode)
 {
   int num_phase = 9, delay = 2;
   int last_phase = num_phase * delay;
   int half_phase = (num_phase / 2) * delay;
+  int center_element;
 
   laser.redraw = TRUE;
 
   if (phase == EX_PHASE_START)         // initialize 'Store[][]' field
   {
-    int center_element = Tile[x][y];
+    center_element = Tile[x][y];
 
     if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
     {
@@ -2411,22 +3092,32 @@ static void Explode_MM(int x, int y, int phase, int mode)
       Tile[x][y] = center_element;
     }
 
-    if (center_element == EL_BOMB || IS_MCDUFFIN(center_element))
-      Store[x][y] = center_element;
-    else
+    if (center_element != EL_GRAY_BALL_ACTIVE)
       Store[x][y] = EL_EMPTY;
+    Store2[x][y] = center_element;
 
-    Store2[x][y] = mode;
     Tile[x][y] = EL_EXPLODING_OPAQUE;
+
+    GfxElement[x][y] = (center_element == EL_BOMB_ACTIVE ? EL_BOMB :
+                       center_element == EL_GRAY_BALL_ACTIVE ? EL_GRAY_BALL :
+                       IS_MCDUFFIN(center_element) ? EL_MCDUFFIN :
+                       center_element);
+
     MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
-    Frame[x][y] = 1;
+
+    ExplodePhase[x][y] = 1;
 
     return;
   }
 
-  Frame[x][y] = (phase < last_phase ? phase + 1 : 0);
+  if (phase == 1)
+    GfxFrame[x][y] = 0;                // restart explosion animation
+
+  ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
 
-  if (phase == half_phase)
+  center_element = Store2[x][y];
+
+  if (phase == half_phase && Store[x][y] == EL_EMPTY)
   {
     Tile[x][y] = EL_EXPLODING_TRANSP;
 
@@ -2436,75 +3127,37 @@ static void Explode_MM(int x, int y, int phase, int mode)
 
   if (phase == last_phase)
   {
-    if (Store[x][y] == EL_BOMB)
+    if (center_element == EL_BOMB_ACTIVE)
     {
       DrawLaser(0, DL_LASER_DISABLED);
       InitLaser();
 
       Bang_MM(laser.start_edge.x, laser.start_edge.y);
-      Store[x][y] = EL_EMPTY;
-
-      game_mm.game_over = TRUE;
-      game_mm.game_over_cause = GAME_OVER_BOMB;
-
-      SetTileCursorActive(FALSE);
 
       laser.overloaded = FALSE;
     }
-    else if (IS_MCDUFFIN(Store[x][y]))
+    else if (IS_MCDUFFIN(center_element) || IS_LASER(center_element))
     {
-      Store[x][y] = EL_EMPTY;
-
-      game.restart_game_message = "Bomb killed Mc Duffin! Play it again?";
+      GameOver_MM(GAME_OVER_BOMB);
     }
 
     Tile[x][y] = Store[x][y];
+
     Store[x][y] = Store2[x][y] = 0;
     MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
 
     InitField(x, y, FALSE);
     DrawField_MM(x, y);
+
+    if (center_element == EL_GRAY_BALL_ACTIVE)
+      ScanLaser_FromLastMirror();
   }
   else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
   {
-    int graphic = IMG_MM_DEFAULT_EXPLODING;
-    int graphic_phase = (phase / delay - 1);
-    Bitmap *bitmap;
-    int src_x, src_y;
+    int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
+    int frame = getGraphicAnimationFrameXY(graphic, x, y);
 
-    if (Store2[x][y] == EX_KETTLE)
-    {
-      if (graphic_phase < 3)
-      {
-       graphic = IMG_MM_KETTLE_EXPLODING;
-      }
-      else if (graphic_phase < 5)
-      {
-       graphic_phase += 3;
-      }
-      else
-      {
-       graphic = IMG_EMPTY;
-       graphic_phase = 0;
-      }
-    }
-    else if (Store2[x][y] == EX_SHORT)
-    {
-      if (graphic_phase < 4)
-      {
-       graphic_phase += 4;
-      }
-      else
-      {
-       graphic = IMG_EMPTY;
-       graphic_phase = 0;
-      }
-    }
-
-    getGraphicSource(graphic, graphic_phase, &bitmap, &src_x, &src_y);
-
-    BlitBitmap(bitmap, drawto_field, src_x, src_y, TILEX, TILEY,
-              cFX + x * TILEX, cFY + y * TILEY);
+    DrawGraphicAnimation_MM(x, y, graphic, frame);
 
     MarkTileDirty(x, y);
   }
@@ -2513,67 +3166,48 @@ static void Explode_MM(int x, int y, int phase, int mode)
 static void Bang_MM(int x, int y)
 {
   int element = Tile[x][y];
-  int mode = EX_NORMAL;
-
-#if 0
-  DrawLaser(0, DL_LASER_ENABLED);
-#endif
-
-  switch (element)
-  {
-    case EL_KETTLE:
-      mode = EX_KETTLE;
-      break;
-
-    case EL_GATE_STONE:
-    case EL_GATE_WOOD:
-      mode = EX_SHORT;
-      break;
-
-    default:
-      mode = EX_NORMAL;
-      break;
-  }
 
   if (IS_PACMAN(element))
     PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
-  else if (element == EL_BOMB || IS_MCDUFFIN(element))
+  else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
     PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
   else if (element == EL_KEY)
     PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
   else
     PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
 
-  Explode_MM(x, y, EX_PHASE_START, mode);
+  Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
 }
 
-void TurnRound(int x, int y)
+static void TurnRound(int x, int y)
 {
   static struct
   {
     int x, y;
   } move_xy[] =
   {
-    { 0, 0 },
-    {-1, 0 },
-    {+1, 0 },
-    { 0, 0 },
-    { 0, -1 },
-    { 0, 0 }, { 0, 0 }, { 0, 0 },
-    { 0, +1 }
+    {  0,  0 },
+    { -1,  0 },
+    { +1,  0 },
+    {  0,  0 },
+    {  0, -1 },
+    {  0,  0 }, { 0, 0 }, { 0, 0 },
+    {  0, +1 }
   };
   static struct
   {
     int left, right, back;
   } turn[] =
   {
-    { 0,       0,              0 },
+    { 0,       0,              0        },
     { MV_DOWN, MV_UP,          MV_RIGHT },
-    { MV_UP,   MV_DOWN,        MV_LEFT },
-    { 0,       0,              0 },
-    { MV_LEFT, MV_RIGHT,       MV_DOWN },
-    { 0,0,0 }, { 0,0,0 },      { 0,0,0 },
-    { MV_RIGHT,        MV_LEFT,        MV_UP }
+    { MV_UP,   MV_DOWN,        MV_LEFT  },
+    { 0,       0,              0        },
+    { MV_LEFT, MV_RIGHT,       MV_DOWN  },
+    { 0,       0,              0        },
+    { 0,       0,              0        },
+    { 0,       0,              0        },
+    { MV_RIGHT,        MV_LEFT,        MV_UP    }
   };
 
   int element = Tile[x][y];
@@ -2621,7 +3255,7 @@ static void StartMoving_MM(int x, int y)
 
     // now make next step
 
-    Moving2Blocked_MM(x, y, &newx, &newy);     // get next screen position
+    Moving2Blocked(x, y, &newx, &newy);        // get next screen position
 
     if (element == EL_PACMAN &&
        IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
@@ -2701,8 +3335,7 @@ static void ContinueMoving_MM(int x, int y)
 
 boolean ClickElement(int x, int y, int button)
 {
-  static unsigned int click_delay = 0;
-  static int click_delay_value = CLICK_DELAY;
+  static DelayCounter click_delay = { CLICK_DELAY };
   static boolean new_button = TRUE;
   boolean element_clicked = FALSE;
   int element;
@@ -2710,8 +3343,8 @@ boolean ClickElement(int x, int y, int button)
   if (button == -1)
   {
     // initialize static variables
-    click_delay = 0;
-    click_delay_value = CLICK_DELAY;
+    click_delay.count = 0;
+    click_delay.value = CLICK_DELAY;
     new_button = TRUE;
 
     return FALSE;
@@ -2724,7 +3357,7 @@ boolean ClickElement(int x, int y, int button)
   if (button == MB_RELEASED)
   {
     new_button = TRUE;
-    click_delay_value = CLICK_DELAY;
+    click_delay.value = CLICK_DELAY;
 
     // release eventually hold auto-rotating mirror
     RotateMirror(x, y, MB_RELEASED);
@@ -2732,7 +3365,7 @@ boolean ClickElement(int x, int y, int button)
     return FALSE;
   }
 
-  if (!FrameReached(&click_delay, click_delay_value) && !new_button)
+  if (!FrameReached(&click_delay) && !new_button)
     return FALSE;
 
   if (button == MB_MIDDLEBUTTON)       // middle button has no function
@@ -2759,29 +3392,25 @@ boolean ClickElement(int x, int y, int button)
   }
   else if (IS_MCDUFFIN(element))
   {
-    if (!laser.fuse_off)
-    {
-      DrawLaser(0, DL_LASER_DISABLED);
+    boolean has_laser = (x == laser.start_edge.x && y == laser.start_edge.y);
 
-      /*
-      BackToFront();
-      */
-    }
+    if (has_laser && !laser.fuse_off)
+      DrawLaser(0, DL_LASER_DISABLED);
 
     element = get_rotated_element(element, BUTTON_ROTATION(button));
-    laser.start_angle = get_element_angle(element);
-
-    InitLaser();
 
     Tile[x][y] = element;
     DrawField_MM(x, y);
 
-    /*
-    BackToFront();
-    */
+    if (has_laser)
+    {
+      laser.start_angle = get_element_angle(element);
 
-    if (!laser.fuse_off)
-      ScanLaser();
+      InitLaser();
+
+      if (!laser.fuse_off)
+       ScanLaser();
+    }
 
     element_clicked = TRUE;
   }
@@ -2818,14 +3447,20 @@ boolean ClickElement(int x, int y, int button)
 
     element_clicked = TRUE;
   }
+  else if (IS_ENVELOPE(element))
+  {
+    Tile[x][y] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(element);
 
-  click_delay_value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
+    element_clicked = TRUE;
+  }
+
+  click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
   new_button = FALSE;
 
   return element_clicked;
 }
 
-void RotateMirror(int x, int y, int button)
+static void RotateMirror(int x, int y, int button)
 {
   if (button == MB_RELEASED)
   {
@@ -2900,20 +3535,20 @@ void RotateMirror(int x, int y, int button)
         IS_POLAR(Tile[x][y]) ||
         IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
     {
-      check = 0;
-
       if (IS_BEAMER(Tile[x][y]))
       {
 #if 0
-       printf("TEST (%d, %d) [%d] [%d]\n",
-              LX, LY,
-              laser.beamer_edge, laser.beamer[1].num);
+       Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
+             LX, LY, laser.beamer_edge, laser.beamer[1].num);
 #endif
 
-       laser.num_edges--;
+       if (check == 1)
+         laser.num_edges--;
       }
 
       ScanLaser();
+
+      check = 0;
     }
 
     if (check == 2)
@@ -2925,7 +3560,7 @@ static void AutoRotateMirrors(void)
 {
   int x, y;
 
-  if (!FrameReached(&rotate_delay, AUTO_ROTATE_DELAY))
+  if (!FrameReached(&rotate_delay))
     return;
 
   for (x = 0; x < lev_fieldx; x++)
@@ -2942,12 +3577,16 @@ static void AutoRotateMirrors(void)
          IS_GRID_WOOD_AUTO(element) ||
          IS_GRID_STEEL_AUTO(element) ||
          element == EL_REFRACTOR)
+      {
        RotateMirror(x, y, MB_RIGHTBUTTON);
+
+       laser.redraw = TRUE;
+      }
     }
   }
 }
 
-boolean ObjHit(int obx, int oby, int bits)
+static boolean ObjHit(int obx, int oby, int bits)
 {
   int i;
 
@@ -2980,7 +3619,7 @@ boolean ObjHit(int obx, int oby, int bits)
   return FALSE;
 }
 
-void DeletePacMan(int px, int py)
+static void DeletePacMan(int px, int py)
 {
   int i, j;
 
@@ -3006,51 +3645,7 @@ void DeletePacMan(int px, int py)
   }
 }
 
-void ColorCycling(void)
-{
-  static int CC, Cc = 0;
-
-  static int color, old = 0xF00, new = 0x010, mult = 1;
-  static unsigned short red, green, blue;
-
-  if (color_status == STATIC_COLORS)
-    return;
-
-  CC = FrameCounter;
-
-  if (CC < Cc || CC > Cc + 2)
-  {
-    Cc = CC;
-
-    color = old + new * mult;
-    if (mult > 0)
-      mult++;
-    else
-      mult--;
-
-    if (ABS(mult) == 16)
-    {
-      mult =- mult / 16;
-      old = color;
-      new = new << 4;
-
-      if (new > 0x100)
-       new = 0x001;
-    }
-
-    red   = 0x0e00 * ((color & 0xF00) >> 8);
-    green = 0x0e00 * ((color & 0x0F0) >> 4);
-    blue  = 0x0e00 * ((color & 0x00F));
-    SetRGB(pen_magicolor[0], red, green, blue);
-
-    red   = 0x1111 * ((color & 0xF00) >> 8);
-    green = 0x1111 * ((color & 0x0F0) >> 4);
-    blue  = 0x1111 * ((color & 0x00F));
-    SetRGB(pen_magicolor[1], red, green, blue);
-  }
-}
-
-static void GameActions_MM_Ext(struct MouseActionInfo action, boolean warp_mode)
+static void GameActions_MM_Ext(void)
 {
   int element;
   int x, y, i;
@@ -3069,15 +3664,27 @@ static void GameActions_MM_Ext(struct MouseActionInfo action, boolean warp_mode)
     else if (IS_MOVING(x, y))
       ContinueMoving_MM(x, y);
     else if (IS_EXPLODING(element))
-      Explode_MM(x, y, Frame[x][y], EX_NORMAL);
+      Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
     else if (element == EL_EXIT_OPENING)
       OpenExit(x, y);
     else if (element == EL_GRAY_BALL_OPENING)
-      OpenSurpriseBall(x, y);
-    else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
+      OpenGrayBall(x, y);
+    else if (IS_ENVELOPE_OPENING(element))
+      OpenEnvelope(x, y);
+    else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE_BASE)
       MeltIce(x, y);
-    else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
+    else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA_BASE)
       GrowAmoeba(x, y);
+    else if (IS_MIRROR(element) ||
+            IS_MIRROR_FIXED(element) ||
+            element == EL_PRISM)
+      DrawFieldTwinkle(x, y);
+    else if (element == EL_GRAY_BALL_ACTIVE ||
+            element == EL_BOMB_ACTIVE ||
+            element == EL_MINE_ACTIVE)
+      DrawFieldAnimated_MM(x, y);
+    else if (!IS_BLOCKED(x, y))
+      DrawFieldAnimatedIfNeeded_MM(x, y);
   }
 
   AutoRotateMirrors();
@@ -3093,7 +3700,7 @@ static void GameActions_MM_Ext(struct MouseActionInfo action, boolean warp_mode)
 
   CT = FrameCounter;
 
-  if (game_mm.num_pacman && FrameReached(&pacman_delay, PACMAN_MOVE_DELAY))
+  if (game_mm.num_pacman && FrameReached(&pacman_delay))
   {
     MovePacMen();
 
@@ -3104,61 +3711,25 @@ static void GameActions_MM_Ext(struct MouseActionInfo action, boolean warp_mode)
     }
   }
 
-  if (FrameReached(&energy_delay, ENERGY_DELAY))
-  {
-    if (game_mm.energy_left > 0)
-    {
-      game_mm.energy_left--;
-
-      redraw_mask |= REDRAW_DOOR_1;
-    }
-    else if (setup.time_limit && !game_mm.game_over)
-    {
-      int i;
-
-      for (i = 15; i >= 0; i--)
-      {
-#if 0
-       SetRGB(pen_ray, 0x0000, 0x0000, i * color_scale);
-#endif
-       pen_ray = GetPixelFromRGB(window,
-                                 native_mm_level.laser_red   * 0x11 * i,
-                                 native_mm_level.laser_green * 0x11 * i,
-                                 native_mm_level.laser_blue  * 0x11 * i);
-
-       DrawLaser(0, DL_LASER_ENABLED);
-       BackToFront();
-       Delay_WithScreenUpdates(50);
-      }
-
-      StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
-#if 0
-      FadeMusic();
-#endif
+  // skip all following game actions if game is over
+  if (game_mm.game_over)
+    return;
 
-      DrawLaser(0, DL_LASER_DISABLED);
-      game_mm.game_over = TRUE;
-      game_mm.game_over_cause = GAME_OVER_NO_ENERGY;
+  if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
+  {
+    FadeOutLaser();
 
-      SetTileCursorActive(FALSE);
+    GameOver_MM(GAME_OVER_NO_ENERGY);
 
-      game.restart_game_message = "Out of magic energy! Play it again?";
+    return;
+  }
 
-#if 0
-      if (Request("Out of magic energy! Play it again?",
-                 REQ_ASK | REQ_STAY_CLOSED))
-      {
-       InitGame();
-      }
-      else
-      {
-       game_status = MAINMENU;
-       DrawMainMenu();
-      }
-#endif
+  if (FrameReached(&energy_delay))
+  {
+    if (game_mm.energy_left > 0)
+      game_mm.energy_left--;
 
-      return;
-    }
+    // when out of energy, wait another frame to play "out of time" sound
   }
 
   element = laser.dest_element;
@@ -3166,15 +3737,18 @@ static void GameActions_MM_Ext(struct MouseActionInfo action, boolean warp_mode)
 #if 0
   if (element != Tile[ELX][ELY])
   {
-    printf("element == %d, Tile[ELX][ELY] == %d\n",
-          element, Tile[ELX][ELY]);
+    Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
+         element, Tile[ELX][ELY]);
   }
 #endif
 
   if (!laser.overloaded && laser.overload_value == 0 &&
       element != EL_BOMB &&
+      element != EL_BOMB_ACTIVE &&
       element != EL_MINE &&
-      element != EL_BALL_GRAY &&
+      element != EL_MINE_ACTIVE &&
+      element != EL_GRAY_BALL &&
+      element != EL_GRAY_BALL_ACTIVE &&
       element != EL_BLOCK_STONE &&
       element != EL_BLOCK_WOOD &&
       element != EL_FUSE_ON &&
@@ -3183,9 +3757,11 @@ static void GameActions_MM_Ext(struct MouseActionInfo action, boolean warp_mode)
       !IS_WALL_AMOEBA(element))
     return;
 
+  overload_delay.value = HEALTH_DELAY(laser.overloaded);
+
   if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
        (!laser.overloaded && laser.overload_value > 0)) &&
-      FrameReached(&overload_delay, HEALTH_DELAY(laser.overloaded)))
+      FrameReached(&overload_delay))
   {
     if (laser.overloaded)
       laser.overload_value++;
@@ -3202,18 +3778,7 @@ static void GameActions_MM_Ext(struct MouseActionInfo action, boolean warp_mode)
 
     if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
     {
-      int color_up = 0xFF * laser.overload_value / MAX_LASER_OVERLOAD;
-      int color_down = 0xFF - color_up;
-
-#if 0
-      SetRGB(pen_ray, (laser.overload_value / 6) * color_scale, 0x0000,
-            (15 - (laser.overload_value / 6)) * color_scale);
-#endif
-      pen_ray =
-       GetPixelFromRGB(window,
-                       (native_mm_level.laser_red  ? 0xFF : color_up),
-                       (native_mm_level.laser_green ? color_down : 0x00),
-                       (native_mm_level.laser_blue  ? color_down : 0x00));
+      SetLaserColor(0xFF);
 
       DrawLaser(0, DL_LASER_ENABLED);
     }
@@ -3225,70 +3790,13 @@ static void GameActions_MM_Ext(struct MouseActionInfo action, boolean warp_mode)
     else
       PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
 
-    if (laser.overloaded)
-    {
-#if 0
-      BlitBitmap(pix[PIX_DOOR], drawto,
-                DOOR_GFX_PAGEX4 + XX_OVERLOAD,
-                DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
-                - laser.overload_value,
-                OVERLOAD_XSIZE, laser.overload_value,
-                DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
-                - laser.overload_value);
-#endif
-      redraw_mask |= REDRAW_DOOR_1;
-    }
-    else
-    {
-#if 0
-      BlitBitmap(pix[PIX_DOOR], drawto,
-                DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
-                OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
-                DX_OVERLOAD, DY_OVERLOAD);
-#endif
-      redraw_mask |= REDRAW_DOOR_1;
-    }
-
     if (laser.overload_value == MAX_LASER_OVERLOAD)
     {
-      int i;
-
       UpdateAndDisplayGameControlValues();
 
-      for (i = 15; i >= 0; i--)
-      {
-#if 0
-       SetRGB(pen_ray, i * color_scale, 0x0000, 0x0000);
-#endif
-
-       pen_ray = GetPixelFromRGB(window, 0x11 * i, 0x00, 0x00);
-
-       DrawLaser(0, DL_LASER_ENABLED);
-       BackToFront();
-       Delay_WithScreenUpdates(50);
-      }
-
-      DrawLaser(0, DL_LASER_DISABLED);
-
-      game_mm.game_over = TRUE;
-      game_mm.game_over_cause = GAME_OVER_OVERLOADED;
-
-      SetTileCursorActive(FALSE);
-
-      game.restart_game_message = "Magic spell hit Mc Duffin! Play it again?";
+      FadeOutLaser();
 
-#if 0
-      if (Request("Magic spell hit Mc Duffin! Play it again?",
-                 REQ_ASK | REQ_STAY_CLOSED))
-      {
-       InitGame();
-      }
-      else
-      {
-       game_status = MAINMENU;
-       DrawMainMenu();
-      }
-#endif
+      GameOver_MM(GAME_OVER_OVERLOADED);
 
       return;
     }
@@ -3304,36 +3812,10 @@ static void GameActions_MM_Ext(struct MouseActionInfo action, boolean warp_mode)
     if (game_mm.cheat_no_explosion)
       return;
 
-#if 0
-    laser.num_damages--;
-    DrawLaser(0, DL_LASER_DISABLED);
-    laser.num_edges = 0;
-#endif
-
     Bang_MM(ELX, ELY);
 
     laser.dest_element = EL_EXPLODING_OPAQUE;
 
-#if 0
-    Bang_MM(ELX, ELY);
-    laser.num_damages--;
-    DrawLaser(0, DL_LASER_DISABLED);
-
-    laser.num_edges = 0;
-    Bang_MM(laser.start_edge.x, laser.start_edge.y);
-
-    if (Request("Bomb killed Mc Duffin! Play it again?",
-               REQ_ASK | REQ_STAY_CLOSED))
-    {
-      InitGame();
-    }
-    else
-    {
-      game_status = MAINMENU;
-      DrawMainMenu();
-    }
-#endif
-
     return;
   }
 
@@ -3347,121 +3829,50 @@ static void GameActions_MM_Ext(struct MouseActionInfo action, boolean warp_mode)
     DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
   }
 
-  if (element == EL_BALL_GRAY && CT > native_mm_level.time_ball)
+  if (element == EL_GRAY_BALL && CT > native_mm_level.time_ball)
   {
-    static int new_elements[] =
-    {
-      EL_MIRROR_START,
-      EL_MIRROR_FIXED_START,
-      EL_POLAR_START,
-      EL_POLAR_CROSS_START,
-      EL_PACMAN_START,
-      EL_KETTLE,
-      EL_BOMB,
-      EL_PRISM
-    };
-    int num_new_elements = sizeof(new_elements) / sizeof(int);
-    int new_element = new_elements[RND(num_new_elements)];
-
-    Store[ELX][ELY] = new_element + RND(get_num_elements(new_element));
-    Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
-
-    // !!! CHECK AGAIN: Laser on Polarizer !!!
-    ScanLaser();
-
-    return;
-
-#if 0
-    int graphic;
-
-    switch (RND(5))
+    if (!Store2[ELX][ELY])     // check if content element not yet determined
     {
-      case 0:
-        element = EL_MIRROR_START + RND(16);
-       break;
-      case 1:
-       {
-         int rnd = RND(3);
+      int last_anim_random_frame = gfx.anim_random_frame;
+      int element_pos;
 
-         element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
-       }
-       break;
-      default:
-       {
-         int rnd = RND(3);
+      if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
+       gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
 
-         element = (rnd == 0 ? EL_FUSE_ON :
-                    rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
-                    rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
-                    rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
-                    EL_MIRROR_FIXED_START + rnd - 25);
-       }
-       break;
-    }
+      element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
+                                     native_mm_level.ball_choice_mode, 0,
+                                     game_mm.ball_choice_pos);
 
-    graphic = el2gfx(element);
+      if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
+       gfx.anim_random_frame = last_anim_random_frame;
 
-    for (i = 0; i < 50; i++)
-    {
-      int x = RND(26);
-      int y = RND(26);
+      game_mm.ball_choice_pos++;
 
-#if 0
-      BlitBitmap(pix[PIX_BACK], drawto,
-                SX + (graphic % GFX_PER_LINE) * TILEX + x,
-                SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
-                SX + ELX * TILEX + x,
-                SY + ELY * TILEY + y);
-#endif
-      MarkTileDirty(ELX, ELY);
-      BackToFront();
+      int new_element = native_mm_level.ball_content[element_pos];
+      int new_element_base = map_wall_to_base_element(new_element);
 
-      DrawLaser(0, DL_LASER_ENABLED);
+      if (IS_WALL(new_element_base))
+      {
+       // always use completely filled wall element
+       new_element = new_element_base | 0x000f;
+      }
+      else if (native_mm_level.rotate_ball_content &&
+              get_num_elements(new_element) > 1)
+      {
+       // randomly rotate newly created game element
+       new_element = get_rotated_element(new_element, RND(16));
+      }
 
-      Delay_WithScreenUpdates(50);
+      Store[ELX][ELY] = new_element;
+      Store2[ELX][ELY] = TRUE;
     }
 
-    Tile[ELX][ELY] = element;
-    DrawField_MM(ELX, ELY);
-
-#if 0
-    printf("NEW ELEMENT: (%d, %d)\n", ELX, ELY);
-#endif
-
-    // above stuff: GRAY BALL -> PRISM !!!
-/*
-    LX = ELX * TILEX + 14;
-    LY = ELY * TILEY + 14;
-    if (laser.current_angle == (laser.current_angle >> 1) << 1)
-      OK = 8;
-    else
-      OK = 4;
-    LX -= OK * XS;
-    LY -= OK * YS;
-
-    laser.num_edges -= 2;
-    laser.num_damages--;
-*/
-
-#if 0
-    for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
-      if (laser.damage[i].is_mirror)
-       break;
-
-    if (i > 0)
-      DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
+    if (native_mm_level.explode_ball)
+      Bang_MM(ELX, ELY);
     else
-      DrawLaser(0, DL_LASER_DISABLED);
-#else
-    DrawLaser(0, DL_LASER_DISABLED);
-#endif
+      Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
 
-    ScanLaser();
-
-    /*
-    printf("TEST ELEMENT: %d\n", Tile[0][0]);
-    */
-#endif
+    laser.dest_element = laser.dest_element_last = Tile[ELX][ELY];
 
     return;
   }
@@ -3470,57 +3881,18 @@ static void GameActions_MM_Ext(struct MouseActionInfo action, boolean warp_mode)
   {
     PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
 
-    {
-      Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
-      Store[ELX][ELY] = EL_WALL_ICE;
-      Store2[ELX][ELY] = laser.wall_mask;
+    Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE_BASE + EL_WALL_CHANGING_BASE;
+    Store[ELX][ELY] = EL_WALL_ICE_BASE;
+    Store2[ELX][ELY] = laser.wall_mask;
 
-      laser.dest_element = Tile[ELX][ELY];
-
-      return;
-    }
-
-    for (i = 0; i < 5; i++)
-    {
-      int phase = i + 1;
-
-      if (i == 4)
-      {
-       Tile[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
-       phase = 0;
-      }
-
-      DrawWallsAnimation_MM(ELX, ELY, Tile[ELX][ELY], phase, laser.wall_mask);
-      BackToFront();
-      Delay_WithScreenUpdates(100);
-    }
-
-    if (Tile[ELX][ELY] == EL_WALL_ICE)
-      Tile[ELX][ELY] = EL_EMPTY;
-
-/*
-    laser.num_edges--;
-    LX = laser.edge[laser.num_edges].x - cSX2;
-    LY = laser.edge[laser.num_edges].y - cSY2;
-*/
-
-    for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
-      if (laser.damage[i].is_mirror)
-       break;
-
-    if (i > 0)
-      DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
-    else
-      DrawLaser(0, DL_LASER_DISABLED);
-
-    ScanLaser();
+    laser.dest_element = Tile[ELX][ELY];
 
     return;
   }
 
   if (IS_WALL_AMOEBA(element) && CT > 60)
   {
-    int k1, k2, k3, dx, dy, de, dm;
+    int k1, k2, k3;
     int element2 = Tile[ELX][ELY];
 
     if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
@@ -3591,44 +3963,17 @@ static void GameActions_MM_Ext(struct MouseActionInfo action, boolean warp_mode)
 
     Tile[ELX][ELY] = element | laser.wall_mask;
 
-    dx = ELX;
-    dy = ELY;
-    de = Tile[ELX][ELY];
-    dm = laser.wall_mask;
-
-#if 1
-    {
-      int x = ELX, y = ELY;
-      int wall_mask = laser.wall_mask;
-
-      ScanLaser();
-      DrawLaser(0, DL_LASER_ENABLED);
-
-      PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
+    int x = ELX, y = ELY;
+    int wall_mask = laser.wall_mask;
 
-      Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
-      Store[x][y] = EL_WALL_AMOEBA;
-      Store2[x][y] = wall_mask;
-
-      return;
-    }
-#endif
-
-    DrawWallsAnimation_MM(dx, dy, de, 4, dm);
     ScanLaser();
     DrawLaser(0, DL_LASER_ENABLED);
 
-    PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
-
-    for (i = 4; i >= 0; i--)
-    {
-      DrawWallsAnimation_MM(dx, dy, de, i, dm);
-
-      BackToFront();
-      Delay_WithScreenUpdates(20);
-    }
+    PlayLevelSound_MM(x, y, element, MM_ACTION_GROWING);
 
-    DrawLaser(0, DL_LASER_ENABLED);
+    Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA_BASE + EL_WALL_CHANGING_BASE;
+    Store[x][y] = EL_WALL_AMOEBA_BASE;
+    Store2[x][y] = wall_mask;
 
     return;
   }
@@ -3693,45 +4038,46 @@ static void GameActions_MM_Ext(struct MouseActionInfo action, boolean warp_mode)
 
   if (element == EL_FUEL_FULL && CT > 10)
   {
-    for (i = game_mm.energy_left; i <= MAX_LASER_ENERGY; i+=2)
+    int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
+    int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
+
+    for (i = start; i <= num_init_game_frames; i++)
     {
-#if 0
-      BlitBitmap(pix[PIX_DOOR], drawto,
-                DOOR_GFX_PAGEX4 + XX_ENERGY,
-                DOOR_GFX_PAGEY1 + YY_ENERGY + ENERGY_YSIZE - i,
-                ENERGY_XSIZE, i, DX_ENERGY,
-                DY_ENERGY + ENERGY_YSIZE - i);
-#endif
+      if (i == num_init_game_frames)
+       StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
+      else if (setup.sound_loops)
+       PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
+      else
+       PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
 
-      redraw_mask |= REDRAW_DOOR_1;
-      BackToFront();
+      game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
+
+      UpdateAndDisplayGameControlValues();
 
-      Delay_WithScreenUpdates(20);
+      BackToFront_MM();
     }
 
-    game_mm.energy_left = MAX_LASER_ENERGY;
-    Tile[ELX][ELY] = EL_FUEL_EMPTY;
+    Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
+
     DrawField_MM(ELX, ELY);
 
     DrawLaser(0, DL_LASER_ENABLED);
 
     return;
   }
-
-  return;
 }
 
-void GameActions_MM(struct MouseActionInfo action, boolean warp_mode)
+void GameActions_MM(struct MouseActionInfo action)
 {
   boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
   boolean button_released = (action.button == MB_RELEASED);
 
-  GameActions_MM_Ext(action, warp_mode);
+  GameActions_MM_Ext();
 
   CheckSingleStepMode_MM(element_clicked, button_released);
 }
 
-void MovePacMen(void)
+static void MovePacMen(void)
 {
   int mx, my, ox, oy, nx, ny;
   int element;
@@ -3807,7 +4153,7 @@ void MovePacMen(void)
     }
 
     DrawField_MM(nx, ny);
-    BackToFront();
+    BackToFront_MM();
 
     if (!laser.fuse_off)
     {
@@ -3836,182 +4182,6 @@ void MovePacMen(void)
   }
 }
 
-void GameWon_MM(void)
-{
-  int hi_pos;
-  boolean raise_level = FALSE;
-
-#if 0
-  if (local_player->MovPos)
-    return;
-
-  local_player->LevelSolved = FALSE;
-#endif
-
-  if (game_mm.energy_left)
-  {
-    if (setup.sound_loops)
-      PlaySoundExt(SND_SIRR, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT,
-                  SND_CTRL_PLAY_LOOP);
-
-    while (game_mm.energy_left > 0)
-    {
-      if (!setup.sound_loops)
-       PlaySoundStereo(SND_SIRR, SOUND_MAX_RIGHT);
-
-      /*
-      if (game_mm.energy_left > 0 && !(game_mm.energy_left % 10))
-       RaiseScore_MM(native_mm_level.score[SC_ZEITBONUS]);
-      */
-
-      RaiseScore_MM(5);
-
-      game_mm.energy_left--;
-      if (game_mm.energy_left >= 0)
-      {
-#if 0
-       BlitBitmap(pix[PIX_DOOR], drawto,
-                  DOOR_GFX_PAGEX5 + XX_ENERGY, DOOR_GFX_PAGEY1 + YY_ENERGY,
-                  ENERGY_XSIZE, ENERGY_YSIZE - game_mm.energy_left,
-                  DX_ENERGY, DY_ENERGY);
-#endif
-       redraw_mask |= REDRAW_DOOR_1;
-      }
-
-      BackToFront();
-      Delay_WithScreenUpdates(10);
-    }
-
-    if (setup.sound_loops)
-      StopSound(SND_SIRR);
-  }
-  else if (native_mm_level.time == 0)          // level without time limit
-  {
-    if (setup.sound_loops)
-      PlaySoundExt(SND_SIRR, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT,
-                  SND_CTRL_PLAY_LOOP);
-
-    while (TimePlayed < 999)
-    {
-      if (!setup.sound_loops)
-       PlaySoundStereo(SND_SIRR, SOUND_MAX_RIGHT);
-      if (TimePlayed < 999 && !(TimePlayed % 10))
-       RaiseScore_MM(native_mm_level.score[SC_TIME_BONUS]);
-      if (TimePlayed < 900 && !(TimePlayed % 10))
-       TimePlayed += 10;
-      else
-       TimePlayed++;
-
-      /*
-      DrawText(DX_TIME, DY_TIME, int2str(TimePlayed, 3), FONT_TEXT_2);
-      */
-
-      BackToFront();
-      Delay_WithScreenUpdates(10);
-    }
-
-    if (setup.sound_loops)
-      StopSound(SND_SIRR);
-  }
-
-  CloseDoor(DOOR_CLOSE_1);
-
-  Request("Level solved!", REQ_CONFIRM);
-
-  if (level_nr == leveldir_current->handicap_level)
-  {
-    leveldir_current->handicap_level++;
-    SaveLevelSetup_SeriesInfo();
-  }
-
-  if (level_editor_test_game)
-    game_mm.score = -1;                // no highscore when playing from editor
-  else if (level_nr < leveldir_current->last_level)
-    raise_level = TRUE;                // advance to next level
-
-  if ((hi_pos = NewHiScore_MM()) >= 0)
-  {
-    game_status = HALLOFFAME;
-
-    // DrawHallOfFame(hi_pos);
-
-    if (raise_level)
-      level_nr++;
-  }
-  else
-  {
-    game_status = MAINMENU;
-
-    if (raise_level)
-      level_nr++;
-
-    // DrawMainMenu();
-  }
-
-  BackToFront();
-}
-
-int NewHiScore_MM(void)
-{
-  int k, l;
-  int position = -1;
-
-  // LoadScore(level_nr);
-
-  if (strcmp(setup.player_name, EMPTY_PLAYER_NAME) == 0 ||
-      game_mm.score < highscore[MAX_SCORE_ENTRIES - 1].Score)
-    return -1;
-
-  for (k = 0; k < MAX_SCORE_ENTRIES; k++)
-  {
-    if (game_mm.score > highscore[k].Score)
-    {
-      // player has made it to the hall of fame
-
-      if (k < MAX_SCORE_ENTRIES - 1)
-      {
-       int m = MAX_SCORE_ENTRIES - 1;
-
-#ifdef ONE_PER_NAME
-       for (l = k; l < MAX_SCORE_ENTRIES; l++)
-         if (!strcmp(setup.player_name, highscore[l].Name))
-           m = l;
-       if (m == k)     // player's new highscore overwrites his old one
-         goto put_into_list;
-#endif
-
-       for (l = m; l>k; l--)
-       {
-         strcpy(highscore[l].Name, highscore[l - 1].Name);
-         highscore[l].Score = highscore[l - 1].Score;
-       }
-      }
-
-#ifdef ONE_PER_NAME
-      put_into_list:
-#endif
-      strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
-      highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
-      highscore[k].Score = game_mm.score;
-      position = k;
-
-      break;
-    }
-
-#ifdef ONE_PER_NAME
-    else if (!strncmp(setup.player_name, highscore[k].Name,
-                     MAX_PLAYER_NAME_LEN))
-      break;   // player already there with a higher score
-#endif
-
-  }
-
-  // if (position >= 0)
-  //   SaveScore(level_nr);
-
-  return position;
-}
-
 static void InitMovingField_MM(int x, int y, int direction)
 {
   int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
@@ -4024,35 +4194,6 @@ static void InitMovingField_MM(int x, int y, int direction)
     Tile[newx][newy] = EL_BLOCKED;
 }
 
-static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
-{
-  int direction = MovDir[x][y];
-  int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
-  int newy = y + (direction == MV_UP   ? -1 : direction == MV_DOWN  ? +1 : 0);
-
-  *goes_to_x = newx;
-  *goes_to_y = newy;
-}
-
-static void Blocked2Moving_MM(int x, int y,
-                             int *comes_from_x, int *comes_from_y)
-{
-  int oldx = x, oldy = y;
-  int direction = MovDir[x][y];
-
-  if (direction == MV_LEFT)
-    oldx++;
-  else if (direction == MV_RIGHT)
-    oldx--;
-  else if (direction == MV_UP)
-    oldy++;
-  else if (direction == MV_DOWN)
-    oldy--;
-
-  *comes_from_x = oldx;
-  *comes_from_y = oldy;
-}
-
 static int MovingOrBlocked2Element_MM(int x, int y)
 {
   int element = Tile[x][y];
@@ -4061,7 +4202,7 @@ static int MovingOrBlocked2Element_MM(int x, int y)
   {
     int oldx, oldy;
 
-    Blocked2Moving_MM(x, y, &oldx, &oldy);
+    Blocked2Moving(x, y, &oldx, &oldy);
 
     return Tile[oldx][oldy];
   }
@@ -4069,16 +4210,6 @@ static int MovingOrBlocked2Element_MM(int x, int y)
   return element;
 }
 
-#if 0
-static void RemoveField(int x, int y)
-{
-  Tile[x][y] = EL_EMPTY;
-  MovPos[x][y] = 0;
-  MovDir[x][y] = 0;
-  MovDelay[x][y] = 0;
-}
-#endif
-
 static void RemoveMovingField_MM(int x, int y)
 {
   int oldx = x, oldy = y, newx = x, newy = y;
@@ -4088,13 +4219,13 @@ static void RemoveMovingField_MM(int x, int y)
 
   if (IS_MOVING(x, y))
   {
-    Moving2Blocked_MM(x, y, &newx, &newy);
+    Moving2Blocked(x, y, &newx, &newy);
     if (Tile[newx][newy] != EL_BLOCKED)
       return;
   }
   else if (Tile[x][y] == EL_BLOCKED)
   {
-    Blocked2Moving_MM(x, y, &oldx, &oldy);
+    Blocked2Moving(x, y, &oldx, &oldy);
     if (!IS_MOVING(oldx, oldy))
       return;
   }
@@ -4108,44 +4239,6 @@ static void RemoveMovingField_MM(int x, int y)
   DrawLevelField_MM(newx, newy);
 }
 
-void PlaySoundLevel(int x, int y, int sound_nr)
-{
-  int sx = SCREENX(x), sy = SCREENY(y);
-  int volume, stereo;
-  int silence_distance = 8;
-
-  if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
-      (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
-    return;
-
-  if (!IN_LEV_FIELD(x, y) ||
-      sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
-      sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
-    return;
-
-  volume = SOUND_MAX_VOLUME;
-
-#ifndef MSDOS
-  stereo = (sx - SCR_FIELDX/2) * 12;
-#else
-  stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
-  if (stereo > SOUND_MAX_RIGHT)
-    stereo = SOUND_MAX_RIGHT;
-  if (stereo < SOUND_MAX_LEFT)
-    stereo = SOUND_MAX_LEFT;
-#endif
-
-  if (!IN_SCR_FIELD(sx, sy))
-  {
-    int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
-    int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
-
-    volume -= volume * (dx > dy ? dx : dy) / silence_distance;
-  }
-
-  PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
-}
-
 static void RaiseScore_MM(int value)
 {
   game_mm.score += value;
@@ -4186,7 +4279,7 @@ void RaiseScoreElement_MM(int element)
 // Mirror Magic game engine snapshot handling functions
 // ----------------------------------------------------------------------------
 
-void SaveEngineSnapshotValues_MM(ListNode **buffers)
+void SaveEngineSnapshotValues_MM(void)
 {
   int x, y;
 
@@ -4201,7 +4294,6 @@ void SaveEngineSnapshotValues_MM(ListNode **buffers)
       engine_snapshot_mm.Hit[x][y]   = Hit[x][y];
       engine_snapshot_mm.Box[x][y]   = Box[x][y];
       engine_snapshot_mm.Angle[x][y] = Angle[x][y];
-      engine_snapshot_mm.Frame[x][y] = Frame[x][y];
     }
   }
 
@@ -4244,7 +4336,6 @@ void LoadEngineSnapshotValues_MM(void)
       Hit[x][y]   = engine_snapshot_mm.Hit[x][y];
       Box[x][y]   = engine_snapshot_mm.Box[x][y];
       Angle[x][y] = engine_snapshot_mm.Angle[x][y];
-      Frame[x][y] = engine_snapshot_mm.Frame[x][y];
     }
   }