added optional button to restart game (door, panel and touch variants)
[rocksndiamonds.git] / src / game_mm / mm_game.c
index 8a7d381e44ebca3acbb941b3e4b2eb0c04d691b5..b258e374b7f0bbeb868be9b873e831e331394af0 100644 (file)
@@ -4,30 +4,31 @@
 // (c) 1994-2017 by Artsoft Entertainment
 //                         Holger Schemel
 //                 info@artsoft.org
-//                 http://www.artsoft.org/
+//                 https://www.artsoft.org/
 // ----------------------------------------------------------------------------
 // mm_game.c
 // ============================================================================
 
+#include <math.h>
+
 #include "main_mm.h"
 
 #include "mm_main.h"
 #include "mm_game.h"
 #include "mm_tools.h"
 
-/* graphic position values for game controls */
+// graphic position values for game controls
 #define ENERGY_XSIZE           32
 #define ENERGY_YSIZE           MAX_LASER_ENERGY
 #define OVERLOAD_XSIZE         ENERGY_XSIZE
 #define OVERLOAD_YSIZE         MAX_LASER_OVERLOAD
 
-/* values for Explode_MM() */
+// 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) */
+// special positions in the game control window (relative to control window)
 #define XX_LEVEL               36
 #define YY_LEVEL               23
 #define XX_KETTLES             29
@@ -39,7 +40,7 @@
 #define XX_OVERLOAD            60
 #define YY_OVERLOAD            YY_ENERGY
 
-/* special positions in the game control window (relative to main window) */
+// special positions in the game control window (relative to main window)
 #define DX_LEVEL               (DX + XX_LEVEL)
 #define DY_LEVEL               (DY + YY_LEVEL)
 #define DX_KETTLES             (DX + XX_KETTLES)
 #define IS_LOOP_SOUND(s)       ((s) == SND_FUEL)
 #define IS_MUSIC_SOUND(s)      ((s) == SND_TYGER || (s) == SND_VOYAGER)
 
-/* game button identifiers */
+// game button identifiers
 #define GAME_CTRL_ID_LEFT      0
 #define GAME_CTRL_ID_MIDDLE    1
 #define GAME_CTRL_ID_RIGHT     2
 
 #define NUM_GAME_BUTTONS       3
 
-/* values for DrawLaser() */
+// values for DrawLaser()
 #define DL_LASER_DISABLED      0
 #define DL_LASER_ENABLED       1
 
-/* values for 'click_delay_value' in ClickElement() */
-#define CLICK_DELAY_SHORT      125
-#define CLICK_DELAY_LONG       250
-#define AUTO_ROTATE_DELAY      CLICK_DELAY_SHORT
-
-/* forward declaration for internal use */
+// values for 'click_delay_value' in ClickElement()
+#define CLICK_DELAY_FIRST      12      // delay (frames) after first click
+#define CLICK_DELAY            6       // delay (frames) for pressed butten
+
+#define AUTO_ROTATE_DELAY      CLICK_DELAY
+#define INIT_GAME_ACTIONS_DELAY        (ONE_SECOND_DELAY / GAME_FRAME_DELAY)
+#define NUM_INIT_CYCLE_STEPS   16
+#define PACMAN_MOVE_DELAY      12
+#define ENERGY_DELAY           (ONE_SECOND_DELAY / GAME_FRAME_DELAY)
+#define HEALTH_DEC_DELAY       3
+#define HEALTH_INC_DELAY       9
+#define HEALTH_DELAY(x)                ((x) ? HEALTH_DEC_DELAY : HEALTH_INC_DELAY)
+
+#define BEGIN_NO_HEADLESS                      \
+  {                                            \
+    boolean last_headless = program.headless;  \
+                                               \
+    program.headless = FALSE;                  \
+
+#define END_NO_HEADLESS                                \
+    program.headless = last_headless;          \
+  }                                            \
+
+// forward declaration for internal use
 static int MovingOrBlocked2Element_MM(int, int);
 static void Bang_MM(int, int);
 static void RaiseScore_MM(int);
+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;
+
+// variables for laser control
+static int last_LX = 0, last_LY = 0, last_hit_mask = 0;
+static int hold_x = -1, hold_y = -1;
+
+// variables for pacman control
+static int pacman_nr = -1;
+
+// various game engine delay counters
+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[NUM_MM_MASKS][16][16 + 1] =
+{
+  {
+    "                ",
+    "    XXXXX       ",
+    "   XXXXXXX      ",
+    "  XXXXXXXXXXX   ",
+    "  XXXXXXXXXXXXX ",
+    "  XXXXXXXXXXXXXX",
+    "  XXXXXXXXXXXXXX",
+    "  XXXXXXXXXXXXX ",
+    "  XXXXXXXXXXXXX ",
+    "  XXXXXXXXXXXXX ",
+    "  XXXXXXXXXXXXX ",
+    "  XXXXXXXXXXXXX ",
+    "  XXXXXXXXXXXXX ",
+    "  XXXXXXXXXXXXX ",
+    "  XXXXXXXXXXXX  ",
+    "  XXXXXXXXXXXX  ",
+  },
+  {
+    "                ",
+    "    XXXXXXXX    ",
+    "  XXXXXXXXXXXX  ",
+    " XXXXXXXXXXXXXX ",
+    " XXXXXXXXXXXXXX ",
+    " XXXXXXXXXXXXXX ",
+    " XXXXXXXXXXXXXX ",
+    " XXXXXXXXXXXXXX ",
+    " XXXXXXXXXXXXXX ",
+    " XXXXXXXXXXXXXX ",
+    " XXXXXXXXXXXXXX ",
+    " XXXXXXXXXXXXXX ",
+    " XXXXXXXXXXXXXX ",
+    "  XXXXXXXXXXXXX ",
+    "  XXXXXXXXXXXX  ",
+    "  XXXXXXXXXXXX  ",
+  },
+  {
+    "                ",
+    "    XXXXXX      ",
+    "  XXXXXXXXX     ",
+    " XXXXXXXXXXX    ",
+    "XXXXXXXXXXXXX   ",
+    "XXXXXXXXXXXXX   ",
+    "XXXXXXXXXXXXXX  ",
+    " XXXXXXXXXXXXX  ",
+    " XXXXXXXXXXXXX  ",
+    " XXXXXXXXXXXXX  ",
+    " XXXXXXXXXXXXX  ",
+    " XXXXXXXXXXXXX  ",
+    " XXXXXXXXXXXXX  ",
+    " XXXXXXXXXXXXX  ",
+    "  XXXXXXXXXXXX  ",
+    "  XXXXXXXXXXXX  ",
+  },
+  {
+    "                ",
+    "    XXXXXX      ",
+    "   XXXXXXXX     ",
+    "  XXXXXXXXXX    ",
+    "  XXXXXXXXXXX   ",
+    "  XXXXXXXXXXX   ",
+    "  XXXXXXXXXXXX  ",
+    "  XXXXXXXXXXXX  ",
+    "  XXXXXXXXXXXX  ",
+    "  XXXXXXXXXXXX  ",
+    "  XXXXXXXXXXXX  ",
+    "  XXXXXXXXXXXX  ",
+    "  XXXXXXXXXXXX  ",
+    "  XXXXXXXXXXXX  ",
+    "  XXXXXXXXXXXX  ",
+    "  XXXXX  XXXXX  ",
+  },
+  {
+    " XXXXXX  XXXXXX ",
+    "XXXXXXXXXXXXXXXX",
+    "XXXXXXXXXXXXXXXX",
+    "XXXXXXXXXXXXXXXX",
+    "XXXXXXXXXXXXXXXX",
+    "XXXXXXXXXXXXXXXX",
+    "XXXXXXXXXXXXXXXX",
+    "                ",
+    "                ",
+    "XXXXXXXXXXXXXXXX",
+    "XXXXXXXXXXXXXXXX",
+    "XXXXXXXXXXXXXXXX",
+    "XXXXXXXXXXXXXXXX",
+    "XXXXXXXXXXXXXXXX",
+    "XXXXXXXXXXXXXXXX",
+    " XXXXXX  XXXXXX ",
+  },
+  {
+    " XXXXXX  XXXXXX ",
+    "XXXXXXX  XXXXXXX",
+    "XXXXXXX  XXXXXXX",
+    "XXXXXXX  XXXXXXX",
+    "XXXXXXX  XXXXXXX",
+    "XXXXXXX  XXXXXXX",
+    "XXXXXXX  XXXXXXX",
+    " XXXXXX  XXXXXX ",
+    " XXXXXX  XXXXXX ",
+    "XXXXXXX  XXXXXXX",
+    "XXXXXXX  XXXXXXX",
+    "XXXXXXX  XXXXXXX",
+    "XXXXXXX  XXXXXXX",
+    "XXXXXXX  XXXXXXX",
+    "XXXXXXX  XXXXXXX",
+    " XXXXXX  XXXXXX ",
+  },
+  {
+    "     XX  XXXXX  ",
+    "    XXX  XXXX   ",
+    "   XXXX  XXX   X",
+    "  XXXXXXXXX   XX",
+    " XXXXXXXXX   XXX",
+    "XXXXXXXXX   XXXX",
+    "XXXXXXXX   XXXXX",
+    "   XXXX   XXX   ",
+    "   XXX   XXXX   ",
+    "XXXXX   XXXXXXXX",
+    "XXXX   XXXXXXXXX",
+    "XXX   XXXXXXXXX ",
+    "XX   XXXXXXXXX  ",
+    "X   XXX  XXXX   ",
+    "   XXXX  XXX    ",
+    "  XXXXX  XX     ",
+  },
+  {
+    "  XXXXX  XX     ",
+    "   XXXX  XXX    ",
+    "X   XXX  XXXX   ",
+    "XX   XXXXXXXXX  ",
+    "XXX   XXXXXXXXX ",
+    "XXXX   XXXXXXXXX",
+    "XXXXX   XXXXXXXX",
+    "   XXX   XXXX   ",
+    "   XXXX   XXX   ",
+    "XXXXXXXX   XXXXX",
+    "XXXXXXXXX   XXXX",
+    " XXXXXXXXX   XXX",
+    "  XXXXXXXXX   XX",
+    "   XXXX  XXX   X",
+    "    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",
+    "XXXXXXXXXXXXXXXX",
+    "XXXXXXXXXXXXXXXX",
+    "XXXXXXXXXXXXXXXX",
+    "XXXXXXXXXXXXXXXX",
+    "XXXXXXXXXXXXXXXX",
+    "XXXXXXXXXXXXXXXX",
+    "XXXXXXXXXXXXXXXX",
+    "XXXXXXXXXXXXXXXX",
+    "XXXXXXXXXXXXXXXX",
+    "XXXXXXXXXXXXXXXX",
+    "XXXXXXXXXXXXXXXX",
+    "XXXXXXXXXXXXXXXX",
+    "XXXXXXXXXXXXXXXX",
+    "XXXXXXXXXXXXXXXX",
+  },
+  {
+    "                ",
+    "      XXXX      ",
+    "    XXXXXXXX    ",
+    "   XXXXXXXXXX   ",
+    "  XXXXXXXXXXXX  ",
+    "  XXXXXXXXXXXX  ",
+    " XXXXXXXXXXXXXX ",
+    " XXXXXXXXXXXXXX ",
+    " XXXXXXXXXXXXXX ",
+    " XXXXXXXXXXXXXX ",
+    "  XXXXXXXXXXXX  ",
+    "  XXXXXXXXXXXX  ",
+    "   XXXXXXXXXX   ",
+    "    XXXXXXXX    ",
+    "      XXXX      ",
+    "                ",
+  },
+};
 
 static int get_element_angle(int element)
 {
@@ -89,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;
 }
