rnd-20030924-B-src
[rocksndiamonds.git] / src / game.c
index f554e6593e1c7fce11e3201751302d7d07101a6b..71aab9a62cb17f5557c41e8933796f4acc6bd28b 100644 (file)
@@ -169,7 +169,9 @@ static void KillHeroUnlessProtected(int, int);
 static void TestIfPlayerTouchesCustomElement(int, int);
 static void TestIfElementTouchesCustomElement(int, int);
 
+static boolean CheckTriggeredElementSideChange(int, int, int, int, int);
 static boolean CheckTriggeredElementChange(int, int, int, int);
+static boolean CheckElementSideChange(int, int, int, int, int, int);
 static boolean CheckElementChange(int, int, int, int);
 
 static void PlaySoundLevel(int, int, int);
@@ -241,6 +243,14 @@ static struct ChangingElementInfo change_delay_list[] =
     NULL,
     NULL
   },
+  {
+    EL_EXIT_CLOSING,
+    EL_EXIT_CLOSED,
+    29,
+    NULL,
+    NULL,
+    NULL
+  },
   {
     EL_SWITCHGATE_OPENING,
     EL_SWITCHGATE_OPEN,
@@ -648,10 +658,13 @@ static void InitField(int x, int y, boolean init_game)
       MovDir[x][y] = 1 << RND(4);
       break;
 
+#if 0
     case EL_SP_EMPTY:
       Feld[x][y] = EL_EMPTY;
       break;
+#endif
 
+#if 0
     case EL_EM_KEY_1_FILE:
       Feld[x][y] = EL_EM_KEY_1;
       break;
@@ -664,6 +677,7 @@ static void InitField(int x, int y, boolean init_game)
     case EL_EM_KEY_4_FILE:
       Feld[x][y] = EL_EM_KEY_4;
       break;
+#endif
 
     case EL_CONVEYOR_BELT_1_SWITCH_LEFT:
     case EL_CONVEYOR_BELT_1_SWITCH_MIDDLE:
@@ -792,7 +806,7 @@ static void InitGameEngine()
     ei->change_events = CE_BITMASK_DEFAULT;
     for (j=0; j < NUM_CHANGE_EVENTS; j++)
     {
-      ei->event_page_num[j] = 0;
+      ei->event_page_nr[j] = 0;
       ei->event_page[j] = &ei->change_page[0];
     }
   }
@@ -831,7 +845,7 @@ static void InitGameEngine()
            !(ei->change_events & CH_EVENT_BIT(k)))
        {
          ei->change_events |= CH_EVENT_BIT(k);
-         ei->event_page_num[k] = j;
+         ei->event_page_nr[k] = j;
          ei->event_page[k] = &ei->change_page[j];
        }
       }
@@ -1014,6 +1028,8 @@ void InitGame()
     player->is_digging = FALSE;
     player->is_collecting = FALSE;
 
+    player->show_envelope = 0;
+
     player->move_delay       = game.initial_move_delay;
     player->move_delay_value = game.initial_move_delay_value;
 
@@ -1090,7 +1106,10 @@ void InitGame()
       JustStopped[x][y] = 0;
       Stop[x][y] = FALSE;
       Pushed[x][y] = FALSE;
-      Changed[x][y] = FALSE;
+
+      Changed[x][y] = CE_BITMASK_DEFAULT;
+      ChangeEvent[x][y] = CE_BITMASK_DEFAULT;
+
       ExplodePhase[x][y] = 0;
       ExplodeField[x][y] = EX_NO_EXPLOSION;
 
@@ -1646,6 +1665,14 @@ void GameWon()
       StopSound(SND_GAME_LEVELTIME_BONUS);
   }
 
+  /* close exit door after last player */
+  if (Feld[ExitX][ExitY] == EL_EXIT_OPEN && AllPlayersGone)
+  {
+    Feld[ExitX][ExitY] = EL_EXIT_CLOSING;
+
+    PlaySoundLevelElementAction(ExitX, ExitY, EL_EXIT_OPEN, ACTION_CLOSING);
+  }
+
   /* Hero disappears */
   DrawLevelField(ExitX, ExitY);
   BackToFront();
