added setup options to skip (un)covering and hatching for BD engine
[rocksndiamonds.git] / src / game.c
index e6e78cbf92b0c713ed52641422fb08e6c7c1254d..47f3611731f7bf4bff3741f45681d2b1fb37b73d 100644 (file)
@@ -2262,6 +2262,8 @@ static void UpdateGameControlValues(void)
   int i, k;
   int time = (game.LevelSolved ?
              game.LevelSolved_CountingTime :
+             level.game_engine_type == GAME_ENGINE_TYPE_BD ?
+             game_bd.time_played :
              level.game_engine_type == GAME_ENGINE_TYPE_EM ?
              game_em.lev->time :
              level.game_engine_type == GAME_ENGINE_TYPE_SP ?
@@ -2271,6 +2273,8 @@ static void UpdateGameControlValues(void)
              game.no_level_time_limit ? TimePlayed : TimeLeft);
   int score = (game.LevelSolved ?
               game.LevelSolved_CountingScore :
+              level.game_engine_type == GAME_ENGINE_TYPE_BD ?
+              game_bd.score :
               level.game_engine_type == GAME_ENGINE_TYPE_EM ?
               game_em.lev->score :
               level.game_engine_type == GAME_ENGINE_TYPE_SP ?
@@ -2278,7 +2282,9 @@ static void UpdateGameControlValues(void)
               level.game_engine_type == GAME_ENGINE_TYPE_MM ?
               game_mm.score :
               game.score);
-  int gems = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
+  int gems = (level.game_engine_type == GAME_ENGINE_TYPE_BD ?
+             game_bd.gems_still_needed :
+             level.game_engine_type == GAME_ENGINE_TYPE_EM ?
              game_em.lev->gems_needed :
              level.game_engine_type == GAME_ENGINE_TYPE_SP ?
              game_sp.infotrons_still_needed :
@@ -2286,9 +2292,15 @@ static void UpdateGameControlValues(void)
              game_mm.kettles_still_needed :
              game.gems_still_needed);
   int gems_total = level.gems_needed;
