fixed bug with calling function with invalid (although ignored) parameter
[rocksndiamonds.git] / src / game_mm / mm_game.c
index fb2385048237889bf373e60c574a208fc67caf04..b55b2bf0295820afb76713f3d5960ba2fdf94348 100644 (file)
@@ -9,6 +9,8 @@
 // mm_game.c
 // ============================================================================
 
+#include <math.h>
+
 #include "main_mm.h"
 
 #include "mm_main.h"
@@ -73,7 +75,7 @@
 #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           (4 * ONE_SECOND_DELAY / GAME_FRAME_DELAY)
+#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)
@@ -92,6 +94,7 @@
 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);
@@ -354,6 +357,60 @@ static boolean CheckLaserPixel(int x, int y)
   return (pixel == WHITE_PIXEL);
 }
 
+static void CheckExitMM()
+{
+  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 (Feld[x][y] == EL_EXIT_CLOSED)
+      {
+       /* initiate opening animation of exit door */
+       Feld[x][y] = EL_EXIT_OPENING;
+
+       exit_element = EL_EXIT_OPEN;
+       exit_x = x;
+       exit_y = y;
+      }
+      else if (IS_RECEIVER(Feld[x][y]))
+      {
+       /* remove field that blocks receiver */
+       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);
+       }
+
+       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 InitMovDir_MM(int x, int y)
 {
   int element = Feld[x][y];
@@ -551,6 +608,7 @@ void InitGameEngine_MM()
   game_mm.game_over_cause = 0;
 
   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;
@@ -683,19 +741,28 @@ void InitGameActions_MM()
 #endif
 
   ScanLaser();
+
+  if (game_mm.kettles_still_needed == 0)
+    CheckExitMM();
+
+  SetTileCursorXY(laser.start_edge.x, laser.start_edge.y);
+  SetTileCursorActive(TRUE);
 }
 
 void AddLaserEdge(int lx, int ly)
 {
-  if (lx < -2 || ly < -2 || lx >= SXSIZE + 2 || ly >= SYSIZE + 2)
+  int clx = dSX + lx;
+  int cly = dSY + ly;
+
+  if (clx < -2 || cly < -2 || clx >= SXSIZE + 2 || cly >= SYSIZE + 2)
   {
     Error(ERR_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;
@@ -717,8 +784,8 @@ boolean StepBehind()
   {
     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);
   }
@@ -796,8 +863,8 @@ int ScanPixel()
       }
       else
       {
-       pixel = (SX + px < REAL_SX || SX + px >= REAL_SX + FULL_SXSIZE ||
-                SY + py < REAL_SY || SY + py >= REAL_SY + FULL_SYSIZE);
+       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)
@@ -1127,8 +1194,8 @@ void DrawLaserExt(int start_edge, int num_edges, int mode)
   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;
 
@@ -1239,7 +1306,7 @@ void DrawLaser(int start_edge, int mode)
       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--)
+      for (i = num_beamers - 1; i >= 0; i--)
       {
        int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
 
@@ -1271,6 +1338,13 @@ void DrawLaser(int start_edge, int mode)
   {
     DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
   }
+
+  game_mm.laser_enabled = mode;
+}
+
+void DrawLaser_MM()
+{
+  DrawLaser(0, game_mm.laser_enabled);
 }
 
 boolean HitElement(int element, int hit_mask)
@@ -1470,48 +1544,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 exit_element = (element == EL_KETTLE ? EL_EXIT_OPEN : EL_RECEIVER);
-       int x, y;
-       static int xy[4][2] =
-       {
-         { +1,  0 },
-         {  0, -1 },
-         { -1,  0 },
-         {  0, +1 }
-       };
-
-       PlayLevelSound_MM(ELX, ELY, exit_element, MM_ACTION_OPENING);
-
-       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);
       }
@@ -1520,16 +1557,13 @@ 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);
     }
 
+    RaiseScoreElement_MM(element);
+
     return FALSE;
   }
 
@@ -1939,8 +1973,12 @@ 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;
 }
 
@@ -2294,7 +2332,7 @@ void OpenSurpriseBall(int x, int y)
       getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
 
       BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