@@ -97,7 +424,7 @@ static int get_opposite_angle(int angle)
 {
   int opposite_angle = angle + ANG_RAY_180;
 
-  /* make sure "opposite_angle" is in valid interval [0, 15] */
+  // make sure "opposite_angle" is in valid interval [0, 15]
   return (opposite_angle + 16) % 16;
 }
 
@@ -105,13 +432,108 @@ static int get_mirrored_angle(int laser_angle, int mirror_angle)
 {
   int reflected_angle = 16 - laser_angle + mirror_angle;
 
-  /* make sure "reflected_angle" is in valid interval [0, 15] */
+  // make sure "reflected_angle" is in valid interval [0, 15]
   return (reflected_angle + 16) % 16;
 }
 
+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_mm, points, num_points, pixel_drawto);
+
+  BEGIN_NO_HEADLESS
+  {
+    DrawLines(laser_bitmap, points, num_points, pixel_buffer);
+  }
+  END_NO_HEADLESS
+}
+
+static boolean CheckLaserPixel(int x, int y)
+{
+  Pixel pixel;
+
+  BEGIN_NO_HEADLESS
+  {
+    pixel = ReadPixel(laser_bitmap, x, y);
+  }
+  END_NO_HEADLESS
+
+  return (pixel == WHITE_PIXEL);
+}
+
+static void CheckExitMM(void)
+{
+  int exit_element = EL_EMPTY;
+  int exit_x = 0;
+  int exit_y = 0;
+  int x, y;
+  static int xy[4][2] =
+  {
+    { +1,  0 },
+    {  0, -1 },
+    { -1,  0 },
+    {  0, +1 }
+  };
+
+  for (y = 0; y < lev_fieldy; y++)
+  {
+    for (x = 0; x < lev_fieldx; x++)
+    {
+      if (Tile[x][y] == EL_EXIT_CLOSED)
+      {
+       // initiate opening animation of exit door
+       Tile[x][y] = EL_EXIT_OPENING;
+
+       exit_element = EL_EXIT_OPEN;
+       exit_x = x;
+       exit_y = y;
+      }
+      else if (IS_RECEIVER(Tile[x][y]))
+      {
+       // remove field that blocks receiver
+       int phase = Tile[x][y] - EL_RECEIVER_START;
+       int blocking_x, blocking_y;
+
+       blocking_x = x + xy[phase][0];
+       blocking_y = y + xy[phase][1];
+
+       if (IN_LEV_FIELD(blocking_x, blocking_y))
+       {
+         Tile[blocking_x][blocking_y] = EL_EMPTY;
+
+         DrawField_MM(blocking_x, blocking_y);
+       }
+
+       exit_element = EL_RECEIVER;
+       exit_x = x;
+       exit_y = y;
+      }
+    }
+  }
+
+  if (exit_element != EL_EMPTY)
+    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 = Feld[x][y];
+  int element = Tile[x][y];
   static int direction[3][4] =
   {
     { MV_RIGHT, MV_UP,    MV_LEFT,  MV_DOWN },
@@ -119,13 +541,13 @@ static void InitMovDir_MM(int x, int y)
     { MV_LEFT,  MV_RIGHT, MV_UP,    MV_DOWN }
   };
 
-  switch(element)
+  switch (element)
   {
     case EL_PACMAN_RIGHT:
     case EL_PACMAN_UP:
     case EL_PACMAN_LEFT:
     case EL_PACMAN_DOWN:
-      Feld[x][y] = EL_PACMAN;
+      Tile[x][y] = EL_PACMAN;
       MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
       break;
 
@@ -136,17 +558,17 @@ static void InitMovDir_MM(int x, int y)
 
 static void InitField(int x, int y, boolean init_game)
 {
-  int element = Feld[x][y];
+  int element = Tile[x][y];
 
   switch (element)
   {
     case EL_DF_EMPTY:
-      Feld[x][y] = EL_EMPTY;
+      Tile[x][y] = EL_EMPTY;
       break;
 
     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;
 
@@ -168,15 +590,15 @@ static void InitField(int x, int y, boolean init_game)
       {
        if (IS_BEAMER_OLD(element))
        {
-         Feld[x][y] = EL_BEAMER_BLUE_START + (element - EL_BEAMER_START);
-         element = Feld[x][y];
+         Tile[x][y] = EL_BEAMER_BLUE_START + (element - EL_BEAMER_START);
+         element = Tile[x][y];
        }
 
        if (!IS_FIBRE_OPTIC(element))
        {
          static int steps_grid_auto = 0;
 
-         if (game_mm.num_cycle == 0)   /* initialize cycle steps for grids */
+         if (game_mm.num_cycle == 0)   // initialize cycle steps for grids
            steps_grid_auto = RND(16) * (RND(2) ? -1 : +1);
 
          if (IS_GRID_STEEL_AUTO(element) ||
@@ -206,56 +628,60 @@ 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;
   }
 }
 
-static void InitCycleElements()
+static void InitCycleElements_RotateSingleStep(void)
 {
-  int i, j;
+  int i;
 
-  if (game_mm.num_cycle == 0)  /* no elements to cycle */
+  if (game_mm.num_cycle == 0)  // no elements to cycle
     return;
 
-  for (i = 0; i < 16; i++)
+  for (i = 0; i < game_mm.num_cycle; i++)
   {
-    for (j = 0; j < game_mm.num_cycle; j++)
-    {
-      int x = game_mm.cycle[j].x;
-      int y = game_mm.cycle[j].y;
-      int step = SIGN(game_mm.cycle[j].steps);
-      int last_element = Feld[x][y];
-      int next_element = get_rotated_element(last_element, step);
+    int x = game_mm.cycle[i].x;
+    int y = game_mm.cycle[i].y;
+    int step = SIGN(game_mm.cycle[i].steps);
+    int last_element = Tile[x][y];
+    int next_element = get_rotated_element(last_element, step);
 
-      if (!game_mm.cycle[j].steps)
-       continue;
-
-      Feld[x][y] = next_element;
-
-      DrawField_MM(x, y);
-      game_mm.cycle[j].steps -= step;
-    }
-
-    BackToFront();
-    ColorCycling();
-
-#ifdef DEBUG
-    if (setup.quick_doors)
+    if (!game_mm.cycle[i].steps)
       continue;
-#endif
 
-    Delay(AUTO_ROTATE_DELAY);
+    Tile[x][y] = next_element;
+
+    game_mm.cycle[i].steps -= step;
   }
 }
 
-static void InitLaser()
+static void InitLaser(void)
 {
-  int start_element = Feld[laser.start_edge.x][laser.start_edge.y];
+  int start_element = Tile[laser.start_edge.x][laser.start_edge.y];
   int step = (IS_LASER(start_element) ? 4 : 0);
 
   LX = laser.start_edge.x * TILEX;
@@ -280,37 +706,52 @@ static void InitLaser()
   laser.num_beamers = 0;
   laser.beamer_edge[0] = 0;
 
-  AddLaserEdge(LX, LY);                /* set laser starting edge */
+  laser.dest_element = EL_EMPTY;
+  laser.wall_mask = 0;
 
-  pen_ray = GetPixelFromRGB(window,
-                           native_mm_level.laser_red   * 0xFF,
-                           native_mm_level.laser_green * 0xFF,
-                           native_mm_level.laser_blue  * 0xFF);
+  AddLaserEdge(LX, LY);                // set laser starting edge
+
+  SetLaserColor(0xFF);
 }
 
-void InitGameEngine_MM()
+void InitGameEngine_MM(void)
 {
   int i, x, y;
 
-  /* set global editor control values */
-  editor.draw_walls_masked = FALSE;
+  BEGIN_NO_HEADLESS
+  {
+    // initialize laser bitmap to current playfield (screen) size
+    ReCreateBitmap(&laser_bitmap, drawto_mm->width, drawto_mm->height);
+    ClearRectangle(laser_bitmap, 0, 0, drawto_mm->width, drawto_mm->height);
+  }
+  END_NO_HEADLESS
 
-  /* set global game control values */
+  // set global game control values
   game_mm.num_cycle = 0;
   game_mm.num_pacman = 0;
 
   game_mm.score = 0;
-  game_mm.energy_left = native_mm_level.time;
+  game_mm.energy_left = 0;     // later set to "native_mm_level.time"
   game_mm.kettles_still_needed =
     (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;
 
-  /* set global laser control values (must be set before "InitLaser()") */
+  game_mm.laser_overload_value = 0;
+  game_mm.laser_enabled = FALSE;
+
+  // set global laser control values (must be set before "InitLaser()")
   laser.start_edge.x = 0;
   laser.start_edge.y = 0;
   laser.start_angle = 0;
@@ -324,128 +765,183 @@ void InitGameEngine_MM()
   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;
+  last_LY = 0;
+  last_hit_mask = 0;
+
+  hold_x = -1;
+  hold_y = -1;
+
+  pacman_nr = -1;
+
   CT = Ct = 0;
 
+  rotate_delay.count = 0;
+  pacman_delay.count = 0;
+  energy_delay.count = 0;
+  overload_delay.count = 0;
+
+  ClickElement(-1, -1, -1);
+
   for (x = 0; x < lev_fieldx; x++)
   {
     for (y = 0; y < lev_fieldy; y++)
     {
-      Feld[x][y] = Ur[x][y];
+      Tile[x][y] = Ur[x][y];
       Hit[x][y] = Box[x][y] = 0;
       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);
     }
   }
 
-#if 0
-  CloseDoor(DOOR_CLOSE_1);
-#endif
-
   DrawLevel_MM();
 }
 
-void InitGameEngine_MM_AfterFadingIn()
+void InitGameActions_MM(void)
 {
-  InitCycleElements();
+  int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
+  int cycle_steps_done = 0;
+  int i;
+
   InitLaser();
 
-#if 0
-  /* copy default game door content to main double buffer */
-  BlitBitmap(pix[PIX_DOOR], drawto,
-            DOOR_GFX_PAGEX5, DOOR_GFX_PAGEY1, DXSIZE, DYSIZE, DX, DY);
-#endif
+  for (i = 0; i <= num_init_game_frames; i++)
+  {
+    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);
 
-#if 0
-  DrawText(DX_LEVEL, DY_LEVEL,
-          int2str(level_nr, 2), FONT_TEXT_2);
-  DrawText(DX_KETTLES, DY_KETTLES,
-          int2str(game_mm.kettles_still_needed, 3), FONT_TEXT_2);
-  DrawText(DX_SCORE, DY_SCORE,
-          int2str(game_mm.score, 4), FONT_TEXT_2);
-#endif
+    game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
 
-#if 0
-  UnmapGameButtons();
-  MapGameButtons();
-#endif
+    UpdateAndDisplayGameControlValues();
 
-#if 0
-  /* copy actual game door content to door double buffer for OpenDoor() */
-  BlitBitmap(drawto, pix[PIX_DB_DOOR],
-            DX, DY, DXSIZE, DYSIZE, DOOR_GFX_PAGEX1, DOOR_GFX_PAGEY1);
-#endif
+    while (cycle_steps_done < NUM_INIT_CYCLE_STEPS * i / num_init_game_frames)
+    {
+      InitCycleElements_RotateSingleStep();
 
-#if 0
-  OpenDoor(DOOR_OPEN_ALL);
-#endif
+      cycle_steps_done++;
+    }
 
-  if (setup.sound_loops)
-    PlaySoundExt(SND_FUEL, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT, SND_CTRL_PLAY_LOOP);
+    AdvanceFrameCounter();
+    AdvanceGfxFrame();
 
-#if 0 // !!! TEMPORARILY DISABLED !!!
-  for (i = 0; i <= game_mm.energy_left; i += 2)
-  {
-    if (!setup.sound_loops)
-      PlaySoundStereo(SND_FUEL, SOUND_MAX_RIGHT);
+    if (PendingEscapeKeyEvent())
+      continue;
 
-#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);
+#ifdef DEBUG
+    if (setup.quick_doors)
+      continue;
 #endif
 
-    redraw_mask |= REDRAW_DOOR_1;
-    BackToFront();
+    DrawLevel_MM();
 
-    ColorCycling();
+    BackToFront_MM();
+  }
 
 #ifdef DEBUG
-    if (setup.quick_doors)
-      continue;
+  if (setup.quick_doors)
+    DrawLevel_MM();
 #endif
 
-    Delay(20);
+  ScanLaser();
+
+  if (game_mm.kettles_still_needed == 0)
+    CheckExitMM();
+
+  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);
   }
 
-  if (setup.sound_loops)
-    StopSound(SND_FUEL);
-#endif
+  DrawLaser(0, DL_LASER_DISABLED);
 
-#if 0
-  if (setup.sound_music && num_bg_loops)
-    PlayMusic(level_nr % num_bg_loops);
-#endif
+  StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
+}
 
-  ScanLaser();
+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)
 {
-  if (lx < -2 || ly < -2 || lx >= SXSIZE + 2 || ly >= SYSIZE + 2)
+  int full_sxsize = MAX(FULL_SXSIZE, lev_fieldx * TILEX);
+  int full_sysize = MAX(FULL_SYSIZE, lev_fieldy * TILEY);
+
+  // 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)
   {
-    Error(ERR_WARN, "AddLaserEdge: out of bounds: %d, %d", lx, ly);
+    Warn("AddLaserEdge: out of bounds: %d, %d", lx, ly);
 
     return;
   }
 
-  laser.edge[laser.num_edges].x = SX + 2 + lx;
-  laser.edge[laser.num_edges].y = SY + 2 + ly;
+  laser.edge[laser.num_edges].x = cSX2 + lx;
+  laser.edge[laser.num_edges].y = cSY2 + ly;
   laser.num_edges++;
 
   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;
@@ -454,14 +950,14 @@ void AddDamagedField(int ex, int ey)
   laser.num_damages++;
 }
 
-boolean StepBehind()
+static boolean StepBehind(void)
 {
   if (laser.num_edges)
   {
     int x = LX - XS;
     int y = LY - YS;
-    int last_x = laser.edge[laser.num_edges - 1].x - SX - 2;
-    int last_y = laser.edge[laser.num_edges - 1].y - SY - 2;
+    int last_x = laser.edge[laser.num_edges - 1].x - cSX2;
+    int last_y = laser.edge[laser.num_edges - 1].y - cSY2;
 
     return ((x - last_x) * XS < 0 || (y - last_y) * YS < 0);
   }
@@ -471,54 +967,93 @@ boolean StepBehind()
 
 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
 }
 
-int ScanPixel()
+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) */
+  // follow laser beam until it hits something (at least the screen border)
   while (hit_mask == HIT_MASK_NO_HIT)
   {
     int i;
 
 #if 0
-    /* for safety */
+    // for safety
     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))
       {
-       int element = Feld[lx][ly];
+       int element = Tile[lx][ly];
 
        if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
        {
@@ -532,23 +1067,16 @@ int ScanPixel()
        }
        else
        {
-         int graphic_mask = getMaskFromElement(element);
-         Bitmap *bitmap;
-         int src_x, src_y;
-         int mask_x, mask_y;
+         int pos = getMaskFromElement(element);
 
-         getGraphicSource(graphic_mask, 0, &bitmap, &src_x, &src_y);
-
-         mask_x = src_x + dx;
-         mask_y = src_y + dy;
-
-         pixel = (ReadPixel(bitmap, mask_x, mask_y) ? 1 : 0);
+         pixel = getPixelFromMask(pos, dx, dy);
        }
       }
       else
       {
-       pixel = (SX + px < REAL_SX || SX + px >= REAL_SX + FULL_SXSIZE ||
-                SY + py < REAL_SY || SY + py >= REAL_SY + FULL_SYSIZE);
+       // 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);
       }
 
       if ((Sign[laser.current_angle] & (1 << i)) && pixel)