@@ -2285,6 +2312,24 @@ void Explode(int ex, int ey, int phase, int mode)
 
   ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
 
+#ifdef DEBUG
+
+  /* activate this even in non-DEBUG version until cause for crash in
+     getGraphicAnimationFrame() (see below) is found and eliminated */
+#endif
+#if 1
+
+  if (GfxElement[x][y] == EL_UNDEFINED)
+  {
+    printf("\n\n\n");
+    printf("Explode(): x = %d, y = %d: GfxElement == EL_UNDEFINED\n", x, y);
+    printf("Explode(): This should never happen!\n");
+    printf("\n\n\n");
+
+    GfxElement[x][y] = EL_EMPTY;
+  }
+#endif
+
   if (phase == first_phase_after_start)
   {
     int element = Store2[x][y];
@@ -2330,6 +2375,8 @@ void Explode(int ex, int ey, int phase, int mode)
       InitMovDir(x, y);
     DrawLevelField(x, y);
 
+    TestIfElementTouchesCustomElement(x, y);
+
     if (CAN_BE_CRUMBLED(element))
       DrawLevelFieldCrumbledSandNeighbours(x, y);
 
@@ -4375,6 +4422,14 @@ void ContinueMoving(int x, int y)
 
     /* copy element change control values to new field */
     ChangeDelay[newx][newy] = ChangeDelay[x][y];
+#if 1
+    Changed[newx][newy] = Changed[x][y];
+    ChangeEvent[newx][newy] = ChangeEvent[x][y];
+
+    ChangeDelay[x][y] = 0;
+    Changed[x][y] = CE_BITMASK_DEFAULT;
+    ChangeEvent[x][y] = CE_BITMASK_DEFAULT;
+#endif
 
     /* copy animation control values to new field */
     GfxFrame[newx][newy]  = GfxFrame[x][y];
@@ -4432,6 +4487,10 @@ void ContinueMoving(int x, int y)
     if (!IN_LEV_FIELD(nextx, nexty) || !IS_FREE(nextx, nexty))
       CheckElementChange(newx, newy, element, CE_COLLISION);
 
+#if 1
+    TestIfElementTouchesCustomElement(x, y);           /* for empty space */
+#endif
+
     TestIfPlayerTouchesCustomElement(newx, newy);
     TestIfElementTouchesCustomElement(newx, newy);
   }
@@ -4973,6 +5032,9 @@ void CheckExit(int x, int y)
     return;
   }
 
+  if (AllPlayersGone)  /* do not re-open exit door closed after last player */
+    return;
+
   Feld[x][y] = EL_EXIT_OPENING;
 
   PlaySoundLevelNearest(x, y, SND_CLASS_EXIT_OPENING);
@@ -5342,7 +5404,7 @@ static void ChangeElementNowExt(int x, int y, int target_element)
   RemoveField(x, y);
   Feld[x][y] = target_element;
 
-  Changed[x][y] = TRUE;                /* no more changes in this frame */
+  Changed[x][y] |= ChangeEvent[x][y];  /* ignore same changes in this frame */
 
   ResetGfxAnimation(x, y);
   ResetRandomAnimationValue(x, y);