-                SX + x * TILEX + dx, SY + y * TILEY + dy);
+                cSX + x * TILEX + dx, cSY + y * TILEY + dy);
 
       MarkTileDirty(x, y);
     }
@@ -2448,11 +2486,15 @@ static void Explode_MM(int x, int y, int phase, int mode)
       game_mm.game_over = TRUE;
       game_mm.game_over_cause = GAME_OVER_BOMB;
 
+      SetTileCursorActive(FALSE);
+
       laser.overloaded = FALSE;
     }
     else if (IS_MCDUFFIN(Store[x][y]))
     {
       Store[x][y] = EL_EMPTY;
+
+      game.restart_game_message = "Bomb killed Mc Duffin ! Play it again ?";
     }
 
     Feld[x][y] = Store[x][y];
@@ -2501,7 +2543,7 @@ static void Explode_MM(int x, int y, int phase, int mode)
     getGraphicSource(graphic, graphic_phase, &bitmap, &src_x, &src_y);
 
     BlitBitmap(bitmap, drawto_field, src_x, src_y, TILEX, TILEY,
-              FX + x * TILEX, FY + y * TILEY);
+              cFX + x * TILEX, cFY + y * TILEY);
 
     MarkTileDirty(x, y);
   }
@@ -2696,11 +2738,12 @@ static void ContinueMoving_MM(int x, int y)
   laser.redraw = TRUE;
 }
 
-void ClickElement(int x, int y, int button)
+boolean ClickElement(int x, int y, int button)
 {
   static unsigned int click_delay = 0;
   static int click_delay_value = CLICK_DELAY;
   static boolean new_button = TRUE;
+  boolean element_clicked = FALSE;
   int element;
 
   if (button == -1)
@@ -2710,12 +2753,12 @@ void ClickElement(int x, int y, int button)
     click_delay_value = CLICK_DELAY;
     new_button = TRUE;
 
-    return;
+    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)
   {
@@ -2725,20 +2768,20 @@ void ClickElement(int x, int y, int button)
     /* release eventually hold auto-rotating mirror */
     RotateMirror(x, y, MB_RELEASED);
 
-    return;
+    return FALSE;
   }
 
   if (!FrameReached(&click_delay, click_delay_value) && !new_button)
-    return;
+    return FALSE;
 
   if (button == MB_MIDDLEBUTTON)       /* middle button has no function */
-    return;
+    return FALSE;
 
   if (!IN_LEV_FIELD(x, y))
-    return;
+    return FALSE;
 
   if (Feld[x][y] == EL_EMPTY)
-    return;
+    return FALSE;
 
   element = Feld[x][y];
 
@@ -2750,6 +2793,8 @@ void ClickElement(int x, int y, int button)
       IS_DF_MIRROR_AUTO(element))
   {
     RotateMirror(x, y, button);
+
+    element_clicked = TRUE;
   }
   else if (IS_MCDUFFIN(element))
   {
@@ -2776,17 +2821,21 @@ void ClickElement(int x, int y, int button)
 
     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)
   {
@@ -2797,16 +2846,22 @@ void ClickElement(int x, int y, 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;
   }
 
   click_delay_value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
   new_button = FALSE;
+
+  return element_clicked;
 }
 
 void RotateMirror(int x, int y, int button)
@@ -2939,24 +2994,24 @@ boolean ObjHit(int obx, int oby, int bits)
 
   if (bits & HIT_POS_CENTER)
   {
-    if (CheckLaserPixel(SX + obx + 15,
-                       SY + oby + 15))
+    if (CheckLaserPixel(cSX + obx + 15,
+                       cSY + oby + 15))
       return TRUE;
   }
 
   if (bits & HIT_POS_EDGE)
   {
     for (i = 0; i < 4; i++)
-      if (CheckLaserPixel(SX + obx + 31 * (i % 2),
-                         SY + oby + 31 * (i / 2)))
+      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 (CheckLaserPixel(SX + 4 + obx + 22 * (i % 2),
-                         SY + 4 + oby + 22 * (i / 2)))
+      if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
+                         cSY + 4 + oby + 22 * (i / 2)))
        return TRUE;
   }
 
