rnd-20040207-2-src
[rocksndiamonds.git] / src / game.c
index 7b225c5eac9621eb61e6ee2dc195b16d6a4b4c76..d304e76ee8b873daeb975bf330464948858afbba 100644 (file)
                                        (condition) ||                  \
                                        (DONT_COLLIDE_WITH(e) &&        \
                                         IS_PLAYER(x, y) &&             \
-                                        !PLAYER_PROTECTED(x, y))))
+                                        !PLAYER_ENEMY_PROTECTED(x, y))))
 #else
 #define ELEMENT_CAN_ENTER_FIELD_GENERIC(e, x, y, condition)            \
                (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) ||                \
@@ -216,7 +216,8 @@ static void ScrollScreen(struct PlayerInfo *, int);
 static void InitBeltMovement(void);
 static void CloseAllOpenTimegates(void);
 static void CheckGravityMovement(struct PlayerInfo *);
-static void KillHeroUnlessProtected(int, int);
+static void KillHeroUnlessEnemyProtected(int, int);
+static void KillHeroUnlessExplosionProtected(int, int);
 
 static void TestIfPlayerTouchesCustomElement(int, int);
 static void TestIfElementTouchesCustomElement(int, int);
@@ -606,6 +607,10 @@ static void InitPlayerField(int x, int y, int element, boolean init_game)
 
     player->present = TRUE;
 
+    player->block_last_field = (element == EL_SP_MURPHY ?
+                               level.sp_block_last_field :
+                               level.block_last_field);
+
     if (!options.network || player->connected)
     {
       player->active = TRUE;
@@ -866,7 +871,7 @@ static void resolve_group_element(int group_element, int recursion_depth)
          group_element - EL_GROUP_START + 1);
 
     /* replace element which caused too deep recursion by question mark */
-    group->element_resolved[group->num_elements_resolved++] = EL_CHAR_QUESTION;
+    group->element_resolved[group->num_elements_resolved++] = EL_UNKNOWN;
 
     return;
   }
@@ -1105,6 +1110,19 @@ static void InitGameEngine()
     element_info[e].push_delay_random = push_delay_list[i].push_delay_random;
   }
 
+  /* set push delay value for Supaplex elements for newer engine versions */
+  if (game.engine_version >= VERSION_IDENT(3,0,9,0))
+  {
+    for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+    {
+      if (IS_SP_ELEMENT(i))
+      {
+       element_info[i].push_delay_fixed  = 6;
+       element_info[i].push_delay_random = 0;
+      }
+    }
+  }
+
   /* ---------- initialize move stepsize ----------------------------------- */
 
   /* initialize move stepsize values to default */
@@ -1210,6 +1228,8 @@ void InitGame()
 
     player->use_murphy_graphic = FALSE;
 
+    player->block_last_field = FALSE;
+
     player->actual_frame_counter = 0;
 
     player->step_counter = 0;
@@ -1365,6 +1385,7 @@ void InitGame()
       ChangeEvent[x][y] = CE_BITMASK_DEFAULT;
 
       ExplodePhase[x][y] = 0;
+      ExplodeDelay[x][y] = 0;
       ExplodeField[x][y] = EX_NO_EXPLOSION;
 
       RunnerVisit[x][y] = 0;
@@ -1399,6 +1420,30 @@ void InitGame()
                    emulate_sb ? EMU_SOKOBAN :
                    emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
 
+  /* initialize explosion and ignition delay */
+  for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+  {
+    if (!IS_CUSTOM_ELEMENT(i))
+    {
+      int num_phase = 9;
+      int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
+      int last_phase = num_phase * delay;
+      int half_phase = (num_phase / 2) * delay;
+
+      element_info[i].explosion_delay = last_phase;
+      element_info[i].ignition_delay = half_phase;
+
+      if (i == EL_BLACK_ORB)
+       element_info[i].ignition_delay = 1;
+    }
+
+    if (element_info[i].explosion_delay < 2)   /* !!! check again !!! */
+      element_info[i].explosion_delay = 2;
+
+    if (element_info[i].ignition_delay < 1)    /* !!! check again !!! */
+      element_info[i].ignition_delay = 1;
+  }
+
   /* correct non-moving belts to start moving left */
   for (i = 0; i < 4; i++)
     if (game.belt_dir[i] == MV_NO_MOVING)
@@ -1774,15 +1819,15 @@ void InitMovDir(int x, int y)
        int move_direction_initial = ei->move_direction_initial;
        int move_pattern = ei->move_pattern;
 