-  int gems_collected = gems_total - gems;
-  int gems_score = level.score[SC_EMERALD];
-  int exit_closed = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
+  int gems_collected = (level.game_engine_type == GAME_ENGINE_TYPE_BD ?
+                       game_bd.game->cave->diamonds_collected :
+                       gems_total - gems);
+  int gems_score = (level.game_engine_type == GAME_ENGINE_TYPE_BD ?
+                   game_bd.game->cave->diamond_value :
+                   level.score[SC_EMERALD]);
+  int exit_closed = (level.game_engine_type == GAME_ENGINE_TYPE_BD ?
+                    game_bd.gems_still_needed > 0 :
+                    level.game_engine_type == GAME_ENGINE_TYPE_EM ?
                     game_em.lev->gems_needed > 0 :
                     level.game_engine_type == GAME_ENGINE_TYPE_SP ?
                     game_sp.infotrons_still_needed > 0 :
@@ -4491,7 +4503,11 @@ void InitGame(void)
     scroll_y = game.forced_scroll_y;
 
   // !!! FIX THIS (START) !!!
-  if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+  if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
+  {
+    InitGameEngine_BD();
+  }
+  else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
   {
     InitGameEngine_EM();
   }
@@ -4818,14 +4834,14 @@ void InitAmoebaNr(int x, int y)
 
 static void LevelSolved_SetFinalGameValues(void)
 {
-  game.time_final = (game.no_level_time_limit ? TimePlayed : TimeLeft);
+  game.time_final = (level.game_engine_type == GAME_ENGINE_TYPE_BD ? game_bd.time_played :
+                    game.no_level_time_limit ? TimePlayed : TimeLeft);
   game.score_time_final = (level.use_step_counter ? TimePlayed :
                           TimePlayed * FRAMES_PER_SECOND + TimeFrames);
 
-  game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
-                     game_em.lev->score :
-                     level.game_engine_type == GAME_ENGINE_TYPE_MM ?
-                     game_mm.score :
+  game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_BD ? game_bd.score :
+                     level.game_engine_type == GAME_ENGINE_TYPE_EM ? game_em.lev->score :
+                     level.game_engine_type == GAME_ENGINE_TYPE_MM ? game_mm.score :
                      game.score);
 
   game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
@@ -4939,7 +4955,13 @@ void GameWon(void)
 
       time_count_steps = MAX(1, ABS(time_final - time) / 100);
 
-      if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
+      if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
+      {
+       // keep previous values (final values already processed here)
+       time_final = time;
+       score_final = score;
+      }
+      else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
       {
        health_final = 0;
        score_final += health * time_score;
@@ -4950,7 +4972,8 @@ void GameWon(void)
     }
 
     // if not counting score after game, immediately update game panel values
-    if (level_editor_test_game || !setup.count_score_after_game)
+    if (level_editor_test_game || !setup.count_score_after_game ||
+       level.game_engine_type == GAME_ENGINE_TYPE_BD)
     {
       time = time_final;
       score = score_final;
@@ -11622,7 +11645,22 @@ static void SetTapeActionFromMouseAction(byte *tape_action,
 
 static void CheckLevelSolved(void)
 {
-  if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+  if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
+  {
+    if (game_bd.level_solved &&
+       !game_bd.game_over)                             // game won
+    {
+      LevelSolved();
+
+      game_bd.game_over = TRUE;
+
+      game.all_players_gone = TRUE;
+    }
+
+    if (game_bd.game_over)                             // game lost
+      game.all_players_gone = TRUE;
+  }
+  else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
   {
     if (game_em.level_solved &&
        !game_em.game_over)                             // game won
@@ -11669,6 +11707,27 @@ static void CheckLevelSolved(void)
   }
 }
 
+static void PlayTimeoutSound(int seconds_left)
+{
+  // will be played directly by BD engine (for classic bonus time sounds)
+  if (level.game_engine_type == GAME_ENGINE_TYPE_BD && checkBonusTime_BD())
+    return;
+
+  // try to use individual "running out of time" sound for each second left
+  int sound = SND_GAME_RUNNING_OUT_OF_TIME_0 - seconds_left;
+
+  // if special sound per second not defined, use default sound
+  if (getSoundInfoEntryFilename(sound) == NULL)
+    sound = SND_GAME_RUNNING_OUT_OF_TIME;
+
+  // if out of time, but player still alive, play special "timeout" sound, if defined
+  if (seconds_left == 0 && !checkGameFailed())
+    if (getSoundInfoEntryFilename(SND_GAME_TIMEOUT) != NULL)
+      sound = SND_GAME_TIMEOUT;
+
+  PlaySound(sound);
+}
+
 static void CheckLevelTime_StepCounter(void)
 {
   int i;
@@ -11680,7 +11739,7 @@ static void CheckLevelTime_StepCounter(void)
     TimeLeft--;
 
     if (TimeLeft <= 10 && game.time_limit && !game.LevelSolved)
-      PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
+      PlayTimeoutSound(TimeLeft);
 
     game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
 
@@ -11700,9 +11759,24 @@ static void CheckLevelTime_StepCounter(void)
 
 static void CheckLevelTime(void)
 {
+  int frames_per_second = FRAMES_PER_SECOND;
   int i;
 
-  if (TimeFrames >= FRAMES_PER_SECOND)
+  if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
+  {
+    // level time may be running slower in native BD engine
+    frames_per_second = getFramesPerSecond_BD();
+
+    // if native engine time changed, force main engine time change
+    if (getTimeLeft_BD() < TimeLeft)
+      TimeFrames = frames_per_second;
+
+    // if last second running, wait for native engine time to exactly reach zero
+    if (getTimeLeft_BD() == 1 && TimeLeft == 1)
+      TimeFrames = frames_per_second - 1;
+  }
+
+  if (TimeFrames >= frames_per_second)
   {
     TimeFrames = 0;
 
@@ -11728,7 +11802,7 @@ static void CheckLevelTime(void)
        TimeLeft--;
 
        if (TimeLeft <= 10 && game.time_limit)
-         PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
+         PlayTimeoutSound(TimeLeft);
 
        /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
           is reset from other values in UpdateGameDoorValues() -- FIX THIS */
@@ -11737,11 +11811,20 @@ static void CheckLevelTime(void)
 
        if (!TimeLeft && game.time_limit)
        {
-         if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+         if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
+         {
+           if (game_bd.game->cave->player_state == GD_PL_LIVING)
+             game_bd.game->cave->player_state = GD_PL_TIMEOUT;
+         }
+         else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+         {
            game_em.lev->killed_out_of_time = TRUE;
+         }
          else
+         {
            for (i = 0; i < MAX_PLAYERS; i++)
              KillPlayer(&stored_player[i]);
+         }
        }
       }
       else if (game.no_level_time_limit && !game.all_players_gone)
@@ -11772,10 +11855,20 @@ void AdvanceFrameAndPlayerCounters(int player_nr)
 {
   int i;
 
+  // handle game and tape time differently for native BD game engine
+
+  // tape time is running in native BD engine even if player is not hatched yet
+  if (!checkGameRunning())
+    return;
+
   // advance frame counters (global frame counter and tape time frame counter)
   FrameCounter++;
   TapeTimeFrames++;
 
+  // level time is running in native BD engine after player is being hatched
+  if (!checkGamePlaying())
+    return;
+
   // advance time frame counter (used to control available time to solve level)
   TimeFrames++;
 
@@ -12126,7 +12219,11 @@ static void GameActionsExt(void)
     game.snapshot.last_action[i] = stored_player[i].effective_action;
   }
 
-  if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+  if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
+  {
+    GameActions_BD_Main();
+  }
+  else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
   {
     GameActions_EM_Main();
   }
@@ -12193,6 +12290,17 @@ void GameActions(void)
   GameActions_CheckSaveEngineSnapshot();
 }
 
+void GameActions_BD_Main(void)
+{
+  byte effective_action[MAX_PLAYERS];
+  int i;
+
+  for (i = 0; i < MAX_PLAYERS; i++)
+    effective_action[i] = stored_player[i].effective_action;
+
+  GameActions_BD(effective_action);
+}
+
 void GameActions_EM_Main(void)
 {
   byte effective_action[MAX_PLAYERS];
@@ -15287,15 +15395,15 @@ void InitPlayLevelSound(void)
   loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
 }
 
-static void PlayLevelSound(int x, int y, int nr)
+static void PlayLevelSoundExt(int x, int y, int nr, boolean is_loop_sound)
 {
   int sx = SCREENX(x), sy = SCREENY(y);
   int volume, stereo_position;
   int max_distance = 8;
-  int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
+  int type = (is_loop_sound ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
 
-  if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
-      (!setup.sound_loops && IS_LOOP_SOUND(nr)))
+  if ((!setup.sound_simple && !is_loop_sound) ||
+      (!setup.sound_loops && is_loop_sound))
     return;
 
   if (!IN_LEV_FIELD(x, y) ||
@@ -15317,7 +15425,7 @@ static void PlayLevelSound(int x, int y, int nr)
                     (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
                     (SCR_FIELDX + 2 * max_distance));
 
-  if (IS_LOOP_SOUND(nr))
+  if (is_loop_sound)
   {
     /* This assures that quieter loop sounds do not overwrite louder ones,
        while restarting sound volume comparison with each new game frame. */
@@ -15332,6 +15440,11 @@ static void PlayLevelSound(int x, int y, int nr)
   PlaySoundExt(nr, volume, stereo_position, type);
 }
 
+static void PlayLevelSound(int x, int y, int nr)
+{
+  PlayLevelSoundExt(x, y, nr, IS_LOOP_SOUND(nr));
+}
+
 static void PlayLevelSoundNearest(int x, int y, int sound_action)
 {
   PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
@@ -15420,6 +15533,226 @@ static void PlayLevelMusic(void)
     PlayMusicLoop(music_nr);
 }
 
+static int getSoundAction_BD(int sample)
+{
+  switch (sample)
+  {
+    case GD_S_STONE:
+    case GD_S_NUT:
+    case GD_S_DIRT_BALL:
+    case GD_S_NITRO:
+    case GD_S_FALLING_WALL:
+      return ACTION_IMPACT;
+
+    case GD_S_NUT_CRACK:
+      return ACTION_BREAKING;
+
+    case GD_S_EXPANDING_WALL:
+    case GD_S_WALL_REAPPEAR:
+    case GD_S_SLIME:
+    case GD_S_LAVA:
+    case GD_S_ACID_SPREAD:
+      return ACTION_GROWING;
+
+    case GD_S_DIAMOND_COLLECT:
+    case GD_S_SKELETON_COLLECT:
+    case GD_S_PNEUMATIC_COLLECT:
+    case GD_S_BOMB_COLLECT:
+    case GD_S_CLOCK_COLLECT:
+    case GD_S_SWEET_COLLECT:
+    case GD_S_KEY_COLLECT:
+    case GD_S_DIAMOND_KEY_COLLECT:
+      return ACTION_COLLECTING;
+
+    case GD_S_BOMB_PLACE:
+    case GD_S_REPLICATOR:
+      return ACTION_DROPPING;
+
+    case GD_S_BLADDER_MOVE:
+      return ACTION_MOVING;
+
+    case GD_S_BLADDER_SPENDER:
+    case GD_S_BLADDER_CONVERT:
+    case GD_S_GRAVITY_CHANGE:
+      return ACTION_CHANGING;
+
+    case GD_S_BITER_EAT:
+      return ACTION_EATING;
+
+    case GD_S_DOOR_OPEN:
+    case GD_S_CRACK:
+      return ACTION_OPENING;
+
+    case GD_S_WALK_EARTH:
+      return ACTION_DIGGING;
+
+    case GD_S_WALK_EMPTY:
+      return ACTION_WALKING;
+
+    case GD_S_SWITCH_BITER:
+    case GD_S_SWITCH_CREATURES:
+    case GD_S_SWITCH_GRAVITY:
+    case GD_S_SWITCH_EXPANDING:
+    case GD_S_SWITCH_CONVEYOR:
+    case GD_S_SWITCH_REPLICATOR:
+    case GD_S_STIRRING:
+      return ACTION_ACTIVATING;
+
+    case GD_S_BOX_PUSH:
+      return ACTION_PUSHING;
+
+    case GD_S_TELEPORTER:
+      return ACTION_PASSING;
+
+    case GD_S_EXPLOSION:
+    case GD_S_BOMB_EXPLOSION:
+    case GD_S_GHOST_EXPLOSION:
+    case GD_S_VOODOO_EXPLOSION:
+    case GD_S_NITRO_EXPLOSION:
+      return ACTION_EXPLODING;
+
+    case GD_S_COVER:
+    case GD_S_AMOEBA:
+    case GD_S_AMOEBA_MAGIC:
+    case GD_S_MAGIC_WALL:
+    case GD_S_PNEUMATIC_HAMMER:
+    case GD_S_WATER:
+      return ACTION_ACTIVE;
+
+    case GD_S_DIAMOND_RANDOM:
+    case GD_S_DIAMOND_1:
+    case GD_S_DIAMOND_2:
+    case GD_S_DIAMOND_3:
+    case GD_S_DIAMOND_4:
+    case GD_S_DIAMOND_5:
+    case GD_S_DIAMOND_6:
+    case GD_S_DIAMOND_7:
+    case GD_S_DIAMOND_8:
+    case GD_S_TIMEOUT_0:
+    case GD_S_TIMEOUT_1:
+    case GD_S_TIMEOUT_2:
+    case GD_S_TIMEOUT_3:
+    case GD_S_TIMEOUT_4:
+    case GD_S_TIMEOUT_5:
+    case GD_S_TIMEOUT_6:
+    case GD_S_TIMEOUT_7:
+    case GD_S_TIMEOUT_8:
+    case GD_S_TIMEOUT_9:
+    case GD_S_TIMEOUT_10:
+    case GD_S_BONUS_LIFE:
+      // kludge to prevent playing as loop sound
+      return ACTION_OTHER;
+
+    case GD_S_FINISHED:
+      return ACTION_DEFAULT;
+
+    default:
+      return ACTION_DEFAULT;
+  }
+}
+
+static int getSoundEffect_BD(int element_bd, int sample)
+{
+  int sound_action = getSoundAction_BD(sample);
+  int sound_effect = element_info[SND_ELEMENT(element_bd)].sound[sound_action];
+  int nr;
+
+  // standard sounds
+  if (sound_action != ACTION_OTHER &&
+      sound_action != ACTION_DEFAULT)
+    return sound_effect;
+
+  // special sounds
+  switch (sample)
+  {
+    case GD_S_DIAMOND_RANDOM:
+      nr = GetSimpleRandom(8);
+      sound_effect = SND_BD_DIAMOND_IMPACT_RANDOM_1 + nr;
+      break;
+
+    case GD_S_DIAMOND_1:
+    case GD_S_DIAMOND_2:
+    case GD_S_DIAMOND_3:
+    case GD_S_DIAMOND_4:
+    case GD_S_DIAMOND_5:
+    case GD_S_DIAMOND_6:
+    case GD_S_DIAMOND_7:
+    case GD_S_DIAMOND_8:
+      nr = sample - GD_S_DIAMOND_1;
+      sound_effect = SND_BD_DIAMOND_IMPACT_RANDOM_1 + nr;
+      break;
+
+    case GD_S_TIMEOUT_0:
+    case GD_S_TIMEOUT_1:
+    case GD_S_TIMEOUT_2:
+    case GD_S_TIMEOUT_3:
+    case GD_S_TIMEOUT_4:
+    case GD_S_TIMEOUT_5:
+    case GD_S_TIMEOUT_6:
+    case GD_S_TIMEOUT_7:
+    case GD_S_TIMEOUT_8:
+    case GD_S_TIMEOUT_9:
+    case GD_S_TIMEOUT_10:
+      nr = sample - GD_S_TIMEOUT_0;
+      sound_effect = SND_GAME_RUNNING_OUT_OF_TIME_0 + nr;
+
+      if (getSoundInfoEntryFilename(sound_effect) == NULL && sample != GD_S_TIMEOUT_0)
+       sound_effect = SND_GAME_RUNNING_OUT_OF_TIME;
+      break;
+
+    case GD_S_FINISHED:
+      sound_effect = SND_GAME_LEVELTIME_BONUS;
+      break;
+
+    case GD_S_BONUS_LIFE:
+      sound_effect = SND_GAME_HEALTH_BONUS;
+      break;
+
+    default:
+      sound_effect = SND_UNDEFINED;
+      break;
+  }
+
+  return sound_effect;
+}
+
+void PlayLevelSound_BD(int xx, int yy, int element_bd, int sample)
+{
+  int element = (element_bd > -1 ? map_element_BD_to_RND(element_bd) : 0);
+  int sound_effect = getSoundEffect_BD(element, sample);
+  int sound_action = getSoundAction_BD(sample);
+  boolean is_loop_sound = IS_LOOP_SOUND(sound_effect);
+  int offset = 0;
+  int x = xx - offset;
+  int y = yy - offset;
+
+  if (sound_action == ACTION_OTHER)
+    is_loop_sound = FALSE;
+
+  if (sound_effect != SND_UNDEFINED)
+    PlayLevelSoundExt(x, y, sound_effect, is_loop_sound);
+}
+
+void StopSound_BD(int element_bd, int sample)
+{
+  int element = (element_bd > -1 ? map_element_BD_to_RND(element_bd) : 0);
+  int sound_effect = getSoundEffect_BD(element, sample);
+
+  if (sound_effect != SND_UNDEFINED)
+    StopSound(sound_effect);
+}
+
+boolean isSoundPlaying_BD(int element_bd, int sample)
+{
+  int element = (element_bd > -1 ? map_element_BD_to_RND(element_bd) : 0);
+  int sound_effect = getSoundEffect_BD(element, sample);
+
+  if (sound_effect != SND_UNDEFINED)
+    return isSoundPlaying(sound_effect);
+
+  return FALSE;
+}
+
 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
 {
   int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
@@ -15744,6 +16077,10 @@ void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
     }
     else
     {
+      // when using BD game engine, cover screen before fading out
+      if (!quick_quit && level.game_engine_type == GAME_ENGINE_TYPE_BD)
+       game_bd.cover_screen = TRUE;
+
       if (quick_quit)
        FadeSkipNextFadeIn();
 
@@ -15866,6 +16203,28 @@ boolean CheckRestartGame(void)
   return TRUE;
 }
 
+boolean checkGameRunning(void)
+{
+  if (game_status != GAME_MODE_PLAYING)
+    return FALSE;
+
+  if (level.game_engine_type == GAME_ENGINE_TYPE_BD && !checkGameRunning_BD())
+    return FALSE;
+
+  return TRUE;
+}
+
+boolean checkGamePlaying(void)
+{
+  if (game_status != GAME_MODE_PLAYING)
+    return FALSE;
+
+  if (level.game_engine_type == GAME_ENGINE_TYPE_BD && !checkGamePlaying_BD())
+    return FALSE;
+
+  return TRUE;
+}
+
 boolean checkGameSolved(void)
 {
   // set for all game engines if level was solved
@@ -15874,7 +16233,9 @@ boolean checkGameSolved(void)
 
 boolean checkGameFailed(void)
 {
-  if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+  if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
+    return (game_bd.game_over && !game_bd.level_solved);
+  else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
     return (game_em.game_over && !game_em.level_solved);
   else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
     return (game_sp.game_over && !game_sp.level_solved);