@@ -3089,9 +3144,10 @@ static void GameActions_MM_Ext(struct MouseActionInfo action, boolean warp_mode)
 
   if (FrameReached(&energy_delay, ENERGY_DELAY))
   {
-    game_mm.energy_left--;
-    if (game_mm.energy_left >= 0)
+    if (game_mm.energy_left > 0)
     {
+      game_mm.energy_left--;
+
 #if 0
       BlitBitmap(pix[PIX_DOOR], drawto,
                 DOOR_GFX_PAGEX5 + XX_ENERGY, DOOR_GFX_PAGEY1 + YY_ENERGY,
@@ -3100,7 +3156,7 @@ static void GameActions_MM_Ext(struct MouseActionInfo action, boolean warp_mode)
 #endif
       redraw_mask |= REDRAW_DOOR_1;
     }
-    else if (setup.time_limit)
+    else if (setup.time_limit && !game_mm.game_over)
     {
       int i;
 
@@ -3120,12 +3176,18 @@ static void GameActions_MM_Ext(struct MouseActionInfo action, boolean warp_mode)
       }
 
       StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
+#if 0
       FadeMusic();
+#endif
 
       DrawLaser(0, DL_LASER_DISABLED);
       game_mm.game_over = TRUE;
       game_mm.game_over_cause = GAME_OVER_NO_ENERGY;
 
+      SetTileCursorActive(FALSE);
+
+      game.restart_game_message = "Out of magic energy ! Play it again ?";
+
 #if 0
       if (Request("Out of magic energy ! Play it again ?",
                  REQ_ASK | REQ_STAY_CLOSED))
@@ -3256,6 +3318,10 @@ static void GameActions_MM_Ext(struct MouseActionInfo action, boolean warp_mode)
       game_mm.game_over = TRUE;
       game_mm.game_over_cause = GAME_OVER_OVERLOADED;
 
+      SetTileCursorActive(FALSE);
+
+      game.restart_game_message = "Magic spell hit Mc Duffin ! Play it again ?";
+
 #if 0
       if (Request("Magic spell hit Mc Duffin ! Play it again ?",
                  REQ_ASK | REQ_STAY_CLOSED))
@@ -3278,7 +3344,7 @@ static void GameActions_MM_Ext(struct MouseActionInfo action, boolean warp_mode)
 
   CT -= Ct;
 
-  if (element == EL_BOMB && CT > 75)
+  if (element == EL_BOMB && CT > native_mm_level.time_bomb)
   {
     if (game_mm.cheat_no_explosion)
       return;
@@ -3316,7 +3382,7 @@ static void GameActions_MM_Ext(struct MouseActionInfo action, boolean warp_mode)
     return;
   }
 
-  if (element == EL_FUSE_ON && CT > 25)
+  if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
   {
     laser.fuse_off = TRUE;
     laser.fuse_x = ELX;
@@ -3326,7 +3392,7 @@ static void GameActions_MM_Ext(struct MouseActionInfo action, boolean warp_mode)
     DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
   }
 
-  if (element == EL_BALL_GRAY && CT > 75)
+  if (element == EL_BALL_GRAY && CT > native_mm_level.time_ball)
   {
     static int new_elements[] =
     {
@@ -3479,8 +3545,8 @@ static void GameActions_MM_Ext(struct MouseActionInfo action, boolean warp_mode)
 
 /*
     laser.num_edges--;
-    LX = laser.edge[laser.num_edges].x - (SX + 2);
-    LY = laser.edge[laser.num_edges].y - (SY + 2);
+    LX = laser.edge[laser.num_edges].x - cSX2;
+    LY = laser.edge[laser.num_edges].y - cSY2;
 */
 
     for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
@@ -3533,12 +3599,12 @@ static void GameActions_MM_Ext(struct MouseActionInfo action, boolean warp_mode)
     {
       if (laser.wall_mask & (1 << i))
       {
-       if (CheckLaserPixel(SX + ELX * TILEX + 14 + (i % 2) * 2,
-                           SY + ELY * TILEY + 31 * (i / 2)))
+       if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
+                           cSY + ELY * TILEY + 31 * (i / 2)))
          break;
 
-       if (CheckLaserPixel(SX + ELX * TILEX + 31 * (i % 2),
-                           SY + ELY * TILEY + 14 + (i / 2) * 2))
+       if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
+                           cSY + ELY * TILEY + 14 + (i / 2) * 2))
          break;
       }
     }