@@ -557,7 +1085,7 @@ int ScanPixel()
 
     if (hit_mask == HIT_MASK_NO_HIT)
     {
-      /* hit nothing -- go on with another step */
+      // hit nothing -- go on with another step
       LX += XS;
       LY += YS;
     }
@@ -566,19 +1094,55 @@ int ScanPixel()
   return hit_mask;
 }
 
-void ScanLaser()
+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)
@@ -596,71 +1160,133 @@ void ScanLaser()
     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
 
-    element = Feld[ELX][ELY];
+    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)
@@ -716,20 +1342,29 @@ void ScanLaser()
     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 != Feld[ELX][ELY])
+  if (laser.dest_element != Tile[ELX][ELY])
   {
-    printf("ALARM: laser.dest_element == %d, Feld[ELX][ELY] == %d\n",
-          laser.dest_element, Feld[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;
@@ -741,34 +1376,50 @@ void ScanLaser()
   if (rf)
     DrawLaser(rf - 1, DL_LASER_ENABLED);
 
-  Ct = CT = Counter();
+  Ct = CT = FrameCounter;
 
 #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
 }
 
-void DrawLaserExt(int start_edge, int num_edges, int mode)
+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)
   {
-    Error(ERR_WARN, "DrawLaserExt: start_edge < 0");
+    Warn("DrawLaserExt: start_edge < 0");
 
     return;
   }
 
   if (num_edges < 0)
   {
-    Error(ERR_WARN, "DrawLaserExt: num_edges < 0");
+    Warn("DrawLaserExt: num_edges < 0");
 
     return;
   }
@@ -776,26 +1427,25 @@ 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
 
-  /* now draw the laser to the backbuffer and (if enabled) to the screen */
-  DrawLines(drawto, &laser.edge[start_edge], num_edges,
-           (mode == DL_LASER_ENABLED ? pen_ray : pen_bg));
+  // now draw the laser to the backbuffer and (if enabled) to the screen
+  DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
 
   redraw_mask |= REDRAW_FIELD;
 
   if (mode == DL_LASER_ENABLED)
     return;
 
-  /* after the laser was deleted, the "damaged" graphics must be restored */
+  // after the laser was deleted, the "damaged" graphics must be restored
   if (laser.num_damages)
   {
     int damage_start = 0;
     int i;
 
-    /* determine the starting edge, from which graphics need to be restored */
+    // determine the starting edge, from which graphics need to be restored
     if (start_edge > 0)
     {
       for (i = 0; i < laser.num_damages; i++)
@@ -809,12 +1459,12 @@ void DrawLaserExt(int start_edge, int num_edges, int mode)
       }
     }
 
-    /* restore graphics from this starting edge to the end of damage list */
+    // restore graphics from this starting edge to the end of damage list
     for (i = damage_start; i < laser.num_damages; i++)
     {
       int lx = laser.damage[i].x;
       int ly = laser.damage[i].y;
-      int element = Feld[lx][ly];
+      int element = Tile[lx][ly];
 
       if (Hit[lx][ly] == laser.damage[i].edge)
        if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
@@ -829,7 +1479,7 @@ void DrawLaserExt(int start_edge, int num_edges, int mode)
 
     elx = laser.damage[damage_start].x;
     ely = laser.damage[damage_start].y;
-    element = Feld[elx][ely];
+    element = Tile[elx][ely];
 
 #if 0
     if (IS_BEAMER(element))
@@ -837,11 +1487,12 @@ 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
 
@@ -849,7 +1500,7 @@ void DrawLaserExt(int start_edge, int num_edges, int mode)
        laser.num_beamers > 0 &&
        start_edge == laser.beamer_edge[laser.num_beamers - 1])
     {
-      /* element is outgoing beamer */
+      // element is outgoing beamer
       laser.num_damages = damage_start + 1;
 
       if (IS_BEAMER(element))
@@ -857,32 +1508,32 @@ void DrawLaserExt(int start_edge, int num_edges, int mode)
     }
     else
     {
-      /* element is incoming beamer or other element */
+      // element is incoming beamer or other element
       laser.num_damages = damage_start;
       laser.current_angle = laser.damage[laser.num_damages].angle;
     }
   }
   else
   {
-    /* no damages but McDuffin himself (who needs to be redrawn anyway) */
+    // no damages but McDuffin himself (who needs to be redrawn anyway)
 
     elx = laser.start_edge.x;
     ely = laser.start_edge.y;
-    element = Feld[elx][ely];
+    element = Tile[elx][ely];
   }
 
   laser.num_edges = start_edge + 1;
   if (start_edge == 0)
     laser.current_angle = laser.start_angle;
 
-  LX = laser.edge[start_edge].x - (SX + 2);
-  LY = laser.edge[start_edge].y - (SY + 2);
+  LX = laser.edge[start_edge].x - cSX2;
+  LY = laser.edge[start_edge].y - cSY2;
   XS = 2 * Step[laser.current_angle].x;
   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)
@@ -897,10 +1548,10 @@ 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 */
+      if (IS_22_5_ANGLE(laser.current_angle))  // neither 90° nor 45° angle
        step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
       else
        step_size = 8;
@@ -910,17 +1561,16 @@ void DrawLaserExt(int start_edge, int num_edges, int mode)
           (laser.num_beamers == 0 ||
            start_edge != laser.beamer_edge[laser.num_beamers - 1])))
       {
-       /* element is incoming beamer or other element */
+       // element is incoming beamer or other element
        step_size = -step_size;
        laser.num_edges--;
       }
 
 #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;
@@ -935,21 +1585,28 @@ 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)
   {
-    Error(ERR_WARN, "DrawLaser: laser.num_edges - start_edge < 0");
+    Warn("DrawLaser: laser.num_edges - start_edge < 0");
 
     return;
   }
 
-  /* check if laser is interrupted by beamer element */
+  // check if laser is interrupted by beamer element
   if (laser.num_beamers > 0 &&
       start_edge < laser.beamer_edge[laser.num_beamers - 1])
   {
@@ -958,7 +1615,7 @@ void DrawLaser(int start_edge, int mode)
       int i;
       int tmp_start_edge = start_edge;
 
-      /* draw laser segments forward from the start to the last beamer */
+      // draw laser segments forward from the start to the last beamer
       for (i = 0; i < laser.num_beamers; i++)
       {
        int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
@@ -967,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);
@@ -976,7 +1633,7 @@ void DrawLaser(int start_edge, int mode)
        tmp_start_edge = laser.beamer_edge[i];
       }
 
-      /* draw last segment from last beamer to the end */
+      // draw last segment from last beamer to the end
       DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
                   DL_LASER_ENABLED);
     }
@@ -986,8 +1643,8 @@ void DrawLaser(int start_edge, int mode)
       int last_num_edges = laser.num_edges;
       int num_beamers = laser.num_beamers;
 
-      /* delete laser segments backward from the end to the first beamer */
-      for (i = num_beamers-1; i >= 0; i--)
+      // delete laser segments backward from the end to the first beamer
+      for (i = num_beamers - 1; i >= 0; i--)
       {
        int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
 
@@ -1002,11 +1659,16 @@ 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
 
-      /* delete first segment from start to the first beamer */
+      // special case when rotating first beamer: delete laser edge on beamer
+      // (but do not start scanning on previous edge to prevent mirror sound)
+      if (last_num_edges - start_edge == 1 && start_edge > 0)
+       DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
+
+      // delete first segment from start to the first beamer
       DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
     }
   }
