added game engine support for playing native Boulder Dash levels
authorHolger Schemel <info@artsoft.org>
Sun, 11 Feb 2024 13:45:53 +0000 (14:45 +0100)
committerHolger Schemel <info@artsoft.org>
Sun, 18 Feb 2024 14:57:43 +0000 (15:57 +0100)
src/game.c
src/game.h
src/game_bd/export_bd.h
src/game_bd/main_bd.c
src/libgame/system.h

index fa1682053736b0e197c2e9b5b910dcef4dc90217..47f3611731f7bf4bff3741f45681d2b1fb37b73d 100644 (file)
@@ -4503,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();
   }
@@ -4968,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;
@@ -11704,6 +11709,10 @@ 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;
 
@@ -11750,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;
 
@@ -11787,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)
@@ -11822,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++;
 
@@ -12176,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();
   }
@@ -12243,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];
@@ -16156,6 +16214,17 @@ boolean checkGameRunning(void)
   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
index 4141b3bcfdf05735a29550560ab3cb400179bbda..83c7f00cfd5ab151e93649aa8ae9f56579e26431 100644 (file)
@@ -448,6 +448,7 @@ void DrawDynamite(int, int);
 void StartGameActions(boolean, boolean, int);
 
 void GameActions(void);
+void GameActions_BD_Main(void);
 void GameActions_EM_Main(void);
 void GameActions_SP_Main(void);
 void GameActions_MM_Main(void);
@@ -472,6 +473,7 @@ void RequestQuitGame(boolean);
 
 boolean CheckRestartGame(void);
 boolean checkGameRunning(void);
+boolean checkGamePlaying(void);
 boolean checkGameSolved(void);
 boolean checkGameFailed(void);
 boolean checkGameEnded(void);
index 3b9e704c6099860251a724690878688095d1470c..a56e948bf282dd3566bf2bfe8db0034a9118d347 100644 (file)
@@ -88,6 +88,10 @@ int map_action_RND_to_BD(int);
 int map_action_BD_to_RND(int);
 
 boolean checkGameRunning_BD(void);
+boolean checkGamePlaying_BD(void);
+boolean checkBonusTime_BD(void);
+int getFramesPerSecond_BD(void);
+int getTimeLeft_BD(void);
 
 void InitGfxBuffers_BD(void);
 
@@ -96,6 +100,8 @@ void setLevelInfoToDefaults_BD(void);
 boolean LoadNativeLevel_BD(char *, int, boolean);
 
 unsigned int InitEngineRandom_BD(int);
+void InitGameEngine_BD(void);
+void GameActions_BD(byte[MAX_PLAYERS]);
 void CoverScreen_BD(void);
 
 void BlitScreenToBitmap_BD(Bitmap *);
index ccb04e23a3f211bebf515d870a31c23aadc57c25..8f80b4f485e6d40bb4407ab930bc802df1cb1788 100644 (file)
@@ -188,6 +188,51 @@ boolean checkGameRunning_BD(void)
   return (game_bd.game != NULL && game_bd.game->state_counter == GAME_INT_CAVE_RUNNING);
 }
 
+boolean checkGamePlaying_BD(void)
+{
+  return (game_bd.game != NULL && game_bd.game->state_counter == GAME_INT_CAVE_RUNNING &&
+         game_bd.game->cave != NULL && game_bd.game->cave->hatched);
+}
+
+boolean checkBonusTime_BD(void)
+{
+  return (game_bd.game != NULL && game_bd.game->state_counter == GAME_INT_CHECK_BONUS_TIME);
+}
+
+int getFramesPerSecond_BD(void)
+{
+  if (game_bd.game != NULL && game_bd.game->cave != NULL && game_bd.game->cave->pal_timing)
+    return FRAMES_PER_SECOND_NTSC;
+
+  return FRAMES_PER_SECOND_PAL;
+}
+
+int getTimeLeft_BD(void)
+{
+  if (game_bd.game != NULL && game_bd.game->cave != NULL)
+    return gd_cave_time_show(game_bd.game->cave, game_bd.game->cave->time);
+
+  return 0;
+}
+
+static void UpdateGameDoorValues_BD(void)
+{
+  GdCave *cave = game_bd.game->cave;
+  int time_secs = gd_cave_time_show(cave, cave->time);
+  int gems_still_needed = MAX(0, (cave->diamonds_needed - cave->diamonds_collected));
+
+  game_bd.time_played = time_secs;
+  game_bd.gems_still_needed = gems_still_needed;
+  game_bd.score = game_bd.game->player_score;
+
+  if (game.LevelSolved)
+  {
+    // update time and score in panel while counting bonus time
+    game.LevelSolved_CountingTime  = game_bd.time_played;
+    game.LevelSolved_CountingScore = game_bd.score;
+  }
+}
+
 unsigned int InitEngineRandom_BD(int seed)
 {
   if (seed == NEW_RANDOMIZE)
@@ -201,6 +246,103 @@ unsigned int InitEngineRandom_BD(int seed)
   return (unsigned int)seed;
 }
 
