fixed bug that could cause an endless loop (freeze) in the MM engine
[rocksndiamonds.git] / src / game_mm / mm_game.c
index 45c65634a9985556c75420f04edc463ead080198..2805fb77d3af6f33e4bebc4ce3967b61de5bf956 100644 (file)
@@ -463,7 +463,7 @@ static void InitMovDir_MM(int x, int y)
   }
 }
 
-static void InitField(int x, int y)
+static void InitField(int x, int y, boolean init_game)
 {
   int element = Tile[x][y];
 
@@ -475,7 +475,7 @@ static void InitField(int x, int y)
 
     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;
 
@@ -535,9 +535,12 @@ static void InitField(int x, int y)
       }
       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))
         {
@@ -699,7 +702,7 @@ void InitGameEngine_MM(void)
       Store[x][y] = Store2[x][y] = 0;
       Stop[x][y] = FALSE;
 
-      InitField(x, y);
+      InitField(x, y, TRUE);
     }
   }
 
@@ -757,7 +760,11 @@ void InitGameActions_MM(void)
   SetTileCursorXY(laser.start_edge.x, laser.start_edge.y);
   SetTileCursorActive(TRUE);
 
+  // restart all delay counters after initially cycling game elements
+  ResetFrameCounter(&rotate_delay);
+  ResetFrameCounter(&pacman_delay);
   ResetFrameCounter(&energy_delay);
+  ResetFrameCounter(&overload_delay);
 }
 
 static void FadeOutLaser(void)
@@ -821,6 +828,13 @@ void AddLaserEdge(int lx, int ly)
 
 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;
@@ -958,7 +972,8 @@ static void DeactivateLaserTargetElement(void)
 
 void ScanLaser(void)
 {
-  int element;
+  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
@@ -1044,6 +1059,8 @@ void ScanLaser(void)
          hit_mask, LX, LY, ELX, ELY);
 #endif
 
+    last_element = element;
+
     element = Tile[ELX][ELY];
     laser.dest_element = element;
 
@@ -1062,6 +1079,12 @@ void ScanLaser(void)
            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(hit_mask))
@@ -1120,6 +1143,14 @@ void ScanLaser(void)
     if (rf)
       DrawLaser(rf - 1, DL_LASER_ENABLED);
     rf = laser.num_edges;
+
+    if (!IS_DF_WALL_STEEL(element))
+    {
+      // only used for scanning DF steel walls; reset for all other elements
+      last_LX = 0;
+      last_LY = 0;
+      last_hit_mask = 0;
+    }
   }
 
 #if 0
@@ -1465,20 +1496,29 @@ boolean HitElement(int element, int hit_mask)
   // this is more precise: check if laser would go through the center
   if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
   {
+    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;
@@ -1631,9 +1671,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)
@@ -1661,6 +1703,10 @@ boolean HitElement(int element, int hit_mask)
     {
       DeletePacMan(ELX, ELY);
     }
+    else if (IS_ENVELOPE(element))
+    {
+      Tile[ELX][ELY] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(Tile[ELX][ELY]);
+    }
 
     RaiseScoreElement_MM(element);
 
@@ -2435,6 +2481,8 @@ static void OpenSurpriseBall(int x, int y)
       BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
                 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
 
+      laser.redraw = TRUE;
+
       MarkTileDirty(x, y);
     }
 
@@ -2442,10 +2490,48 @@ static void OpenSurpriseBall(int x, int 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);
+
+      ScanLaser();
+    }
+  }
+}
+
+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_MM(nr);
     }
   }
 }
@@ -2532,33 +2618,43 @@ static void GrowAmoeba(int x, int y)
 }
 
 static void DrawFieldAnimated_MM(int x, int y)
+{
+  DrawField_MM(x, y);
+
+  laser.redraw = TRUE;
+}
+
+static void DrawFieldAnimatedIfNeeded_MM(int x, int y)
 {
   int element = Tile[x][y];
+  int graphic = el2gfx(element);
 
-  if (IS_BLOCKED(x, y))
+  if (!getGraphicInfo_NewFrame(x, y, graphic))
     return;
 
   DrawField_MM(x, y);
 
-  if (IS_MIRROR(element) ||
-      IS_MIRROR_FIXED(element) ||
-      element == EL_PRISM)
+  laser.redraw = TRUE;
+}
+
+static void DrawFieldTwinkle(int x, int y)
+{
+  if (MovDelay[x][y] != 0)     // wait some time before next frame
   {
-    if (MovDelay[x][y] != 0)   // wait some time before next frame
-    {
-      MovDelay[x][y]--;
+    MovDelay[x][y]--;
 
-      if (MovDelay[x][y] != 0)
-      {
-       int graphic = IMG_TWINKLE_WHITE;
-       int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
+    DrawField_MM(x, y);
 
-       DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
-      }
+    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;
+    laser.redraw = TRUE;
+  }
 }
 
 static void Explode_MM(int x, int y, int phase, int mode)
@@ -2582,15 +2678,14 @@ static void Explode_MM(int x, int y, int phase, int mode)
       Tile[x][y] = center_element;
     }
 