@@ -1014,46 +1676,137 @@ void DrawLaser(int start_edge, int mode)
   {
     DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
   }
+
+  game_mm.laser_enabled = mode;
 }
 
-boolean HitElement(int element, int hit_mask)
+void DrawLaser_MM(void)
 {
-  if (HitOnlyAnEdge(element, hit_mask))
-    return FALSE;
+  DrawLaser(0, game_mm.laser_enabled);
+}
+
+static boolean HitElement(int element, int hit_mask)
+{
+  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);
 
-  /* this is more precise: check if laser would go through the center */
-  if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
+  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 (!IS_DF_SLOPE(element) && !through_center)
   {
-    /* skip the whole element before continuing the scan */
+    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;
@@ -1063,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 ||
@@ -1076,14 +1829,14 @@ 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) &&
       ((element - EL_POLAR_START) % 2 ||
        (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
   {
-    PlaySoundStereo(SND_KINK, ST(ELX));
+    PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
 
     laser.num_damages--;
 
@@ -1093,27 +1846,45 @@ boolean HitElement(int element, int hit_mask)
   if (IS_POLAR_CROSS(element) &&
       (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
   {
-    PlaySoundStereo(SND_KINK, ST(ELX));
+    PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
 
     laser.num_damages--;
 
     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)
   {
-#if 0
-    if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
-      printf("EXACT MATCH @ (%d, %d)\n", ELX, ELY);
-    else
-      printf("FUZZY MATCH @ (%d, %d)\n", ELX, ELY);
-#endif
+    int correction = 2;
 
-    LX = ELX * TILEX + 14;
-    LY = ELY * TILEY + 14;
+    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)
+      Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
+    else
+      Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
+#endif
+
+    LX = ELX * TILEX + 14;
+    LY = ELY * TILEY + 14;
 
     AddLaserEdge(LX, LY);
   }
@@ -1124,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)
   {
@@ -1142,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));
 
@@ -1152,31 +1927,107 @@ 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)) &&
+    // draw sparkles on mirror
+    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)
-      PlaySoundStereo(SND_LASER, ST(ELX));
+      PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
 
     laser.overloaded =
       (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);
   }
 
@@ -1187,9 +2038,19 @@ 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)
   {
-    PlaySoundStereo(SND_KINK, ST(ELX));
+    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;
@@ -1200,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)
@@ -1213,47 +2076,11 @@ boolean HitElement(int element, int hit_mask)
       if (game_mm.kettles_still_needed > 0)
        game_mm.kettles_still_needed--;
 
-      RaiseScore_MM(10);
+      game.snapshot.collected_item = TRUE;
 
       if (game_mm.kettles_still_needed == 0)
       {
-       int x, y;
-       static int xy[4][2] =
-       {
-         { +1,  0 },
-         {  0, -1 },
-         { -1,  0 },
-         {  0, +1 }
-       };
-
-        PlaySoundStereo(SND_KLING, ST(ELX));
-
-       for (y = 0; y < lev_fieldy; y++)
-       {
-         for (x = 0; x < lev_fieldx; x++)
-         {
-           /* initiate opening animation of exit door */
-           if (Feld[x][y] == EL_EXIT_CLOSED)
-             Feld[x][y] = EL_EXIT_OPENING;
-
-           /* remove field that blocks receiver */
-           if (IS_RECEIVER(Feld[x][y]))
-           {
-             int phase = Feld[x][y] - EL_RECEIVER_START;
-             int blocking_x, blocking_y;
-
-             blocking_x = x + xy[phase][0];
-             blocking_y = y + xy[phase][1];
-
-             if (IN_LEV_FIELD(blocking_x, blocking_y))
-             {
-               Feld[blocking_x][blocking_y] = EL_EMPTY;
-
-               DrawField_MM(blocking_x, blocking_y);
-             }
-           }
-         }
-       }
+       CheckExitMM();
 
        DrawLaser(0, DL_LASER_ENABLED);
       }
@@ -1262,33 +2089,34 @@ boolean HitElement(int element, int hit_mask)
     {
       game_mm.num_keys++;
     }
-    else if (element == EL_LIGHTBALL)
-    {
-      RaiseScore_MM(10);
-    }
     else if (IS_PACMAN(element))
     {
       DeletePacMan(ELX, ELY);
-      RaiseScore_MM(50);
+    }
+    else if (IS_ENVELOPE(element))
+    {
+      Tile[ELX][ELY] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(Tile[ELX][ELY]);
     }
 
+    RaiseScoreElement_MM(element);
+
     return FALSE;
   }
 
   if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
   {
-    PlaySoundStereo(SND_KINK, ST(ELX));
+    PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
 
     DrawLaser(0, DL_LASER_ENABLED);
 
-    if (Feld[ELX][ELY] == EL_LIGHTBULB_OFF)
+    if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
     {
-      Feld[ELX][ELY] = EL_LIGHTBULB_ON;
+      Tile[ELX][ELY] = EL_LIGHTBULB_ON;
       game_mm.lights_still_needed--;
     }
     else
     {
-      Feld[ELX][ELY] = EL_LIGHTBULB_OFF;
+      Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
       game_mm.lights_still_needed++;
     }
 
@@ -1304,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)) &&
@@ -1316,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--;
@@ -1346,7 +2174,7 @@ boolean HitElement(int element, int hit_mask)
 
       if (IS_BEAMER(element))
       {
-       laser.current_angle = get_element_angle(Feld[ELX][ELY]);
+       laser.current_angle = get_element_angle(Tile[ELX][ELY]);
        XS = 2 * Step[laser.current_angle].x;
        YS = 2 * Step[laser.current_angle].y;
       }
@@ -1378,19 +2206,20 @@ 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 */
+  // 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 ||
        hit_mask == HIT_MASK_TOPRIGHT ||
        hit_mask == HIT_MASK_BOTTOMLEFT ||
        hit_mask == HIT_MASK_BOTTOMRIGHT) &&
-      laser.current_angle % 4)                 /* angle is not 90° */
+      laser.current_angle % 4)                 // angle is not 90°
   {
     int dx, dy;
 
@@ -1409,7 +2238,7 @@ boolean HitOnlyAnEdge(int element, int hit_mask)
       dx = -1;
       dy = +1;
     }
-    else /* (hit_mask == HIT_MASK_BOTTOMRIGHT) */
+    else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
     {
       dx = +1;
       dy = +1;
@@ -1421,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))
@@ -1444,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);
@@ -1457,7 +2286,7 @@ boolean HitPolarizer(int element, int hit_mask)
     if (laser.current_angle == grid_angle ||
        laser.current_angle == get_opposite_angle(grid_angle))
     {
-      /* skip the whole element before continuing the scan */
+      // skip the whole element before continuing the scan
       do
       {
        LX += XS;
@@ -1482,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;
@@ -1501,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 */
+  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;
 
@@ -1554,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);
 
@@ -1597,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);
 
@@ -1632,20 +2463,21 @@ 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))
-     return FALSE;
+  if (HitOnlyAnEdge(hit_mask))
+    return FALSE;
 
-   PlaySoundStereo(SND_AUTSCH, ST(ELX));
-   laser.overloaded = TRUE;
+  PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
 
-   return TRUE;
+  laser.overloaded = TRUE;
+
+  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 &&
@@ -1653,7 +2485,8 @@ boolean HitLaserDestination(int element, int hit_mask)
        game_mm.kettles_still_needed == 0 &&
        laser.current_angle == get_opposite_angle(get_element_angle(element))))
   {
-    PlaySoundStereo(SND_HOLZ, ST(ELX));
+    PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
+
     return TRUE;
   }
 
@@ -1679,20 +2512,24 @@ boolean HitLaserDestination(int element, int hit_mask)
   AddDamagedField(ELX, ELY);
 
   if (game_mm.lights_still_needed == 0)
+  {
     game_mm.level_solved = TRUE;
 
+    SetTileCursorActive(FALSE);
+  }
+
   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° */