+void InitGameEngine_BD(void)
+{
+  game_bd.level_solved = FALSE;
+  game_bd.game_over = FALSE;
+  game_bd.cover_screen = FALSE;
+
+  game_bd.time_played = 0;
+  game_bd.gems_still_needed = 0;
+  game_bd.score = 0;
+
+  gd_caveset_last_selected       = native_bd_level.cave_nr;
+  gd_caveset_last_selected_level = native_bd_level.level_nr;
+
+  if (game_bd.game != NULL)
+    gd_game_free(game_bd.game);
+
+  game_bd.game = gd_game_new(native_bd_level.cave_nr, native_bd_level.level_nr);
+
+  // default: start with completely covered playfield
+  int next_state = GAME_INT_START_UNCOVER + 1;
+
+  // when skipping uncovering, start with uncovered playfield
+  if (setup.bd_skip_uncovering)
+    next_state = GAME_INT_LOAD_CAVE + 1;
+
+  // fast-forward game engine until cave loaded (covered or uncovered)
+  while (game_bd.game->state_counter < next_state)
+    play_game_func(game_bd.game, 0);
+
+  // when skipping uncovering, continue with uncovered playfield
+  if (setup.bd_skip_uncovering)
+    game_bd.game->state_counter = GAME_INT_UNCOVER_ALL + 1;
+
+  if (setup.bd_skip_uncovering)
+    gd_scroll(game_bd.game, TRUE, TRUE);
+
+  RedrawPlayfield_BD(TRUE);
+
+  UpdateGameDoorValues_BD();
+}
+
+void GameActions_BD(byte action[MAX_PLAYERS])
+{
+  GdCave *cave = game_bd.game->cave;
+  boolean player_found = FALSE;
+  int player_x = 0;
+  int player_y = 0;
+  int x, y;
+
+  if (cave->getp)
+  {
+    for (y = 0; y < cave->h && !player_found; y++)
+    {
+      for (x = 0; x < cave->w && !player_found; x++)
+      {
+       int element = *cave->getp(cave, x, y);
+
+       if (element == O_PLAYER ||
+           element == O_PLAYER_BOMB ||
+           element == O_PLAYER_STIRRING ||
+           element == O_PLAYER_PNEUMATIC_LEFT ||
+           element == O_PLAYER_PNEUMATIC_RIGHT)
+       {
+         player_x = x;
+         player_y = y;
+
+         player_found = TRUE;
+       }
+      }
+    }
+  }
+
+  UpdateEngineValues(get_scroll_x(),
+                    get_scroll_y(),
+                    player_x,
+                    player_y);
+
+  if (setup.bd_skip_hatching && !game_bd.game->cave->hatched &&
+      game_bd.game->state_counter == GAME_INT_CAVE_RUNNING)
+  {
+    // fast-forward game engine until player hatched
+    while (!game_bd.game->cave->hatched)
+    {
+      play_game_func(game_bd.game, 0);
+
+      // also record or replay tape action during fast-forward
+      action = TapeCorrectAction_BD(action);
+    }
+  }
+
+  play_game_func(game_bd.game, action[0]);
+
+  RedrawPlayfield_BD(FALSE);
+
+  UpdateGameDoorValues_BD();
+}
+
 
 // ============================================================================
 // graphics functions
index 4bc436eb9c8677cfb94e09e8d09f551b91bb8679..b36dcd615c81ccadb75bbc935865bacf060957a2 100644 (file)
 #define MAX_VSYNC_FRAME_DELAY  16      // maximum value for vsync to work
 #define FRAMES_PER_SECOND      (ONE_SECOND_DELAY / GAME_FRAME_DELAY)
 #define FRAMES_PER_SECOND_SP   35
+#define FRAMES_PER_SECOND_PAL  50
+#define FRAMES_PER_SECOND_NTSC 60
 
 // maximum playfield size supported by libgame functions
 #define MAX_PLAYFIELD_WIDTH    128