X-Git-Url: https://git.artsoft.org/?a=blobdiff_plain;f=src%2Fgame.c;h=4bd3f46cbf61cccdc10ef407d2b9578ab508d284;hb=0ff3f484ffbb8e3404e1dc4536a292335369f2d2;hp=4e6cdf9fd1903ce197d7a2b95537ee7bc62d5457;hpb=535a7107455245d92cdf769087ff425b9da67b3c;p=rocksndiamonds.git diff --git a/src/game.c b/src/game.c index 4e6cdf9f..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) || \ @@ -155,16 +164,27 @@ #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, g) (element_info[e].in_group[g] == TRUE) +#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)) @@ -196,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); @@ -586,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; @@ -625,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; @@ -702,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; @@ -782,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; - Feld[x][y] = group->element_resolved[random_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; + + group->choice_pos++; + + Feld[x][y] = group->element_resolved[element_pos]; InitField(x, y, init_game); } @@ -825,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; } @@ -833,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++) @@ -1020,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; } } } @@ -1053,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 */ @@ -1068,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 */ @@ -1150,6 +1228,8 @@ void InitGame() player->use_murphy_graphic = FALSE; + player->block_last_field = FALSE; + player->actual_frame_counter = 0; player->step_counter = 0; @@ -1305,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; @@ -1339,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) @@ -1361,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; @@ -1375,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++) { @@ -1406,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; } @@ -1706,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++) { @@ -1731,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]; @@ -2241,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; @@ -2261,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 */ @@ -2305,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); } } } @@ -2322,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) { @@ -2356,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; @@ -2435,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; } @@ -2526,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; } @@ -2542,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 @@ -2562,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]; @@ -2578,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]; @@ -2597,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; @@ -2607,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); @@ -2656,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) @@ -2721,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 @@ -3212,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 @@ -3254,7 +3531,7 @@ void Impact(int x, int y) { if (CAN_SMASH_PLAYER(element)) { - KillHeroUnlessProtected(x, y + 1); + KillHeroUnlessEnemyProtected(x, y + 1); return; } } @@ -3274,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; @@ -4593,7 +4872,7 @@ 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]); @@ -4685,44 +4964,65 @@ void StartMoving(int x, int y) } } +#if 1 + /* 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)) + CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy) + +#if 0 + && + !IS_FREE(newx, newy) +#endif + +) { int new_element = Feld[newx][newy]; - int sound; - /* no element can dig solid indestructible elements */ - if (IS_INDESTRUCTIBLE(new_element) && - !IS_DIGGABLE(new_element) && - !IS_COLLECTIBLE(new_element)) - return; +#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 (AmoebaNr[newx][newy] && - (new_element == EL_AMOEBA_FULL || - new_element == EL_BD_AMOEBA || - new_element == EL_AMOEBA_GROWING)) + if (!IS_FREE(newx, newy)) { - AmoebaCnt[AmoebaNr[newx][newy]]--; - AmoebaCnt2[AmoebaNr[newx][newy]]--; - } + int action = (IS_DIGGABLE(new_element) ? ACTION_DIGGING : + IS_COLLECTIBLE(new_element) ? ACTION_COLLECTING : + ACTION_BREAKING); - if (IS_MOVING(newx, newy)) - RemoveMovingField(newx, newy); - else - { - RemoveField(newx, newy); - DrawLevelField(newx, newy); - } + /* 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 + { + RemoveField(newx, newy); + DrawLevelField(newx, newy); + } - sound = (IS_DIGGABLE(new_element) ? ACTION_DIGGING : - IS_COLLECTIBLE(new_element) ? ACTION_COLLECTING : - ACTION_BREAKING); + PlayLevelSoundAction(x, y, action); + } - PlayLevelSoundAction(x, y, sound); + if (new_element == element_info[element].move_enter_element) + element_info[element].can_leave_element = TRUE; if (move_pattern & MV_MAZE_RUNNER_STYLE) { @@ -4730,6 +5030,9 @@ void StartMoving(int x, int y) PlayerVisit[x][y] /= 8; /* expire player visit path */ } } + +#endif + else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy)) { if (!IS_FREE(newx, newy)) @@ -4901,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); @@ -5017,23 +5321,24 @@ void ContinueMoving(int x, int y) ResetGfxAnimation(x, y); /* reset animation values for old field */ - if (IS_CUSTOM_ELEMENT(element) && !IS_PLAYER(x, y)) +#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)) { - int new_element = element_info[element].move_leave_element; - - Feld[x][y] = new_element; - - if (new_element != EL_EMPTY) - { - InitField(x, y, FALSE); - - TestIfElementTouchesCustomElement(x, y); + Feld[x][y] = ei->move_leave_element; + InitField(x, y, FALSE); - if (GFX_CRUMBLED(new_element)) - DrawLevelFieldCrumbledSandNeighbours(x, y); - } + 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)) @@ -5093,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 @@ -5118,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); @@ -6085,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); @@ -6102,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); @@ -6341,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))) @@ -7420,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) @@ -7439,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 && @@ -7827,6 +8168,7 @@ void ScrollPlayer(struct PlayerInfo *player, int mode) #if 0 DrawPlayer(player); #endif + return; } else if (!FrameReached(&player->actual_frame_counter, 1)) @@ -7835,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 */ @@ -7873,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; @@ -8070,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; @@ -8092,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); @@ -8134,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); @@ -8172,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); @@ -8192,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); @@ -8260,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 @@ -8352,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 @@ -8442,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_ENEMY_PROTECTED(x, y)) + KillHero(PLAYERINFO(x, y)); +} + +static void KillHeroUnlessExplosionProtected(int x, int y) { - if (!PLAYER_PROTECTED(x, y)) + if (!PLAYER_EXPLOSION_PROTECTED(x, y)) KillHero(PLAYERINFO(x, y)); } @@ -8621,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 && @@ -8644,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 || @@ -9308,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); @@ -9345,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]; @@ -9365,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);