+  // 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 ||
                                            hit_mask == HIT_MASK_LEFT ||
                                            hit_mask == HIT_MASK_RIGHT ||
                                            hit_mask == HIT_MASK_BOTTOM))
   {
-    PlaySoundStereo(SND_HUI, ST(ELX));
+    PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
 
     LX -= XS;
     LY -= YS;
@@ -1700,7 +2537,7 @@ boolean HitReflectingWalls(int element, int hit_mask)
     if (!IS_DF_GRID(element))
       AddLaserEdge(LX, LY);
 
-    /* check if laser hits wall with an angle of 45° */
+    // check if laser hits wall with an angle of 45°
     if (!IS_22_5_ANGLE(laser.current_angle))
     {
       if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
@@ -1709,7 +2546,7 @@ boolean HitReflectingWalls(int element, int hit_mask)
        laser.current_angle = get_mirrored_angle(laser.current_angle,
                                                 ANG_MIRROR_0);
       }
-      else     /* hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT */
+      else     // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
       {
        LY += 2 * YS;
        laser.current_angle = get_mirrored_angle(laser.current_angle,
@@ -1748,7 +2585,7 @@ boolean HitReflectingWalls(int element, int hit_mask)
 
       return FALSE;
     }
-    else       /* hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT */
+    else       // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
     {
       laser.current_angle = get_mirrored_angle(laser.current_angle,
                                               ANG_MIRROR_90);
@@ -1775,7 +2612,7 @@ boolean HitReflectingWalls(int element, int hit_mask)
     }
   }
 
-  /* reflection at the edge of reflecting DF style wall */
+  // reflection at the edge of reflecting DF style wall
   if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
   {
     if (((laser.current_angle == 1 || laser.current_angle == 3) &&
@@ -1791,7 +2628,7 @@ boolean HitReflectingWalls(int element, int hit_mask)
        (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
         ANG_MIRROR_135 : ANG_MIRROR_45);
 
-      PlaySoundStereo(SND_HUI, ST(ELX));
+      PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
 
       AddDamagedField(ELX, ELY);
       AddLaserEdge(LX, LY);
@@ -1810,7 +2647,7 @@ boolean HitReflectingWalls(int element, int hit_mask)
     }
   }
 
-  /* reflection inside an edge of reflecting DF style wall */
+  // reflection inside an edge of reflecting DF style wall
   if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
   {
     if (((laser.current_angle == 1 || laser.current_angle == 3) &&
@@ -1827,7 +2664,7 @@ boolean HitReflectingWalls(int element, int hit_mask)
         hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
         ANG_MIRROR_135 : ANG_MIRROR_45);
 
-      PlaySoundStereo(SND_HUI, ST(ELX));
+      PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
 
       /*
       AddDamagedField(ELX, ELY);
@@ -1851,7 +2688,7 @@ boolean HitReflectingWalls(int element, int hit_mask)
     }
   }
 
-  /* check if laser hits DF style wall with an angle of 90° */
+  // check if laser hits DF style wall with an angle of 90°
   if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
   {
     if ((IS_HORIZ_ANGLE(laser.current_angle) &&
@@ -1859,9 +2696,7 @@ boolean HitReflectingWalls(int element, int hit_mask)
        (IS_VERT_ANGLE(laser.current_angle) &&
         (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
     {
-      static int last_LX = 0, last_LY = 0, last_hit_mask = 0;
-
-      /* laser at last step touched nothing or the same side of the wall */
+      // laser at last step touched nothing or the same side of the wall
       if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
       {
        AddDamagedField(ELX, ELY);
@@ -1878,7 +2713,7 @@ boolean HitReflectingWalls(int element, int hit_mask)
     }
   }
 
-  if (!HitOnlyAnEdge(element, hit_mask))
+  if (!HitOnlyAnEdge(hit_mask))
   {
     laser.overloaded = TRUE;
 
@@ -1888,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 &&
@@ -1920,19 +2755,27 @@ boolean HitAbsorbingWalls(int element, int hit_mask)
       element == EL_BLOCK_WOOD ||
       element == EL_GATE_WOOD)
   {
-    PlaySoundStereo(SND_HOLZ, ST(ELX));
+    PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
 
     return TRUE;
   }
 
   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° */
+    // check if laser hits wall with an angle of 90°
     if (IS_90_ANGLE(laser.current_angle))
       mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
 
@@ -1963,7 +2806,7 @@ boolean HitAbsorbingWalls(int element, int hit_mask)
   {
     int elx = (LX - 2 * XS) / TILEX;
     int ely = (LY - 2 * YS) / TILEY;
-    int element2 = Feld[elx][ely];
+    int element2 = Tile[elx][ely];
     int mask;
 
     if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
@@ -1982,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;
   }
@@ -1990,14 +2833,14 @@ boolean HitAbsorbingWalls(int element, int hit_mask)
   return TRUE;
 }
 
-void OpenExit(int x, int y)
+static void OpenExit(int x, int y)
 {
   int delay = 6;
 
-  if (!MovDelay[x][y])         /* next animation frame */
+  if (!MovDelay[x][y])         // next animation frame
     MovDelay[x][y] = 4 * delay;
 
-  if (MovDelay[x][y])          /* wait some time before next frame */
+  if (MovDelay[x][y])          // wait some time before next frame
   {
     int phase;
 
@@ -2009,88 +2852,142 @@ void OpenExit(int x, int y)
 
     if (!MovDelay[x][y])
     {
-      Feld[x][y] = EL_EXIT_OPEN;
+      Tile[x][y] = EL_EXIT_OPEN;
       DrawField_MM(x, y);
     }
   }
 }
 
-void OpenSurpriseBall(int x, int y)
+static void OpenGrayBall(int x, int y)
 {
   int delay = 2;
 
-  if (!MovDelay[x][y])         /* next animation frame */
+  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 */
+  if (MovDelay[x][y])          // wait some time before next frame
   {
     MovDelay[x][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_mm, gx + dx, gy + dy, 6, 6,
+                cSX + x * TILEX + dx, cSY + y * TILEY + dy);
 
-      BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
-                SX + x * TILEX + dx, SY + y * TILEY + dy);
+      laser.redraw = TRUE;
 
       MarkTileDirty(x, y);
     }
 
     if (!MovDelay[x][y])
     {
-      Feld[x][y] = Store[x][y];
-      Store[x][y] = 0;
+      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);
+
+      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);
     }
   }
 }
 
-void MeltIce(int x, int y)
+static void MeltIce(int x, int y)
 {
   int frames = 5;
   int delay = 5;
 
-  if (!MovDelay[x][y])         /* next animation frame */
+  if (!MovDelay[x][y])         // next animation frame
     MovDelay[x][y] = frames * delay;
 
-  if (MovDelay[x][y])          /* wait some time before next frame */
+  if (MovDelay[x][y])          // wait some time before next frame
   {
     int phase;
     int wall_mask = Store2[x][y];
-    int real_element = Feld[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;
-
-      Feld[x][y] = real_element & (wall_mask ^ 0xFF);
+      Tile[x][y] = real_element & (wall_mask ^ 0xFF);
       Store[x][y] = Store2[x][y] = 0;
 
-      DrawWalls_MM(x, y, Feld[x][y]);
-
-      if (Feld[x][y] == EL_WALL_ICE)
-       Feld[x][y] = EL_EMPTY;
+      DrawWalls_MM(x, y, Tile[x][y]);
 
-      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);
+      if (Tile[x][y] == EL_WALL_ICE_BASE)
+       Tile[x][y] = EL_EMPTY;
 
-      ScanLaser();
+      ScanLaser_FromLastMirror();
     }
     else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
     {
@@ -2101,29 +2998,29 @@ void MeltIce(int x, int y)
   }
 }
 
-void GrowAmoeba(int x, int y)
+static void GrowAmoeba(int x, int y)
 {
   int frames = 5;
   int delay = 1;
 
-  if (!MovDelay[x][y])         /* next animation frame */
+  if (!MovDelay[x][y])         // next animation frame
     MovDelay[x][y] = frames * delay;
 
-  if (MovDelay[x][y])          /* wait some time before next frame */
+  if (MovDelay[x][y])          // wait some time before next frame
   {
     int phase;
     int wall_mask = Store2[x][y];
-    int real_element = Feld[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;
 
     if (!MovDelay[x][y])
     {
-      Feld[x][y] = real_element;
+      Tile[x][y] = real_element;
       Store[x][y] = Store2[x][y] = 0;
 
-      DrawWalls_MM(x, y, Feld[x][y]);
+      DrawWalls_MM(x, y, Tile[x][y]);
       DrawLaser(0, DL_LASER_ENABLED);
     }
     else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
@@ -2133,45 +3030,96 @@ 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 */
+  if (phase == EX_PHASE_START)         // initialize 'Store[][]' field
   {
-    int center_element = Feld[x][y];
+    center_element = Tile[x][y];
 
     if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
     {
-      /* put moving element to center field (and let it explode there) */
+      // put moving element to center field (and let it explode there)
       center_element = MovingOrBlocked2Element_MM(x, y);
       RemoveMovingField_MM(x, y);
 
-      Feld[x][y] = center_element;
+      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;
+
+    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);
 
-    Store2[x][y] = mode;
-    Feld[x][y] = EL_EXPLODING_OPAQUE;
     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);
+
+  center_element = Store2[x][y];
 
-  if (phase == half_phase)
+  if (phase == half_phase && Store[x][y] == EL_EMPTY)
   {
-    Feld[x][y] = EL_EXPLODING_TRANSP;
+    Tile[x][y] = EL_EXPLODING_TRANSP;
 
     if (x == ELX && y == ELY)
       ScanLaser();
@@ -2179,69 +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)
     {
-      laser.num_damages--;
       DrawLaser(0, DL_LASER_DISABLED);
-      laser.num_edges = 0;
+      InitLaser();
 
       Bang_MM(laser.start_edge.x, laser.start_edge.y);
-      Store[x][y] = EL_EMPTY;
+
+      laser.overloaded = FALSE;
     }
-    else if (IS_MCDUFFIN(Store[x][y]))
+    else if (IS_MCDUFFIN(center_element) || IS_LASER(center_element))
     {
-      game_mm.game_over = TRUE;
-      game_mm.game_over_cause = GAME_OVER_BOMB;
-      Store[x][y] = EL_EMPTY;
+      GameOver_MM(GAME_OVER_BOMB);
     }
 
-    Feld[x][y] = Store[x][y];
+    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;
-
-    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);
+    int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
+    int frame = getGraphicAnimationFrameXY(graphic, x, y);
 
-    BlitBitmap(bitmap, drawto_field, src_x, src_y, TILEX, TILEY,
-              FX + x * TILEX, FY + y * TILEY);
+    DrawGraphicAnimation_MM(x, y, graphic, frame);
 
     MarkTileDirty(x, y);
   }
@@ -2249,71 +3165,52 @@ static void Explode_MM(int x, int y, int phase, int mode)
 
 static void Bang_MM(int x, int y)
 {
-  int element = Feld[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;
-  }
+  int element = Tile[x][y];
 
   if (IS_PACMAN(element))
-    PlaySoundStereo(SND_QUIEK, ST(x));
-  else if (element == EL_BOMB || IS_MCDUFFIN(element))
-    PlaySoundStereo(SND_ROAAAR, ST(x));
+    PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
+  else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
+    PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
   else if (element == EL_KEY)
-    PlaySoundStereo(SND_KLING, ST(x));
+    PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
   else
-    PlaySoundStereo((mode == EX_SHORT ? SND_WHOOSH : SND_KABUMM), ST(x));
+    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 = Feld[x][y];
+  int element = Tile[x][y];
   int old_move_dir = MovDir[x][y];
   int right_dir = turn[old_move_dir].right;
   int back_dir = turn[old_move_dir].back;
@@ -2325,7 +3222,7 @@ void TurnRound(int x, int y)
     boolean can_turn_right = FALSE;
 
     if (IN_LEV_FIELD(right_x, right_y) &&
-       IS_EATABLE4PACMAN(Feld[right_x][right_y]))
+       IS_EATABLE4PACMAN(Tile[right_x][right_y]))
       can_turn_right = TRUE;
 
     if (can_turn_right)
@@ -2339,7 +3236,7 @@ void TurnRound(int x, int y)
 
 static void StartMoving_MM(int x, int y)
 {
-  int element = Feld[x][y];
+  int element = Tile[x][y];
 
   if (Stop[x][y])
     return;
@@ -2348,7 +3245,7 @@ static void StartMoving_MM(int x, int y)
   {
     int newx, newy;
 
-    if (MovDelay[x][y])                /* wait some time before next movement */
+    if (MovDelay[x][y])                // wait some time before next movement
     {
       MovDelay[x][y]--;
 
@@ -2356,23 +3253,23 @@ static void StartMoving_MM(int x, int y)
        return;
     }
 
-    /* now make next step */
+    // 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(Feld[newx][newy]) &&
+       IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
        !ObjHit(newx, newy, HIT_POS_CENTER))
     {
-      Store[newx][newy] = Feld[newx][newy];
-      Feld[newx][newy] = EL_EMPTY;
+      Store[newx][newy] = Tile[newx][newy];
+      Tile[newx][newy] = EL_EMPTY;
 
       DrawField_MM(newx, newy);
     }
     else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
             ObjHit(newx, newy, HIT_POS_CENTER))
     {
-      /* object was running against a wall */
+      // object was running against a wall
 
       TurnRound(x, y);
 
@@ -2388,7 +3285,7 @@ static void StartMoving_MM(int x, int y)
 
 static void ContinueMoving_MM(int x, int y)
 {
-  int element = Feld[x][y];
+  int element = Tile[x][y];
   int direction = MovDir[x][y];
   int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
   int dy = (direction == MV_UP   ? -1 : direction == MV_DOWN  ? +1 : 0);
@@ -2398,10 +3295,10 @@ static void ContinueMoving_MM(int x, int y)
 
   MovPos[x][y] += step;
 
-  if (ABS(MovPos[x][y]) >= TILEX)      /* object reached its destination */
+  if (ABS(MovPos[x][y]) >= TILEX)      // object reached its destination
   {
-    Feld[x][y] = EL_EMPTY;
-    Feld[newx][newy] = element;
+    Tile[x][y] = EL_EMPTY;
+    Tile[newx][newy] = element;
 
     MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
     MovDelay[newx][newy] = 0;
@@ -2428,7 +3325,7 @@ static void ContinueMoving_MM(int x, int y)
       }
     }
   }
-  else                         /* still moving on */
+  else                         // still moving on
   {
     DrawField_MM(x, y);
   }
@@ -2436,42 +3333,51 @@ static void ContinueMoving_MM(int x, int y)
   laser.redraw = TRUE;
 }
 
-void ClickElement(int mx, int my, int button)
+boolean ClickElement(int x, int y, int button)
 {
-  static unsigned int click_delay = 0;
-  static int click_delay_value = CLICK_DELAY_SHORT;
+  static DelayCounter click_delay = { CLICK_DELAY };
   static boolean new_button = TRUE;
+  boolean element_clicked = FALSE;
   int element;
-  int x = (mx - SX) / TILEX, y = (my - SY) / TILEY;
 
-  /* do not rotate objects hit by the laser after the game was solved */
+  if (button == -1)
+  {
+    // initialize static variables
+    click_delay.count = 0;
+    click_delay.value = CLICK_DELAY;
+    new_button = TRUE;
+
+    return FALSE;
+  }
+
+  // do not rotate objects hit by the laser after the game was solved
   if (game_mm.level_solved && Hit[x][y])
-    return;
+    return FALSE;
 
   if (button == MB_RELEASED)
   {
     new_button = TRUE;
-    click_delay_value = CLICK_DELAY_SHORT;
+    click_delay.value = CLICK_DELAY;
 
-    /* release eventually hold auto-rotating mirror */
+    // release eventually hold auto-rotating mirror
     RotateMirror(x, y, MB_RELEASED);
 
-    return;
+    return FALSE;
   }
 
-  if (!DelayReached(&click_delay, click_delay_value) && !new_button)
-    return;
+  if (!FrameReached(&click_delay) && !new_button)
+    return FALSE;
 
-  if (button == MB_MIDDLEBUTTON)       /* middle button has no function */
-    return;
+  if (button == MB_MIDDLEBUTTON)       // middle button has no function
+    return FALSE;
 
-  if (!IN_PIX_FIELD(mx - SX, my - SY))
-    return;
+  if (!IN_LEV_FIELD(x, y))
+    return FALSE;
 
-  if (Feld[x][y] == EL_EMPTY)
-    return;
+  if (Tile[x][y] == EL_EMPTY)
+    return FALSE;
 
-  element = Feld[x][y];
+  element = Tile[x][y];
 
   if (IS_MIRROR(element) ||
       IS_BEAMER(element) ||
@@ -2481,43 +3387,45 @@ void ClickElement(int mx, int my, int button)
       IS_DF_MIRROR_AUTO(element))
   {
     RotateMirror(x, y, button);
+
+    element_clicked = TRUE;
   }
   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();
 
-    Feld[x][y] = element;
+    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;
   }
   else if (element == EL_FUSE_ON && laser.fuse_off)
   {
     if (x != laser.fuse_x || y != laser.fuse_y)
-      return;
+      return FALSE;
 
     laser.fuse_off = FALSE;
     laser.fuse_x = laser.fuse_y = -1;
 
     DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
     ScanLaser();
+
+    element_clicked = TRUE;
   }
   else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
   {
@@ -2528,57 +3436,67 @@ void ClickElement(int mx, int my, int button)
 
     DrawLaser(0, DL_LASER_DISABLED);
     DrawGraphic_MM(x, y, IMG_MM_FUSE);
+
+    element_clicked = TRUE;
   }
   else if (element == EL_LIGHTBALL)
   {
     Bang_MM(x, y);
-    RaiseScore_MM(10);
+    RaiseScoreElement_MM(element);
     DrawLaser(0, DL_LASER_ENABLED);
+
+    element_clicked = TRUE;
+  }
+  else if (IS_ENVELOPE(element))
+  {
+    Tile[x][y] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(element);
+
+    element_clicked = TRUE;
   }
 
-  click_delay_value = (new_button ? CLICK_DELAY_LONG : CLICK_DELAY_SHORT);
+  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)
 {
-  static int hold_x = -1, hold_y = -1;
-
   if (button == MB_RELEASED)
   {
-    /* release eventually hold auto-rotating mirror */
+    // release eventually hold auto-rotating mirror
     hold_x = -1;
     hold_y = -1;
 
     return;
   }
 
-  if (IS_MIRROR(Feld[x][y]) ||
-      IS_POLAR_CROSS(Feld[x][y]) ||
-      IS_POLAR(Feld[x][y]) ||
-      IS_BEAMER(Feld[x][y]) ||
-      IS_DF_MIRROR(Feld[x][y]) ||
-      IS_GRID_STEEL_AUTO(Feld[x][y]) ||
-      IS_GRID_WOOD_AUTO(Feld[x][y]))
+  if (IS_MIRROR(Tile[x][y]) ||
+      IS_POLAR_CROSS(Tile[x][y]) ||
+      IS_POLAR(Tile[x][y]) ||
+      IS_BEAMER(Tile[x][y]) ||
+      IS_DF_MIRROR(Tile[x][y]) ||
+      IS_GRID_STEEL_AUTO(Tile[x][y]) ||
+      IS_GRID_WOOD_AUTO(Tile[x][y]))
   {
-    Feld[x][y] = get_rotated_element(Feld[x][y], BUTTON_ROTATION(button));
+    Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
   }
-  else if (IS_DF_MIRROR_AUTO(Feld[x][y]))
+  else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
   {
     if (button == MB_LEFTBUTTON)
     {
-      /* left mouse button only for manual adjustment, no auto-rotating;
-        freeze mirror for until mouse button released */
+      // left mouse button only for manual adjustment, no auto-rotating;
+      // freeze mirror for until mouse button released
       hold_x = x;
       hold_y = y;
     }
     else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
     {
-      Feld[x][y] = get_rotated_element(Feld[x][y], ROTATE_RIGHT);
+      Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
     }
   }
 
-  if (IS_GRID_STEEL_AUTO(Feld[x][y]) || IS_GRID_WOOD_AUTO(Feld[x][y]))
+  if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
   {
     int edge = Hit[x][y];
 
@@ -2596,7 +3514,8 @@ void RotateMirror(int x, int y, int button)
 
     if (edge == 0)
     {
-      Error(ERR_WARN, "RotateMirror: inconsistent field Hit[][]!\n");
+      Warn("RotateMirror: inconsistent field Hit[][]!\n");
+
       edge = 1;
     }
 
@@ -2612,24 +3531,24 @@ void RotateMirror(int x, int y, int button)
 
     DrawField_MM(x, y);
 
-    if ((IS_BEAMER(Feld[x][y]) ||
-        IS_POLAR(Feld[x][y]) ||
-        IS_POLAR_CROSS(Feld[x][y])) && x == ELX && y == ELY)
+    if ((IS_BEAMER(Tile[x][y]) ||
+        IS_POLAR(Tile[x][y]) ||
+        IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
     {
-      check = 0;
-
-      if (IS_BEAMER(Feld[x][y]))
+      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)
@@ -2637,21 +3556,20 @@ void RotateMirror(int x, int y, int button)
   }
 }
 
-void AutoRotateMirrors()
+static void AutoRotateMirrors(void)
 {
-  static unsigned int rotate_delay = 0;
   int x, y;
 
-  if (!DelayReached(&rotate_delay, AUTO_ROTATE_DELAY))
+  if (!FrameReached(&rotate_delay))
     return;
 
   for (x = 0; x < lev_fieldx; x++)
   {
     for (y = 0; y < lev_fieldy; y++)
     {
-      int element = Feld[x][y];
+      int element = Tile[x][y];
 
-      /* do not rotate objects hit by the laser after the game was solved */
+      // do not rotate objects hit by the laser after the game was solved
       if (game_mm.level_solved && Hit[x][y])
        continue;
 
@@ -2659,12 +3577,16 @@ void AutoRotateMirrors()
          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;
 
@@ -2673,32 +3595,31 @@ boolean ObjHit(int obx, int oby, int bits)
 
   if (bits & HIT_POS_CENTER)
   {
-    if (ReadPixel(drawto, SX + obx + 15, SY + oby + 15) == pen_ray)
+    if (CheckLaserPixel(cSX + obx + 15,
+                       cSY + oby + 15))
       return TRUE;
   }
 
   if (bits & HIT_POS_EDGE)
   {
     for (i = 0; i < 4; i++)
-      if (ReadPixel(drawto,
-                   SX + obx + 31 * (i % 2),
-                   SY + oby + 31 * (i / 2)) == pen_ray)
+      if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
+                         cSY + oby + 31 * (i / 2)))
        return TRUE;
   }
 
   if (bits & HIT_POS_BETWEEN)
   {
     for (i = 0; i < 4; i++)
-      if (ReadPixel(drawto,
-                   SX + 4 + obx + 22 * (i % 2),
-                   SY + 4 + oby + 22 * (i / 2)) == pen_ray)
+      if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
+                         cSY + 4 + oby + 22 * (i / 2)))
        return TRUE;
   }
 
   return FALSE;
 }
 
-void DeletePacMan(int px, int py)
+static void DeletePacMan(int px, int py)
 {
   int i, j;
 
@@ -2724,100 +3645,62 @@ void DeletePacMan(int px, int py)
   }
 }
 
-void ColorCycling(void)
+static void GameActions_MM_Ext(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 = Counter();
-
-  if (CC < Cc || CC > Cc + 50)
-  {
-    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(byte action[MAX_PLAYERS], boolean warp_mode)
-{
-  static unsigned int action_delay = 0;
-  static unsigned int pacman_delay = 0;
-  static unsigned int energy_delay = 0;
-  static unsigned int overload_delay = 0;
   int element;
   int x, y, i;
 
   int r, d;
 
-  WaitUntilDelayReached(&action_delay, GameFrameDelay);
-
   for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
     Stop[x][y] = FALSE;
 
   for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
   {
-    element = Feld[x][y];
+    element = Tile[x][y];
 
     if (!IS_MOVING(x, y) && CAN_MOVE(element))
       StartMoving_MM(x, y);
     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();
 
 #if 1
-  /* !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!! */
+  // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
 
-  /* redraw after Explode_MM() ... */
+  // redraw after Explode_MM() ...
   if (laser.redraw)
     DrawLaser(0, DL_LASER_ENABLED);
   laser.redraw = FALSE;
 #endif
 
-  CT = Counter();
+  CT = FrameCounter;
 
-  if (game_mm.num_pacman && DelayReached(&pacman_delay, 250))
+  if (game_mm.num_pacman && FrameReached(&pacman_delay))
   {
     MovePacMen();
 
@@ -2828,76 +3711,44 @@ static void GameActions_MM_Ext(byte action[MAX_PLAYERS], boolean warp_mode)
     }
   }
 
-  if (DelayReached(&energy_delay, 4000))
-  {
-    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;
-    }
-    else if (setup.time_limit)
-    {
-      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);
+  // skip all following game actions if game is over
+  if (game_mm.game_over)
+    return;
 
-       DrawLaser(0, DL_LASER_ENABLED);
-       BackToFront();
-       Delay(50);
-      }
+  if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
+  {
+    FadeOutLaser();
 
-      StopSound(SND_WARNTON);
-      FadeMusic();
+    GameOver_MM(GAME_OVER_NO_ENERGY);
 
-      DrawLaser(0, DL_LASER_DISABLED);
-      game_mm.game_over = TRUE;
-      game_mm.game_over_cause = GAME_OVER_NO_ENERGY;
+    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;
 
 #if 0
-  if (element != Feld[ELX][ELY])
+  if (element != Tile[ELX][ELY])
   {
-    printf("element == %d, Feld[ELX][ELY] == %d\n",
-          element, Feld[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 &&
@@ -2906,9 +3757,11 @@ static void GameActions_MM_Ext(byte action[MAX_PLAYERS], 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)) &&
-      DelayReached(&overload_delay, 60 + !laser.overloaded * 120))
+      FrameReached(&overload_delay))
   {
     if (laser.overloaded)
       laser.overload_value++;
@@ -2921,95 +3774,29 @@ static void GameActions_MM_Ext(byte action[MAX_PLAYERS], boolean warp_mode)
       laser.overload_value = 0;
     }
 
+    game_mm.laser_overload_value = laser.overload_value;
+
     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);
-      BackToFront();
-    }
-
-    if (laser.overloaded)
-    {
-      if (setup.sound_loops)
-       PlaySoundExt(SND_WARNTON, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT,
-                    SND_CTRL_PLAY_LOOP);
-      else
-       PlaySoundStereo(SND_WARNTON, SOUND_MAX_RIGHT);
     }
 
     if (!laser.overloaded)
-      StopSound(SND_WARNTON);
-
-    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;
-    }
+      StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
+    else if (setup.sound_loops)
+      PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
     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;
-    }
+      PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
 
     if (laser.overload_value == MAX_LASER_OVERLOAD)
     {
-      int i;
-
-      for (i = 15; i >= 0; i--)
-      {
-#if 0
-       SetRGB(pen_ray, i * color_scale, 0x0000, 0x0000);
-#endif
+      UpdateAndDisplayGameControlValues();
 
-       pen_ray = GetPixelFromRGB(window, 0x11 * i, 0x00, 0x00);
-
-       DrawLaser(0, DL_LASER_ENABLED);
-       BackToFront();
-       Delay(50);
-      }
-
-      DrawLaser(0, DL_LASER_DISABLED);
+      FadeOutLaser();
 
-      game_mm.game_over = TRUE;
-      game_mm.game_over_cause = GAME_OVER_OVERLOADED;
-
-#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;
     }
@@ -3020,45 +3807,19 @@ static void GameActions_MM_Ext(byte action[MAX_PLAYERS], boolean warp_mode)
 
   CT -= Ct;
 
-  if (element == EL_BOMB && CT > 1500)
+  if (element == EL_BOMB && CT > native_mm_level.time_bomb)
   {
     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;
   }
 
-  if (element == EL_FUSE_ON && CT > 500)
+  if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
   {
     laser.fuse_off = TRUE;
     laser.fuse_x = ELX;
@@ -3068,181 +3829,71 @@ static void GameActions_MM_Ext(byte action[MAX_PLAYERS], boolean warp_mode)
     DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
   }
 
-  if (element == EL_BALL_GRAY && CT > 1500)
+  if (element == EL_GRAY_BALL && CT > native_mm_level.time_ball)
   {
-    static int new_elements[] =
+    if (!Store2[ELX][ELY])     // check if content element not yet determined
     {
-      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));
-    Feld[ELX][ELY] = EL_GRAY_BALL_OPENING;
-
-    /* !!! CHECK AGAIN: Laser on Polarizer !!! */
-    ScanLaser();
+      int last_anim_random_frame = gfx.anim_random_frame;
+      int element_pos;
 
-    return;
+      if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
+       gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
 
-#if 0
-    int graphic;
-
-    switch (RND(5))
-    {
-      case 0:
-        element = EL_MIRROR_START + RND(16);
-       break;
-      case 1:
-       {
-         int rnd = RND(3);
-
-         element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
-       }
-       break;
-      default:
-       {
-         int rnd = RND(3);
-
-         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(50);
+      Store[ELX][ELY] = new_element;
+      Store2[ELX][ELY] = TRUE;
     }
 
-    Feld[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;
+    if (native_mm_level.explode_ball)
+      Bang_MM(ELX, ELY);
     else
-      OK = 4;
-    LX -= OK * XS;
-    LY -= OK * YS;
-
-    laser.num_edges -= 2;
-    laser.num_damages--;
-*/
+      Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
 
-#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);
-    else
-      DrawLaser(0, DL_LASER_DISABLED);
-#else
-    DrawLaser(0, DL_LASER_DISABLED);
-#endif
-
-    ScanLaser();
-
-    /*
-    printf("TEST ELEMENT: %d\n", Feld[0][0]);
-    */
-#endif
+    laser.dest_element = laser.dest_element_last = Tile[ELX][ELY];
 
     return;
   }
 
-  if (IS_WALL_ICE(element) && CT > 1000)
+  if (IS_WALL_ICE(element) && CT > 50)
   {
-    PlaySoundStereo(SND_SLURP, ST(ELX));
-
-    {
-      Feld[ELX][ELY] = Feld[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
-      Store[ELX][ELY] = EL_WALL_ICE;
-      Store2[ELX][ELY] = laser.wall_mask;
+    PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
 
-      laser.dest_element = Feld[ELX][ELY];
-
-      return;
-    }
+    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;
 
-    for (i = 0; i < 5; i++)
-    {
-      int phase = i + 1;
-
-      if (i == 4)
-      {
-       Feld[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
-       phase = 0;
-      }
-
-      DrawWallsAnimation_MM(ELX, ELY, Feld[ELX][ELY], phase, laser.wall_mask);
-      BackToFront();
-      Delay(100);
-    }
-
-    if (Feld[ELX][ELY] == EL_WALL_ICE)
-      Feld[ELX][ELY] = EL_EMPTY;
-
-/*
-    laser.num_edges--;
-    LX = laser.edge[laser.num_edges].x - (SX + 2);
-    LY = laser.edge[laser.num_edges].y - (SY + 2);
-*/
-
-    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 > 1200)
+  if (IS_WALL_AMOEBA(element) && CT > 60)
   {
-    int k1, k2, k3, dx, dy, de, dm;
-    int element2 = Feld[ELX][ELY];
+    int k1, k2, k3;
+    int element2 = Tile[ELX][ELY];
 
     if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
       return;
@@ -3275,13 +3926,12 @@ static void GameActions_MM_Ext(byte action[MAX_PLAYERS], boolean warp_mode)
     {
       if (laser.wall_mask & (1 << i))
       {
-       if (ReadPixel(drawto,
-                     SX + ELX * TILEX + 14 + (i % 2) * 2,
-                     SY + ELY * TILEY + 31 * (i / 2)) == pen_ray)
+       if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
+                           cSY + ELY * TILEY + 31 * (i / 2)))
          break;
-       if (ReadPixel(drawto,
-                     SX + ELX * TILEX + 31 * (i % 2),
-                     SY + ELY * TILEY + 14 + (i / 2) * 2) == pen_ray)
+
+       if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
+                           cSY + ELY * TILEY + 14 + (i / 2) * 2))
          break;
       }
     }
@@ -3292,9 +3942,8 @@ static void GameActions_MM_Ext(byte action[MAX_PLAYERS], boolean warp_mode)
     {
       if (laser.wall_mask & (1 << i))
       {
-       if (ReadPixel(drawto,
-                     SX + ELX * TILEX + 31 * (i % 2),
-                     SY + ELY * TILEY + 31 * (i / 2)) == pen_ray)
+       if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
+                           cSY + ELY * TILEY + 31 * (i / 2)))
          break;
       }
     }
