X-Git-Url: https://git.artsoft.org/?a=blobdiff_plain;f=src%2Fgame.c;h=506def33ce0682ae91f3903b2802715300dcba56;hb=8e65ad775c169be345f846f9a3cb1be4bddbd4ce;hp=ea30528996c15d9b1a706d899d0d6f0853dd5bd7;hpb=3f0fff81d235d4259a00b504e24a29f3171fb5bb;p=rocksndiamonds.git diff --git a/src/game.c b/src/game.c index ea305289..506def33 100644 --- a/src/game.c +++ b/src/game.c @@ -89,123 +89,126 @@ // game panel display and control definitions #define GAME_PANEL_LEVEL_NUMBER 0 #define GAME_PANEL_GEMS 1 -#define GAME_PANEL_INVENTORY_COUNT 2 -#define GAME_PANEL_INVENTORY_FIRST_1 3 -#define GAME_PANEL_INVENTORY_FIRST_2 4 -#define GAME_PANEL_INVENTORY_FIRST_3 5 -#define GAME_PANEL_INVENTORY_FIRST_4 6 -#define GAME_PANEL_INVENTORY_FIRST_5 7 -#define GAME_PANEL_INVENTORY_FIRST_6 8 -#define GAME_PANEL_INVENTORY_FIRST_7 9 -#define GAME_PANEL_INVENTORY_FIRST_8 10 -#define GAME_PANEL_INVENTORY_LAST_1 11 -#define GAME_PANEL_INVENTORY_LAST_2 12 -#define GAME_PANEL_INVENTORY_LAST_3 13 -#define GAME_PANEL_INVENTORY_LAST_4 14 -#define GAME_PANEL_INVENTORY_LAST_5 15 -#define GAME_PANEL_INVENTORY_LAST_6 16 -#define GAME_PANEL_INVENTORY_LAST_7 17 -#define GAME_PANEL_INVENTORY_LAST_8 18 -#define GAME_PANEL_KEY_1 19 -#define GAME_PANEL_KEY_2 20 -#define GAME_PANEL_KEY_3 21 -#define GAME_PANEL_KEY_4 22 -#define GAME_PANEL_KEY_5 23 -#define GAME_PANEL_KEY_6 24 -#define GAME_PANEL_KEY_7 25 -#define GAME_PANEL_KEY_8 26 -#define GAME_PANEL_KEY_WHITE 27 -#define GAME_PANEL_KEY_WHITE_COUNT 28 -#define GAME_PANEL_SCORE 29 -#define GAME_PANEL_HIGHSCORE 30 -#define GAME_PANEL_TIME 31 -#define GAME_PANEL_TIME_HH 32 -#define GAME_PANEL_TIME_MM 33 -#define GAME_PANEL_TIME_SS 34 -#define GAME_PANEL_TIME_ANIM 35 -#define GAME_PANEL_HEALTH 36 -#define GAME_PANEL_HEALTH_ANIM 37 -#define GAME_PANEL_FRAME 38 -#define GAME_PANEL_SHIELD_NORMAL 39 -#define GAME_PANEL_SHIELD_NORMAL_TIME 40 -#define GAME_PANEL_SHIELD_DEADLY 41 -#define GAME_PANEL_SHIELD_DEADLY_TIME 42 -#define GAME_PANEL_EXIT 43 -#define GAME_PANEL_EMC_MAGIC_BALL 44 -#define GAME_PANEL_EMC_MAGIC_BALL_SWITCH 45 -#define GAME_PANEL_LIGHT_SWITCH 46 -#define GAME_PANEL_LIGHT_SWITCH_TIME 47 -#define GAME_PANEL_TIMEGATE_SWITCH 48 -#define GAME_PANEL_TIMEGATE_SWITCH_TIME 49 -#define GAME_PANEL_SWITCHGATE_SWITCH 50 -#define GAME_PANEL_EMC_LENSES 51 -#define GAME_PANEL_EMC_LENSES_TIME 52 -#define GAME_PANEL_EMC_MAGNIFIER 53 -#define GAME_PANEL_EMC_MAGNIFIER_TIME 54 -#define GAME_PANEL_BALLOON_SWITCH 55 -#define GAME_PANEL_DYNABOMB_NUMBER 56 -#define GAME_PANEL_DYNABOMB_SIZE 57 -#define GAME_PANEL_DYNABOMB_POWER 58 -#define GAME_PANEL_PENGUINS 59 -#define GAME_PANEL_SOKOBAN_OBJECTS 60 -#define GAME_PANEL_SOKOBAN_FIELDS 61 -#define GAME_PANEL_ROBOT_WHEEL 62 -#define GAME_PANEL_CONVEYOR_BELT_1 63 -#define GAME_PANEL_CONVEYOR_BELT_2 64 -#define GAME_PANEL_CONVEYOR_BELT_3 65 -#define GAME_PANEL_CONVEYOR_BELT_4 66 -#define GAME_PANEL_CONVEYOR_BELT_1_SWITCH 67 -#define GAME_PANEL_CONVEYOR_BELT_2_SWITCH 68 -#define GAME_PANEL_CONVEYOR_BELT_3_SWITCH 69 -#define GAME_PANEL_CONVEYOR_BELT_4_SWITCH 70 -#define GAME_PANEL_MAGIC_WALL 71 -#define GAME_PANEL_MAGIC_WALL_TIME 72 -#define GAME_PANEL_GRAVITY_STATE 73 -#define GAME_PANEL_GRAPHIC_1 74 -#define GAME_PANEL_GRAPHIC_2 75 -#define GAME_PANEL_GRAPHIC_3 76 -#define GAME_PANEL_GRAPHIC_4 77 -#define GAME_PANEL_GRAPHIC_5 78 -#define GAME_PANEL_GRAPHIC_6 79 -#define GAME_PANEL_GRAPHIC_7 80 -#define GAME_PANEL_GRAPHIC_8 81 -#define GAME_PANEL_ELEMENT_1 82 -#define GAME_PANEL_ELEMENT_2 83 -#define GAME_PANEL_ELEMENT_3 84 -#define GAME_PANEL_ELEMENT_4 85 -#define GAME_PANEL_ELEMENT_5 86 -#define GAME_PANEL_ELEMENT_6 87 -#define GAME_PANEL_ELEMENT_7 88 -#define GAME_PANEL_ELEMENT_8 89 -#define GAME_PANEL_ELEMENT_COUNT_1 90 -#define GAME_PANEL_ELEMENT_COUNT_2 91 -#define GAME_PANEL_ELEMENT_COUNT_3 92 -#define GAME_PANEL_ELEMENT_COUNT_4 93 -#define GAME_PANEL_ELEMENT_COUNT_5 94 -#define GAME_PANEL_ELEMENT_COUNT_6 95 -#define GAME_PANEL_ELEMENT_COUNT_7 96 -#define GAME_PANEL_ELEMENT_COUNT_8 97 -#define GAME_PANEL_CE_SCORE_1 98 -#define GAME_PANEL_CE_SCORE_2 99 -#define GAME_PANEL_CE_SCORE_3 100 -#define GAME_PANEL_CE_SCORE_4 101 -#define GAME_PANEL_CE_SCORE_5 102 -#define GAME_PANEL_CE_SCORE_6 103 -#define GAME_PANEL_CE_SCORE_7 104 -#define GAME_PANEL_CE_SCORE_8 105 -#define GAME_PANEL_CE_SCORE_1_ELEMENT 106 -#define GAME_PANEL_CE_SCORE_2_ELEMENT 107 -#define GAME_PANEL_CE_SCORE_3_ELEMENT 108 -#define GAME_PANEL_CE_SCORE_4_ELEMENT 109 -#define GAME_PANEL_CE_SCORE_5_ELEMENT 110 -#define GAME_PANEL_CE_SCORE_6_ELEMENT 111 -#define GAME_PANEL_CE_SCORE_7_ELEMENT 112 -#define GAME_PANEL_CE_SCORE_8_ELEMENT 113 -#define GAME_PANEL_PLAYER_NAME 114 -#define GAME_PANEL_LEVEL_NAME 115 -#define GAME_PANEL_LEVEL_AUTHOR 116 - -#define NUM_GAME_PANEL_CONTROLS 117 +#define GAME_PANEL_GEMS_NEEDED 2 +#define GAME_PANEL_GEMS_COLLECTED 3 +#define GAME_PANEL_GEMS_SCORE 4 +#define GAME_PANEL_INVENTORY_COUNT 5 +#define GAME_PANEL_INVENTORY_FIRST_1 6 +#define GAME_PANEL_INVENTORY_FIRST_2 7 +#define GAME_PANEL_INVENTORY_FIRST_3 8 +#define GAME_PANEL_INVENTORY_FIRST_4 9 +#define GAME_PANEL_INVENTORY_FIRST_5 10 +#define GAME_PANEL_INVENTORY_FIRST_6 11 +#define GAME_PANEL_INVENTORY_FIRST_7 12 +#define GAME_PANEL_INVENTORY_FIRST_8 13 +#define GAME_PANEL_INVENTORY_LAST_1 14 +#define GAME_PANEL_INVENTORY_LAST_2 15 +#define GAME_PANEL_INVENTORY_LAST_3 16 +#define GAME_PANEL_INVENTORY_LAST_4 17 +#define GAME_PANEL_INVENTORY_LAST_5 18 +#define GAME_PANEL_INVENTORY_LAST_6 19 +#define GAME_PANEL_INVENTORY_LAST_7 20 +#define GAME_PANEL_INVENTORY_LAST_8 21 +#define GAME_PANEL_KEY_1 22 +#define GAME_PANEL_KEY_2 23 +#define GAME_PANEL_KEY_3 24 +#define GAME_PANEL_KEY_4 25 +#define GAME_PANEL_KEY_5 26 +#define GAME_PANEL_KEY_6 27 +#define GAME_PANEL_KEY_7 28 +#define GAME_PANEL_KEY_8 29 +#define GAME_PANEL_KEY_WHITE 30 +#define GAME_PANEL_KEY_WHITE_COUNT 31 +#define GAME_PANEL_SCORE 32 +#define GAME_PANEL_HIGHSCORE 33 +#define GAME_PANEL_TIME 34 +#define GAME_PANEL_TIME_HH 35 +#define GAME_PANEL_TIME_MM 36 +#define GAME_PANEL_TIME_SS 37 +#define GAME_PANEL_TIME_ANIM 38 +#define GAME_PANEL_HEALTH 39 +#define GAME_PANEL_HEALTH_ANIM 40 +#define GAME_PANEL_FRAME 41 +#define GAME_PANEL_SHIELD_NORMAL 42 +#define GAME_PANEL_SHIELD_NORMAL_TIME 43 +#define GAME_PANEL_SHIELD_DEADLY 44 +#define GAME_PANEL_SHIELD_DEADLY_TIME 45 +#define GAME_PANEL_EXIT 46 +#define GAME_PANEL_EMC_MAGIC_BALL 47 +#define GAME_PANEL_EMC_MAGIC_BALL_SWITCH 48 +#define GAME_PANEL_LIGHT_SWITCH 49 +#define GAME_PANEL_LIGHT_SWITCH_TIME 50 +#define GAME_PANEL_TIMEGATE_SWITCH 51 +#define GAME_PANEL_TIMEGATE_SWITCH_TIME 52 +#define GAME_PANEL_SWITCHGATE_SWITCH 53 +#define GAME_PANEL_EMC_LENSES 54 +#define GAME_PANEL_EMC_LENSES_TIME 55 +#define GAME_PANEL_EMC_MAGNIFIER 56 +#define GAME_PANEL_EMC_MAGNIFIER_TIME 57 +#define GAME_PANEL_BALLOON_SWITCH 58 +#define GAME_PANEL_DYNABOMB_NUMBER 59 +#define GAME_PANEL_DYNABOMB_SIZE 60 +#define GAME_PANEL_DYNABOMB_POWER 61 +#define GAME_PANEL_PENGUINS 62 +#define GAME_PANEL_SOKOBAN_OBJECTS 63 +#define GAME_PANEL_SOKOBAN_FIELDS 64 +#define GAME_PANEL_ROBOT_WHEEL 65 +#define GAME_PANEL_CONVEYOR_BELT_1 66 +#define GAME_PANEL_CONVEYOR_BELT_2 67 +#define GAME_PANEL_CONVEYOR_BELT_3 68 +#define GAME_PANEL_CONVEYOR_BELT_4 69 +#define GAME_PANEL_CONVEYOR_BELT_1_SWITCH 70 +#define GAME_PANEL_CONVEYOR_BELT_2_SWITCH 71 +#define GAME_PANEL_CONVEYOR_BELT_3_SWITCH 72 +#define GAME_PANEL_CONVEYOR_BELT_4_SWITCH 73 +#define GAME_PANEL_MAGIC_WALL 74 +#define GAME_PANEL_MAGIC_WALL_TIME 75 +#define GAME_PANEL_GRAVITY_STATE 76 +#define GAME_PANEL_GRAPHIC_1 77 +#define GAME_PANEL_GRAPHIC_2 78 +#define GAME_PANEL_GRAPHIC_3 79 +#define GAME_PANEL_GRAPHIC_4 80 +#define GAME_PANEL_GRAPHIC_5 81 +#define GAME_PANEL_GRAPHIC_6 82 +#define GAME_PANEL_GRAPHIC_7 83 +#define GAME_PANEL_GRAPHIC_8 84 +#define GAME_PANEL_ELEMENT_1 85 +#define GAME_PANEL_ELEMENT_2 86 +#define GAME_PANEL_ELEMENT_3 87 +#define GAME_PANEL_ELEMENT_4 88 +#define GAME_PANEL_ELEMENT_5 89 +#define GAME_PANEL_ELEMENT_6 90 +#define GAME_PANEL_ELEMENT_7 91 +#define GAME_PANEL_ELEMENT_8 92 +#define GAME_PANEL_ELEMENT_COUNT_1 93 +#define GAME_PANEL_ELEMENT_COUNT_2 94 +#define GAME_PANEL_ELEMENT_COUNT_3 95 +#define GAME_PANEL_ELEMENT_COUNT_4 96 +#define GAME_PANEL_ELEMENT_COUNT_5 97 +#define GAME_PANEL_ELEMENT_COUNT_6 98 +#define GAME_PANEL_ELEMENT_COUNT_7 99 +#define GAME_PANEL_ELEMENT_COUNT_8 100 +#define GAME_PANEL_CE_SCORE_1 101 +#define GAME_PANEL_CE_SCORE_2 102 +#define GAME_PANEL_CE_SCORE_3 103 +#define GAME_PANEL_CE_SCORE_4 104 +#define GAME_PANEL_CE_SCORE_5 105 +#define GAME_PANEL_CE_SCORE_6 106 +#define GAME_PANEL_CE_SCORE_7 107 +#define GAME_PANEL_CE_SCORE_8 108 +#define GAME_PANEL_CE_SCORE_1_ELEMENT 109 +#define GAME_PANEL_CE_SCORE_2_ELEMENT 110 +#define GAME_PANEL_CE_SCORE_3_ELEMENT 111 +#define GAME_PANEL_CE_SCORE_4_ELEMENT 112 +#define GAME_PANEL_CE_SCORE_5_ELEMENT 113 +#define GAME_PANEL_CE_SCORE_6_ELEMENT 114 +#define GAME_PANEL_CE_SCORE_7_ELEMENT 115 +#define GAME_PANEL_CE_SCORE_8_ELEMENT 116 +#define GAME_PANEL_PLAYER_NAME 117 +#define GAME_PANEL_LEVEL_NAME 118 +#define GAME_PANEL_LEVEL_AUTHOR 119 + +#define NUM_GAME_PANEL_CONTROLS 120 struct GamePanelOrderInfo { @@ -242,6 +245,21 @@ static struct GamePanelControlInfo game_panel_controls[] = &game.panel.gems, TYPE_INTEGER, }, + { + GAME_PANEL_GEMS_NEEDED, + &game.panel.gems_needed, + TYPE_INTEGER, + }, + { + GAME_PANEL_GEMS_COLLECTED, + &game.panel.gems_collected, + TYPE_INTEGER, + }, + { + GAME_PANEL_GEMS_SCORE, + &game.panel.gems_score, + TYPE_INTEGER, + }, { GAME_PANEL_INVENTORY_COUNT, &game.panel.inventory_count, @@ -1017,19 +1035,22 @@ static struct GamePanelControlInfo game_panel_controls[] = #define GAME_CTRL_ID_SAVE 5 #define GAME_CTRL_ID_PAUSE2 6 #define GAME_CTRL_ID_LOAD 7 -#define GAME_CTRL_ID_PANEL_STOP 8 -#define GAME_CTRL_ID_PANEL_PAUSE 9 -#define GAME_CTRL_ID_PANEL_PLAY 10 -#define GAME_CTRL_ID_TOUCH_STOP 11 -#define GAME_CTRL_ID_TOUCH_PAUSE 12 -#define SOUND_CTRL_ID_MUSIC 13 -#define SOUND_CTRL_ID_LOOPS 14 -#define SOUND_CTRL_ID_SIMPLE 15 -#define SOUND_CTRL_ID_PANEL_MUSIC 16 -#define SOUND_CTRL_ID_PANEL_LOOPS 17 -#define SOUND_CTRL_ID_PANEL_SIMPLE 18 - -#define NUM_GAME_BUTTONS 19 +#define GAME_CTRL_ID_RESTART 8 +#define GAME_CTRL_ID_PANEL_STOP 9 +#define GAME_CTRL_ID_PANEL_PAUSE 10 +#define GAME_CTRL_ID_PANEL_PLAY 11 +#define GAME_CTRL_ID_PANEL_RESTART 12 +#define GAME_CTRL_ID_TOUCH_STOP 13 +#define GAME_CTRL_ID_TOUCH_PAUSE 14 +#define GAME_CTRL_ID_TOUCH_RESTART 15 +#define SOUND_CTRL_ID_MUSIC 16 +#define SOUND_CTRL_ID_LOOPS 17 +#define SOUND_CTRL_ID_SIMPLE 18 +#define SOUND_CTRL_ID_PANEL_MUSIC 19 +#define SOUND_CTRL_ID_PANEL_LOOPS 20 +#define SOUND_CTRL_ID_PANEL_SIMPLE 21 + +#define NUM_GAME_BUTTONS 22 // forward declaration for internal use @@ -1814,6 +1835,29 @@ static void InitPlayerField(int x, int y, int element, boolean init_game) } } +static void InitFieldForEngine_RND(int x, int y) +{ + int element = Tile[x][y]; + + // convert BD engine elements to corresponding R'n'D engine elements + element = (element == EL_BD_EMPTY ? EL_EMPTY : + element == EL_BD_PLAYER ? EL_PLAYER_1 : + element == EL_BD_INBOX ? EL_PLAYER_1 : + element == EL_BD_SAND ? EL_SAND : + element == EL_BD_STEELWALL ? EL_STEELWALL : + element == EL_BD_EXIT_CLOSED ? EL_EXIT_CLOSED : + element == EL_BD_EXIT_OPEN ? EL_EXIT_OPEN : + element); + + Tile[x][y] = element; +} + +static void InitFieldForEngine(int x, int y) +{ + if (level.game_engine_type == GAME_ENGINE_TYPE_RND) + InitFieldForEngine_RND(x, y); +} + static void InitField(int x, int y, boolean init_game) { int element = Tile[x][y]; @@ -2241,6 +2285,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 ? @@ -2250,6 +2296,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 ? @@ -2257,14 +2305,25 @@ 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 : level.game_engine_type == GAME_ENGINE_TYPE_MM ? game_mm.kettles_still_needed : game.gems_still_needed); - int exit_closed = (level.game_engine_type == GAME_ENGINE_TYPE_EM ? + int gems_needed = level.gems_needed; + int gems_collected = (level.game_engine_type == GAME_ENGINE_TYPE_BD ? + game_bd.game->cave->diamonds_collected : + gems_needed - 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 : @@ -2289,6 +2348,9 @@ static void UpdateGameControlValues(void) // used instead of "level_nr" (for network games) game_panel_controls[GAME_PANEL_LEVEL_NUMBER].value = levelset.level_nr; game_panel_controls[GAME_PANEL_GEMS].value = gems; + game_panel_controls[GAME_PANEL_GEMS_NEEDED].value = gems_needed; + game_panel_controls[GAME_PANEL_GEMS_COLLECTED].value = gems_collected; + game_panel_controls[GAME_PANEL_GEMS_SCORE].value = gems_score; game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value = 0; for (i = 0; i < MAX_NUM_KEYS; i++) @@ -2683,7 +2745,7 @@ static void DisplayGameControlValues(void) if (PANEL_DEACTIVATED(pos)) continue; - if (pos->class == get_hash_from_key("extra_panel_items") && + if (pos->class == get_hash_from_string("extra_panel_items") && !setup.prefer_extra_panel_items) continue; @@ -2775,7 +2837,7 @@ static void DisplayGameControlValues(void) int width, height; int dst_x = PANEL_XPOS(pos); int dst_y = PANEL_YPOS(pos); - boolean skip = (pos->class == get_hash_from_key("mm_engine_only") && + boolean skip = (pos->class == get_hash_from_string("mm_engine_only") && level.game_engine_type != GAME_ENGINE_TYPE_MM); if (graphic != IMG_UNDEFINED && !skip) @@ -3095,6 +3157,9 @@ static void InitGameEngine(void) game_em.use_single_button = (game.engine_version > VERSION_IDENT(4,0,0,2)); + game_em.use_push_delay = + (game.engine_version > VERSION_IDENT(4,3,7,1)); + game_em.use_snap_key_bug = (game.engine_version < VERSION_IDENT(4,0,1,0)); @@ -3279,6 +3344,8 @@ static void InitGameEngine(void) change->actual_trigger_side = CH_SIDE_NONE; change->actual_trigger_ce_value = 0; change->actual_trigger_ce_score = 0; + change->actual_trigger_x = -1; + change->actual_trigger_y = -1; } } @@ -3474,8 +3541,9 @@ static void InitGameEngine(void) level.game_engine_type == GAME_ENGINE_TYPE_EM && !setup.forced_scroll_delay ? 0 : setup.scroll_delay ? setup.scroll_delay_value : 0); - game.scroll_delay_value = - MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY); + if (game.forced_scroll_delay_value == -1) + game.scroll_delay_value = + MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY); // ---------- initialize game engine snapshots ------------------------------ for (i = 0; i < MAX_PLAYERS; i++) @@ -3834,6 +3902,8 @@ void InitGame(void) TimeFrames = 0; TimePlayed = 0; TimeLeft = level.time; + + TapeTimeFrames = 0; TapeTime = 0; ScreenMovDir = MV_NONE; @@ -3864,6 +3934,8 @@ void InitGame(void) game.LevelSolved_CountingScore = 0; game.LevelSolved_CountingHealth = 0; + game.RestartGameRequested = FALSE; + game.panel.active = TRUE; game.no_level_time_limit = (level.time == 0); @@ -3902,8 +3974,6 @@ void InitGame(void) game.explosions_delayed = TRUE; - game.envelope_active = FALSE; - // special case: set custom artwork setting to initial value game.use_masked_elements = game.use_masked_elements_initial; @@ -3958,6 +4028,8 @@ void InitGame(void) SCAN_PLAYFIELD(x, y) { + InitFieldForEngine(x, y); + if (emulate_bd && !IS_BD_ELEMENT(Tile[x][y])) emulate_bd = FALSE; if (emulate_sp && !IS_SP_ELEMENT(Tile[x][y])) @@ -3970,6 +4042,10 @@ void InitGame(void) InitBeltMovement(); + // required if level does not contain any "empty space" element + if (element_info[EL_EMPTY].use_gfx_element) + game.use_masked_elements = TRUE; + for (i = 0; i < MAX_PLAYERS; i++) { struct PlayerInfo *player = &stored_player[i]; @@ -4444,8 +4520,17 @@ void InitGame(void) scroll_y = SCROLL_POSITION_Y(local_player->jy); } + if (game.forced_scroll_x != ARG_UNDEFINED_VALUE) + scroll_x = game.forced_scroll_x; + if (game.forced_scroll_y != ARG_UNDEFINED_VALUE) + 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(); } @@ -4534,9 +4619,9 @@ void InitGame(void) } game.restart_level = FALSE; - game.request_active = FALSE; - game.request_active_or_moving = FALSE; + game.envelope_active = FALSE; + game.any_door_active = FALSE; if (level.game_engine_type == GAME_ENGINE_TYPE_MM) InitGameActions_MM(); @@ -4774,14 +4859,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 ? @@ -4821,6 +4906,31 @@ static void LevelSolved(void) LevelSolved_SetFinalGameValues(); } +static boolean AdvanceToNextLevel(void) +{ + if (setup.increment_levels && + level_nr < leveldir_current->last_level && + !network_playing) + { + level_nr++; // advance to next level + TapeErase(); // start with empty tape + + if (setup.auto_play_next_level) + { + scores.continue_playing = TRUE; + scores.next_level_nr = level_nr; + + LoadLevel(level_nr); + + SaveLevelSetup_SeriesInfo(); + } + + return TRUE; + } + + return FALSE; +} + void GameWon(void) { static int time_count_steps; @@ -4895,7 +5005,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; @@ -4906,7 +5022,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; @@ -5044,8 +5161,21 @@ void GameEnd(void) // used instead of "level_nr" (needed for network games) int last_level_nr = levelset.level_nr; boolean tape_saved = FALSE; + boolean game_over = checkGameFailed(); + + // Important note: This function is not only called after "GameWon()", but also after + // "game over" (if automatically asking for restarting the game is disabled in setup) - game.LevelSolved_GameEnd = TRUE; + // do not handle game end if game over and automatically asking for game restart + if (game_over && setup.ask_on_game_over) + return; + + // do not handle game end if request dialog is already active + if (checkRequestActive()) + return; + + if (game.LevelSolved) + game.LevelSolved_GameEnd = TRUE; if (game.LevelSolved_SaveTape && !score_info_tape_play) { @@ -5072,7 +5202,7 @@ void GameEnd(void) return; } - if (!game.LevelSolved_SaveScore) + if (!game.GamePlayed || (!game.LevelSolved_SaveScore && !level.bd_intermission)) { SetGameStatus(GAME_MODE_MAIN); @@ -5089,27 +5219,13 @@ void GameEnd(void) } // save score and score tape before potentially erasing tape below - NewHighScore(last_level_nr, tape_saved); - - if (setup.increment_levels && - level_nr < leveldir_current->last_level && - !network_playing) - { - level_nr++; // advance to next level - TapeErase(); // start with empty tape - - if (setup.auto_play_next_level) - { - scores.continue_playing = TRUE; - scores.next_level_nr = level_nr; - - LoadLevel(level_nr); + if (game.LevelSolved_SaveScore) + NewHighScore(last_level_nr, tape_saved); - SaveLevelSetup_SeriesInfo(); - } - } + // increment and load next level (if possible and not configured otherwise) + AdvanceToNextLevel(); - if (scores.last_added >= 0 && setup.show_scores_after_game) + if (game.LevelSolved_SaveScore && scores.last_added >= 0 && setup.show_scores_after_game) { SetGameStatus(GAME_MODE_SCORES); @@ -5674,14 +5790,47 @@ static void DrawRelocateScreen(int old_x, int old_y, int x, int y, { // relocation _without_ centering of screen - int center_scroll_x = SCROLL_POSITION_X(old_x); - int center_scroll_y = SCROLL_POSITION_Y(old_y); - int offset_x = x + (scroll_x - center_scroll_x); - int offset_y = y + (scroll_y - center_scroll_y); + // apply distance between old and new player position to scroll position + int shifted_scroll_x = scroll_x + (x - old_x); + int shifted_scroll_y = scroll_y + (y - old_y); + + // make sure that shifted scroll position does not scroll beyond screen + new_scroll_x = SCROLL_POSITION_X(shifted_scroll_x + MIDPOSX); + new_scroll_y = SCROLL_POSITION_Y(shifted_scroll_y + MIDPOSY); + + // special case for teleporting from one end of the playfield to the other + // (this kludge prevents the destination area to be shifted by half a tile + // against the source destination for even screen width or screen height; + // probably most useful when used with high "game.forced_scroll_delay_value" + // in combination with "game.forced_scroll_x" and "game.forced_scroll_y") + if (quick_relocation) + { + if (EVEN(SCR_FIELDX)) + { + // relocate (teleport) between left and right border (half or full) + if (scroll_x == SBX_Left && new_scroll_x == SBX_Right - 1) + new_scroll_x = SBX_Right; + else if (scroll_x == SBX_Left + 1 && new_scroll_x == SBX_Right) + new_scroll_x = SBX_Right - 1; + else if (scroll_x == SBX_Right && new_scroll_x == SBX_Left + 1) + new_scroll_x = SBX_Left; + else if (scroll_x == SBX_Right - 1 && new_scroll_x == SBX_Left) + new_scroll_x = SBX_Left + 1; + } - // for new screen position, apply previous offset to center position - new_scroll_x = SCROLL_POSITION_X(offset_x); - new_scroll_y = SCROLL_POSITION_Y(offset_y); + if (EVEN(SCR_FIELDY)) + { + // relocate (teleport) between top and bottom border (half or full) + if (scroll_y == SBY_Upper && new_scroll_y == SBY_Lower - 1) + new_scroll_y = SBY_Lower; + else if (scroll_y == SBY_Upper + 1 && new_scroll_y == SBY_Lower) + new_scroll_y = SBY_Lower - 1; + else if (scroll_y == SBY_Lower && new_scroll_y == SBY_Upper + 1) + new_scroll_y = SBY_Upper; + else if (scroll_y == SBY_Lower - 1 && new_scroll_y == SBY_Upper) + new_scroll_y = SBY_Upper + 1; + } + } } if (quick_relocation) @@ -9397,7 +9546,7 @@ static void Life(int ax, int ay) for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++) { - int xx = ax+x1, yy = ay+y1; + int xx = ax + x1, yy = ay + y1; int old_element = Tile[xx][yy]; int num_neighbours = 0; @@ -9406,7 +9555,7 @@ static void Life(int ax, int ay) for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++) { - int x = xx+x2, y = yy+y2; + int x = xx + x2, y = yy + y2; if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy)) continue; @@ -10642,13 +10791,22 @@ static void CreateFieldExt(int x, int y, int element, boolean is_change) if (GFX_CRUMBLED(new_element)) TEST_DrawLevelFieldCrumbledNeighbours(x, y); + if (old_element == EL_EXPLOSION) + { + Store[x][y] = Store2[x][y] = 0; + + // check if new element replaces an exploding player, requiring cleanup + if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present) + StorePlayer[x][y] = 0; + } + // check if element under the player changes from accessible to unaccessible // (needed for special case of dropping element which then changes) // (must be checked after creating new element for walkable group elements) if (IS_PLAYER(x, y) && !player_explosion_protected && IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element)) { - Bang(x, y); + KillPlayer(PLAYERINFO(x, y)); return; } @@ -10711,6 +10869,8 @@ static boolean ChangeElement(int x, int y, int element, int page) change->actual_trigger_side = CH_SIDE_NONE; change->actual_trigger_ce_value = 0; change->actual_trigger_ce_score = 0; + change->actual_trigger_x = -1; + change->actual_trigger_y = -1; } // do not change elements more than a specified maximum number of changes @@ -10720,7 +10880,9 @@ static boolean ChangeElement(int x, int y, int element, int page) ChangeCount[x][y]++; // count number of changes in the same frame if (ei->has_anim_event) - HandleGlobalAnimEventByElementChange(element, page, x, y); + HandleGlobalAnimEventByElementChange(element, page, x, y, + change->actual_trigger_x, + change->actual_trigger_y); if (change->explode) { @@ -11050,6 +11212,8 @@ static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y, change->actual_trigger_side = trigger_side; change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y]; change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element); + change->actual_trigger_x = trigger_x; + change->actual_trigger_y = trigger_y; if ((change->can_change && !change_done) || change->has_action) { @@ -11164,6 +11328,8 @@ static boolean CheckElementChangeExt(int x, int y, change->actual_trigger_side = trigger_side; change->actual_trigger_ce_value = CustomValue[x][y]; change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element); + change->actual_trigger_x = x; + change->actual_trigger_y = y; // special case: trigger element not at (x,y) position for some events if (check_trigger_element) @@ -11187,6 +11353,8 @@ static boolean CheckElementChangeExt(int x, int y, change->actual_trigger_ce_value = CustomValue[xx][yy]; change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element); + change->actual_trigger_x = xx; + change->actual_trigger_y = yy; } if (change->can_change && !change_done) @@ -11526,7 +11694,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 @@ -11573,6 +11756,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; @@ -11584,7 +11788,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; @@ -11604,12 +11808,26 @@ 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; - TapeTime++; for (i = 0; i < MAX_PLAYERS; i++) { @@ -11633,7 +11851,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 */ @@ -11642,11 +11860,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) @@ -11656,6 +11883,12 @@ static void CheckLevelTime(void) game_em.lev->time = (game.no_level_time_limit ? TimePlayed : TimeLeft); } + } + + if (TapeTimeFrames >= FRAMES_PER_SECOND) + { + TapeTimeFrames = 0; + TapeTime++; if (tape.recording || tape.playing) DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime); @@ -11671,8 +11904,21 @@ void AdvanceFrameAndPlayerCounters(int player_nr) { int i; - // advance frame counters (global frame counter and time frame counter) + // 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++; // advance player counters (counters for move delay, move animation etc.) @@ -12022,7 +12268,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(); } @@ -12089,6 +12339,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]; @@ -15183,15 +15444,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) || @@ -15213,7 +15474,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. */ @@ -15228,6 +15489,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) : @@ -15316,6 +15582,338 @@ static void PlayLevelMusic(void) PlayMusicLoop(music_nr); } +static int getSoundAction_BD(int sample) +{ + switch (sample) + { + case GD_S_STONE_PUSHING: + case GD_S_MEGA_STONE_PUSHING: + case GD_S_FLYING_STONE_PUSHING: + case GD_S_WAITING_STONE_PUSHING: + case GD_S_CHASING_STONE_PUSHING: + case GD_S_NUT_PUSHING: + case GD_S_NITRO_PACK_PUSHING: + case GD_S_BLADDER_PUSHING: + case GD_S_BOX_PUSHING: + return ACTION_PUSHING; + + case GD_S_STONE_FALLING: + case GD_S_MEGA_STONE_FALLING: + case GD_S_FLYING_STONE_FALLING: + case GD_S_NUT_FALLING: + case GD_S_DIRT_BALL_FALLING: + case GD_S_DIRT_LOOSE_FALLING: + case GD_S_NITRO_PACK_FALLING: + case GD_S_FALLING_WALL_FALLING: + return ACTION_FALLING; + + case GD_S_STONE_IMPACT: + case GD_S_MEGA_STONE_IMPACT: + case GD_S_FLYING_STONE_IMPACT: + case GD_S_NUT_IMPACT: + case GD_S_DIRT_BALL_IMPACT: + case GD_S_DIRT_LOOSE_IMPACT: + case GD_S_NITRO_PACK_IMPACT: + case GD_S_FALLING_WALL_IMPACT: + return ACTION_IMPACT; + + case GD_S_NUT_CRACKING: + return ACTION_BREAKING; + + case GD_S_EXPANDING_WALL: + case GD_S_WALL_REAPPEARING: + case GD_S_SLIME: + case GD_S_LAVA: + case GD_S_ACID_SPREADING: + return ACTION_GROWING; + + case GD_S_DIAMOND_COLLECTING: + case GD_S_FLYING_DIAMOND_COLLECTING: + case GD_S_SKELETON_COLLECTING: + case GD_S_PNEUMATIC_COLLECTING: + case GD_S_BOMB_COLLECTING: + case GD_S_CLOCK_COLLECTING: + case GD_S_SWEET_COLLECTING: + case GD_S_KEY_COLLECTING: + case GD_S_DIAMOND_KEY_COLLECTING: + return ACTION_COLLECTING; + + case GD_S_BOMB_PLACING: + case GD_S_REPLICATOR: + return ACTION_DROPPING; + + case GD_S_BLADDER_MOVING: + return ACTION_MOVING; + + case GD_S_BLADDER_SPENDER: + case GD_S_BLADDER_CONVERTING: + case GD_S_GRAVITY_CHANGING: + return ACTION_CHANGING; + + case GD_S_BITER_EATING: + return ACTION_EATING; + + case GD_S_DOOR_OPENING: + case GD_S_CRACKING: + return ACTION_OPENING; + + case GD_S_DIRT_WALKING: + return ACTION_DIGGING; + + case GD_S_EMPTY_WALKING: + 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_TELEPORTER: + return ACTION_PASSING; + + case GD_S_EXPLODING: + case GD_S_BOMB_EXPLODING: + case GD_S_GHOST_EXPLODING: + case GD_S_VOODOO_EXPLODING: + case GD_S_NITRO_PACK_EXPLODING: + return ACTION_EXPLODING; + + case GD_S_COVERING: + case GD_S_AMOEBA: + case GD_S_MAGIC_WALL: + case GD_S_PNEUMATIC_HAMMER: + case GD_S_WATER: + return ACTION_ACTIVE; + + case GD_S_DIAMOND_FALLING_RANDOM: + case GD_S_DIAMOND_FALLING_1: + case GD_S_DIAMOND_FALLING_2: + case GD_S_DIAMOND_FALLING_3: + case GD_S_DIAMOND_FALLING_4: + case GD_S_DIAMOND_FALLING_5: + case GD_S_DIAMOND_FALLING_6: + case GD_S_DIAMOND_FALLING_7: + case GD_S_DIAMOND_FALLING_8: + case GD_S_DIAMOND_IMPACT_RANDOM: + case GD_S_DIAMOND_IMPACT_1: + case GD_S_DIAMOND_IMPACT_2: + case GD_S_DIAMOND_IMPACT_3: + case GD_S_DIAMOND_IMPACT_4: + case GD_S_DIAMOND_IMPACT_5: + case GD_S_DIAMOND_IMPACT_6: + case GD_S_DIAMOND_IMPACT_7: + case GD_S_DIAMOND_IMPACT_8: + case GD_S_FLYING_DIAMOND_FALLING_RANDOM: + case GD_S_FLYING_DIAMOND_FALLING_1: + case GD_S_FLYING_DIAMOND_FALLING_2: + case GD_S_FLYING_DIAMOND_FALLING_3: + case GD_S_FLYING_DIAMOND_FALLING_4: + case GD_S_FLYING_DIAMOND_FALLING_5: + case GD_S_FLYING_DIAMOND_FALLING_6: + case GD_S_FLYING_DIAMOND_FALLING_7: + case GD_S_FLYING_DIAMOND_FALLING_8: + case GD_S_FLYING_DIAMOND_IMPACT_RANDOM: + case GD_S_FLYING_DIAMOND_IMPACT_1: + case GD_S_FLYING_DIAMOND_IMPACT_2: + case GD_S_FLYING_DIAMOND_IMPACT_3: + case GD_S_FLYING_DIAMOND_IMPACT_4: + case GD_S_FLYING_DIAMOND_IMPACT_5: + case GD_S_FLYING_DIAMOND_IMPACT_6: + case GD_S_FLYING_DIAMOND_IMPACT_7: + case GD_S_FLYING_DIAMOND_IMPACT_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: + // trigger special post-processing (and force sound to be non-looping) + return ACTION_OTHER; + + case GD_S_AMOEBA_MAGIC: + case GD_S_FINISHED: + // trigger special post-processing (and force sound to be looping) + 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 post-processing for some sounds + switch (sample) + { + case GD_S_DIAMOND_FALLING_RANDOM: + case GD_S_DIAMOND_FALLING_1: + case GD_S_DIAMOND_FALLING_2: + case GD_S_DIAMOND_FALLING_3: + case GD_S_DIAMOND_FALLING_4: + case GD_S_DIAMOND_FALLING_5: + case GD_S_DIAMOND_FALLING_6: + case GD_S_DIAMOND_FALLING_7: + case GD_S_DIAMOND_FALLING_8: + nr = (sample == GD_S_DIAMOND_FALLING_RANDOM ? GetSimpleRandom(8) : + sample - GD_S_DIAMOND_FALLING_1); + sound_effect = SND_BD_DIAMOND_FALLING_RANDOM_1 + nr; + + if (getSoundInfoEntryFilename(sound_effect) == NULL) + sound_effect = SND_BD_DIAMOND_FALLING; + break; + + case GD_S_DIAMOND_IMPACT_RANDOM: + case GD_S_DIAMOND_IMPACT_1: + case GD_S_DIAMOND_IMPACT_2: + case GD_S_DIAMOND_IMPACT_3: + case GD_S_DIAMOND_IMPACT_4: + case GD_S_DIAMOND_IMPACT_5: + case GD_S_DIAMOND_IMPACT_6: + case GD_S_DIAMOND_IMPACT_7: + case GD_S_DIAMOND_IMPACT_8: + nr = (sample == GD_S_DIAMOND_IMPACT_RANDOM ? GetSimpleRandom(8) : + sample - GD_S_DIAMOND_IMPACT_1); + sound_effect = SND_BD_DIAMOND_IMPACT_RANDOM_1 + nr; + + if (getSoundInfoEntryFilename(sound_effect) == NULL) + sound_effect = SND_BD_DIAMOND_IMPACT; + break; + + case GD_S_FLYING_DIAMOND_FALLING_RANDOM: + case GD_S_FLYING_DIAMOND_FALLING_1: + case GD_S_FLYING_DIAMOND_FALLING_2: + case GD_S_FLYING_DIAMOND_FALLING_3: + case GD_S_FLYING_DIAMOND_FALLING_4: + case GD_S_FLYING_DIAMOND_FALLING_5: + case GD_S_FLYING_DIAMOND_FALLING_6: + case GD_S_FLYING_DIAMOND_FALLING_7: + case GD_S_FLYING_DIAMOND_FALLING_8: + nr = (sample == GD_S_FLYING_DIAMOND_FALLING_RANDOM ? GetSimpleRandom(8) : + sample - GD_S_FLYING_DIAMOND_FALLING_1); + sound_effect = SND_BD_FLYING_DIAMOND_FALLING_RANDOM_1 + nr; + + if (getSoundInfoEntryFilename(sound_effect) == NULL) + sound_effect = SND_BD_FLYING_DIAMOND_FALLING; + break; + + case GD_S_FLYING_DIAMOND_IMPACT_RANDOM: + case GD_S_FLYING_DIAMOND_IMPACT_1: + case GD_S_FLYING_DIAMOND_IMPACT_2: + case GD_S_FLYING_DIAMOND_IMPACT_3: + case GD_S_FLYING_DIAMOND_IMPACT_4: + case GD_S_FLYING_DIAMOND_IMPACT_5: + case GD_S_FLYING_DIAMOND_IMPACT_6: + case GD_S_FLYING_DIAMOND_IMPACT_7: + case GD_S_FLYING_DIAMOND_IMPACT_8: + nr = (sample == GD_S_FLYING_DIAMOND_IMPACT_RANDOM ? GetSimpleRandom(8) : + sample - GD_S_FLYING_DIAMOND_IMPACT_1); + sound_effect = SND_BD_FLYING_DIAMOND_IMPACT_RANDOM_1 + nr; + + if (getSoundInfoEntryFilename(sound_effect) == NULL) + sound_effect = SND_BD_FLYING_DIAMOND_IMPACT; + 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_BONUS_LIFE: + sound_effect = SND_GAME_HEALTH_BONUS; + break; + + case GD_S_AMOEBA_MAGIC: + sound_effect = SND_BD_AMOEBA_OTHER; + break; + + case GD_S_FINISHED: + sound_effect = SND_GAME_LEVELTIME_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_game(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; + + // some sound actions are always looping in native BD game engine + if (sound_action == ACTION_DEFAULT) + is_loop_sound = TRUE; + + // some sound actions are always non-looping in native BD game engine + if (sound_action == ACTION_FALLING || + sound_action == ACTION_MOVING || + 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_game(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_game(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); @@ -15635,9 +16233,15 @@ void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message) } if (network.enabled) + { SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER); + } 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(); @@ -15694,7 +16298,17 @@ static void RequestRestartGame(void) int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM); int door_state = DOOR_CLOSE_1; - if (Request(message, request_mode | REQ_STAY_OPEN) && has_started_game) + boolean restart_wanted = (Request(message, request_mode | REQ_STAY_OPEN) && has_started_game); + + // if no restart wanted, continue with next level for BD style intermission levels + if (!restart_wanted && !level_editor_test_game && level.bd_intermission) + { + boolean success = AdvanceToNextLevel(); + + restart_wanted = (success && setup.auto_play_next_level); + } + + if (restart_wanted) { CloseDoor(door_state); @@ -15737,8 +16351,12 @@ boolean CheckRestartGame(void) return FALSE; } - // do not handle game over if request dialog is already active - if (game.request_active) + // do not ask to play again if request dialog is already active + if (checkRequestActive()) + return FALSE; + + // do not ask to play again if request dialog already handled + if (game.RestartGameRequested) return FALSE; // do not ask to play again if game was never actually played @@ -15749,11 +16367,35 @@ boolean CheckRestartGame(void) if (!setup.ask_on_game_over) return FALSE; + game.RestartGameRequested = TRUE; + RequestRestartGame(); 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 @@ -15762,7 +16404,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); @@ -15777,6 +16421,11 @@ boolean checkGameEnded(void) return (checkGameSolved() || checkGameFailed()); } +boolean checkRequestActive(void) +{ + return (game.request_active || game.envelope_active || game.any_door_active); +} + // ---------------------------------------------------------------------------- // random generator functions @@ -15958,6 +16607,7 @@ static ListNode *SaveEngineSnapshotBuffers(void) SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames)); SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed)); SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft)); + SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTimeFrames)); SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime)); SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir)); @@ -16173,6 +16823,11 @@ static struct GAME_CTRL_ID_LOAD, NULL, TRUE, FALSE, "load game" }, + { + IMG_GFX_GAME_BUTTON_RESTART, &game.button.restart, + GAME_CTRL_ID_RESTART, NULL, + TRUE, FALSE, "restart game" + }, { IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop, GAME_CTRL_ID_PANEL_STOP, NULL, @@ -16188,6 +16843,11 @@ static struct GAME_CTRL_ID_PANEL_PLAY, NULL, FALSE, FALSE, "play game" }, + { + IMG_GFX_GAME_BUTTON_PANEL_RESTART, &game.button.panel_restart, + GAME_CTRL_ID_PANEL_RESTART, NULL, + FALSE, FALSE, "restart game" + }, { IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop, GAME_CTRL_ID_TOUCH_STOP, NULL, @@ -16198,6 +16858,11 @@ static struct GAME_CTRL_ID_TOUCH_PAUSE, NULL, FALSE, TRUE, "pause game" }, + { + IMG_GFX_GAME_BUTTON_TOUCH_RESTART, &game.button.touch_restart, + GAME_CTRL_ID_TOUCH_RESTART, NULL, + FALSE, TRUE, "restart game" + }, { IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music, SOUND_CTRL_ID_MUSIC, &setup.sound_music, @@ -16277,7 +16942,10 @@ void CreateGameButtons(void) id == GAME_CTRL_ID_PLAY || id == GAME_CTRL_ID_PANEL_PLAY || id == GAME_CTRL_ID_SAVE || - id == GAME_CTRL_ID_LOAD) + id == GAME_CTRL_ID_LOAD || + id == GAME_CTRL_ID_RESTART || + id == GAME_CTRL_ID_PANEL_RESTART || + id == GAME_CTRL_ID_TOUCH_RESTART) { button_type = GD_TYPE_NORMAL_BUTTON; checked = FALSE; @@ -16400,6 +17068,10 @@ void ModifyPauseButtons(void) }; int i; + // do not redraw pause button on closed door (may happen when restarting game) + if (!(GetDoorState() & DOOR_OPEN_1)) + return; + for (i = 0; ids[i] > -1; i++) ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END); } @@ -16621,6 +17293,13 @@ static void HandleGameButtonsExt(int id, int button) TapeQuickLoad(); break; + case GAME_CTRL_ID_RESTART: + case GAME_CTRL_ID_PANEL_RESTART: + case GAME_CTRL_ID_TOUCH_RESTART: + TapeRestartGame(); + + break; + case SOUND_CTRL_ID_MUSIC: case SOUND_CTRL_ID_PANEL_MUSIC: if (setup.sound_music)