-    if (center_element == EL_BOMB_ACTIVE || IS_MCDUFFIN(center_element))
-      Store[x][y] = center_element;
-    else
-      Store[x][y] = EL_EMPTY;
-
+    Store[x][y] = center_element;
     Store2[x][y] = mode;
 
     Tile[x][y] = EL_EXPLODING_OPAQUE;
-    GfxElement[x][y] = center_element;
+
+    GfxElement[x][y] = (center_element == EL_BOMB_ACTIVE ? EL_BOMB :
+                       IS_MCDUFFIN(center_element) ? EL_MCDUFFIN :
+                       center_element);
 
     MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
 
@@ -2620,7 +2715,6 @@ static void Explode_MM(int x, int y, int phase, int mode)
       InitLaser();
 
       Bang_MM(laser.start_edge.x, laser.start_edge.y);
-      Store[x][y] = EL_EMPTY;
 
       GameOver_MM(GAME_OVER_DELAYED);
 
@@ -2628,16 +2722,15 @@ static void Explode_MM(int x, int y, int phase, int mode)
     }
     else if (IS_MCDUFFIN(Store[x][y]))
     {
-      Store[x][y] = EL_EMPTY;
-
       GameOver_MM(GAME_OVER_BOMB);
     }
 
-    Tile[x][y] = Store[x][y];
+    Tile[x][y] = EL_EMPTY;
+
     Store[x][y] = Store2[x][y] = 0;
     MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
 
-    InitField(x, y);
+    InitField(x, y, FALSE);
     DrawField_MM(x, y);
   }
   else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
@@ -2941,6 +3034,12 @@ boolean ClickElement(int x, int y, int button)
 
     element_clicked = TRUE;
   }
+  else if (IS_ENVELOPE(element))
+  {
+    Tile[x][y] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(element);
+
+    element_clicked = TRUE;
+  }
 
   click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
   new_button = FALSE;
@@ -3023,8 +3122,6 @@ void RotateMirror(int x, int y, int button)
         IS_POLAR(Tile[x][y]) ||
         IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
     {
-      check = 0;
-
       if (IS_BEAMER(Tile[x][y]))
       {
 #if 0
@@ -3032,12 +3129,13 @@ void RotateMirror(int x, int y, int button)
              LX, LY, laser.beamer_edge, laser.beamer[1].num);
 #endif
 
-#if 0
-       laser.num_edges--;
-#endif
+       if (check == 1)
+         laser.num_edges--;
       }
 
       ScanLaser();
+
+      check = 0;
     }
 
     if (check == 2)
@@ -3198,12 +3296,22 @@ static void GameActions_MM_Ext(void)
       OpenExit(x, y);
     else if (element == EL_GRAY_BALL_OPENING)
       OpenSurpriseBall(x, y);
+    else if (IS_ENVELOPE_OPENING(element))
+      OpenEnvelope(x, y);
     else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
       MeltIce(x, y);
     else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
       GrowAmoeba(x, y);
-    else
+    else if (IS_MIRROR(element) ||
+            IS_MIRROR_FIXED(element) ||
+            element == EL_PRISM)
+      DrawFieldTwinkle(x, y);
+    else if (element == EL_GRAY_BALL_OPENING ||
+            element == EL_BOMB_ACTIVE ||
+            element == EL_MINE_ACTIVE)
       DrawFieldAnimated_MM(x, y);
+    else if (!IS_BLOCKED(x, y))
+      DrawFieldAnimatedIfNeeded_MM(x, y);
   }
 
   AutoRotateMirrors();
@@ -3392,7 +3500,11 @@ static void GameActions_MM_Ext(void)
 
       int new_element = native_mm_level.ball_content[element_pos];
 
-      Store[ELX][ELY] = new_element + RND(get_num_elements(new_element));
+      // randomly rotate newly created game element, if needed
+      if (native_mm_level.rotate_ball_content)
+       new_element = get_rotated_element(new_element, RND(16));
+
+      Store[ELX][ELY] = new_element;
       Store2[ELX][ELY] = TRUE;
     }