@@ -3303,8 +3952,8 @@ static void GameActions_MM_Ext(byte action[MAX_PLAYERS], boolean warp_mode)
 
     if (laser.num_beamers > 0 ||
        k1 < 1 || k2 < 4 || k3 < 4 ||
-       ReadPixel(drawto, SX + ELX * TILEX + 14, SY + ELY * TILEY + 14)
-       == pen_ray)
+       CheckLaserPixel(cSX + ELX * TILEX + 14,
+                       cSY + ELY * TILEY + 14))
     {
       laser.num_edges = r;
       laser.num_damages = d;
@@ -3312,52 +3961,25 @@ static void GameActions_MM_Ext(byte action[MAX_PLAYERS], boolean warp_mode)
       DrawLaser(0, DL_LASER_DISABLED);
     }
 
-    Feld[ELX][ELY] = element | laser.wall_mask;
-
-    dx = ELX;
-    dy = ELY;
-    de = Feld[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);
-
-      PlaySoundStereo(SND_AMOEBE, ST(dx));
-
-      Feld[x][y] = Feld[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
-      Store[x][y] = EL_WALL_AMOEBA;
-      Store2[x][y] = wall_mask;
+    Tile[ELX][ELY] = element | laser.wall_mask;
 
-      return;
-    }
-#endif
+    int x = ELX, y = ELY;
+    int wall_mask = laser.wall_mask;
 
