From c3f4524693a17a8d75c7d25da24d4003b234d114 Mon Sep 17 00:00:00 2001 From: Holger Schemel Date: Sun, 11 Feb 2024 14:45:53 +0100 Subject: [PATCH] added game engine support for playing native Boulder Dash levels --- src/game.c | 79 ++++++++++++++++++++-- src/game.h | 2 + src/game_bd/export_bd.h | 6 ++ src/game_bd/main_bd.c | 142 ++++++++++++++++++++++++++++++++++++++++ src/libgame/system.h | 2 + 5 files changed, 226 insertions(+), 5 deletions(-) diff --git a/src/game.c b/src/game.c index fa168205..47f36117 100644 --- a/src/game.c +++ b/src/game.c @@ -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 diff --git a/src/game.h b/src/game.h index 4141b3bc..83c7f00c 100644 --- a/src/game.h +++ b/src/game.h @@ -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); diff --git a/src/game_bd/export_bd.h b/src/game_bd/export_bd.h index 3b9e704c..a56e948b 100644 --- a/src/game_bd/export_bd.h +++ b/src/game_bd/export_bd.h @@ -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 *); diff --git a/src/game_bd/main_bd.c b/src/game_bd/main_bd.c index ccb04e23..8f80b4f4 100644 --- a/src/game_bd/main_bd.c +++ b/src/game_bd/main_bd.c @@ -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 diff --git a/src/libgame/system.h b/src/libgame/system.h index 4bc436eb..b36dcd61 100644 --- a/src/libgame/system.h +++ b/src/libgame/system.h @@ -546,6 +546,8 @@ #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 -- 2.34.1