rnd-19991010-1-src
[rocksndiamonds.git] / src / game.c
index e3fc38ba736906daf1b8440a5405bf9e6ee105d7..b732e32c47c16cda02c32f1d03a3043d530d224f 100644 (file)
 /* forward declaration for internal use */
 static void CloseAllOpenTimegates(void);
 static void CheckGravityMovement(struct PlayerInfo *);
-static void KillHeroUnlessShield(struct PlayerInfo *);
+static void KillHeroUnlessProtected(int, int);
 
 static void MapGameButtons();
 static void HandleGameButtons(struct GadgetInfo *);
@@ -519,6 +519,7 @@ void InitGame()
   game.light_time_left = 0;
   game.timegate_time_left = 0;
   game.switchgate_pos = 0;
+  game.balloon_dir = MV_NO_MOVING;
 
   for (i=0; i<4; i++)
   {
@@ -894,7 +895,7 @@ void InitAmoebaNr(int x, int y)
 void GameWon()
 {
   int hi_pos;
-  int bumplevel = FALSE;
+  boolean raise_level = FALSE;
 
   if (local_player->MovPos)
     return;
@@ -965,17 +966,26 @@ void GameWon()
     SaveTape(tape.level_nr);           /* Ask to save tape */
   }
 
+  if (level_nr == leveldir_current->handicap_level)
+  {
+    leveldir_current->handicap_level++;
+    SaveLevelSetup_SeriesInfo();
+
+    if (level_nr < leveldir_current->last_level)
+      raise_level = TRUE;
+  }
+
   if ((hi_pos = NewHiScore()) >= 0) 
   {
     game_status = HALLOFFAME;
     DrawHallOfFame(hi_pos);
-    if (bumplevel && TAPE_IS_EMPTY(tape))
+    if (raise_level)
       level_nr++;
   }
   else
   {
     game_status = MAINMENU;
-    if (bumplevel && TAPE_IS_EMPTY(tape))
+    if (raise_level)
       level_nr++;
     DrawMainMenu();
   }
@@ -1280,7 +1290,7 @@ void Explode(int ex, int ey, int phase, int mode)
       if (element == EL_EXPLODING)
        element = Store2[x][y];
 
-      if (IS_PLAYER(ex, ey) && !SHIELD_ON(PLAYERINFO(ex, ey)))
+      if (IS_PLAYER(ex, ey) && !PLAYER_PROTECTED(ex, ey))
       {
        switch(StorePlayer[ex][ey])
        {
@@ -1328,6 +1338,10 @@ void Explode(int ex, int ey, int phase, int mode)
        Store[x][y] = EL_EDELSTEIN_ROT;
       else if (element == EL_ERZ_EDEL_LILA)
        Store[x][y] = EL_EDELSTEIN_LILA;
+      else if (element == EL_WALL_PEARL)
+       Store[x][y] = EL_PEARL;
+      else if (element == EL_WALL_CRYSTAL)
+       Store[x][y] = EL_CRYSTAL;
       else if (!IS_PFORTE(Store[x][y]))
        Store[x][y] = EL_LEERRAUM;
 
@@ -1381,7 +1395,7 @@ void Explode(int ex, int ey, int phase, int mode)
     int element = Store2[x][y];
 
     if (IS_PLAYER(x, y))
-      KillHeroUnlessShield(PLAYERINFO(x, y));
+      KillHeroUnlessProtected(x, y);
     else if (IS_EXPLOSIVE(element))
     {
       Feld[x][y] = Store2[x][y];
@@ -1764,11 +1778,17 @@ void Impact(int x, int y)
     Bang(x, y);
     return;
   }
+  else if (element == EL_PEARL)
+  {
+    Feld[x][y] = EL_PEARL_BREAKING;
+    PlaySoundLevel(x, y, SND_KNACK);
+    return;
+  }
 
   if (element == EL_TROPFEN && (lastline || object_hit))       /* acid drop */
   {
     if (object_hit && IS_PLAYER(x, y+1))
-      KillHeroUnlessShield(PLAYERINFO(x, y+1));
+      KillHeroUnlessProtected(x, y+1);
     else if (object_hit && smashed == EL_PINGUIN)
       Bang(x, y+1);
     else
@@ -1801,7 +1821,7 @@ void Impact(int x, int y)
 
     if (IS_PLAYER(x, y+1))
     {
-      KillHeroUnlessShield(PLAYERINFO(x, y+1));
+      KillHeroUnlessProtected(x, y+1);
       return;
     }
     else if (smashed == EL_PINGUIN)
@@ -1841,6 +1861,12 @@ void Impact(int x, int y)
          RaiseScoreElement(EL_KOKOSNUSS);
          return;
        }
+       else if (smashed == EL_PEARL)
+       {
+         Feld[x][y+1] = EL_PEARL_BREAKING;
+         PlaySoundLevel(x, y, SND_KNACK);
+         return;
+       }
        else if (smashed == EL_DIAMANT)
        {
          Feld[x][y+1] = EL_LEERRAUM;
@@ -2204,6 +2230,11 @@ void TurnRound(int x, int y)
     if (MovDir[x][y] != old_move_dir)
       MovDelay[x][y] = 9;
   }
+  else if (element == EL_BALLOON)
+  {
+    MovDir[x][y] = game.balloon_dir;
+    MovDelay[x][y] = 0;
+  }
   else if (element == EL_ROBOT || element == EL_SONDE || element == EL_PINGUIN)
   {
     int attr_x = -1, attr_y = -1;
@@ -2508,7 +2539,8 @@ void StartMoving(int x, int y)
   {
     int newx, newy;
 
-    if (element == EL_SONDE && JustBeingPushed(x, y))
+    if ((element == EL_SONDE || element == EL_BALLOON)
+       && JustBeingPushed(x, y))
       return;
 
     if (!MovDelay[x][y])       /* start new movement phase */
@@ -2610,7 +2642,7 @@ void StartMoving(int x, int y)
     Moving2Blocked(x, y, &newx, &newy);        /* get next screen position */
 
     if (IS_ENEMY(element) && IS_PLAYER(newx, newy) &&
-       !SHIELD_ON(PLAYERINFO(newx, newy)))
+       !PLAYER_PROTECTED(newx, newy))
     {
 
 #if 1
@@ -2624,8 +2656,8 @@ void StartMoving(int x, int y)
 #endif
 
     }
-    else if ((element == EL_PINGUIN ||
-             element == EL_ROBOT || element == EL_SONDE) &&
+    else if ((element == EL_PINGUIN || element == EL_ROBOT ||
+             element == EL_SONDE || element == EL_BALLOON) &&
             IN_LEV_FIELD(newx, newy) &&
             MovDir[x][y] == MV_DOWN && Feld[newx][newy] == EL_SALZSAEURE)
     {
@@ -2803,6 +2835,9 @@ void StartMoving(int x, int y)
       else if (element == EL_SP_ELECTRON)
        DrawGraphicAnimation(x, y, GFX2_SP_ELECTRON, 8, 2, ANIM_NORMAL);
 
+      if (DONT_TOUCH(element))
+       TestIfBadThingHitsHero(x, y);
+
       return;
     }
 
@@ -3484,7 +3519,8 @@ void NussKnacken(int x, int y)
   {
     MovDelay[x][y]--;
     if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
-      DrawGraphic(SCREENX(x), SCREENY(y), GFX_CRACKINGNUT+3-MovDelay[x][y]/2);
+      DrawGraphic(SCREENX(x), SCREENY(y),
+                 GFX_CRACKINGNUT + 3 - MovDelay[x][y]/2);
 
     if (!MovDelay[x][y])
     {
@@ -3494,6 +3530,26 @@ void NussKnacken(int x, int y)
   }
 }
 
+void BreakingPearl(int x, int y)
+{
+  if (!MovDelay[x][y])         /* next animation frame */
+    MovDelay[x][y] = 9;
+
+  if (MovDelay[x][y])          /* wait some time before next frame */
+  {
+    MovDelay[x][y]--;
+    if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
+      DrawGraphic(SCREENX(x), SCREENY(y),
+                 GFX_PEARL_BREAKING + 4 - MovDelay[x][y]/2);
+
+    if (!MovDelay[x][y])
+    {
+      Feld[x][y] = EL_LEERRAUM;
+      DrawLevelField(x, y);
+    }
+  }
+}
+
 void SiebAktivieren(int x, int y, int typ)
 {
   int graphic = (typ == 1 ? GFX_SIEB_VOLL : GFX_SIEB2_VOLL) + 3;
@@ -3900,10 +3956,10 @@ static void CheckBuggyBase(int x, int y)
 
   if (element == EL_SP_BUG)
   {
-    if (!MovDelay[x][y])       /* start activating buggy base */
+    if (!MovDelay[x][y])       /* wait some time before activating base */
       MovDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
 
-    if (MovDelay[x][y])                /* wait some time before activating base */
+    if (MovDelay[x][y])
     {
       MovDelay[x][y]--;
       if (MovDelay[x][y] < 5 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
@@ -3919,7 +3975,7 @@ static void CheckBuggyBase(int x, int y)
     if (!MovDelay[x][y])       /* start activating buggy base */
       MovDelay[x][y] = 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND);
 
-    if (MovDelay[x][y])                /* wait some time before activating base */
+    if (MovDelay[x][y])
     {
       MovDelay[x][y]--;
       if (MovDelay[x][y])
@@ -3956,6 +4012,61 @@ static void CheckBuggyBase(int x, int y)
   }
 }
 
+static void CheckTrap(int x, int y)
+{
+  int element = Feld[x][y];
+
+  if (element == EL_TRAP_INACTIVE)
+  {
+    if (!MovDelay[x][y])       /* wait some time before activating trap */
+      MovDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
+
+    if (MovDelay[x][y])
+    {
+      MovDelay[x][y]--;
+      if (MovDelay[x][y])
+       return;
+
+      Feld[x][y] = EL_TRAP_ACTIVE;
+    }
+  }
+  else if (element == EL_TRAP_ACTIVE)
+  {
+    int delay = 4;
+    int num_frames = 8;
+
+    if (!MovDelay[x][y])       /* start activating trap */
+      MovDelay[x][y] = num_frames * delay;
+
+    if (MovDelay[x][y])
+    {
+      MovDelay[x][y]--;
+
+      if (MovDelay[x][y])
+      {
+       if (!(MovDelay[x][y] % delay))
+       {
+         int phase = MovDelay[x][y]/delay;
+
+         if (phase >= num_frames/2)
+           phase = num_frames - phase;
+
+         if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
+         {
+           DrawGraphic(SCREENX(x),SCREENY(y), GFX_TRAP_INACTIVE + phase - 1);
+           ErdreichAnbroeckeln(SCREENX(x), SCREENY(y));
+         }
+       }
+
+       return;
+      }
+
+      Feld[x][y] = EL_TRAP_INACTIVE;
+      DrawLevelField(x, y);
+    }
+  }
+}
+
 static void DrawBeltAnimation(int x, int y, int element)
 {
   int belt_nr = getBeltNrFromElement(element);
@@ -4050,7 +4161,8 @@ static void PlayerActions(struct PlayerInfo *player, byte player_action)
       if (IN_LEV_FIELD(jx+dx, jy) && IS_PUSHABLE(Feld[jx+dx][jy]))
       {
        int el = Feld[jx+dx][jy];
-       int push_delay = (IS_SB_ELEMENT(el) || el == EL_SONDE ? 2 : 10);
+       int push_delay = (IS_SB_ELEMENT(el) || el == EL_SONDE ? 2 :
+                         el == EL_BALLOON ? 0 : 10);
 
        if (tape.delay_played + push_delay >= tape.pos[tape.counter].delay)
        {
@@ -4240,6 +4352,8 @@ void GameActions()
       Blurb(x, y);
     else if (element == EL_CRACKINGNUT)
       NussKnacken(x, y);
+    else if (element == EL_PEARL_BREAKING)
+      BreakingPearl(x, y);
     else if (element == EL_AUSGANG_ZU)
       AusgangstuerPruefen(x, y);
     else if (element == EL_AUSGANG_ACT)
@@ -4257,6 +4371,8 @@ void GameActions()
       CheckForDragon(x, y);
     else if (element == EL_SP_BUG || element == EL_SP_BUG_ACTIVE)
       CheckBuggyBase(x, y);
+    else if (element == EL_TRAP_INACTIVE || element == EL_TRAP_ACTIVE)
+      CheckTrap(x, y);
     else if (element == EL_SP_TERMINAL)
       DrawGraphicAnimation(x, y, GFX2_SP_TERMINAL, 7, 12, ANIM_NORMAL);
     else if (element == EL_SP_TERMINAL_ACTIVE)
@@ -4390,12 +4506,12 @@ void GameActions()
     {
       TimeLeft--;
 
-      if (TimeLeft <= 10)
+      if (TimeLeft <= 10 && setup.time_limit)
        PlaySoundStereo(SND_GONG, PSND_MAX_RIGHT);
 
       DrawText(DX_TIME, DY_TIME, int2str(TimeLeft, 3), FS_SMALL, FC_YELLOW);
 
-      if (!TimeLeft)
+      if (!TimeLeft && setup.time_limit)
        for (i=0; i<MAX_PLAYERS; i++)
          KillHero(&stored_player[i]);
     }
@@ -4540,14 +4656,7 @@ boolean MoveFigureOneStep(struct PlayerInfo *player,
       BuryHero(player);
     }
     else
-    {
-#if 1
       TestIfBadThingHitsHero(new_jx, new_jy);
-#else
-      if (player->shield_time_left == 0)
-       KillHero(player);
-#endif
-    }
 
     return MF_MOVING;
   }
@@ -4866,7 +4975,7 @@ void TestIfGoodThingHitsBadThing(int goodx, int goody)
 
       if (player->shield_active_time_left > 0)
        Bang(killx, killy);
-      else if (player->shield_passive_time_left == 0)
+      else if (!PLAYER_PROTECTED(goodx, goody))
        KillHero(player);
     }
     else
@@ -4892,6 +5001,9 @@ void TestIfBadThingHitsGoodThing(int badx, int bady)
     MV_DOWN
   };
 
+  if (Feld[badx][bady] == EL_EXPLODING)        /* skip just exploding bad things */
+    return;
+
   for (i=0; i<4; i++)
   {
     int x, y, element;
@@ -4928,7 +5040,7 @@ void TestIfBadThingHitsGoodThing(int badx, int bady)
 
       if (player->shield_active_time_left > 0)
        Bang(badx, bady);
-      else if (player->shield_passive_time_left == 0)
+      else if (!PLAYER_PROTECTED(killx, killy))
        KillHero(player);
     }
     else
@@ -5008,10 +5120,10 @@ void KillHero(struct PlayerInfo *player)
   BuryHero(player);
 }
 
-static void KillHeroUnlessShield(struct PlayerInfo *player)
+static void KillHeroUnlessProtected(int x, int y)
 {
-  if (!SHIELD_ON(player))
-    KillHero(player);
+  if (!PLAYER_PROTECTED(x, y))
+    KillHero(PLAYERINFO(x, y));
 }
 
 void BuryHero(struct PlayerInfo *player)
@@ -5073,6 +5185,36 @@ int DigField(struct PlayerInfo *player,
   if (IS_MOVING(x, y) || IS_PLAYER(x, y))
     return MF_NO_ACTION;
 
+  if (IS_TUBE(Feld[jx][jy]))
+  {
+    int i = 0;
+    int tube_leave_directions[][2] =
+    {
+      { EL_TUBE_CROSS,         MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
+      { EL_TUBE_VERTICAL,                           MV_UP | MV_DOWN },
+      { EL_TUBE_HORIZONTAL,    MV_LEFT | MV_RIGHT                   },
+      { EL_TUBE_VERT_LEFT,     MV_LEFT |            MV_UP | MV_DOWN },
+      { EL_TUBE_VERT_RIGHT,              MV_RIGHT | MV_UP | MV_DOWN },
+      { EL_TUBE_HORIZ_UP,      MV_LEFT | MV_RIGHT | MV_UP           },
+      { EL_TUBE_HORIZ_DOWN,    MV_LEFT | MV_RIGHT |         MV_DOWN },
+      { EL_TUBE_LEFT_UP,       MV_LEFT |            MV_UP           },
+      { EL_TUBE_LEFT_DOWN,     MV_LEFT |                    MV_DOWN },
+      { EL_TUBE_RIGHT_UP,                MV_RIGHT | MV_UP           },
+      { EL_TUBE_RIGHT_DOWN,              MV_RIGHT |         MV_DOWN },
+      { -1,                     MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN }
+    };
+
+    while (tube_leave_directions[i][0] != Feld[jx][jy])
+    {
+      i++;
+      if (tube_leave_directions[i][0] == -1)   /* should not happen */
+       break;
+    }
+
+    if (!(tube_leave_directions[i][1] & move_direction))
+      return MF_NO_ACTION;     /* tube has no opening in this direction */
+  }
+
   element = Feld[x][y];
 
   switch (element)
@@ -5083,6 +5225,7 @@ int DigField(struct PlayerInfo *player,
 
     case EL_ERDREICH:
     case EL_SAND_INVISIBLE:
+    case EL_TRAP_INACTIVE:
       Feld[x][y] = EL_LEERRAUM;
       PlaySoundLevel(x, y, SND_SCHLURF);
       break;
@@ -5100,8 +5243,12 @@ int DigField(struct PlayerInfo *player,
     case EL_EDELSTEIN_LILA:
     case EL_DIAMANT:
     case EL_SP_INFOTRON:
+    case EL_PEARL:
+    case EL_CRYSTAL:
       RemoveField(x, y);
-      local_player->gems_still_needed -= (element == EL_DIAMANT ? 3 : 1);
+      local_player->gems_still_needed -= (element == EL_DIAMANT ? 3 :
+                                         element == EL_PEARL ? 5 :
+                                         element == EL_CRYSTAL ? 8 : 1);
       if (local_player->gems_still_needed < 0)
        local_player->gems_still_needed = 0;
       RaiseScoreElement(element);
@@ -5297,6 +5444,23 @@ int DigField(struct PlayerInfo *player,
       return MF_ACTION;
       break;
 
+    case EL_BALLOON_SEND_LEFT:
+    case EL_BALLOON_SEND_RIGHT:
+    case EL_BALLOON_SEND_UP:
+    case EL_BALLOON_SEND_DOWN:
+    case EL_BALLOON_SEND_ANY:
+      if (element == EL_BALLOON_SEND_ANY)
+       game.balloon_dir = move_direction;
+      else
+       game.balloon_dir = (element == EL_BALLOON_SEND_LEFT  ? MV_LEFT :
+                           element == EL_BALLOON_SEND_RIGHT ? MV_RIGHT :
+                           element == EL_BALLOON_SEND_UP    ? MV_UP :
+                           element == EL_BALLOON_SEND_DOWN  ? MV_DOWN :
+                           MV_NO_MOVING);
+
+      return MF_ACTION;
+      break;
+
     case EL_SP_EXIT:
       if (local_player->gems_still_needed > 0)
        return MF_NO_ACTION;
@@ -5344,7 +5508,7 @@ int DigField(struct PlayerInfo *player,
       else if (IS_SP_ELEMENT(element))
        PlaySoundLevel(x+dx, y+dy, SND_SP_ZONKPUSH);
       else
-       PlaySoundLevel(x+dx, y+dy, SND_KLOPF);
+       PlaySoundLevel(x+dx, y+dy, SND_PUSCH);  /* better than "SND_KLOPF" */
       break;
 
     case EL_PFORTE1:
@@ -5450,7 +5614,47 @@ int DigField(struct PlayerInfo *player,
       DOUBLE_PLAYER_SPEED(player);
 
       PlaySoundLevel(x, y, SND_GATE);
+      break;
 
+    case EL_TUBE_CROSS:
+    case EL_TUBE_VERTICAL:
+    case EL_TUBE_HORIZONTAL:
+    case EL_TUBE_VERT_LEFT:
+    case EL_TUBE_VERT_RIGHT:
+    case EL_TUBE_HORIZ_UP:
+    case EL_TUBE_HORIZ_DOWN:
+    case EL_TUBE_LEFT_UP:
+    case EL_TUBE_LEFT_DOWN:
+    case EL_TUBE_RIGHT_UP:
+    case EL_TUBE_RIGHT_DOWN:
+      {
+       int i = 0;
+       int tube_enter_directions[][2] =
+       {
+         { EL_TUBE_CROSS,      MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
+         { EL_TUBE_VERTICAL,                        MV_UP | MV_DOWN },
+         { EL_TUBE_HORIZONTAL, MV_LEFT | MV_RIGHT                   },
+         { EL_TUBE_VERT_LEFT,            MV_RIGHT | MV_UP | MV_DOWN },
+         { EL_TUBE_VERT_RIGHT, MV_LEFT            | MV_UP | MV_DOWN },
+         { EL_TUBE_HORIZ_UP,   MV_LEFT | MV_RIGHT |         MV_DOWN },
+         { EL_TUBE_HORIZ_DOWN, MV_LEFT | MV_RIGHT | MV_UP           },
+         { EL_TUBE_LEFT_UP,              MV_RIGHT |         MV_DOWN },
+         { EL_TUBE_LEFT_DOWN,            MV_RIGHT | MV_UP           },
+         { EL_TUBE_RIGHT_UP,   MV_LEFT |                    MV_DOWN },
+         { EL_TUBE_RIGHT_DOWN, MV_LEFT |            MV_UP           },
+         { -1,                 MV_NO_MOVING                         }
+       };
+
+       while (tube_enter_directions[i][0] != element)
+       {
+         i++;
+         if (tube_enter_directions[i][0] == -1)        /* should not happen */
+           break;
+       }
+
+       if (!(tube_enter_directions[i][1] & move_direction))
+         return MF_NO_ACTION;  /* tube has no opening in this direction */
+      }
       break;
 
     case EL_AUSGANG_ZU:
@@ -5491,6 +5695,8 @@ int DigField(struct PlayerInfo *player,
     case EL_SOKOBAN_OBJEKT:
     case EL_SONDE:
     case EL_SP_DISK_YELLOW:
+    case EL_BALLOON:
+    case EL_SPRING:
       if (mode == DF_SNAP)
        return MF_NO_ACTION;
 
@@ -5516,7 +5722,7 @@ int DigField(struct PlayerInfo *player,
       if (player->push_delay == 0)
        player->push_delay = FrameCounter;
       if (!FrameReached(&player->push_delay, player->push_delay_value) &&
-         !tape.playing)
+         !tape.playing && element != EL_BALLOON)
        return MF_NO_ACTION;
 
       if (IS_SB_ELEMENT(element))
@@ -5545,11 +5751,14 @@ int DigField(struct PlayerInfo *player,
        Feld[x+dx][y+dy] = element;
       }
 
-      player->push_delay_value = 2;
+      player->push_delay_value = (element == EL_BALLOON ? 0 : 2);
 
       DrawLevelField(x, y);
       DrawLevelField(x+dx, y+dy);
-      PlaySoundLevel(x+dx, y+dy, SND_PUSCH);
+      if (element == EL_BALLOON)
+       PlaySoundLevel(x+dx, y+dy, SND_SCHLURF);
+      else
+       PlaySoundLevel(x+dx, y+dy, SND_PUSCH);
 
       if (IS_SB_ELEMENT(element) &&
          local_player->sokobanfields_still_needed == 0 &&