-    DrawWallsAnimation_MM(dx, dy, de, 4, dm);
     ScanLaser();
     DrawLaser(0, DL_LASER_ENABLED);
 
-    PlaySoundStereo(SND_AMOEBE, ST(dx));
+    PlayLevelSound_MM(x, y, element, MM_ACTION_GROWING);
 
-    for (i = 4; i >= 0; i--)
-    {
-      DrawWallsAnimation_MM(dx, dy, de, i, dm);
-
-      BackToFront();
-      Delay(20);
-    }
-
-    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;
   }
 
   if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
-      laser.stops_inside_element && CT > 1500)
+      laser.stops_inside_element && CT > native_mm_level.time_block)
   {
     int x, y;
     int k;
@@ -3379,7 +4001,7 @@ static void GameActions_MM_Ext(byte action[MAX_PLAYERS], boolean warp_mode)
       x = ELX + Step[k * 4].x;
       y = ELY + Step[k * 4].y;
 
-      if (!IN_LEV_FIELD(x, y) || Feld[x][y] != EL_EMPTY)
+      if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
        continue;
 
       if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
@@ -3395,10 +4017,10 @@ static void GameActions_MM_Ext(byte action[MAX_PLAYERS], boolean warp_mode)
       return;
     }
 
-    PlaySoundStereo(SND_BONG, ST(ELX));
+    PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
 
-    Feld[ELX][ELY] = 0;
-    Feld[x][y] = element;
+    Tile[ELX][ELY] = 0;
+    Tile[x][y] = element;
 
     DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
     DrawField_MM(x, y);
@@ -3414,79 +4036,81 @@ static void GameActions_MM_Ext(byte action[MAX_PLAYERS], boolean warp_mode)
     return;
   }
 
-  if (element == EL_FUEL_FULL && CT > 200)
+  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(20);
+      BackToFront_MM();
     }
 