-       if (move_direction_initial == MV_PREVIOUS)
+       if (move_direction_initial == MV_START_PREVIOUS)
        {
          if (MovDir[x][y] != MV_NO_MOVING)
            return;
 
-         move_direction_initial = MV_AUTOMATIC;
+         move_direction_initial = MV_START_AUTOMATIC;
        }
 
-       if (move_direction_initial == MV_RANDOM)
+       if (move_direction_initial == MV_START_RANDOM)
          MovDir[x][y] = 1 << RND(4);
        else if (move_direction_initial & MV_ANY_DIRECTION)
          MovDir[x][y] = move_direction_initial;
@@ -2319,6 +2364,10 @@ void CheckDynamite(int x, int y)
 void RelocatePlayer(int x, int y, int element)
 {
   struct PlayerInfo *player = &stored_player[element - EL_PLAYER_1];
+  boolean ffwd_delay = (tape.playing && tape.fast_forward);
+  boolean no_delay = (tape.index_search);
+  int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
+  int wait_delay_value = (no_delay ? 0 : frame_delay_value);
 
   if (player->GameOver)                /* do not reanimate dead player */
     return;
@@ -2339,7 +2388,7 @@ void RelocatePlayer(int x, int y, int element)
       DrawPlayer(player);
 
       BackToFront();
-      Delay(GAME_FRAME_DELAY);
+      Delay(wait_delay_value);
     }
 
     DrawPlayer(player);                /* needed here only to cleanup last field */
@@ -2383,11 +2432,11 @@ void RelocatePlayer(int x, int y, int element)
       /* scroll in two steps of half tile size to make things smoother */
       BlitBitmap(drawto_field, window, fx, fy, SXSIZE, SYSIZE, SX, SY);
       FlushDisplay();
-      Delay(GAME_FRAME_DELAY);
+      Delay(wait_delay_value);
 
       /* scroll second step to align at full tile size */
       BackToFront();
-      Delay(GAME_FRAME_DELAY);
+      Delay(wait_delay_value);
     }
   }
 }
@@ -2400,6 +2449,9 @@ void Explode(int ex, int ey, int phase, int mode)
   int last_phase = num_phase * delay;
   int half_phase = (num_phase / 2) * delay;
   int first_phase_after_start = EX_PHASE_START + 1;
+  int border_element;
+
+  int last_phase_TEST = last_phase;
 
   if (game.explosions_delayed)
   {
@@ -2434,6 +2486,10 @@ void Explode(int ex, int ey, int phase, int mode)
       Feld[ex][ey] = center_element;
     }
 