@@ -5368,10 +5430,20 @@ static boolean ChangeElementNow(int x, int y, int element, int page)
 {
   struct ElementChangeInfo *change = &element_info[element].change_page[page];
 
-  if (Changed[x][y])           /* do not change already changed elements */
+  /* always use default change event to prevent running into a loop */
+  if (ChangeEvent[x][y] == CE_BITMASK_DEFAULT)
+    ChangeEvent[x][y] = CH_EVENT_BIT(CE_DELAY);
+
+  /* do not change already changed elements with same change event */
+#if 0
+  if (Changed[x][y] & ChangeEvent[x][y])
     return FALSE;
+#else
+  if (Changed[x][y])
+    return FALSE;
+#endif
 
-  Changed[x][y] = TRUE;                /* no more changes in this frame */
+  Changed[x][y] |= ChangeEvent[x][y];  /* ignore same changes in this frame */
 
   CheckTriggeredElementChange(x, y, Feld[x][y], CE_OTHER_IS_CHANGING);
 
@@ -5479,6 +5551,16 @@ static void ChangeElement(int x, int y, int page)
   int element = MovingOrBlocked2Element(x, y);
   struct ElementChangeInfo *change = &element_info[element].change_page[page];
 
+#ifdef DEBUG
+  if (!CAN_CHANGE(element))
+  {
+    printf("\n\n\n");
+    printf("ChangeElement(): element = %d\n", element);
+    printf("Explode(): This should never happen!\n");
+    printf("\n\n\n");
+  }
+#endif
+
   if (ChangeDelay[x][y] == 0)          /* initialize element change */
   {
     ChangeDelay[x][y] = (    change->delay_fixed  * change->delay_frames +
@@ -5520,25 +5602,20 @@ static void ChangeElement(int x, int y, int page)
   }
 }
 
-static boolean CheckTriggeredElementChange(int lx, int ly, int trigger_element,
-                                          int trigger_event)
+static boolean CheckTriggeredElementSideChange(int lx, int ly,
+                                              int trigger_element,
+                                              int trigger_side,
+                                              int trigger_event)
 {
   int i, j, x, y;
 
   if (!(trigger_events[trigger_element] & CH_EVENT_BIT(trigger_event)))
     return FALSE;
 
-#if 0
-  /* prevent this function from running into a loop */
-  if (trigger_event == CE_OTHER_IS_CHANGING)
-    Changed[lx][ly] = TRUE;
-#endif
-
   for (i=0; i < NUM_CUSTOM_ELEMENTS; i++)
   {
     int element = EL_CUSTOM_START + i;
 
-#if 1
     boolean change_element = FALSE;
     int page;
 
@@ -5548,8 +5625,10 @@ static boolean CheckTriggeredElementChange(int lx, int ly, int trigger_element,
 
     for (j=0; j < element_info[element].num_change_pages; j++)
     {
-      if (element_info[element].change_page[j].trigger_element ==
-         trigger_element)
+      struct ElementChangeInfo *change = &element_info[element].change_page[j];
+
+      if (change->sides & trigger_side &&
+         change->trigger_element == trigger_element)
       {
        change_element = TRUE;
        page = j;
@@ -5561,54 +5640,52 @@ static boolean CheckTriggeredElementChange(int lx, int ly, int trigger_element,
     if (!change_element)
       continue;
 
-#else
-    if (!CAN_CHANGE(element) ||
-       !HAS_ANY_CHANGE_EVENT(element, trigger_event) ||
-       element_info[element].change->trigger_element != trigger_element)
-      continue;
-#endif
-
     for (y=0; y<lev_fieldy; y++) for (x=0; x<lev_fieldx; x++)
     {
-      if (x == lx && y == ly)  /* do not change trigger element itself */
-       continue;
-
 #if 0
-      if (Changed[x][y])       /* do not change already changed elements */
+      if (x == lx && y == ly)  /* do not change trigger element itself */
        continue;
 #endif
 
       if (Feld[x][y] == element)
       {
        ChangeDelay[x][y] = 1;
+       ChangeEvent[x][y] = CH_EVENT_BIT(trigger_event);
        ChangeElement(x, y, page);
-
-#if 0
-       Changed[x][y] = TRUE;   /* prevent element from being changed again */
-#endif
       }
     }
   }
 
-#if 0
-  /* reset change prevention array */
-  for (y=0; y<lev_fieldy; y++) for (x=0; x<lev_fieldx; x++)
-    Changed[x][y] = FALSE;
-#endif
-
   return TRUE;
 }
 
-static boolean CheckElementChangeExt(int x, int y, int element,
-                                    int trigger_event, int page)
+static boolean CheckTriggeredElementChange(int lx, int ly, int trigger_element,
+                                          int trigger_event)
+{
+  return CheckTriggeredElementSideChange(lx, ly, trigger_element, CH_SIDE_ANY,
+                                        trigger_event);
+}
+
+static boolean CheckElementSideChange(int x, int y, int element, int side,
+                                     int trigger_event, int page)
 {
   if (!CAN_CHANGE(element) || !HAS_ANY_CHANGE_EVENT(element, trigger_event))
     return FALSE;
 
   if (Feld[x][y] == EL_BLOCKED)
+  {
     Blocked2Moving(x, y, &x, &y);
+    element = Feld[x][y];
+  }
+
+  if (page < 0)
+    page = element_info[element].event_page_nr[trigger_event];
+
+  if (!(element_info[element].change_page[page].sides & side))
+    return FALSE;
 
   ChangeDelay[x][y] = 1;
+  ChangeEvent[x][y] = CH_EVENT_BIT(trigger_event);
   ChangeElement(x, y, page);
 
   return TRUE;
@@ -5616,9 +5693,7 @@ static boolean CheckElementChangeExt(int x, int y, int element,
 
 static boolean CheckElementChange(int x, int y, int element, int trigger_event)
 {
-  int page = element_info[element].event_page_num[trigger_event];
-
-  return CheckElementChangeExt(x, y, element, trigger_event, page);
+  return CheckElementSideChange(x, y, element, CH_SIDE_ANY, trigger_event, -1);
 }
 
 static void PlayerActions(struct PlayerInfo *player, byte player_action)
@@ -5808,7 +5883,8 @@ void GameActions()
 
   for (y=0; y<lev_fieldy; y++) for (x=0; x<lev_fieldx; x++)
   {
-    Changed[x][y] = FALSE;
+    Changed[x][y] = CE_BITMASK_DEFAULT;
+    ChangeEvent[x][y] = CE_BITMASK_DEFAULT;
 
     Stop[x][y] = FALSE;
     if (JustStopped[x][y] > 0)
@@ -5886,7 +5962,7 @@ void GameActions()
     /* this may take place after moving, so 'element' may have changed */
     if (IS_CHANGING(x, y))
     {
-      ChangeElement(x, y, element_info[element].event_page_num[CE_DELAY]);
+      ChangeElement(x, y, element_info[element].event_page_nr[CE_DELAY]);
       element = Feld[x][y];
       graphic = el_act_dir2img(element, GfxAction[x][y], MovDir[x][y]);
     }
@@ -6222,6 +6298,15 @@ void GameActions()
       stored_player[i].StepFrame += move_frames;
   }
 #endif
+
+#if 1
+  if (local_player->show_envelope != 0 && local_player->MovPos == 0)
+  {
+    ShowEnvelope(local_player->show_envelope - EL_ENVELOPE_1);
+
+    local_player->show_envelope = 0;
+  }
+#endif
 }
 
 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
@@ -6332,8 +6417,22 @@ static void CheckGravityMovement(struct PlayerInfo *player)
 boolean MoveFigureOneStep(struct PlayerInfo *player,
                          int dx, int dy, int real_dx, int real_dy)
 {
+  static int change_sides[4][2] =
+  {
+    /* enter side        leave side */
+    { CH_SIDE_RIGHT,   CH_SIDE_LEFT    },      /* moving left  */
+    { CH_SIDE_LEFT,    CH_SIDE_RIGHT   },      /* moving right */
+    { CH_SIDE_BOTTOM,  CH_SIDE_TOP     },      /* moving up    */
+    { CH_SIDE_TOP,     CH_SIDE_BOTTOM  }       /* moving down  */
+  };
+  int move_direction = (dx == -1 ? MV_LEFT :
+                       dx == +1 ? MV_RIGHT :
+                       dy == -1 ? MV_UP :
+                       dy == +1 ? MV_DOWN : MV_NO_MOVING);
+  int enter_side = change_sides[MV_DIR_BIT(move_direction)][0];
+  int leave_side = change_sides[MV_DIR_BIT(move_direction)][1];
   int jx = player->jx, jy = player->jy;
-  int new_jx = jx+dx, new_jy = jy+dy;
+  int new_jx = jx + dx, new_jy = jy + dy;
   int element;
   int can_move;
 
@@ -6385,15 +6484,33 @@ boolean MoveFigureOneStep(struct PlayerInfo *player,
   StorePlayer[jx][jy] = 0;
   player->last_jx = jx;
   player->last_jy = jy;
-  jx = player->jx = new_jx;
-  jy = player->jy = new_jy;
-  StorePlayer[jx][jy] = player->element_nr;
+  player->jx = new_jx;
+  player->jy = new_jy;
+  StorePlayer[new_jx][new_jy] = player->element_nr;
 
   player->MovPos =
     (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
 
   ScrollFigure(player, SCROLL_INIT);
 
+#if 1
+  if (IS_CUSTOM_ELEMENT(Feld[jx][jy]))
+  {
+    CheckTriggeredElementSideChange(jx, jy, Feld[jx][jy], leave_side,
+                                   CE_OTHER_GETS_LEFT);
+    CheckElementSideChange(jx, jy, Feld[jx][jy], leave_side,
+                          CE_LEFT_BY_PLAYER, -1);
+  }
+
+  if (IS_CUSTOM_ELEMENT(Feld[new_jx][new_jy]))
+  {
+    CheckTriggeredElementSideChange(new_jx, new_jy, Feld[new_jx][new_jy],
+                                   enter_side, CE_OTHER_GETS_ENTERED);
+    CheckElementSideChange(new_jx, new_jy, Feld[new_jx][new_jy], enter_side,
+                          CE_ENTERED_BY_PLAYER, -1);
+  }
+#endif
+
   return MF_MOVING;
 }
 
@@ -6613,7 +6730,7 @@ void ScrollFigure(struct PlayerInfo *player, int mode)
   DrawPlayer(player);  /* needed here only to cleanup last field */
 #endif
 
-  if (player->MovPos == 0)
+  if (player->MovPos == 0)     /* player reached destination field */
   {
     if (IS_PASSABLE(Feld[last_jx][last_jy]))
     {
@@ -6732,6 +6849,14 @@ void TestIfElementTouchesCustomElement(int x, int y)
     { +1, 0 },
     { 0, +1 }
   };
+  static int change_sides[4][2] =
+  {
+    /* center side       border side */
+    { CH_SIDE_TOP,     CH_SIDE_BOTTOM  },      /* check top    */
+    { CH_SIDE_LEFT,    CH_SIDE_RIGHT   },      /* check left   */
+    { CH_SIDE_RIGHT,   CH_SIDE_LEFT    },      /* check right  */
+    { CH_SIDE_BOTTOM,  CH_SIDE_TOP     }       /* check bottom */
+  };
   boolean change_center_element = FALSE;
   int center_element_change_page = 0;
   int center_element = Feld[x][y];
@@ -6748,6 +6873,8 @@ void TestIfElementTouchesCustomElement(int x, int y)
   {
     int xx = x + xy[i][0];
     int yy = y + xy[i][1];
+    int center_side = change_sides[i][0];
+    int border_side = change_sides[i][1];
     int border_element;
 
     if (!IN_LEV_FIELD(xx, yy))
@@ -6766,6 +6893,7 @@ void TestIfElementTouchesCustomElement(int x, int y)
          &element_info[center_element].change_page[j];
 
        if (change->events & CH_EVENT_BIT(CE_OTHER_IS_TOUCHING) &&
+           change->sides & border_side &&
            change->trigger_element == border_element)
        {
          change_center_element = TRUE;
@@ -6786,9 +6914,11 @@ void TestIfElementTouchesCustomElement(int x, int y)
          &element_info[border_element].change_page[j];
 
        if (change->events & CH_EVENT_BIT(CE_OTHER_IS_TOUCHING) &&
+           change->sides & center_side &&
            change->trigger_element == center_element)
        {
-         CheckElementChangeExt(xx,yy, border_element,CE_OTHER_IS_TOUCHING, j);
+         CheckElementSideChange(xx, yy, border_element, CH_SIDE_ANY,
+                                CE_OTHER_IS_TOUCHING, j);
          break;
        }
       }
@@ -6796,8 +6926,8 @@ void TestIfElementTouchesCustomElement(int x, int y)
   }
 
   if (change_center_element)
-    CheckElementChangeExt(x, y, center_element, CE_OTHER_IS_TOUCHING,
-                         center_element_change_page);
+    CheckElementSideChange(x, y, center_element, CH_SIDE_ANY,
+                          CE_OTHER_IS_TOUCHING, center_element_change_page);
 
 #if 0
   check_changing = FALSE;
@@ -7133,6 +7263,16 @@ static boolean checkDiagonalPushing(struct PlayerInfo *player,
 int DigField(struct PlayerInfo *player,
             int x, int y, int real_dx, int real_dy, int mode)
 {
+#if 0
+  static int change_sides[4][2] =
+  {
+    /* enter side        leave side */
+    { CH_SIDE_RIGHT,   CH_SIDE_LEFT    },      /* moving left  */
+    { CH_SIDE_LEFT,    CH_SIDE_RIGHT   },      /* moving right */
+    { CH_SIDE_BOTTOM,  CH_SIDE_TOP     },      /* moving up    */
+    { CH_SIDE_TOP,     CH_SIDE_BOTTOM  }       /* moving down  */
+  };
+#endif
   boolean use_spring_bug = (game.engine_version < VERSION_IDENT(2,2,0));
   int jx = player->jx, jy = player->jy;
   int dx = x - jx, dy = y - jy;
@@ -7141,6 +7281,10 @@ int DigField(struct PlayerInfo *player,
                        dx == +1 ? MV_RIGHT :
                        dy == -1 ? MV_UP :
                        dy == +1 ? MV_DOWN : MV_NO_MOVING);
+#if 0
+  int enter_side = change_sides[MV_DIR_BIT(move_direction)][0];
+  int leave_side = change_sides[MV_DIR_BIT(move_direction)][1];
+#endif
   int element;
 
   if (player->MovPos == 0)
@@ -7491,7 +7635,7 @@ int DigField(struct PlayerInfo *player,
 
        CheckTriggeredElementChange(x, y, element, CE_OTHER_GETS_DIGGED);
 
-       TestIfElementTouchesCustomElement(x, y);
+       TestIfElementTouchesCustomElement(x, y);        /* for empty space */
 
        break;
       }
@@ -7553,9 +7697,13 @@ int DigField(struct PlayerInfo *player,
                             el2edimg(EL_KEY_1 + key_nr));
          redraw_mask |= REDRAW_DOOR_1;
        }
-       else if (element == EL_ENVELOPE)
+       else if (IS_ENVELOPE(element))
        {
-         ShowEnvelope();
+#if 1
+         player->show_envelope = element;
+#else
+         ShowEnvelope(element - EL_ENVELOPE_1);
+#endif
        }
        else if (IS_DROPPABLE(element)) /* can be collected and dropped */
        {
@@ -7584,7 +7732,7 @@ int DigField(struct PlayerInfo *player,
 
        CheckTriggeredElementChange(x, y, element, CE_OTHER_GETS_COLLECTED);
 
-       TestIfElementTouchesCustomElement(x, y);
+       TestIfElementTouchesCustomElement(x, y);        /* for empty space */
 
        break;
       }
@@ -7694,6 +7842,24 @@ int DigField(struct PlayerInfo *player,
   if (Feld[x][y] != element)           /* really digged/collected something */
     player->is_collecting = !player->is_digging;
 
+#if 0
+  if (IS_CUSTOM_ELEMENT(Feld[jx][jy]))
+  {
+    CheckTriggeredElementSideChange(jx, jy, Feld[jx][jy], leave_side,
+                                   CE_OTHER_GETS_LEFT);
+    CheckElementSideChange(jx, jy, Feld[jx][jy], leave_side,
+                          CE_LEFT_BY_PLAYER, -1);
+  }
+
+  if (IS_CUSTOM_ELEMENT(Feld[x][y]))
+  {
+    CheckTriggeredElementSideChange(x, y, Feld[x][y], enter_side,
+                                   CE_OTHER_GETS_ENTERED);
+    CheckElementSideChange(x, y, Feld[x][y], enter_side,
+                          CE_ENTERED_BY_PLAYER, -1);
+  }
+#endif
+
   return MF_MOVING;
 }