@@ -3549,8 +3615,8 @@ static void GameActions_MM_Ext(struct MouseActionInfo action, boolean warp_mode)
     {
       if (laser.wall_mask & (1 << i))
       {
-       if (CheckLaserPixel(SX + ELX * TILEX + 31 * (i % 2),
-                           SY + ELY * TILEY + 31 * (i / 2)))
+       if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
+                           cSY + ELY * TILEY + 31 * (i / 2)))
          break;
       }
     }
@@ -3559,8 +3625,8 @@ static void GameActions_MM_Ext(struct MouseActionInfo action, boolean warp_mode)
 
     if (laser.num_beamers > 0 ||
        k1 < 1 || k2 < 4 || k3 < 4 ||
-       CheckLaserPixel(SX + ELX * TILEX + 14,
-                       SY + ELY * TILEY + 14))
+       CheckLaserPixel(cSX + ELX * TILEX + 14,
+                       cSY + ELY * TILEY + 14))
     {
       laser.num_edges = r;
       laser.num_damages = d;
@@ -3613,7 +3679,7 @@ static void GameActions_MM_Ext(struct MouseActionInfo action, boolean warp_mode)
   }
 
   if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
-      laser.stops_inside_element && CT > 75)
+      laser.stops_inside_element && CT > native_mm_level.time_block)
   {
     int x, y;
     int k;
@@ -3702,9 +3768,12 @@ static void GameActions_MM_Ext(struct MouseActionInfo action, boolean warp_mode)
 
 void GameActions_MM(struct MouseActionInfo action, boolean warp_mode)
 {
-  ClickElement(action.lx, action.ly, action.button);
+  boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
+  boolean button_released = (action.button == MB_RELEASED);
 
   GameActions_MM_Ext(action, warp_mode);
+
+  CheckSingleStepMode_MM(element_clicked, button_released);
 }
 
 void MovePacMen()
@@ -3772,8 +3841,8 @@ void MovePacMen()
       getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
 
       CT = FrameCounter;
-      ox = SX + ox * TILEX;
-      oy = SY + oy * TILEY;
+      ox = cSX + ox * TILEX;
+      oy = cSY + oy * TILEY;
 
       for (i = 1; i < 33; i += 2)
        BlitBitmap(bitmap, window,
@@ -3872,7 +3941,7 @@ void GameWon_MM()
       if (!setup.sound_loops)
        PlaySoundStereo(SND_SIRR, SOUND_MAX_RIGHT);
       if (TimePlayed < 999 && !(TimePlayed % 10))
-       RaiseScore_MM(native_mm_level.score[SC_ZEITBONUS]);
+       RaiseScore_MM(native_mm_level.score[SC_TIME_BONUS]);
       if (TimePlayed < 900 && !(TimePlayed % 10))
        TimePlayed += 10;
       else
@@ -4141,6 +4210,10 @@ 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;
 
@@ -4148,7 +4221,192 @@ void RaiseScoreElement_MM(int element)
       RaiseScore_MM(native_mm_level.score[SC_KEY]);
       break;
 
+    case EL_KETTLE:
+    case EL_CELL:
+      RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
+      break;
+
+    case EL_LIGHTBALL:
+      RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
+      break;
+
     default:
       break;
   }
 }
+
+
+/* ------------------------------------------------------------------------- */
+/* Mirror Magic game engine snapshot handling functions                      */
+/* ------------------------------------------------------------------------- */
+
+void SaveEngineSnapshotValues_MM(ListNode **buffers)
+{
+  int x, y;
+
+  engine_snapshot_mm.game_mm = game_mm;
+  engine_snapshot_mm.laser = laser;
+
+  for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
+  {
+    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];
+      engine_snapshot_mm.Frame[x][y] = Frame[x][y];
+    }
+  }
+
+  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()
+{
+  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];
+      Frame[x][y] = engine_snapshot_mm.Frame[x][y];
+    }
+  }
+
+  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 int getAngleFromTouchDelta(int dx, int dy,  int base)
+{
+  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;
+
+  return (int)(deg * base / 360.0 + 0.5) % base;
+}
+
+int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
+{
+  // 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 = Feld[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))
+  {
+    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;
+
+       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;
+}