+#if 1
+    last_phase = element_info[center_element].explosion_delay;
+#endif
+
     for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
     {
       int xx = x - ex + 1;
@@ -2513,7 +2569,7 @@ void Explode(int ex, int ey, int phase, int mode)
       RemoveField(x, y);
 #endif
 
-      if (IS_PLAYER(ex, ey) && !PLAYER_PROTECTED(ex, ey))
+      if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
       {
        switch(StorePlayer[ex][ey])
        {
@@ -2604,6 +2660,9 @@ void Explode(int ex, int ey, int phase, int mode)
 #endif
 
       ExplodePhase[x][y] = 1;
+#if 1
+      ExplodeDelay[x][y] = last_phase;
+#endif
       Stop[x][y] = TRUE;
     }
 
@@ -2620,6 +2679,10 @@ void Explode(int ex, int ey, int phase, int mode)
   x = ex;
   y = ey;
 
+#if 1
+  last_phase = ExplodeDelay[x][y];
+#endif
+
   ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
 
 #ifdef DEBUG
@@ -2640,6 +2703,52 @@ void Explode(int ex, int ey, int phase, int mode)
   }
 #endif
 
+#if 1
+
+  border_element = Store2[x][y];
+  if (IS_PLAYER(x, y))
+    border_element = StorePlayer[x][y];
+
+  if (phase == element_info[border_element].ignition_delay ||
+      phase == last_phase)
+  {
+    boolean border_explosion = FALSE;
+
+    if (IS_PLAYER(x, y))
+    {
+      KillHeroUnlessExplosionProtected(x, y);
+      border_explosion = TRUE;
+
+      if (phase == last_phase)
+       printf("::: IS_PLAYER\n");
+    }
+    else if (CAN_EXPLODE_BY_FIRE(border_element))
+    {
+      Feld[x][y] = Store2[x][y];
+      Store2[x][y] = 0;
+      Bang(x, y);
+      border_explosion = TRUE;
+
+      if (phase == last_phase)
+       printf("::: CAN_EXPLODE_BY_FIRE\n");
+    }
+    else if (border_element == EL_AMOEBA_TO_DIAMOND)
+    {
+      AmoebeUmwandeln(x, y);
+      border_explosion = TRUE;
+
+      if (phase == last_phase)
+       printf("::: EL_AMOEBA_TO_DIAMOND\n");
+    }
+
+#if 0
+    if (border_explosion && phase == last_phase)
+      return;
+#endif
+  }
+
+#else
+
   if (phase == first_phase_after_start)
   {
     int element = Store2[x][y];
@@ -2656,7 +2765,7 @@ void Explode(int ex, int ey, int phase, int mode)
     int element = Store2[x][y];
 
     if (IS_PLAYER(x, y))
-      KillHeroUnlessProtected(x, y);
+      KillHeroUnlessExplosionProtected(x, y);
     else if (CAN_EXPLODE_BY_FIRE(element))
     {
       Feld[x][y] = Store2[x][y];
@@ -2666,6 +2775,7 @@ void Explode(int ex, int ey, int phase, int mode)
     else if (element == EL_AMOEBA_TO_DIAMOND)
       AmoebeUmwandeln(x, y);
   }
+#endif
 
   if (phase == last_phase)
   {
@@ -2714,7 +2824,11 @@ void Explode(int ex, int ey, int phase, int mode)
     if (ELEM_IS_PLAYER(element))
       RelocatePlayer(x, y, element);
   }
+#if 1
+  else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
+#else
   else if (phase >= delay && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
+#endif
   {
 #if 1
     int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
@@ -2813,7 +2927,7 @@ void Bang(int x, int y)
 #endif
 
 #if 1
-  if (IS_PLAYER(x, y) && !PLAYER_PROTECTED(x, y))
+  if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
 #else
   if (IS_PLAYER(x, y))
 #endif
@@ -3304,7 +3418,7 @@ void Impact(int x, int y)
   if (impact && element == EL_AMOEBA_DROP)
   {
     if (object_hit && IS_PLAYER(x, y + 1))
-      KillHeroUnlessProtected(x, y + 1);
+      KillHeroUnlessEnemyProtected(x, y + 1);
     else if (object_hit && smashed == EL_PENGUIN)
       Bang(x, y + 1);
     else
@@ -3346,7 +3460,7 @@ void Impact(int x, int y)
     {
       if (CAN_SMASH_PLAYER(element))
       {
-       KillHeroUnlessProtected(x, y + 1);
+       KillHeroUnlessEnemyProtected(x, y + 1);
        return;
       }
     }
@@ -4687,7 +4801,7 @@ void StartMoving(int x, int y)
 
     if (DONT_COLLIDE_WITH(element) &&
        IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
-       !PLAYER_PROTECTED(newx, newy))
+       !PLAYER_ENEMY_PROTECTED(newx, newy))
     {
 #if 1
       TestIfBadThingRunsIntoHero(x, y, MovDir[x][y]);
@@ -5238,15 +5352,7 @@ void ContinueMoving(int x, int y)
 #if 0
     if (IN_LEV_FIELD(nextx, nexty))
     {
-      static int opposite_directions[] =
-      {
-       MV_RIGHT,
-       MV_LEFT,
-       MV_DOWN,
-       MV_UP
-      };
-      int move_dir_bit = MV_DIR_BIT(direction);
-      int opposite_direction = opposite_directions[move_dir_bit];
+      int opposite_direction = MV_DIR_OPPOSITE(direction);
       int hitting_side = direction;
       int touched_side = opposite_direction;
       int touched_element = MovingOrBlocked2Element(nextx, nexty);
@@ -6209,7 +6315,7 @@ static void ChangeElementNowExt(int x, int y, int target_element)
 
   /* check if element under player changes from accessible to unaccessible
      (needed for special case of dropping element which then changes) */
-  if (IS_PLAYER(x, y) && !PLAYER_PROTECTED(x, y) &&
+  if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y) &&
       IS_ACCESSIBLE(Feld[x][y]) && !IS_ACCESSIBLE(target_element))
   {
     Bang(x, y);
@@ -6224,7 +6330,7 @@ static void ChangeElementNowExt(int x, int y, int target_element)
   ResetGfxAnimation(x, y);
   ResetRandomAnimationValue(x, y);
 
-  if (element_info[Feld[x][y]].move_direction_initial == MV_PREVIOUS)
+  if (element_info[Feld[x][y]].move_direction_initial == MV_START_PREVIOUS)
     MovDir[x][y] = previous_move_direction;
 
   InitField(x, y, FALSE);
@@ -7991,6 +8097,7 @@ void ScrollPlayer(struct PlayerInfo *player, int mode)
 #if 0
     DrawPlayer(player);
 #endif
+
     return;
   }
   else if (!FrameReached(&player->actual_frame_counter, 1))
@@ -7999,7 +8106,8 @@ void ScrollPlayer(struct PlayerInfo *player, int mode)
   player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
   player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
 
-  if (Feld[last_jx][last_jy] == EL_PLAYER_IS_LEAVING)
+  if (!player->block_last_field &&
+      Feld[last_jx][last_jy] == EL_PLAYER_IS_LEAVING)
     Feld[last_jx][last_jy] = EL_EMPTY;
 
   /* before DrawPlayer() to draw correct player graphic for this case */
@@ -8037,6 +8145,10 @@ void ScrollPlayer(struct PlayerInfo *player, int mode)
     }
 #endif
 
+    if (player->block_last_field &&
+       Feld[last_jx][last_jy] == EL_PLAYER_IS_LEAVING)
+      Feld[last_jx][last_jy] = EL_EMPTY;
+
     player->last_jx = jx;
     player->last_jy = jy;
 
@@ -8308,15 +8420,7 @@ void TestIfElementHitsCustomElement(int x, int y, int direction)
 
   if (IN_LEV_FIELD(hitx, hity))
   {
-    static int opposite_directions[] =
-    {
-      MV_RIGHT,
-      MV_LEFT,
-      MV_DOWN,
-      MV_UP
-    };
-    int move_dir_bit = MV_DIR_BIT(direction);
-    int opposite_direction = opposite_directions[move_dir_bit];
+    int opposite_direction = MV_DIR_OPPOSITE(direction);
     int hitting_side = direction;
     int touched_side = opposite_direction;
     int touched_element = MovingOrBlocked2Element(hitx, hity);
@@ -8445,7 +8549,7 @@ void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
 
       if (player->shield_deadly_time_left > 0)
        Bang(kill_x, kill_y);
-      else if (!PLAYER_PROTECTED(good_x, good_y))
+      else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
        KillHero(player);
     }
     else
@@ -8537,7 +8641,7 @@ void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
 
       if (player->shield_deadly_time_left > 0)
        Bang(bad_x, bad_y);
-      else if (!PLAYER_PROTECTED(kill_x, kill_y))
+      else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
        KillHero(player);
     }
     else
@@ -8627,9 +8731,15 @@ void KillHero(struct PlayerInfo *player)
   BuryHero(player);
 }
 
-static void KillHeroUnlessProtected(int x, int y)
+static void KillHeroUnlessEnemyProtected(int x, int y)
 {
-  if (!PLAYER_PROTECTED(x, y))
+  if (!PLAYER_ENEMY_PROTECTED(x, y))
+    KillHero(PLAYERINFO(x, y));
+}
+
+static void KillHeroUnlessExplosionProtected(int x, int y)
+{
+  if (!PLAYER_EXPLOSION_PROTECTED(x, y))
     KillHero(PLAYERINFO(x, y));
 }
 
@@ -9498,6 +9608,11 @@ boolean DropElement(struct PlayerInfo *player)
 
     PlayLevelSoundAction(jx, jy, ACTION_DROPPING);
 
+#if 1
+    /* needed if previous element just changed to "empty" in the last frame */
+    Changed[jx][jy] = 0;               /* allow another change */
+#endif
+
     CheckTriggeredElementChange(jx, jy, new_element, CE_OTHER_GETS_DROPPED);
     CheckElementChange(jx, jy, new_element, CE_DROPPED_BY_PLAYER);
 
@@ -9535,7 +9650,7 @@ boolean DropElement(struct PlayerInfo *player)
     int move_stepsize = element_info[new_element].move_stepsize;
     int direction, dx, dy, nextx, nexty;
 
-    if (element_info[new_element].move_direction_initial == MV_AUTOMATIC)
+    if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
       MovDir[jx][jy] = player->MovDir;
 
     direction = MovDir[jx][jy];
@@ -9555,7 +9670,7 @@ boolean DropElement(struct PlayerInfo *player)
     }
     else
     {
-      Changed[jx][jy] = 0;            /* allow another change */
+      Changed[jx][jy] = 0;             /* allow another change */
 
 #if 1
       TestIfElementHitsCustomElement(jx, jy, direction);