+ printf("Player frame reset! (%d => %d, %d => %d)\n",
+ player->GfxAction, action, player->GfxDir, dir);
+#endif
+
+ player->GfxAction = action;
+ player->GfxDir = dir;
+ player->Frame = 0;
+ player->StepFrame = 0;
+ }
+}
+
+#if USE_GFX_RESET_GFX_ANIMATION
+static void ResetGfxFrame(int x, int y, boolean redraw)
+{
+ int element = Feld[x][y];
+ int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
+ int last_gfx_frame = GfxFrame[x][y];
+
+ if (graphic_info[graphic].anim_global_sync)
+ GfxFrame[x][y] = FrameCounter;
+ else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
+ GfxFrame[x][y] = CustomValue[x][y];
+ else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
+ GfxFrame[x][y] = element_info[element].collect_score;
+ else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
+ GfxFrame[x][y] = ChangeDelay[x][y];
+
+ if (redraw && GfxFrame[x][y] != last_gfx_frame)
+ DrawLevelGraphicAnimation(x, y, graphic);
+}
+#endif
+
+static void ResetGfxAnimation(int x, int y)
+{
+ GfxAction[x][y] = ACTION_DEFAULT;
+ GfxDir[x][y] = MovDir[x][y];
+ GfxFrame[x][y] = 0;
+
+#if USE_GFX_RESET_GFX_ANIMATION
+ ResetGfxFrame(x, y, FALSE);
+#endif
+}
+
+static void ResetRandomAnimationValue(int x, int y)
+{
+ GfxRandom[x][y] = INIT_GFX_RANDOM();
+}
+
+void InitMovingField(int x, int y, int direction)
+{
+ int element = Feld[x][y];
+ int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
+ int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
+ int newx = x + dx;
+ int newy = y + dy;
+ boolean is_moving_before, is_moving_after;
+#if 0
+ boolean continues_moving = (WasJustMoving[x][y] && direction == MovDir[x][y]);
+#endif
+
+ /* check if element was/is moving or being moved before/after mode change */
+#if 1
+#if 1
+ is_moving_before = (WasJustMoving[x][y] != 0);
+#else
+ /* (!!! this does not work -- WasJustMoving is NOT a boolean value !!!) */
+ is_moving_before = WasJustMoving[x][y];
+#endif
+#else
+ is_moving_before = (getElementMoveStepsizeExt(x, y, MovDir[x][y]) != 0);
+#endif
+ is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
+
+ /* reset animation only for moving elements which change direction of moving
+ or which just started or stopped moving
+ (else CEs with property "can move" / "not moving" are reset each frame) */
+#if USE_GFX_RESET_ONLY_WHEN_MOVING
+#if 1
+ if (is_moving_before != is_moving_after ||
+ direction != MovDir[x][y])
+ ResetGfxAnimation(x, y);
+#else
+ if ((is_moving_before || is_moving_after) && !continues_moving)
+ ResetGfxAnimation(x, y);
+#endif
+#else
+ if (!continues_moving)
+ ResetGfxAnimation(x, y);
+#endif
+
+ MovDir[x][y] = direction;
+ GfxDir[x][y] = direction;
+
+#if USE_GFX_RESET_ONLY_WHEN_MOVING
+ GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
+ direction == MV_DOWN && CAN_FALL(element) ?
+ ACTION_FALLING : ACTION_MOVING);
+#else
+ GfxAction[x][y] = (direction == MV_DOWN && CAN_FALL(element) ?
+ ACTION_FALLING : ACTION_MOVING);
+#endif
+
+ /* this is needed for CEs with property "can move" / "not moving" */
+
+ if (is_moving_after)
+ {
+ if (Feld[newx][newy] == EL_EMPTY)
+ Feld[newx][newy] = EL_BLOCKED;
+
+ MovDir[newx][newy] = MovDir[x][y];
+
+#if USE_NEW_CUSTOM_VALUE
+ CustomValue[newx][newy] = CustomValue[x][y];
+#endif
+
+ GfxFrame[newx][newy] = GfxFrame[x][y];
+ GfxRandom[newx][newy] = GfxRandom[x][y];
+ GfxAction[newx][newy] = GfxAction[x][y];
+ GfxDir[newx][newy] = GfxDir[x][y];
+ }
+}
+
+void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
+{
+ int direction = MovDir[x][y];
+ int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
+ int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
+
+ *goes_to_x = newx;
+ *goes_to_y = newy;
+}
+
+void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
+{
+ int oldx = x, oldy = y;
+ int direction = MovDir[x][y];
+
+ if (direction == MV_LEFT)
+ oldx++;
+ else if (direction == MV_RIGHT)
+ oldx--;
+ else if (direction == MV_UP)
+ oldy++;
+ else if (direction == MV_DOWN)
+ oldy--;
+
+ *comes_from_x = oldx;
+ *comes_from_y = oldy;
+}
+
+int MovingOrBlocked2Element(int x, int y)
+{
+ int element = Feld[x][y];
+
+ if (element == EL_BLOCKED)
+ {
+ int oldx, oldy;
+
+ Blocked2Moving(x, y, &oldx, &oldy);
+ return Feld[oldx][oldy];
+ }
+ else
+ return element;
+}
+
+static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
+{
+ /* like MovingOrBlocked2Element(), but if element is moving
+ and (x,y) is the field the moving element is just leaving,
+ return EL_BLOCKED instead of the element value */
+ int element = Feld[x][y];
+
+ if (IS_MOVING(x, y))
+ {
+ if (element == EL_BLOCKED)
+ {
+ int oldx, oldy;
+
+ Blocked2Moving(x, y, &oldx, &oldy);
+ return Feld[oldx][oldy];
+ }
+ else
+ return EL_BLOCKED;
+ }
+ else
+ return element;
+}
+
+static void RemoveField(int x, int y)
+{
+ Feld[x][y] = EL_EMPTY;
+
+ MovPos[x][y] = 0;
+ MovDir[x][y] = 0;
+ MovDelay[x][y] = 0;
+
+#if USE_NEW_CUSTOM_VALUE
+ CustomValue[x][y] = 0;
+#endif
+
+ AmoebaNr[x][y] = 0;
+ ChangeDelay[x][y] = 0;
+ ChangePage[x][y] = -1;
+ Pushed[x][y] = FALSE;
+
+#if 0
+ ExplodeField[x][y] = EX_TYPE_NONE;
+#endif
+
+ GfxElement[x][y] = EL_UNDEFINED;
+ GfxAction[x][y] = ACTION_DEFAULT;
+ GfxDir[x][y] = MV_NONE;
+#if 0
+ /* !!! this would prevent the removed tile from being redrawn !!! */
+ GfxRedraw[x][y] = GFX_REDRAW_NONE;
+#endif
+}
+
+void RemoveMovingField(int x, int y)
+{
+ int oldx = x, oldy = y, newx = x, newy = y;
+ int element = Feld[x][y];
+ int next_element = EL_UNDEFINED;
+
+ if (element != EL_BLOCKED && !IS_MOVING(x, y))
+ return;
+
+ if (IS_MOVING(x, y))
+ {
+ Moving2Blocked(x, y, &newx, &newy);
+
+ if (Feld[newx][newy] != EL_BLOCKED)
+ {
+ /* element is moving, but target field is not free (blocked), but
+ already occupied by something different (example: acid pool);
+ in this case, only remove the moving field, but not the target */
+
+ RemoveField(oldx, oldy);
+
+ Store[oldx][oldy] = Store2[oldx][oldy] = 0;
+
+ TEST_DrawLevelField(oldx, oldy);
+
+ return;
+ }
+ }
+ else if (element == EL_BLOCKED)
+ {
+ Blocked2Moving(x, y, &oldx, &oldy);
+ if (!IS_MOVING(oldx, oldy))
+ return;
+ }
+
+ if (element == EL_BLOCKED &&
+ (Feld[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
+ Feld[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
+ Feld[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
+ Feld[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
+ Feld[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
+ Feld[oldx][oldy] == EL_AMOEBA_DROPPING))
+ next_element = get_next_element(Feld[oldx][oldy]);
+
+ RemoveField(oldx, oldy);
+ RemoveField(newx, newy);
+
+ Store[oldx][oldy] = Store2[oldx][oldy] = 0;
+
+ if (next_element != EL_UNDEFINED)
+ Feld[oldx][oldy] = next_element;
+
+ TEST_DrawLevelField(oldx, oldy);
+ TEST_DrawLevelField(newx, newy);
+}
+
+void DrawDynamite(int x, int y)
+{
+ int sx = SCREENX(x), sy = SCREENY(y);
+ int graphic = el2img(Feld[x][y]);
+ int frame;
+
+ if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
+ return;
+
+ if (IS_WALKABLE_INSIDE(Back[x][y]))
+ return;
+
+ if (Back[x][y])
+ DrawGraphic(sx, sy, el2img(Back[x][y]), 0);
+ else if (Store[x][y])
+ DrawGraphic(sx, sy, el2img(Store[x][y]), 0);
+
+ frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
+
+ if (Back[x][y] || Store[x][y])
+ DrawGraphicThruMask(sx, sy, graphic, frame);
+ else
+ DrawGraphic(sx, sy, graphic, frame);
+}
+
+void CheckDynamite(int x, int y)
+{
+ if (MovDelay[x][y] != 0) /* dynamite is still waiting to explode */
+ {
+ MovDelay[x][y]--;
+
+ if (MovDelay[x][y] != 0)
+ {
+ DrawDynamite(x, y);
+ PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
+
+ return;
+ }
+ }
+
+ StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
+
+ Bang(x, y);
+}
+
+static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
+{
+ boolean num_checked_players = 0;
+ int i;
+
+ for (i = 0; i < MAX_PLAYERS; i++)
+ {
+ if (stored_player[i].active)
+ {
+ int sx = stored_player[i].jx;
+ int sy = stored_player[i].jy;
+
+ if (num_checked_players == 0)
+ {
+ *sx1 = *sx2 = sx;
+ *sy1 = *sy2 = sy;
+ }
+ else
+ {
+ *sx1 = MIN(*sx1, sx);
+ *sy1 = MIN(*sy1, sy);
+ *sx2 = MAX(*sx2, sx);
+ *sy2 = MAX(*sy2, sy);
+ }
+
+ num_checked_players++;
+ }
+ }
+}
+
+static boolean checkIfAllPlayersFitToScreen_RND()
+{
+ int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
+
+ setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
+
+ return (sx2 - sx1 < SCR_FIELDX &&
+ sy2 - sy1 < SCR_FIELDY);
+}
+
+static void setScreenCenteredToAllPlayers(int *sx, int *sy)
+{
+ int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
+
+ setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
+
+ *sx = (sx1 + sx2) / 2;
+ *sy = (sy1 + sy2) / 2;
+}
+
+void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
+ boolean center_screen, boolean quick_relocation)
+{
+ boolean ffwd_delay = (tape.playing && tape.fast_forward);
+ boolean no_delay = (tape.warp_forward);
+ int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
+ int wait_delay_value = (no_delay ? 0 : frame_delay_value);
+
+ if (quick_relocation)
+ {
+ if (!IN_VIS_FIELD(SCREENX(x), SCREENY(y)) || center_screen)
+ {
+ if (!level.shifted_relocation || center_screen)
+ {
+ /* quick relocation (without scrolling), with centering of screen */
+
+ scroll_x = (x < SBX_Left + MIDPOSX ? SBX_Left :
+ x > SBX_Right + MIDPOSX ? SBX_Right :
+ x - MIDPOSX);
+
+ scroll_y = (y < SBY_Upper + MIDPOSY ? SBY_Upper :
+ y > SBY_Lower + MIDPOSY ? SBY_Lower :
+ y - MIDPOSY);
+ }
+ else
+ {
+ /* quick relocation (without scrolling), but do not center screen */
+
+ int center_scroll_x = (old_x < SBX_Left + MIDPOSX ? SBX_Left :
+ old_x > SBX_Right + MIDPOSX ? SBX_Right :
+ old_x - MIDPOSX);
+
+ int center_scroll_y = (old_y < SBY_Upper + MIDPOSY ? SBY_Upper :
+ old_y > SBY_Lower + MIDPOSY ? SBY_Lower :
+ old_y - MIDPOSY);
+
+ int offset_x = x + (scroll_x - center_scroll_x);
+ int offset_y = y + (scroll_y - center_scroll_y);
+
+ scroll_x = (offset_x < SBX_Left + MIDPOSX ? SBX_Left :
+ offset_x > SBX_Right + MIDPOSX ? SBX_Right :
+ offset_x - MIDPOSX);
+
+ scroll_y = (offset_y < SBY_Upper + MIDPOSY ? SBY_Upper :
+ offset_y > SBY_Lower + MIDPOSY ? SBY_Lower :
+ offset_y - MIDPOSY);
+ }
+ }
+ else
+ {
+#if 1
+ if (!level.shifted_relocation || center_screen)
+ {
+ /* quick relocation (without scrolling), with centering of screen */
+
+ scroll_x = (x < SBX_Left + MIDPOSX ? SBX_Left :
+ x > SBX_Right + MIDPOSX ? SBX_Right :
+ x - MIDPOSX);
+
+ scroll_y = (y < SBY_Upper + MIDPOSY ? SBY_Upper :
+ y > SBY_Lower + MIDPOSY ? SBY_Lower :
+ y - MIDPOSY);
+ }
+ else
+ {
+ /* quick relocation (without scrolling), but do not center screen */
+
+ int center_scroll_x = (old_x < SBX_Left + MIDPOSX ? SBX_Left :
+ old_x > SBX_Right + MIDPOSX ? SBX_Right :
+ old_x - MIDPOSX);
+
+ int center_scroll_y = (old_y < SBY_Upper + MIDPOSY ? SBY_Upper :
+ old_y > SBY_Lower + MIDPOSY ? SBY_Lower :
+ old_y - MIDPOSY);
+
+ int offset_x = x + (scroll_x - center_scroll_x);
+ int offset_y = y + (scroll_y - center_scroll_y);
+
+ scroll_x = (offset_x < SBX_Left + MIDPOSX ? SBX_Left :
+ offset_x > SBX_Right + MIDPOSX ? SBX_Right :
+ offset_x - MIDPOSX);
+
+ scroll_y = (offset_y < SBY_Upper + MIDPOSY ? SBY_Upper :
+ offset_y > SBY_Lower + MIDPOSY ? SBY_Lower :
+ offset_y - MIDPOSY);
+ }
+#else
+ /* quick relocation (without scrolling), inside visible screen area */
+
+ int offset = game.scroll_delay_value;
+
+ if ((move_dir == MV_LEFT && scroll_x > x - MIDPOSX + offset) ||
+ (move_dir == MV_RIGHT && scroll_x < x - MIDPOSX - offset))
+ scroll_x = x - MIDPOSX + (scroll_x < x - MIDPOSX ? -offset : +offset);
+
+ if ((move_dir == MV_UP && scroll_y > y - MIDPOSY + offset) ||
+ (move_dir == MV_DOWN && scroll_y < y - MIDPOSY - offset))
+ scroll_y = y - MIDPOSY + (scroll_y < y - MIDPOSY ? -offset : +offset);
+
+ /* don't scroll over playfield boundaries */
+ if (scroll_x < SBX_Left || scroll_x > SBX_Right)
+ scroll_x = (scroll_x < SBX_Left ? SBX_Left : SBX_Right);
+
+ /* don't scroll over playfield boundaries */
+ if (scroll_y < SBY_Upper || scroll_y > SBY_Lower)
+ scroll_y = (scroll_y < SBY_Upper ? SBY_Upper : SBY_Lower);
+#endif
+ }
+
+ RedrawPlayfield(TRUE, 0,0,0,0);
+ }
+ else
+ {
+#if 1
+ int scroll_xx, scroll_yy;
+
+ if (!level.shifted_relocation || center_screen)
+ {
+ /* visible relocation (with scrolling), with centering of screen */
+
+ scroll_xx = (x < SBX_Left + MIDPOSX ? SBX_Left :
+ x > SBX_Right + MIDPOSX ? SBX_Right :
+ x - MIDPOSX);
+
+ scroll_yy = (y < SBY_Upper + MIDPOSY ? SBY_Upper :
+ y > SBY_Lower + MIDPOSY ? SBY_Lower :
+ y - MIDPOSY);
+ }
+ else
+ {
+ /* visible relocation (with scrolling), but do not center screen */
+
+ int center_scroll_x = (old_x < SBX_Left + MIDPOSX ? SBX_Left :
+ old_x > SBX_Right + MIDPOSX ? SBX_Right :
+ old_x - MIDPOSX);
+
+ int center_scroll_y = (old_y < SBY_Upper + MIDPOSY ? SBY_Upper :
+ old_y > SBY_Lower + MIDPOSY ? SBY_Lower :
+ old_y - MIDPOSY);
+
+ int offset_x = x + (scroll_x - center_scroll_x);
+ int offset_y = y + (scroll_y - center_scroll_y);
+
+ scroll_xx = (offset_x < SBX_Left + MIDPOSX ? SBX_Left :
+ offset_x > SBX_Right + MIDPOSX ? SBX_Right :
+ offset_x - MIDPOSX);
+
+ scroll_yy = (offset_y < SBY_Upper + MIDPOSY ? SBY_Upper :
+ offset_y > SBY_Lower + MIDPOSY ? SBY_Lower :
+ offset_y - MIDPOSY);
+ }
+
+#else
+
+ /* visible relocation (with scrolling), with centering of screen */
+
+ int scroll_xx = (x < SBX_Left + MIDPOSX ? SBX_Left :
+ x > SBX_Right + MIDPOSX ? SBX_Right :
+ x - MIDPOSX);
+
+ int scroll_yy = (y < SBY_Upper + MIDPOSY ? SBY_Upper :
+ y > SBY_Lower + MIDPOSY ? SBY_Lower :
+ y - MIDPOSY);
+#endif
+
+ ScrollScreen(NULL, SCROLL_GO_ON); /* scroll last frame to full tile */
+
+ while (scroll_x != scroll_xx || scroll_y != scroll_yy)
+ {
+ int dx = 0, dy = 0;
+ int fx = FX, fy = FY;
+
+ dx = (scroll_xx < scroll_x ? +1 : scroll_xx > scroll_x ? -1 : 0);
+ dy = (scroll_yy < scroll_y ? +1 : scroll_yy > scroll_y ? -1 : 0);
+
+ if (dx == 0 && dy == 0) /* no scrolling needed at all */
+ break;
+
+ scroll_x -= dx;
+ scroll_y -= dy;
+
+ fx += dx * TILEX / 2;
+ fy += dy * TILEY / 2;
+
+ ScrollLevel(dx, dy);
+ DrawAllPlayers();
+
+ /* scroll in two steps of half tile size to make things smoother */
+ BlitBitmap(drawto_field, window, fx, fy, SXSIZE, SYSIZE, SX, SY);
+ FlushDisplay();
+ Delay(wait_delay_value);
+
+ /* scroll second step to align at full tile size */
+ BackToFront();
+ Delay(wait_delay_value);
+ }
+
+ DrawAllPlayers();
+ BackToFront();
+ Delay(wait_delay_value);
+ }
+}
+
+void RelocatePlayer(int jx, int jy, int el_player_raw)
+{
+ int el_player = GET_PLAYER_ELEMENT(el_player_raw);
+ int player_nr = GET_PLAYER_NR(el_player);
+ struct PlayerInfo *player = &stored_player[player_nr];
+ boolean ffwd_delay = (tape.playing && tape.fast_forward);
+ boolean no_delay = (tape.warp_forward);
+ int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
+ int wait_delay_value = (no_delay ? 0 : frame_delay_value);
+ int old_jx = player->jx;
+ int old_jy = player->jy;
+ int old_element = Feld[old_jx][old_jy];
+ int element = Feld[jx][jy];
+ boolean player_relocated = (old_jx != jx || old_jy != jy);
+
+ int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
+ int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
+ int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
+ int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
+ int leave_side_horiz = move_dir_horiz;
+ int leave_side_vert = move_dir_vert;
+ int enter_side = enter_side_horiz | enter_side_vert;
+ int leave_side = leave_side_horiz | leave_side_vert;
+
+ if (player->GameOver) /* do not reanimate dead player */
+ return;
+
+ if (!player_relocated) /* no need to relocate the player */
+ return;
+
+ if (IS_PLAYER(jx, jy)) /* player already placed at new position */
+ {
+ RemoveField(jx, jy); /* temporarily remove newly placed player */
+ DrawLevelField(jx, jy);
+ }
+
+ if (player->present)
+ {
+ while (player->MovPos)
+ {
+ ScrollPlayer(player, SCROLL_GO_ON);
+ ScrollScreen(NULL, SCROLL_GO_ON);
+
+ AdvanceFrameAndPlayerCounters(player->index_nr);
+
+ DrawPlayer(player);
+
+ BackToFront();
+ Delay(wait_delay_value);
+ }
+
+ DrawPlayer(player); /* needed here only to cleanup last field */
+ DrawLevelField(player->jx, player->jy); /* remove player graphic */
+
+ player->is_moving = FALSE;
+ }
+
+ if (IS_CUSTOM_ELEMENT(old_element))
+ CheckElementChangeByPlayer(old_jx, old_jy, old_element,
+ CE_LEFT_BY_PLAYER,
+ player->index_bit, leave_side);
+
+ CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
+ CE_PLAYER_LEAVES_X,
+ player->index_bit, leave_side);
+
+ Feld[jx][jy] = el_player;
+ InitPlayerField(jx, jy, el_player, TRUE);
+
+ /* "InitPlayerField()" above sets Feld[jx][jy] to EL_EMPTY, but it may be
+ possible that the relocation target field did not contain a player element,
+ but a walkable element, to which the new player was relocated -- in this
+ case, restore that (already initialized!) element on the player field */
+ if (!ELEM_IS_PLAYER(element)) /* player may be set on walkable element */
+ {
+ Feld[jx][jy] = element; /* restore previously existing element */
+#if 0
+ /* !!! do not initialize already initialized element a second time !!! */
+ /* (this causes at least problems with "element creation" CE trigger for
+ already existing elements, and existing Sokoban fields counted twice) */
+ InitField(jx, jy, FALSE);
+#endif
+ }
+
+ /* only visually relocate centered player */
+ DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir,
+ FALSE, level.instant_relocation);
+
+ TestIfPlayerTouchesBadThing(jx, jy);
+ TestIfPlayerTouchesCustomElement(jx, jy);
+
+ if (IS_CUSTOM_ELEMENT(element))
+ CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
+ player->index_bit, enter_side);
+
+ CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
+ player->index_bit, enter_side);
+
+#if 1
+ if (player->is_switching)
+ {
+ /* ensure that relocation while still switching an element does not cause
+ a new element to be treated as also switched directly after relocation
+ (this is important for teleporter switches that teleport the player to
+ a place where another teleporter switch is in the same direction, which
+ would then incorrectly be treated as immediately switched before the
+ direction key that caused the switch was released) */
+
+ player->switch_x += jx - old_jx;
+ player->switch_y += jy - old_jy;
+ }
+#endif
+}
+
+void Explode(int ex, int ey, int phase, int mode)
+{
+ int x, y;
+ int last_phase;
+ int border_element;
+
+ /* !!! eliminate this variable !!! */
+ int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
+
+ if (game.explosions_delayed)
+ {
+ ExplodeField[ex][ey] = mode;
+ return;
+ }
+
+ if (phase == EX_PHASE_START) /* initialize 'Store[][]' field */
+ {
+ int center_element = Feld[ex][ey];
+ int artwork_element, explosion_element; /* set these values later */
+
+#if 0
+ /* --- This is only really needed (and now handled) in "Impact()". --- */
+ /* do not explode moving elements that left the explode field in time */
+ if (game.engine_version >= VERSION_IDENT(2,2,0,7) &&
+ center_element == EL_EMPTY &&
+ (mode == EX_TYPE_NORMAL || mode == EX_TYPE_CENTER))
+ return;
+#endif
+
+#if 0
+ /* !!! at this place, the center element may be EL_BLOCKED !!! */
+ if (mode == EX_TYPE_NORMAL ||
+ mode == EX_TYPE_CENTER ||
+ mode == EX_TYPE_CROSS)
+ PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
+#endif
+
+ /* remove things displayed in background while burning dynamite */
+ if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
+ Back[ex][ey] = 0;
+
+ if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
+ {
+ /* put moving element to center field (and let it explode there) */
+ center_element = MovingOrBlocked2Element(ex, ey);
+ RemoveMovingField(ex, ey);
+ Feld[ex][ey] = center_element;
+ }
+
+ /* now "center_element" is finally determined -- set related values now */
+ artwork_element = center_element; /* for custom player artwork */
+ explosion_element = center_element; /* for custom player artwork */
+
+ if (IS_PLAYER(ex, ey))
+ {
+ int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
+
+ artwork_element = stored_player[player_nr].artwork_element;
+
+ if (level.use_explosion_element[player_nr])
+ {
+ explosion_element = level.explosion_element[player_nr];
+ artwork_element = explosion_element;
+ }
+ }
+
+#if 1
+ if (mode == EX_TYPE_NORMAL ||
+ mode == EX_TYPE_CENTER ||
+ mode == EX_TYPE_CROSS)
+ PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
+#endif
+
+ last_phase = element_info[explosion_element].explosion_delay + 1;
+
+ for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
+ {
+ int xx = x - ex + 1;
+ int yy = y - ey + 1;
+ int element;
+
+ if (!IN_LEV_FIELD(x, y) ||
+ (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
+ (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
+ continue;
+
+ element = Feld[x][y];
+
+ if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
+ {
+ element = MovingOrBlocked2Element(x, y);
+
+ if (!IS_EXPLOSION_PROOF(element))
+ RemoveMovingField(x, y);
+ }
+
+ /* indestructible elements can only explode in center (but not flames) */
+ if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
+ mode == EX_TYPE_BORDER)) ||
+ element == EL_FLAMES)
+ continue;
+
+ /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
+ behaviour, for example when touching a yamyam that explodes to rocks
+ with active deadly shield, a rock is created under the player !!! */
+ /* (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8) */
+#if 0
+ if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
+ (game.engine_version < VERSION_IDENT(3,1,0,0) ||
+ (x == ex && y == ey && mode != EX_TYPE_BORDER)))
+#else
+ if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
+#endif
+ {
+ if (IS_ACTIVE_BOMB(element))
+ {
+ /* re-activate things under the bomb like gate or penguin */
+ Feld[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
+ Back[x][y] = 0;
+ }
+
+ continue;
+ }
+
+ /* save walkable background elements while explosion on same tile */
+ if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
+ (x != ex || y != ey || mode == EX_TYPE_BORDER))
+ Back[x][y] = element;
+
+ /* ignite explodable elements reached by other explosion */
+ if (element == EL_EXPLOSION)
+ element = Store2[x][y];
+
+ if (AmoebaNr[x][y] &&
+ (element == EL_AMOEBA_FULL ||
+ element == EL_BD_AMOEBA ||
+ element == EL_AMOEBA_GROWING))
+ {
+ AmoebaCnt[AmoebaNr[x][y]]--;
+ AmoebaCnt2[AmoebaNr[x][y]]--;
+ }
+
+ RemoveField(x, y);
+
+ if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
+ {
+ int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
+
+ Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
+
+ if (PLAYERINFO(ex, ey)->use_murphy)
+ Store[x][y] = EL_EMPTY;
+ }
+
+ /* !!! check this case -- currently needed for rnd_rado_negundo_v,
+ !!! levels 015 018 019 020 021 022 023 026 027 028 !!! */
+ else if (ELEM_IS_PLAYER(center_element))
+ Store[x][y] = EL_EMPTY;
+ else if (center_element == EL_YAMYAM)
+ Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
+ else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
+ Store[x][y] = element_info[center_element].content.e[xx][yy];
+#if 1
+ /* needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
+ (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
+ otherwise) -- FIX THIS !!! */
+ else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
+ Store[x][y] = element_info[element].content.e[1][1];
+#else
+ else if (!CAN_EXPLODE(element))
+ Store[x][y] = element_info[element].content.e[1][1];
+#endif
+ else
+ Store[x][y] = EL_EMPTY;
+
+ if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
+ center_element == EL_AMOEBA_TO_DIAMOND)
+ Store2[x][y] = element;
+
+ Feld[x][y] = EL_EXPLOSION;
+ GfxElement[x][y] = artwork_element;
+
+ ExplodePhase[x][y] = 1;
+ ExplodeDelay[x][y] = last_phase;
+
+ Stop[x][y] = TRUE;
+ }
+
+ if (center_element == EL_YAMYAM)
+ game.yamyam_content_nr =
+ (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
+
+ return;
+ }
+
+ if (Stop[ex][ey])
+ return;
+
+ x = ex;
+ y = ey;
+
+ if (phase == 1)
+ GfxFrame[x][y] = 0; /* restart explosion animation */
+
+ last_phase = ExplodeDelay[x][y];
+
+ ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
+
+#ifdef DEBUG
+
+ /* activate this even in non-DEBUG version until cause for crash in
+ getGraphicAnimationFrame() (see below) is found and eliminated */
+
+#endif
+#if 1
+
+#if 1
+ /* this can happen if the player leaves an explosion just in time */
+ if (GfxElement[x][y] == EL_UNDEFINED)
+ GfxElement[x][y] = EL_EMPTY;
+#else
+ if (GfxElement[x][y] == EL_UNDEFINED)
+ {
+ printf("\n\n");
+ printf("Explode(): x = %d, y = %d: GfxElement == EL_UNDEFINED\n", x, y);
+ printf("Explode(): This should never happen!\n");
+ printf("\n\n");
+
+ GfxElement[x][y] = EL_EMPTY;
+ }
+#endif
+
+#endif
+
+ border_element = Store2[x][y];
+ if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
+ border_element = StorePlayer[x][y];
+
+ if (phase == element_info[border_element].ignition_delay ||
+ phase == last_phase)
+ {
+ boolean border_explosion = FALSE;
+
+ if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
+ !PLAYER_EXPLOSION_PROTECTED(x, y))
+ {
+ KillPlayerUnlessExplosionProtected(x, y);
+ border_explosion = TRUE;
+ }
+ else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
+ {
+ Feld[x][y] = Store2[x][y];
+ Store2[x][y] = 0;
+ Bang(x, y);
+ border_explosion = TRUE;
+ }
+ else if (border_element == EL_AMOEBA_TO_DIAMOND)
+ {
+ AmoebeUmwandeln(x, y);
+ Store2[x][y] = 0;
+ border_explosion = TRUE;
+ }
+
+ /* if an element just explodes due to another explosion (chain-reaction),
+ do not immediately end the new explosion when it was the last frame of
+ the explosion (as it would be done in the following "if"-statement!) */
+ if (border_explosion && phase == last_phase)
+ 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)
+ {
+ int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
+ int explosion_element = EL_PLAYER_1 + player_nr;
+ int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
+ int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
+
+ if (level.use_explosion_element[player_nr])
+ explosion_element = level.explosion_element[player_nr];
+
+ Feld[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
+ element_info[explosion_element].content.e[xx][yy]);
+ }
+
+ /* 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_NONE;
+ ChangeDelay[x][y] = 0;
+ ChangePage[x][y] = -1;
+
+#if USE_NEW_CUSTOM_VALUE
+ CustomValue[x][y] = 0;
+#endif
+
+ InitField_WithBug2(x, y, FALSE);
+
+ TEST_DrawLevelField(x, y);
+
+ TestIfElementTouchesCustomElement(x, y);
+
+ if (GFX_CRUMBLED(element))
+ TEST_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)))
+ {
+ int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
+ int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
+
+ if (phase == delay)
+ TEST_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);
+ }
+}
+
+void DynaExplode(int ex, int ey)
+{
+ int i, j;
+ int dynabomb_element = Feld[ex][ey];
+ int dynabomb_size = 1;
+ boolean dynabomb_xl = FALSE;
+ struct PlayerInfo *player;
+ static int xy[4][2] =
+ {
+ { 0, -1 },
+ { -1, 0 },
+ { +1, 0 },
+ { 0, +1 }
+ };
+
+ if (IS_ACTIVE_BOMB(dynabomb_element))
+ {
+ player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
+ dynabomb_size = player->dynabomb_size;
+ dynabomb_xl = player->dynabomb_xl;
+ player->dynabombs_left++;
+ }
+
+ Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
+
+ for (i = 0; i < NUM_DIRECTIONS; i++)
+ {
+ for (j = 1; j <= dynabomb_size; j++)
+ {
+ int x = ex + j * xy[i][0];
+ int y = ey + j * xy[i][1];
+ int element;
+
+ if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Feld[x][y]))
+ break;
+
+ element = Feld[x][y];
+
+ /* do not restart explosions of fields with active bombs */
+ if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
+ continue;
+
+ Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
+
+ if (element != EL_EMPTY && element != EL_EXPLOSION &&
+ !IS_DIGGABLE(element) && !dynabomb_xl)
+ break;
+ }
+ }
+}
+
+void Bang(int x, int y)
+{
+ int element = MovingOrBlocked2Element(x, y);
+ int explosion_type = EX_TYPE_NORMAL;
+
+ if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
+ {
+ struct PlayerInfo *player = PLAYERINFO(x, y);
+
+#if USE_FIX_CE_ACTION_WITH_PLAYER
+ element = Feld[x][y] = player->initial_element;
+#else
+ element = Feld[x][y] = (player->use_murphy ? EL_SP_MURPHY :
+ player->element_nr);
+#endif
+
+ if (level.use_explosion_element[player->index_nr])
+ {
+ int explosion_element = level.explosion_element[player->index_nr];
+
+ if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
+ explosion_type = EX_TYPE_CROSS;
+ else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
+ explosion_type = EX_TYPE_CENTER;
+ }
+ }
+
+ switch (element)
+ {
+ case EL_BUG:
+ case EL_SPACESHIP:
+ case EL_BD_BUTTERFLY:
+ case EL_BD_FIREFLY:
+ case EL_YAMYAM:
+ case EL_DARK_YAMYAM:
+ case EL_ROBOT:
+ case EL_PACMAN:
+ case EL_MOLE:
+ RaiseScoreElement(element);
+ break;
+
+ case EL_DYNABOMB_PLAYER_1_ACTIVE:
+ case EL_DYNABOMB_PLAYER_2_ACTIVE:
+ case EL_DYNABOMB_PLAYER_3_ACTIVE:
+ case EL_DYNABOMB_PLAYER_4_ACTIVE:
+ case EL_DYNABOMB_INCREASE_NUMBER:
+ case EL_DYNABOMB_INCREASE_SIZE:
+ case EL_DYNABOMB_INCREASE_POWER:
+ explosion_type = EX_TYPE_DYNA;
+ break;
+
+ case EL_DC_LANDMINE:
+#if 0
+ case EL_EM_EXIT_OPEN:
+ case EL_EM_STEEL_EXIT_OPEN:
+#endif
+ explosion_type = EX_TYPE_CENTER;
+ break;
+
+ case EL_PENGUIN:
+ case EL_LAMP:
+ case EL_LAMP_ACTIVE:
+ case EL_AMOEBA_TO_DIAMOND:
+ if (!IS_PLAYER(x, y)) /* penguin and player may be at same field */
+ explosion_type = EX_TYPE_CENTER;
+ break;
+
+ default:
+ if (element_info[element].explosion_type == EXPLODES_CROSS)
+ explosion_type = EX_TYPE_CROSS;
+ else if (element_info[element].explosion_type == EXPLODES_1X1)
+ explosion_type = EX_TYPE_CENTER;
+ break;
+ }
+
+ if (explosion_type == EX_TYPE_DYNA)
+ DynaExplode(x, y);
+ else
+ Explode(x, y, EX_PHASE_START, explosion_type);
+
+ CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
+}
+
+void SplashAcid(int x, int y)
+{
+ if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
+ (!IN_LEV_FIELD(x - 1, y - 2) ||
+ !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
+ Feld[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
+
+ if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
+ (!IN_LEV_FIELD(x + 1, y - 2) ||
+ !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
+ Feld[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
+
+ PlayLevelSound(x, y, SND_ACID_SPLASHING);
+}
+
+static void InitBeltMovement()
+{
+ static int belt_base_element[4] =
+ {
+ EL_CONVEYOR_BELT_1_LEFT,
+ EL_CONVEYOR_BELT_2_LEFT,
+ EL_CONVEYOR_BELT_3_LEFT,
+ EL_CONVEYOR_BELT_4_LEFT
+ };
+ static int belt_base_active_element[4] =
+ {
+ EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
+ EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
+ EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
+ EL_CONVEYOR_BELT_4_LEFT_ACTIVE
+ };
+
+ int x, y, i, j;
+
+ /* set frame order for belt animation graphic according to belt direction */
+ for (i = 0; i < NUM_BELTS; i++)
+ {
+ int belt_nr = i;
+
+ for (j = 0; j < NUM_BELT_PARTS; j++)
+ {
+ int element = belt_base_active_element[belt_nr] + j;
+ int graphic_1 = el2img(element);
+ int graphic_2 = el2panelimg(element);
+
+ if (game.belt_dir[i] == MV_LEFT)
+ {
+ graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
+ graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
+ }
+ else
+ {
+ graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
+ graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
+ }
+ }
+ }
+
+ SCAN_PLAYFIELD(x, y)
+ {
+ int element = Feld[x][y];
+
+ for (i = 0; i < NUM_BELTS; i++)
+ {
+ if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
+ {
+ int e_belt_nr = getBeltNrFromBeltElement(element);
+ int belt_nr = i;
+
+ if (e_belt_nr == belt_nr)
+ {
+ int belt_part = Feld[x][y] - belt_base_element[belt_nr];
+
+ Feld[x][y] = belt_base_active_element[belt_nr] + belt_part;
+ }
+ }
+ }
+ }
+}
+
+static void ToggleBeltSwitch(int x, int y)
+{
+ static int belt_base_element[4] =
+ {
+ EL_CONVEYOR_BELT_1_LEFT,
+ EL_CONVEYOR_BELT_2_LEFT,
+ EL_CONVEYOR_BELT_3_LEFT,
+ EL_CONVEYOR_BELT_4_LEFT
+ };
+ static int belt_base_active_element[4] =
+ {
+ EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
+ EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
+ EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
+ EL_CONVEYOR_BELT_4_LEFT_ACTIVE
+ };
+ static int belt_base_switch_element[4] =
+ {
+ EL_CONVEYOR_BELT_1_SWITCH_LEFT,
+ EL_CONVEYOR_BELT_2_SWITCH_LEFT,
+ EL_CONVEYOR_BELT_3_SWITCH_LEFT,
+ EL_CONVEYOR_BELT_4_SWITCH_LEFT
+ };
+ static int belt_move_dir[4] =
+ {
+ MV_LEFT,
+ MV_NONE,
+ MV_RIGHT,
+ MV_NONE,
+ };
+
+ int element = Feld[x][y];
+ int belt_nr = getBeltNrFromBeltSwitchElement(element);
+ int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
+ int belt_dir = belt_move_dir[belt_dir_nr];
+ int xx, yy, i;
+
+ if (!IS_BELT_SWITCH(element))
+ return;
+
+ game.belt_dir_nr[belt_nr] = belt_dir_nr;
+ game.belt_dir[belt_nr] = belt_dir;
+
+ if (belt_dir_nr == 3)
+ belt_dir_nr = 1;
+
+ /* set frame order for belt animation graphic according to belt direction */
+ for (i = 0; i < NUM_BELT_PARTS; i++)
+ {
+ int element = belt_base_active_element[belt_nr] + i;
+ int graphic_1 = el2img(element);
+ int graphic_2 = el2panelimg(element);
+
+ if (belt_dir == MV_LEFT)
+ {
+ graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
+ graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
+ }
+ else
+ {
+ graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
+ graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
+ }
+ }
+
+ SCAN_PLAYFIELD(xx, yy)
+ {
+ int element = Feld[xx][yy];
+
+ if (IS_BELT_SWITCH(element))
+ {
+ int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
+
+ if (e_belt_nr == belt_nr)
+ {
+ Feld[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
+ TEST_DrawLevelField(xx, yy);
+ }
+ }
+ else if (IS_BELT(element) && belt_dir != MV_NONE)
+ {
+ int e_belt_nr = getBeltNrFromBeltElement(element);
+
+ if (e_belt_nr == belt_nr)
+ {
+ int belt_part = Feld[xx][yy] - belt_base_element[belt_nr];
+
+ Feld[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
+ TEST_DrawLevelField(xx, yy);
+ }
+ }
+ else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
+ {
+ int e_belt_nr = getBeltNrFromBeltActiveElement(element);
+
+ if (e_belt_nr == belt_nr)
+ {
+ int belt_part = Feld[xx][yy] - belt_base_active_element[belt_nr];
+
+ Feld[xx][yy] = belt_base_element[belt_nr] + belt_part;
+ TEST_DrawLevelField(xx, yy);
+ }
+ }
+ }
+}
+
+static void ToggleSwitchgateSwitch(int x, int y)
+{
+ int xx, yy;
+
+ game.switchgate_pos = !game.switchgate_pos;
+
+ SCAN_PLAYFIELD(xx, yy)
+ {
+ int element = Feld[xx][yy];
+
+#if !USE_BOTH_SWITCHGATE_SWITCHES
+ if (element == EL_SWITCHGATE_SWITCH_UP ||
+ element == EL_SWITCHGATE_SWITCH_DOWN)
+ {
+ Feld[xx][yy] = EL_SWITCHGATE_SWITCH_UP + game.switchgate_pos;
+ TEST_DrawLevelField(xx, yy);
+ }
+ else if (element == EL_DC_SWITCHGATE_SWITCH_UP ||
+ element == EL_DC_SWITCHGATE_SWITCH_DOWN)
+ {
+ Feld[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP + game.switchgate_pos;
+ TEST_DrawLevelField(xx, yy);
+ }
+#else
+ if (element == EL_SWITCHGATE_SWITCH_UP)
+ {
+ Feld[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
+ TEST_DrawLevelField(xx, yy);
+ }
+ else if (element == EL_SWITCHGATE_SWITCH_DOWN)
+ {
+ Feld[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
+ TEST_DrawLevelField(xx, yy);
+ }
+ else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
+ {
+ Feld[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
+ TEST_DrawLevelField(xx, yy);
+ }
+ else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
+ {
+ Feld[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
+ TEST_DrawLevelField(xx, yy);
+ }
+#endif
+ else if (element == EL_SWITCHGATE_OPEN ||
+ element == EL_SWITCHGATE_OPENING)
+ {
+ Feld[xx][yy] = EL_SWITCHGATE_CLOSING;
+
+ PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
+ }
+ else if (element == EL_SWITCHGATE_CLOSED ||
+ element == EL_SWITCHGATE_CLOSING)
+ {
+ Feld[xx][yy] = EL_SWITCHGATE_OPENING;
+
+ PlayLevelSoundAction(xx, yy, ACTION_OPENING);
+ }
+ }
+}
+
+static int getInvisibleActiveFromInvisibleElement(int element)
+{
+ return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
+ element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
+ element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
+ element);
+}
+
+static int getInvisibleFromInvisibleActiveElement(int element)
+{
+ return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
+ element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
+ element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
+ element);
+}
+
+static void RedrawAllLightSwitchesAndInvisibleElements()
+{
+ int x, y;
+
+ SCAN_PLAYFIELD(x, y)
+ {
+ int element = Feld[x][y];
+
+ if (element == EL_LIGHT_SWITCH &&
+ game.light_time_left > 0)
+ {
+ Feld[x][y] = EL_LIGHT_SWITCH_ACTIVE;
+ TEST_DrawLevelField(x, y);
+ }
+ else if (element == EL_LIGHT_SWITCH_ACTIVE &&
+ game.light_time_left == 0)
+ {
+ Feld[x][y] = EL_LIGHT_SWITCH;
+ TEST_DrawLevelField(x, y);
+ }
+ else if (element == EL_EMC_DRIPPER &&
+ game.light_time_left > 0)
+ {
+ Feld[x][y] = EL_EMC_DRIPPER_ACTIVE;
+ TEST_DrawLevelField(x, y);
+ }
+ else if (element == EL_EMC_DRIPPER_ACTIVE &&
+ game.light_time_left == 0)
+ {
+ Feld[x][y] = EL_EMC_DRIPPER;
+ TEST_DrawLevelField(x, y);
+ }
+ else if (element == EL_INVISIBLE_STEELWALL ||
+ element == EL_INVISIBLE_WALL ||
+ element == EL_INVISIBLE_SAND)
+ {
+ if (game.light_time_left > 0)
+ Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
+
+ TEST_DrawLevelField(x, y);
+
+ /* uncrumble neighbour fields, if needed */
+ if (element == EL_INVISIBLE_SAND)
+ TEST_DrawLevelFieldCrumbledSandNeighbours(x, y);
+ }
+ else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
+ element == EL_INVISIBLE_WALL_ACTIVE ||
+ element == EL_INVISIBLE_SAND_ACTIVE)
+ {
+ if (game.light_time_left == 0)
+ Feld[x][y] = getInvisibleFromInvisibleActiveElement(element);
+
+ TEST_DrawLevelField(x, y);
+
+ /* re-crumble neighbour fields, if needed */
+ if (element == EL_INVISIBLE_SAND)
+ TEST_DrawLevelFieldCrumbledSandNeighbours(x, y);
+ }
+ }
+}
+
+static void RedrawAllInvisibleElementsForLenses()
+{
+ int x, y;
+
+ SCAN_PLAYFIELD(x, y)
+ {
+ int element = Feld[x][y];
+
+ if (element == EL_EMC_DRIPPER &&
+ game.lenses_time_left > 0)
+ {
+ Feld[x][y] = EL_EMC_DRIPPER_ACTIVE;
+ TEST_DrawLevelField(x, y);
+ }
+ else if (element == EL_EMC_DRIPPER_ACTIVE &&
+ game.lenses_time_left == 0)
+ {
+ Feld[x][y] = EL_EMC_DRIPPER;
+ TEST_DrawLevelField(x, y);
+ }
+ else if (element == EL_INVISIBLE_STEELWALL ||
+ element == EL_INVISIBLE_WALL ||
+ element == EL_INVISIBLE_SAND)
+ {
+ if (game.lenses_time_left > 0)
+ Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
+
+ TEST_DrawLevelField(x, y);
+
+ /* uncrumble neighbour fields, if needed */
+ if (element == EL_INVISIBLE_SAND)
+ TEST_DrawLevelFieldCrumbledSandNeighbours(x, y);
+ }
+ else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
+ element == EL_INVISIBLE_WALL_ACTIVE ||
+ element == EL_INVISIBLE_SAND_ACTIVE)
+ {
+ if (game.lenses_time_left == 0)
+ Feld[x][y] = getInvisibleFromInvisibleActiveElement(element);
+
+ TEST_DrawLevelField(x, y);
+
+ /* re-crumble neighbour fields, if needed */
+ if (element == EL_INVISIBLE_SAND)
+ TEST_DrawLevelFieldCrumbledSandNeighbours(x, y);
+ }
+ }
+}
+
+static void RedrawAllInvisibleElementsForMagnifier()
+{
+ int x, y;
+
+ SCAN_PLAYFIELD(x, y)
+ {
+ int element = Feld[x][y];
+
+ if (element == EL_EMC_FAKE_GRASS &&
+ game.magnify_time_left > 0)
+ {
+ Feld[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
+ TEST_DrawLevelField(x, y);
+ }
+ else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
+ game.magnify_time_left == 0)
+ {
+ Feld[x][y] = EL_EMC_FAKE_GRASS;
+ TEST_DrawLevelField(x, y);
+ }
+ else if (IS_GATE_GRAY(element) &&
+ game.magnify_time_left > 0)
+ {
+ Feld[x][y] = (IS_RND_GATE_GRAY(element) ?
+ element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
+ IS_EM_GATE_GRAY(element) ?
+ element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
+ IS_EMC_GATE_GRAY(element) ?
+ element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
+ IS_DC_GATE_GRAY(element) ?
+ EL_DC_GATE_WHITE_GRAY_ACTIVE :
+ element);
+ TEST_DrawLevelField(x, y);
+ }
+ else if (IS_GATE_GRAY_ACTIVE(element) &&
+ game.magnify_time_left == 0)
+ {
+ Feld[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
+ element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
+ IS_EM_GATE_GRAY_ACTIVE(element) ?
+ element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
+ IS_EMC_GATE_GRAY_ACTIVE(element) ?
+ element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
+ IS_DC_GATE_GRAY_ACTIVE(element) ?
+ EL_DC_GATE_WHITE_GRAY :
+ element);
+ TEST_DrawLevelField(x, y);
+ }
+ }
+}
+
+static void ToggleLightSwitch(int x, int y)
+{
+ int element = Feld[x][y];
+
+ game.light_time_left =
+ (element == EL_LIGHT_SWITCH ?
+ level.time_light * FRAMES_PER_SECOND : 0);
+
+ RedrawAllLightSwitchesAndInvisibleElements();
+}
+
+static void ActivateTimegateSwitch(int x, int y)
+{
+ int xx, yy;
+
+ game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
+
+ SCAN_PLAYFIELD(xx, yy)
+ {
+ int element = Feld[xx][yy];
+
+ if (element == EL_TIMEGATE_CLOSED ||
+ element == EL_TIMEGATE_CLOSING)
+ {
+ Feld[xx][yy] = EL_TIMEGATE_OPENING;
+ PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
+ }
+
+ /*
+ else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
+ {
+ Feld[xx][yy] = EL_TIMEGATE_SWITCH;
+ TEST_DrawLevelField(xx, yy);
+ }
+ */
+
+ }
+
+#if 1
+ Feld[x][y] = (Feld[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
+ EL_DC_TIMEGATE_SWITCH_ACTIVE);
+#else
+ Feld[x][y] = EL_TIMEGATE_SWITCH_ACTIVE;
+#endif
+}
+
+void Impact(int x, int y)
+{
+ boolean last_line = (y == lev_fieldy - 1);
+ boolean object_hit = FALSE;
+ boolean impact = (last_line || object_hit);
+ int element = Feld[x][y];
+ int smashed = EL_STEELWALL;
+
+ if (!last_line) /* check if element below was hit */
+ {
+ if (Feld[x][y + 1] == EL_PLAYER_IS_LEAVING)
+ return;
+
+ object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
+ MovDir[x][y + 1] != MV_DOWN ||
+ MovPos[x][y + 1] <= TILEY / 2));
+
+ /* do not smash moving elements that left the smashed field in time */
+ if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
+ ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
+ object_hit = FALSE;
+
+#if USE_QUICKSAND_IMPACT_BUGFIX
+ if (Feld[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
+ {
+ RemoveMovingField(x, y + 1);
+ Feld[x][y + 1] = EL_QUICKSAND_EMPTY;
+ Feld[x][y + 2] = EL_ROCK;
+ TEST_DrawLevelField(x, y + 2);
+
+ object_hit = TRUE;
+ }
+
+ if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
+ {
+ RemoveMovingField(x, y + 1);
+ Feld[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
+ Feld[x][y + 2] = EL_ROCK;
+ TEST_DrawLevelField(x, y + 2);
+
+ object_hit = TRUE;
+ }