-    game_mm.energy_left = MAX_LASER_ENERGY;
-    Feld[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(byte action[MAX_PLAYERS], boolean warp_mode)
+void GameActions_MM(struct MouseActionInfo action)
 {
-  if (!button_status)
-    ClickElement(0, 0, MB_NOT_PRESSED);
+  boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
+  boolean button_released = (action.button == MB_RELEASED);
+
+  GameActions_MM_Ext();
 
-  GameActions_MM_Ext(action, warp_mode);
+  CheckSingleStepMode_MM(element_clicked, button_released);
 }
 
-void MovePacMen()
+static void MovePacMen(void)
 {
-  static int p = -1;
   int mx, my, ox, oy, nx, ny;
   int element;
   int l;
 
-  if (++p >= game_mm.num_pacman)
-    p = 0;
+  if (++pacman_nr >= game_mm.num_pacman)
+    pacman_nr = 0;
 
-  game_mm.pacman[p].dir--;
+  game_mm.pacman[pacman_nr].dir--;
 
   for (l = 1; l < 5; l++)
   {
-    game_mm.pacman[p].dir++;
+    game_mm.pacman[pacman_nr].dir++;
 
-    if (game_mm.pacman[p].dir > 4)
-      game_mm.pacman[p].dir = 1;
+    if (game_mm.pacman[pacman_nr].dir > 4)
+      game_mm.pacman[pacman_nr].dir = 1;
 
-    if (game_mm.pacman[p].dir % 2)
+    if (game_mm.pacman[pacman_nr].dir % 2)
     {
       mx = 0;
-      my = game_mm.pacman[p].dir - 2;
+      my = game_mm.pacman[pacman_nr].dir - 2;
     }
     else
     {
       my = 0;
-      mx = 3 - game_mm.pacman[p].dir;
+      mx = 3 - game_mm.pacman[pacman_nr].dir;
     }
 
-    ox = game_mm.pacman[p].x;
-    oy = game_mm.pacman[p].y;
+    ox = game_mm.pacman[pacman_nr].x;
+    oy = game_mm.pacman[pacman_nr].y;
     nx = ox + mx;
     ny = oy + my;
-    element = Feld[nx][ny];
+    element = Tile[nx][ny];
 
     if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
       continue;
@@ -3497,39 +4121,39 @@ void MovePacMen()
     if (ObjHit(nx, ny, HIT_POS_CENTER))
       continue;
 
-    Feld[ox][oy] = EL_EMPTY;
-    Feld[nx][ny] =
+    Tile[ox][oy] = EL_EMPTY;
+    Tile[nx][ny] =
       EL_PACMAN_RIGHT - 1 +
-      (game_mm.pacman[p].dir - 1 +
-       (game_mm.pacman[p].dir % 2) * 2);
+      (game_mm.pacman[pacman_nr].dir - 1 +
+       (game_mm.pacman[pacman_nr].dir % 2) * 2);
 
-    game_mm.pacman[p].x = nx;
-    game_mm.pacman[p].y = ny;
+    game_mm.pacman[pacman_nr].x = nx;
+    game_mm.pacman[pacman_nr].y = ny;
 
     DrawGraphic_MM(ox, oy, IMG_EMPTY);
 
     if (element != EL_EMPTY)
     {
-      int graphic = el2gfx(Feld[nx][ny]);
+      int graphic = el2gfx(Tile[nx][ny]);
       Bitmap *bitmap;
       int src_x, src_y;
       int i;
 
       getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
 
-      CT = Counter();
-      ox = SX + ox * TILEX;
-      oy = SY + oy * TILEY;
+      CT = FrameCounter;
+      ox = cSX + ox * TILEX;
+      oy = cSY + oy * TILEY;
 
       for (i = 1; i < 33; i += 2)
        BlitBitmap(bitmap, window,
                   src_x, src_y, TILEX, TILEY,
                   ox + i * mx, oy + i * my);
-      Ct = Ct + Counter() - CT;
+      Ct = Ct + FrameCounter - CT;
     }
 
     DrawField_MM(nx, ny);
-    BackToFront();
+    BackToFront_MM();
 
     if (!laser.fuse_off)
     {
@@ -3558,186 +4182,6 @@ void MovePacMen()
   }
 }
 
-void GameWon_MM()
-{
-  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(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_ZEITBONUS]);
-      if (TimePlayed < 900 && !(TimePlayed % 10))
-       TimePlayed += 10;
-      else
-       TimePlayed++;
-
-      /*
-      DrawText(DX_TIME, DY_TIME, int2str(TimePlayed, 3), FONT_TEXT_2);
-      */
-
-      BackToFront();
-      Delay(10);
-    }
-
-    if (setup.sound_loops)
-      StopSound(SND_SIRR);
-  }
-
-#if 0
-  FadeSounds();
-#endif
-
-  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()
-{
-  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);
@@ -3746,87 +4190,48 @@ static void InitMovingField_MM(int x, int y, int direction)
   MovDir[x][y] = direction;
   MovDir[newx][newy] = direction;
 
-  if (Feld[newx][newy] == EL_EMPTY)
-    Feld[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;
+  if (Tile[newx][newy] == EL_EMPTY)
+    Tile[newx][newy] = EL_BLOCKED;
 }
 
 static int MovingOrBlocked2Element_MM(int x, int y)
 {
-  int element = Feld[x][y];
+  int element = Tile[x][y];
 
   if (element == EL_BLOCKED)
   {
     int oldx, oldy;
 
-    Blocked2Moving_MM(x, y, &oldx, &oldy);
+    Blocked2Moving(x, y, &oldx, &oldy);
 
-    return Feld[oldx][oldy];
+    return Tile[oldx][oldy];
   }
 
   return element;
 }
 
-#if 0
-static void RemoveField(int x, int y)
-{
-  Feld[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;
 
-  if (Feld[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
+  if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
     return;
 
   if (IS_MOVING(x, y))
   {
-    Moving2Blocked_MM(x, y, &newx, &newy);
-    if (Feld[newx][newy] != EL_BLOCKED)
+    Moving2Blocked(x, y, &newx, &newy);
+    if (Tile[newx][newy] != EL_BLOCKED)
       return;
   }
-  else if (Feld[x][y] == EL_BLOCKED)
+  else if (Tile[x][y] == EL_BLOCKED)
   {
-    Blocked2Moving_MM(x, y, &oldx, &oldy);
+    Blocked2Moving(x, y, &oldx, &oldy);
     if (!IS_MOVING(oldx, oldy))
       return;
   }
 
-  Feld[oldx][oldy] = EL_EMPTY;
-  Feld[newx][newy] = EL_EMPTY;
+  Tile[oldx][oldy] = EL_EMPTY;
+  Tile[newx][newy] = EL_EMPTY;
   MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
   MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
 
@@ -3834,67 +4239,211 @@ static void RemoveMovingField_MM(int x, int y)
   DrawLevelField_MM(newx, newy);
 }
 
-void PlaySoundLevel(int x, int y, int sound_nr)
+static void RaiseScore_MM(int value)
 {
-  int sx = SCREENX(x), sy = SCREENY(y);
-  int volume, stereo;
-  int silence_distance = 8;
+  game_mm.score += value;
+}
 
-  if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
-      (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
-    return;
+void RaiseScoreElement_MM(int element)
+{
+  switch (element)
+  {
+    case EL_PACMAN:
+    case EL_PACMAN_RIGHT:
+    case EL_PACMAN_UP:
+    case EL_PACMAN_LEFT:
+    case EL_PACMAN_DOWN:
+      RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
+      break;
 
-  if (!IN_LEV_FIELD(x, y) ||
-      sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
-      sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
-    return;
+    case EL_KEY:
+      RaiseScore_MM(native_mm_level.score[SC_KEY]);
+      break;
 
-  volume = SOUND_MAX_VOLUME;
+    case EL_KETTLE:
+    case EL_CELL:
+      RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
+      break;
 
-#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
+    case EL_LIGHTBALL:
+      RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
+      break;
+
+    default:
+      break;
+  }
+}
+
+
+// ----------------------------------------------------------------------------
+// Mirror Magic game engine snapshot handling functions
+// ----------------------------------------------------------------------------
+
+void SaveEngineSnapshotValues_MM(void)
+{
+  int x, y;
 
-  if (!IN_SCR_FIELD(sx, sy))
+  engine_snapshot_mm.game_mm = game_mm;
+  engine_snapshot_mm.laser = laser;
+
+  for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
   {
-    int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
-    int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
+    for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
+    {
+      engine_snapshot_mm.Ur[x][y]    = Ur[x][y];
+      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];
+    }
+  }
 
-    volume -= volume * (dx > dy ? dx : dy) / silence_distance;
+  engine_snapshot_mm.LX = LX;
+  engine_snapshot_mm.LY = LY;
+  engine_snapshot_mm.XS = XS;
+  engine_snapshot_mm.YS = YS;
+  engine_snapshot_mm.ELX = ELX;
+  engine_snapshot_mm.ELY = ELY;
+  engine_snapshot_mm.CT = CT;
+  engine_snapshot_mm.Ct = Ct;
+
+  engine_snapshot_mm.last_LX = last_LX;
+  engine_snapshot_mm.last_LY = last_LY;
+  engine_snapshot_mm.last_hit_mask = last_hit_mask;
+  engine_snapshot_mm.hold_x = hold_x;
+  engine_snapshot_mm.hold_y = hold_y;
+  engine_snapshot_mm.pacman_nr = pacman_nr;
+
+  engine_snapshot_mm.rotate_delay = rotate_delay;
+  engine_snapshot_mm.pacman_delay = pacman_delay;
+  engine_snapshot_mm.energy_delay = energy_delay;
+  engine_snapshot_mm.overload_delay = overload_delay;
+}
+
+void LoadEngineSnapshotValues_MM(void)
+{
+  int x, y;
+
+  // stored engine snapshot buffers already restored at this point
+
+  game_mm = engine_snapshot_mm.game_mm;
+  laser   = engine_snapshot_mm.laser;
+
+  for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
+  {
+    for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
+    {
+      Ur[x][y]    = engine_snapshot_mm.Ur[x][y];
+      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];
+    }
   }
 
-  PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
+  LX  = engine_snapshot_mm.LX;
+  LY  = engine_snapshot_mm.LY;
+  XS  = engine_snapshot_mm.XS;
+  YS  = engine_snapshot_mm.YS;
+  ELX = engine_snapshot_mm.ELX;
+  ELY = engine_snapshot_mm.ELY;
+  CT  = engine_snapshot_mm.CT;
+  Ct  = engine_snapshot_mm.Ct;
+
+  last_LX       = engine_snapshot_mm.last_LX;
+  last_LY       = engine_snapshot_mm.last_LY;
+  last_hit_mask = engine_snapshot_mm.last_hit_mask;
+  hold_x        = engine_snapshot_mm.hold_x;
+  hold_y        = engine_snapshot_mm.hold_y;
+  pacman_nr     = engine_snapshot_mm.pacman_nr;
+
+  rotate_delay   = engine_snapshot_mm.rotate_delay;
+  pacman_delay   = engine_snapshot_mm.pacman_delay;
+  energy_delay   = engine_snapshot_mm.energy_delay;
+  overload_delay = engine_snapshot_mm.overload_delay;
+
+  RedrawPlayfield_MM();
 }
 
-static void RaiseScore_MM(int value)
+static int getAngleFromTouchDelta(int dx, int dy,  int base)
 {
-  game_mm.score += value;
+  double pi = 3.141592653;
+  double rad = atan2((double)-dy, (double)dx);
+  double rad2 = (rad < 0 ? rad + 2 * pi : rad);
+  double deg = rad2 * 180.0 / pi;
 
-#if 0
-  DrawText(DX_SCORE, DY_SCORE, int2str(game_mm.score, 4),
-          FONT_TEXT_2);
-#endif
+  return (int)(deg * base / 360.0 + 0.5) % base;
 }
 
-void RaiseScoreElement_MM(int element)
+int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
 {
-  switch(element)
+  // calculate start (source) position to be at the middle of the tile
+  int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
+  int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
+  int dx = dst_mx - src_mx;
+  int dy = dst_my - src_my;
+  int element;
+  int base = 16;
+  int phases = 16;
+  int angle_old = -1;
+  int angle_new = -1;
+  int button = 0;
+  int i;
+
+  if (!IN_LEV_FIELD(x, y))
+    return 0;
+
+  element = Tile[x][y];
+
+  if (!IS_MCDUFFIN(element) &&
+      !IS_MIRROR(element) &&
+      !IS_BEAMER(element) &&
+      !IS_POLAR(element) &&
+      !IS_POLAR_CROSS(element) &&
+      !IS_DF_MIRROR(element))
+    return 0;
+
+  angle_old = get_element_angle(element);
+
+  if (IS_MCDUFFIN(element))
   {
-    case EL_PACMAN:
-      RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
-      break;
+    angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
+                dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
+                dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
+                dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
+                -1);
+  }
+  else if (IS_MIRROR(element) ||
+          IS_DF_MIRROR(element))
+  {
+    for (i = 0; i < laser.num_damages; i++)
+    {
+      if (laser.damage[i].x == x &&
+         laser.damage[i].y == y &&
+         ObjHit(x, y, HIT_POS_CENTER))
+      {
+       angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
+       angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
 
-    case EL_KEY:
-      RaiseScore_MM(native_mm_level.score[SC_KEY]);
-      break;
+       break;
+      }
+    }
+  }
 
-    default:
-      break;
+  if (angle_new == -1)
+  {
+    if (IS_MIRROR(element) ||
+       IS_DF_MIRROR(element) ||
+       IS_POLAR(element))
+      base = 32;
+
+    if (IS_POLAR_CROSS(element))
+      phases = 4;
+
+    angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
   }
+
+  button = (angle_new == angle_old ? 0 :
+           (angle_new - angle_old + phases) % phases < (phases / 2) ?
+           MB_LEFTBUTTON : MB_RIGHTBUTTON);
+
+  return button;
 }