X-Git-Url: https://git.artsoft.org/?a=blobdiff_plain;f=src%2Fgame.c;h=4bd3f46cbf61cccdc10ef407d2b9578ab508d284;hb=0ff3f484ffbb8e3404e1dc4536a292335369f2d2;hp=1f3b8e02fb64939b490c64c05318e8f3220ce748;hpb=e1feae09b7599525167992970fcc1c731214cc97;p=rocksndiamonds.git diff --git a/src/game.c b/src/game.c index 1f3b8e02..4bd3f46c 100644 --- a/src/game.c +++ b/src/game.c @@ -101,11 +101,20 @@ #define GET_MAX_MOVE_DELAY(e) ( (element_info[e].move_delay_fixed) + \ (element_info[e].move_delay_random)) +#if 1 +#define ELEMENT_CAN_ENTER_FIELD_GENERIC(e, x, y, condition) \ + (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \ + (condition) || \ + (DONT_COLLIDE_WITH(e) && \ + IS_PLAYER(x, y) && \ + !PLAYER_ENEMY_PROTECTED(x, y)))) +#else #define ELEMENT_CAN_ENTER_FIELD_GENERIC(e, x, y, condition) \ (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \ (condition) || \ (DONT_COLLIDE_WITH(e) && \ IS_FREE_OR_PLAYER(x, y)))) +#endif #define ELEMENT_CAN_ENTER_FIELD_GENERIC_2(x, y, condition) \ (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \ @@ -144,6 +153,7 @@ Feld[x][y] == EL_EXIT_OPEN || \ Feld[x][y] == EL_ACID)) +#if 0 #if 1 #define MAZE_RUNNER_CAN_ENTER_FIELD(x, y) \ (IN_LEV_FIELD(x, y) && IS_FREE(x, y)) @@ -152,6 +162,32 @@ (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \ IS_FOOD_DARK_YAMYAM(Feld[x][y]))) #endif +#endif + +#define GROUP_NR(e) ((e) - EL_GROUP_START) +#define MOVE_ENTER_EL(e) (element_info[e].move_enter_element) +#define IS_IN_GROUP(e, nr) (element_info[e].in_group[nr] == TRUE) +#define IS_IN_GROUP_EL(e, ge) (IS_IN_GROUP(e, (ge) - EL_GROUP_START)) + +#define IS_EQUAL_OR_IN_GROUP(e, ge) \ + (IS_GROUP_ELEMENT(ge) ? IS_IN_GROUP(e, GROUP_NR(ge)) : (e) == (ge)) + +#if 1 +#define CE_ENTER_FIELD_COND(e, x, y) \ + (!IS_PLAYER(x, y) && \ + (Feld[x][y] == EL_ACID || \ + IS_EQUAL_OR_IN_GROUP(Feld[x][y], MOVE_ENTER_EL(e)))) +#else +#define CE_ENTER_FIELD_COND(e, x, y) \ + (!IS_PLAYER(x, y) && \ + (Feld[x][y] == EL_ACID || \ + Feld[x][y] == MOVE_ENTER_EL(e) || \ + (IS_GROUP_ELEMENT(MOVE_ENTER_EL(e)) && \ + IS_IN_GROUP_EL(Feld[x][y], MOVE_ENTER_EL(e))))) +#endif + +#define CUSTOM_ELEMENT_CAN_ENTER_FIELD(e, x, y) \ + ELEMENT_CAN_ENTER_FIELD_GENERIC(e, x, y, CE_ENTER_FIELD_COND(e, x, y)) #define MOLE_CAN_ENTER_FIELD(x, y, condition) \ (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || (condition))) @@ -180,7 +216,8 @@ static void ScrollScreen(struct PlayerInfo *, int); static void InitBeltMovement(void); static void CloseAllOpenTimegates(void); static void CheckGravityMovement(struct PlayerInfo *); -static void KillHeroUnlessProtected(int, int); +static void KillHeroUnlessEnemyProtected(int, int); +static void KillHeroUnlessExplosionProtected(int, int); static void TestIfPlayerTouchesCustomElement(int, int); static void TestIfElementTouchesCustomElement(int, int); @@ -570,6 +607,10 @@ static void InitPlayerField(int x, int y, int element, boolean init_game) player->present = TRUE; + player->block_last_field = (element == EL_SP_MURPHY ? + level.sp_block_last_field : + level.block_last_field); + if (!options.network || player->connected) { player->active = TRUE; @@ -609,6 +650,18 @@ static void InitField(int x, int y, boolean init_game) InitPlayerField(x, y, element, init_game); break; + case EL_SOKOBAN_FIELD_PLAYER: + element = Feld[x][y] = EL_PLAYER_1; + InitField(x, y, init_game); + + element = Feld[x][y] = EL_SOKOBAN_FIELD_EMPTY; + InitField(x, y, init_game); + break; + + case EL_SOKOBAN_FIELD_EMPTY: + local_player->sokobanfields_still_needed++; + break; + case EL_STONEBLOCK: if (x < lev_fieldx-1 && Feld[x+1][y] == EL_ACID) Feld[x][y] = EL_ACID_POOL_TOPLEFT; @@ -686,10 +739,6 @@ static void InitField(int x, int y, boolean init_game) local_player->lights_still_needed++; break; - case EL_SOKOBAN_FIELD_EMPTY: - local_player->sokobanfields_still_needed++; - break; - case EL_PENGUIN: local_player->friends_still_needed++; break; @@ -766,9 +815,22 @@ static void InitField(int x, int y, boolean init_game) else if (IS_GROUP_ELEMENT(element)) { struct ElementGroupInfo *group = element_info[element].group; - int random_pos = RND(group->num_elements_resolved); + int last_anim_random_frame = gfx.anim_random_frame; + int element_pos; + + if (group->choice_mode == ANIM_RANDOM) + gfx.anim_random_frame = RND(group->num_elements_resolved); + + element_pos = getAnimationFrame(group->num_elements_resolved, 1, + group->choice_mode, 0, + group->choice_pos); + + if (group->choice_mode == ANIM_RANDOM) + gfx.anim_random_frame = last_anim_random_frame; - Feld[x][y] = group->element_resolved[random_pos]; + group->choice_pos++; + + Feld[x][y] = group->element_resolved[element_pos]; InitField(x, y, init_game); } @@ -809,7 +871,7 @@ static void resolve_group_element(int group_element, int recursion_depth) group_element - EL_GROUP_START + 1); /* replace element which caused too deep recursion by question mark */ - group->element_resolved[group->num_elements_resolved++] = EL_CHAR_QUESTION; + group->element_resolved[group->num_elements_resolved++] = EL_UNKNOWN; return; } @@ -817,8 +879,10 @@ static void resolve_group_element(int group_element, int recursion_depth) if (recursion_depth == 0) /* initialization */ { group = element_info[group_element].group; - group->num_elements_resolved = 0; group_nr = group_element - EL_GROUP_START; + + group->num_elements_resolved = 0; + group->choice_pos = 0; } for (i = 0; i < actual_group->num_elements; i++) @@ -1004,7 +1068,16 @@ static void InitGameEngine() { int trigger_element = ei->change_page[j].trigger_element; - trigger_events[trigger_element] |= ei->change_page[j].events; + if (IS_GROUP_ELEMENT(trigger_element)) + { + struct ElementGroupInfo *group = element_info[trigger_element].group; + + for (k = 0; k < group->num_elements_resolved; k++) + trigger_events[group->element_resolved[k]] + |= ei->change_page[j].events; + } + else + trigger_events[trigger_element] |= ei->change_page[j].events; } } } @@ -1037,6 +1110,19 @@ static void InitGameEngine() element_info[e].push_delay_random = push_delay_list[i].push_delay_random; } + /* set push delay value for Supaplex elements for newer engine versions */ + if (game.engine_version >= VERSION_IDENT(3,0,9,0)) + { + for (i = 0; i < MAX_NUM_ELEMENTS; i++) + { + if (IS_SP_ELEMENT(i)) + { + element_info[i].push_delay_fixed = 6; + element_info[i].push_delay_random = 0; + } + } + } + /* ---------- initialize move stepsize ----------------------------------- */ /* initialize move stepsize values to default */ @@ -1052,6 +1138,14 @@ static void InitGameEngine() element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize; } + /* ---------- initialize move dig/leave ---------------------------------- */ + + for (i = 0; i < MAX_NUM_ELEMENTS; i++) + { + element_info[i].can_leave_element = FALSE; + element_info[i].can_leave_element_last = FALSE; + } + /* ---------- initialize gem count --------------------------------------- */ /* initialize gem count values for each element */ @@ -1134,6 +1228,8 @@ void InitGame() player->use_murphy_graphic = FALSE; + player->block_last_field = FALSE; + player->actual_frame_counter = 0; player->step_counter = 0; @@ -1289,6 +1385,7 @@ void InitGame() ChangeEvent[x][y] = CE_BITMASK_DEFAULT; ExplodePhase[x][y] = 0; + ExplodeDelay[x][y] = 0; ExplodeField[x][y] = EX_NO_EXPLOSION; RunnerVisit[x][y] = 0; @@ -1323,6 +1420,30 @@ void InitGame() emulate_sb ? EMU_SOKOBAN : emulate_sp ? EMU_SUPAPLEX : EMU_NONE); + /* initialize explosion and ignition delay */ + for (i = 0; i < MAX_NUM_ELEMENTS; i++) + { + if (!IS_CUSTOM_ELEMENT(i)) + { + int num_phase = 9; + int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2); + int last_phase = num_phase * delay; + int half_phase = (num_phase / 2) * delay; + + element_info[i].explosion_delay = last_phase; + element_info[i].ignition_delay = half_phase; + + if (i == EL_BLACK_ORB) + element_info[i].ignition_delay = 1; + } + + if (element_info[i].explosion_delay < 2) /* !!! check again !!! */ + element_info[i].explosion_delay = 2; + + if (element_info[i].ignition_delay < 1) /* !!! check again !!! */ + element_info[i].ignition_delay = 1; + } + /* correct non-moving belts to start moving left */ for (i = 0; i < 4; i++) if (game.belt_dir[i] == MV_NO_MOVING) @@ -1345,7 +1466,9 @@ void InitGame() { player->present = TRUE; player->active = TRUE; + some_player->present = FALSE; + some_player->active = FALSE; StorePlayer[jx][jy] = player->element_nr; player->jx = player->last_jx = jx; @@ -1359,7 +1482,7 @@ void InitGame() if (tape.playing) { - /* when playing a tape, eliminate all players who do not participate */ + /* when playing a tape, eliminate all players which do not participate */ for (i = 0; i < MAX_PLAYERS; i++) { @@ -1390,6 +1513,8 @@ void InitGame() int jx = player->jx, jy = player->jy; player->active = FALSE; + player->present = FALSE; + StorePlayer[jx][jy] = 0; Feld[jx][jy] = EL_EMPTY; } @@ -1690,23 +1815,37 @@ void InitMovDir(int x, int y) default: if (IS_CUSTOM_ELEMENT(element)) { - if (element_info[element].move_direction_initial != MV_NO_MOVING) - MovDir[x][y] = element_info[element].move_direction_initial; - else if (element_info[element].move_pattern == MV_ALL_DIRECTIONS || - element_info[element].move_pattern == MV_TURNING_LEFT || - element_info[element].move_pattern == MV_TURNING_RIGHT || - element_info[element].move_pattern == MV_TURNING_LEFT_RIGHT || - element_info[element].move_pattern == MV_TURNING_RIGHT_LEFT || - element_info[element].move_pattern == MV_TURNING_RANDOM) + struct ElementInfo *ei = &element_info[element]; + int move_direction_initial = ei->move_direction_initial; + int move_pattern = ei->move_pattern; + + if (move_direction_initial == MV_START_PREVIOUS) + { + if (MovDir[x][y] != MV_NO_MOVING) + return; + + move_direction_initial = MV_START_AUTOMATIC; + } + + if (move_direction_initial == MV_START_RANDOM) + MovDir[x][y] = 1 << RND(4); + else if (move_direction_initial & MV_ANY_DIRECTION) + MovDir[x][y] = move_direction_initial; + else if (move_pattern == MV_ALL_DIRECTIONS || + move_pattern == MV_TURNING_LEFT || + move_pattern == MV_TURNING_RIGHT || + move_pattern == MV_TURNING_LEFT_RIGHT || + move_pattern == MV_TURNING_RIGHT_LEFT || + move_pattern == MV_TURNING_RANDOM) MovDir[x][y] = 1 << RND(4); - else if (element_info[element].move_pattern == MV_HORIZONTAL) + else if (move_pattern == MV_HORIZONTAL) MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT); - else if (element_info[element].move_pattern == MV_VERTICAL) + else if (move_pattern == MV_VERTICAL) MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN); - else if (element_info[element].move_pattern & MV_ANY_DIRECTION) + else if (move_pattern & MV_ANY_DIRECTION) MovDir[x][y] = element_info[element].move_pattern; - else if (element_info[element].move_pattern == MV_ALONG_LEFT_SIDE || - element_info[element].move_pattern == MV_ALONG_RIGHT_SIDE) + else if (move_pattern == MV_ALONG_LEFT_SIDE || + move_pattern == MV_ALONG_RIGHT_SIDE) { for (i = 0; i < 4; i++) { @@ -1715,7 +1854,7 @@ void InitMovDir(int x, int y) if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1)) { - if (element_info[element].move_pattern == MV_ALONG_RIGHT_SIDE) + if (move_pattern == MV_ALONG_RIGHT_SIDE) MovDir[x][y] = direction[0][i]; else MovDir[x][y] = direction[1][i]; @@ -2225,6 +2364,10 @@ void CheckDynamite(int x, int y) void RelocatePlayer(int x, int y, int element) { struct PlayerInfo *player = &stored_player[element - EL_PLAYER_1]; + boolean ffwd_delay = (tape.playing && tape.fast_forward); + boolean no_delay = (tape.index_search); + int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay); + int wait_delay_value = (no_delay ? 0 : frame_delay_value); if (player->GameOver) /* do not reanimate dead player */ return; @@ -2245,7 +2388,7 @@ void RelocatePlayer(int x, int y, int element) DrawPlayer(player); BackToFront(); - Delay(GAME_FRAME_DELAY); + Delay(wait_delay_value); } DrawPlayer(player); /* needed here only to cleanup last field */ @@ -2289,11 +2432,11 @@ void RelocatePlayer(int x, int y, int element) /* scroll in two steps of half tile size to make things smoother */ BlitBitmap(drawto_field, window, fx, fy, SXSIZE, SYSIZE, SX, SY); FlushDisplay(); - Delay(GAME_FRAME_DELAY); + Delay(wait_delay_value); /* scroll second step to align at full tile size */ BackToFront(); - Delay(GAME_FRAME_DELAY); + Delay(wait_delay_value); } } } @@ -2306,6 +2449,9 @@ void Explode(int ex, int ey, int phase, int mode) int last_phase = num_phase * delay; int half_phase = (num_phase / 2) * delay; int first_phase_after_start = EX_PHASE_START + 1; + int border_element; + + int last_phase_TEST = last_phase; if (game.explosions_delayed) { @@ -2340,6 +2486,10 @@ void Explode(int ex, int ey, int phase, int mode) Feld[ex][ey] = center_element; } +#if 1 + last_phase = element_info[center_element].explosion_delay; +#endif + for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++) { int xx = x - ex + 1; @@ -2419,22 +2569,22 @@ void Explode(int ex, int ey, int phase, int mode) RemoveField(x, y); #endif - if (IS_PLAYER(ex, ey) && !PLAYER_PROTECTED(ex, ey)) + if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey)) { switch(StorePlayer[ex][ey]) { case EL_PLAYER_2: - Store[x][y] = EL_EMERALD_RED; + Store[x][y] = EL_PLAYER_IS_EXPLODING_2; break; case EL_PLAYER_3: - Store[x][y] = EL_EMERALD; + Store[x][y] = EL_PLAYER_IS_EXPLODING_3; break; case EL_PLAYER_4: - Store[x][y] = EL_EMERALD_PURPLE; + Store[x][y] = EL_PLAYER_IS_EXPLODING_4; break; case EL_PLAYER_1: default: - Store[x][y] = EL_EMERALD_YELLOW; + Store[x][y] = EL_PLAYER_IS_EXPLODING_1; break; } @@ -2510,6 +2660,9 @@ void Explode(int ex, int ey, int phase, int mode) #endif ExplodePhase[x][y] = 1; +#if 1 + ExplodeDelay[x][y] = last_phase; +#endif Stop[x][y] = TRUE; } @@ -2526,6 +2679,10 @@ void Explode(int ex, int ey, int phase, int mode) x = ex; y = ey; +#if 1 + last_phase = ExplodeDelay[x][y]; +#endif + ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0); #ifdef DEBUG @@ -2546,6 +2703,127 @@ void Explode(int ex, int ey, int phase, int mode) } #endif +#if 1 + + border_element = Store2[x][y]; + if (IS_PLAYER(x, y)) + border_element = StorePlayer[x][y]; + + if (phase == element_info[border_element].ignition_delay || + phase == last_phase) + { + if (IS_PLAYER(x, y)) + { + if (phase == 2) + printf("::: IS_PLAYER\n"); + + KillHeroUnlessExplosionProtected(x, y); + return; + } + else if (CAN_EXPLODE_BY_FIRE(border_element)) + { + if (phase == 2) + printf("::: CAN_EXPLODE_BY_FIRE\n"); + + Feld[x][y] = Store2[x][y]; + Store2[x][y] = 0; + Bang(x, y); + return; + } + else if (border_element == EL_AMOEBA_TO_DIAMOND) + { + if (phase == 2) + printf("::: EL_AMOEBA_TO_DIAMOND\n"); + + AmoebeUmwandeln(x, y); + return; + } + } + + if (phase == last_phase) + { + int element; + + element = Feld[x][y] = Store[x][y]; + Store[x][y] = Store2[x][y] = 0; + GfxElement[x][y] = EL_UNDEFINED; + + /* player can escape from explosions and might therefore be still alive */ + if (element >= EL_PLAYER_IS_EXPLODING_1 && + element <= EL_PLAYER_IS_EXPLODING_4) + Feld[x][y] = (stored_player[element - EL_PLAYER_IS_EXPLODING_1].active ? + EL_EMPTY : + element == EL_PLAYER_IS_EXPLODING_1 ? EL_EMERALD_YELLOW : + element == EL_PLAYER_IS_EXPLODING_2 ? EL_EMERALD_RED : + element == EL_PLAYER_IS_EXPLODING_3 ? EL_EMERALD : + EL_EMERALD_PURPLE); + + /* restore probably existing indestructible background element */ + if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y])) + element = Feld[x][y] = Back[x][y]; + Back[x][y] = 0; + + MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0; + GfxDir[x][y] = MV_NO_MOVING; + ChangeDelay[x][y] = 0; + ChangePage[x][y] = -1; + + InitField(x, y, FALSE); +#if 1 + /* !!! not needed !!! */ + if (CAN_MOVE(element)) + InitMovDir(x, y); +#endif + DrawLevelField(x, y); + + TestIfElementTouchesCustomElement(x, y); + + if (GFX_CRUMBLED(element)) + DrawLevelFieldCrumbledSandNeighbours(x, y); + + if (IS_PLAYER(x, y) && !PLAYERINFO(x,y)->present) + StorePlayer[x][y] = 0; + + if (ELEM_IS_PLAYER(element)) + RelocatePlayer(x, y, element); + } + else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y))) + { +#if 1 + int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING); +#else + int stored = Store[x][y]; + int graphic = (game.emulation != EMU_SUPAPLEX ? IMG_EXPLOSION : + stored == EL_SP_INFOTRON ? IMG_SP_EXPLOSION_INFOTRON : + IMG_SP_EXPLOSION); +#endif + int frame = getGraphicAnimationFrame(graphic, phase - delay); + +#if 0 + printf("::: %d ['%s'] -> %d\n", GfxElement[x][y], + element_info[GfxElement[x][y]].token_name, + graphic); +#endif + + if (phase == delay) + DrawLevelFieldCrumbledSand(x, y); + + if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY) + { + DrawLevelElement(x, y, Back[x][y]); + DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame); + } + else if (IS_WALKABLE_UNDER(Back[x][y])) + { + DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame); + DrawLevelElementThruMask(x, y, Back[x][y]); + } + else if (!IS_WALKABLE_INSIDE(Back[x][y])) + DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame); + } + +#else + if (phase == first_phase_after_start) { int element = Store2[x][y]; @@ -2562,7 +2840,7 @@ void Explode(int ex, int ey, int phase, int mode) int element = Store2[x][y]; if (IS_PLAYER(x, y)) - KillHeroUnlessProtected(x, y); + KillHeroUnlessExplosionProtected(x, y); else if (CAN_EXPLODE_BY_FIRE(element)) { Feld[x][y] = Store2[x][y]; @@ -2581,6 +2859,17 @@ void Explode(int ex, int ey, int phase, int mode) Store[x][y] = Store2[x][y] = 0; GfxElement[x][y] = EL_UNDEFINED; + /* player can escape from explosions and might therefore be still alive */ + if (element >= EL_PLAYER_IS_EXPLODING_1 && + element <= EL_PLAYER_IS_EXPLODING_4) + Feld[x][y] = (stored_player[element - EL_PLAYER_IS_EXPLODING_1].active ? + EL_EMPTY : + element == EL_PLAYER_IS_EXPLODING_1 ? EL_EMERALD_YELLOW : + element == EL_PLAYER_IS_EXPLODING_2 ? EL_EMERALD_RED : + element == EL_PLAYER_IS_EXPLODING_3 ? EL_EMERALD : + EL_EMERALD_PURPLE); + + /* restore probably existing indestructible background element */ if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y])) element = Feld[x][y] = Back[x][y]; Back[x][y] = 0; @@ -2591,8 +2880,11 @@ void Explode(int ex, int ey, int phase, int mode) ChangePage[x][y] = -1; InitField(x, y, FALSE); +#if 1 + /* !!! not needed !!! */ if (CAN_MOVE(element)) InitMovDir(x, y); +#endif DrawLevelField(x, y); TestIfElementTouchesCustomElement(x, y); @@ -2640,6 +2932,7 @@ void Explode(int ex, int ey, int phase, int mode) else if (!IS_WALKABLE_INSIDE(Back[x][y])) DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame); } +#endif } void DynaExplode(int ex, int ey) @@ -2705,7 +2998,7 @@ void Bang(int x, int y) #endif #if 1 - if (IS_PLAYER(x, y) && !PLAYER_PROTECTED(x, y)) + if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y)) #else if (IS_PLAYER(x, y)) #endif @@ -3196,7 +3489,7 @@ void Impact(int x, int y) if (impact && element == EL_AMOEBA_DROP) { if (object_hit && IS_PLAYER(x, y + 1)) - KillHeroUnlessProtected(x, y + 1); + KillHeroUnlessEnemyProtected(x, y + 1); else if (object_hit && smashed == EL_PENGUIN) Bang(x, y + 1); else @@ -3238,7 +3531,7 @@ void Impact(int x, int y) { if (CAN_SMASH_PLAYER(element)) { - KillHeroUnlessProtected(x, y + 1); + KillHeroUnlessEnemyProtected(x, y + 1); return; } } @@ -3258,11 +3551,13 @@ void Impact(int x, int y) return; } } - else if ((element == EL_SP_INFOTRON || - element == EL_SP_ZONK) && - (smashed == EL_SP_SNIKSNAK || - smashed == EL_SP_ELECTRON || - smashed == EL_SP_DISK_ORANGE)) + else if (((element == EL_SP_INFOTRON || + element == EL_SP_ZONK) && + (smashed == EL_SP_SNIKSNAK || + smashed == EL_SP_ELECTRON || + smashed == EL_SP_DISK_ORANGE)) || + (element == EL_SP_INFOTRON && + smashed == EL_SP_DISK_YELLOW)) { Bang(x, y + 1); return; @@ -3793,8 +4088,10 @@ inline static void TurnRoundExt(int x, int y) move_pattern == MV_TURNING_RANDOM || move_pattern == MV_ALL_DIRECTIONS) { - boolean can_turn_left = ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y); - boolean can_turn_right = ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y); + boolean can_turn_left = + CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y); + boolean can_turn_right = + CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y); if (move_pattern == MV_TURNING_LEFT) MovDir[x][y] = left_dir; @@ -3838,9 +4135,9 @@ inline static void TurnRoundExt(int x, int y) } else if (move_pattern == MV_ALONG_LEFT_SIDE) { - if (ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y)) + if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y)) MovDir[x][y] = left_dir; - else if (!ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y)) + else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y)) MovDir[x][y] = right_dir; if (MovDir[x][y] != old_move_dir) @@ -3848,9 +4145,9 @@ inline static void TurnRoundExt(int x, int y) } else if (move_pattern == MV_ALONG_RIGHT_SIDE) { - if (ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y)) + if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y)) MovDir[x][y] = right_dir; - else if (!ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y)) + else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y)) MovDir[x][y] = left_dir; if (MovDir[x][y] != old_move_dir) @@ -3910,14 +4207,14 @@ inline static void TurnRoundExt(int x, int y) new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL); Moving2Blocked(x, y, &newx, &newy); - if (ELEMENT_CAN_ENTER_FIELD_OR_ACID(element, newx, newy)) + if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy)) return; MovDir[x][y] = new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL); Moving2Blocked(x, y, &newx, &newy); - if (ELEMENT_CAN_ENTER_FIELD_OR_ACID(element, newx, newy)) + if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy)) return; MovDir[x][y] = old_move_dir; @@ -3931,8 +4228,7 @@ inline static void TurnRoundExt(int x, int y) MovDelay[x][y] = 0; } - else if (move_pattern & MV_MAZE_RUNNER_STYLE || - element == EL_MAZE_RUNNER) + else if (move_pattern & MV_MAZE_RUNNER_STYLE) { static int test_xy[7][2] = { @@ -3976,7 +4272,7 @@ inline static void TurnRoundExt(int x, int y) break; } - if (!MAZE_RUNNER_CAN_ENTER_FIELD(xx, yy)) + if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy)) continue; move_dir_preference = -1 * RunnerVisit[xx][yy]; @@ -4576,10 +4872,11 @@ void StartMoving(int x, int y) if (DONT_COLLIDE_WITH(element) && IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) && - !PLAYER_PROTECTED(newx, newy)) + !PLAYER_ENEMY_PROTECTED(newx, newy)) { #if 1 TestIfBadThingRunsIntoHero(x, y, MovDir[x][y]); + return; #else /* player killed by element which is deadly when colliding with */ @@ -4638,6 +4935,7 @@ void StartMoving(int x, int y) DrawPlayerField(x, y); else DrawLevelField(x, y); + return; } } @@ -4661,42 +4959,80 @@ void StartMoving(int x, int y) DrawPlayerField(x, y); else DrawLevelField(x, y); + return; } } - else if ((move_pattern & MV_MAZE_RUNNER_STYLE || - element == EL_MAZE_RUNNER) && IN_LEV_FIELD(newx, newy)) - { + #if 1 - if (IS_FREE(newx, newy)) -#else - if (IS_FOOD_DARK_YAMYAM(Feld[newx][newy])) + + /* + else if (move_pattern & MV_MAZE_RUNNER_STYLE && IN_LEV_FIELD(newx, newy)) + */ + + else if (IS_CUSTOM_ELEMENT(element) && + CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy) + +#if 0 + && + !IS_FREE(newx, newy) #endif + +) + { + int new_element = Feld[newx][newy]; + +#if 0 + printf("::: '%s' digs '%s' [%d]\n", + element_info[element].token_name, + element_info[Feld[newx][newy]].token_name, + StorePlayer[newx][newy]); +#endif + + if (!IS_FREE(newx, newy)) { + int action = (IS_DIGGABLE(new_element) ? ACTION_DIGGING : + IS_COLLECTIBLE(new_element) ? ACTION_COLLECTING : + ACTION_BREAKING); + + /* no element can dig solid indestructible elements */ + if (IS_INDESTRUCTIBLE(new_element) && + !IS_DIGGABLE(new_element) && + !IS_COLLECTIBLE(new_element)) + return; + + if (AmoebaNr[newx][newy] && + (new_element == EL_AMOEBA_FULL || + new_element == EL_BD_AMOEBA || + new_element == EL_AMOEBA_GROWING)) + { + AmoebaCnt[AmoebaNr[newx][newy]]--; + AmoebaCnt2[AmoebaNr[newx][newy]]--; + } + if (IS_MOVING(newx, newy)) RemoveMovingField(newx, newy); else { - Feld[newx][newy] = EL_EMPTY; + RemoveField(newx, newy); DrawLevelField(newx, newy); } - PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING); + PlayLevelSoundAction(x, y, action); } - else if (!IS_FREE(newx, newy)) + + if (new_element == element_info[element].move_enter_element) + element_info[element].can_leave_element = TRUE; + + if (move_pattern & MV_MAZE_RUNNER_STYLE) { -#if 0 - if (IS_PLAYER(x, y)) - DrawPlayerField(x, y); - else - DrawLevelField(x, y); -#endif - return; + RunnerVisit[x][y] = FrameCounter; + PlayerVisit[x][y] /= 8; /* expire player visit path */ } - - RunnerVisit[x][y] = FrameCounter; - PlayerVisit[x][y] /= 8; /* expire player visit path */ } + +#endif + else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy)) { if (!IS_FREE(newx, newy)) @@ -4804,6 +5140,7 @@ void StartMoving(int x, int y) DrawLevelField(x, y); MovDelay[newx][newy] = 0; /* start amoeba shrinking delay */ + return; /* wait for shrinking amoeba */ } else /* element == EL_PACMAN */ @@ -4867,6 +5204,7 @@ void StartMoving(int x, int y) void ContinueMoving(int x, int y) { int element = Feld[x][y]; + struct ElementInfo *ei = &element_info[element]; int direction = MovDir[x][y]; int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0); int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0); @@ -4983,6 +5321,24 @@ void ContinueMoving(int x, int y) ResetGfxAnimation(x, y); /* reset animation values for old field */ +#if 1 + /* some elements can leave other elements behind after moving */ + if (IS_CUSTOM_ELEMENT(element) && !IS_PLAYER(x, y) && + ei->move_leave_element != EL_EMPTY && + (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || + ei->can_leave_element_last)) + { + Feld[x][y] = ei->move_leave_element; + InitField(x, y, FALSE); + + if (GFX_CRUMBLED(Feld[x][y])) + DrawLevelFieldCrumbledSandNeighbours(x, y); + } + + ei->can_leave_element_last = ei->can_leave_element; + ei->can_leave_element = FALSE; +#endif + #if 0 /* 2.1.1 (does not work correctly for spring) */ if (!CAN_MOVE(element)) @@ -5042,7 +5398,7 @@ void ContinueMoving(int x, int y) Impact(x, newy); #if 1 - TestIfElementTouchesCustomElement(x, y); /* for empty space */ + TestIfElementTouchesCustomElement(x, y); /* empty or new element */ #endif #if 0 @@ -5067,15 +5423,7 @@ void ContinueMoving(int x, int y) #if 0 if (IN_LEV_FIELD(nextx, nexty)) { - static int opposite_directions[] = - { - MV_RIGHT, - MV_LEFT, - MV_DOWN, - MV_UP - }; - int move_dir_bit = MV_DIR_BIT(direction); - int opposite_direction = opposite_directions[move_dir_bit]; + int opposite_direction = MV_DIR_OPPOSITE(direction); int hitting_side = direction; int touched_side = opposite_direction; int touched_element = MovingOrBlocked2Element(nextx, nexty); @@ -6034,9 +6382,11 @@ static void ChangeActiveTrap(int x, int y) static void ChangeElementNowExt(int x, int y, int target_element) { + int previous_move_direction = MovDir[x][y]; + /* check if element under player changes from accessible to unaccessible (needed for special case of dropping element which then changes) */ - if (IS_PLAYER(x, y) && !PLAYER_PROTECTED(x, y) && + if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y) && IS_ACCESSIBLE(Feld[x][y]) && !IS_ACCESSIBLE(target_element)) { Bang(x, y); @@ -6051,6 +6401,9 @@ static void ChangeElementNowExt(int x, int y, int target_element) ResetGfxAnimation(x, y); ResetRandomAnimationValue(x, y); + if (element_info[Feld[x][y]].move_direction_initial == MV_START_PREVIOUS) + MovDir[x][y] = previous_move_direction; + InitField(x, y, FALSE); if (CAN_MOVE(Feld[x][y])) InitMovDir(x, y); @@ -6290,7 +6643,12 @@ static boolean CheckTriggeredElementSideChange(int lx, int ly, change->events & CH_EVENT_BIT(trigger_event) && #endif change->sides & trigger_side && - change->trigger_element == trigger_element) +#if 1 + IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element) +#else + change->trigger_element == trigger_element +#endif + ) { #if 0 if (!(change->events & CH_EVENT_BIT(trigger_event))) @@ -7369,6 +7727,38 @@ void ScrollLevel(int dx, int dy) redraw_mask |= REDRAW_FIELD; } +static boolean canEnterSupaplexPort(int x, int y, int dx, int dy) +{ + int nextx = x + dx, nexty = y + dy; + int element = Feld[x][y]; + + if ((dx == -1 && + element != EL_SP_PORT_LEFT && + element != EL_SP_GRAVITY_PORT_LEFT && + element != EL_SP_PORT_HORIZONTAL && + element != EL_SP_PORT_ANY) || + (dx == +1 && + element != EL_SP_PORT_RIGHT && + element != EL_SP_GRAVITY_PORT_RIGHT && + element != EL_SP_PORT_HORIZONTAL && + element != EL_SP_PORT_ANY) || + (dy == -1 && + element != EL_SP_PORT_UP && + element != EL_SP_GRAVITY_PORT_UP && + element != EL_SP_PORT_VERTICAL && + element != EL_SP_PORT_ANY) || + (dy == +1 && + element != EL_SP_PORT_DOWN && + element != EL_SP_GRAVITY_PORT_DOWN && + element != EL_SP_PORT_VERTICAL && + element != EL_SP_PORT_ANY) || + !IN_LEV_FIELD(nextx, nexty) || + !IS_FREE(nextx, nexty)) + return FALSE; + + return TRUE; +} + static void CheckGravityMovement(struct PlayerInfo *player) { if (game.gravity && !player->programmed_action) @@ -7388,7 +7778,9 @@ static void CheckGravityMovement(struct PlayerInfo *player) boolean player_is_moving_to_valid_field = (IN_LEV_FIELD(new_jx, new_jy) && (Feld[new_jx][new_jy] == EL_SP_BASE || - Feld[new_jx][new_jy] == EL_SAND)); + Feld[new_jx][new_jy] == EL_SAND || + (IS_SP_PORT(Feld[new_jx][new_jy]) && + canEnterSupaplexPort(new_jx, new_jy, dx, dy)))); /* !!! extend EL_SAND to anything diggable !!! */ if (field_under_player_is_free && @@ -7776,6 +8168,7 @@ void ScrollPlayer(struct PlayerInfo *player, int mode) #if 0 DrawPlayer(player); #endif + return; } else if (!FrameReached(&player->actual_frame_counter, 1)) @@ -7784,7 +8177,8 @@ void ScrollPlayer(struct PlayerInfo *player, int mode) player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize; player->GfxPos = move_stepsize * (player->MovPos / move_stepsize); - if (Feld[last_jx][last_jy] == EL_PLAYER_IS_LEAVING) + if (!player->block_last_field && + Feld[last_jx][last_jy] == EL_PLAYER_IS_LEAVING) Feld[last_jx][last_jy] = EL_EMPTY; /* before DrawPlayer() to draw correct player graphic for this case */ @@ -7822,6 +8216,10 @@ void ScrollPlayer(struct PlayerInfo *player, int mode) } #endif + if (player->block_last_field && + Feld[last_jx][last_jy] == EL_PLAYER_IS_LEAVING) + Feld[last_jx][last_jy] = EL_EMPTY; + player->last_jx = jx; player->last_jy = jy; @@ -8019,7 +8417,12 @@ void TestIfElementTouchesCustomElement(int x, int y) if (change->can_change && change->events & CH_EVENT_BIT(CE_OTHER_IS_TOUCHING) && change->sides & border_side && - change->trigger_element == border_element) +#if 1 + IS_EQUAL_OR_IN_GROUP(border_element, change->trigger_element) +#else + change->trigger_element == border_element +#endif + ) { change_center_element = TRUE; center_element_change_page = j; @@ -8041,7 +8444,12 @@ void TestIfElementTouchesCustomElement(int x, int y) if (change->can_change && change->events & CH_EVENT_BIT(CE_OTHER_IS_TOUCHING) && change->sides & center_side && - change->trigger_element == center_element) +#if 1 + IS_EQUAL_OR_IN_GROUP(center_element, change->trigger_element) +#else + change->trigger_element == center_element +#endif + ) { CheckElementSideChange(xx, yy, border_element, CH_SIDE_ANY, CE_OTHER_IS_TOUCHING, j); @@ -8083,15 +8491,7 @@ void TestIfElementHitsCustomElement(int x, int y, int direction) if (IN_LEV_FIELD(hitx, hity)) { - static int opposite_directions[] = - { - MV_RIGHT, - MV_LEFT, - MV_DOWN, - MV_UP - }; - int move_dir_bit = MV_DIR_BIT(direction); - int opposite_direction = opposite_directions[move_dir_bit]; + int opposite_direction = MV_DIR_OPPOSITE(direction); int hitting_side = direction; int touched_side = opposite_direction; int touched_element = MovingOrBlocked2Element(hitx, hity); @@ -8121,7 +8521,13 @@ void TestIfElementHitsCustomElement(int x, int y, int direction) if (change->can_change && change->events & CH_EVENT_BIT(CE_OTHER_IS_HITTING) && change->sides & touched_side && - change->trigger_element == touched_element) + +#if 1 + IS_EQUAL_OR_IN_GROUP(touched_element, change->trigger_element) +#else + change->trigger_element == touched_element +#endif + ) { CheckElementSideChange(x, y, hitting_element, CH_SIDE_ANY, CE_OTHER_IS_HITTING, i); @@ -8141,7 +8547,12 @@ void TestIfElementHitsCustomElement(int x, int y, int direction) if (change->can_change && change->events & CH_EVENT_BIT(CE_OTHER_GETS_HIT) && change->sides & hitting_side && - change->trigger_element == hitting_element) +#if 1 + IS_EQUAL_OR_IN_GROUP(hitting_element, change->trigger_element) +#else + change->trigger_element == hitting_element +#endif + ) { CheckElementSideChange(hitx, hity, touched_element, CH_SIDE_ANY, CE_OTHER_GETS_HIT, i); @@ -8209,7 +8620,7 @@ void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir) if (player->shield_deadly_time_left > 0) Bang(kill_x, kill_y); - else if (!PLAYER_PROTECTED(good_x, good_y)) + else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y)) KillHero(player); } else @@ -8301,7 +8712,7 @@ void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir) if (player->shield_deadly_time_left > 0) Bang(bad_x, bad_y); - else if (!PLAYER_PROTECTED(kill_x, kill_y)) + else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y)) KillHero(player); } else @@ -8391,9 +8802,15 @@ void KillHero(struct PlayerInfo *player) BuryHero(player); } -static void KillHeroUnlessProtected(int x, int y) +static void KillHeroUnlessEnemyProtected(int x, int y) { - if (!PLAYER_PROTECTED(x, y)) + if (!PLAYER_ENEMY_PROTECTED(x, y)) + KillHero(PLAYERINFO(x, y)); +} + +static void KillHeroUnlessExplosionProtected(int x, int y) +{ + if (!PLAYER_EXPLOSION_PROTECTED(x, y)) KillHero(PLAYERINFO(x, y)); } @@ -8570,6 +8987,10 @@ int DigField(struct PlayerInfo *player, case EL_SP_GRAVITY_PORT_RIGHT: case EL_SP_GRAVITY_PORT_UP: case EL_SP_GRAVITY_PORT_DOWN: +#if 1 + if (!canEnterSupaplexPort(x, y, dx, dy)) + return MF_NO_ACTION; +#else if ((dx == -1 && element != EL_SP_PORT_LEFT && element != EL_SP_GRAVITY_PORT_LEFT && @@ -8593,6 +9014,7 @@ int DigField(struct PlayerInfo *player, !IN_LEV_FIELD(nextx, nexty) || !IS_FREE(nextx, nexty)) return MF_NO_ACTION; +#endif if (element == EL_SP_GRAVITY_PORT_LEFT || element == EL_SP_GRAVITY_PORT_RIGHT || @@ -9257,6 +9679,11 @@ boolean DropElement(struct PlayerInfo *player) PlayLevelSoundAction(jx, jy, ACTION_DROPPING); +#if 1 + /* needed if previous element just changed to "empty" in the last frame */ + Changed[jx][jy] = 0; /* allow another change */ +#endif + CheckTriggeredElementChange(jx, jy, new_element, CE_OTHER_GETS_DROPPED); CheckElementChange(jx, jy, new_element, CE_DROPPED_BY_PLAYER); @@ -9294,7 +9721,7 @@ boolean DropElement(struct PlayerInfo *player) int move_stepsize = element_info[new_element].move_stepsize; int direction, dx, dy, nextx, nexty; - if (element_info[new_element].move_direction_initial == MV_NO_MOVING) + if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC) MovDir[jx][jy] = player->MovDir; direction = MovDir[jx][jy]; @@ -9314,7 +9741,7 @@ boolean DropElement(struct PlayerInfo *player) } else { - Changed[jx][jy] = 0; /* allow another change */ + Changed[jx][jy] = 0; /* allow another change */ #if 1 TestIfElementHitsCustomElement(jx, jy, direction);