+ /* add block delay adjustment (also possible when not blocking) */
+ last_field_block_delay += block_delay_adjustment;
+
+ Feld[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
+ MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
+ }
+
+ if (player->MovPos != 0) /* player has not yet reached destination */
+ return;
+ }
+ else if (!FrameReached(&player->actual_frame_counter, 1))
+ return;
+
+ if (player->MovPos != 0)
+ {
+ player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
+ player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
+
+ /* before DrawPlayer() to draw correct player graphic for this case */
+ if (player->MovPos == 0)
+ CheckGravityMovement(player);
+ }
+
+ if (player->MovPos == 0) /* player reached destination field */
+ {
+ if (player->move_delay_reset_counter > 0)
+ {
+ player->move_delay_reset_counter--;
+
+ if (player->move_delay_reset_counter == 0)
+ {
+ /* continue with normal speed after quickly moving through gate */
+ HALVE_PLAYER_SPEED(player);
+
+ /* be able to make the next move without delay */
+ player->move_delay = 0;
+ }
+ }
+
+ player->last_jx = jx;
+ player->last_jy = jy;
+
+ if (Feld[jx][jy] == EL_EXIT_OPEN ||
+ Feld[jx][jy] == EL_EM_EXIT_OPEN ||
+ Feld[jx][jy] == EL_EM_EXIT_OPENING ||
+ Feld[jx][jy] == EL_STEEL_EXIT_OPEN ||
+ Feld[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
+ Feld[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
+ Feld[jx][jy] == EL_SP_EXIT_OPEN ||
+ Feld[jx][jy] == EL_SP_EXIT_OPENING) /* <-- special case */
+ {
+ DrawPlayer(player); /* needed here only to cleanup last field */
+ RemovePlayer(player);
+
+ if (local_player->friends_still_needed == 0 ||
+ IS_SP_ELEMENT(Feld[jx][jy]))
+ PlayerWins(player);
+ }
+
+ /* this breaks one level: "machine", level 000 */
+ {
+ int move_direction = player->MovDir;
+ int enter_side = MV_DIR_OPPOSITE(move_direction);
+ int leave_side = move_direction;
+ int old_jx = last_jx;
+ int old_jy = last_jy;
+ int old_element = Feld[old_jx][old_jy];
+ int new_element = Feld[jx][jy];
+
+ 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);
+
+ if (IS_CUSTOM_ELEMENT(new_element))
+ CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
+ player->index_bit, enter_side);
+
+ CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
+ CE_PLAYER_ENTERS_X,
+ player->index_bit, enter_side);
+
+ CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
+ CE_MOVE_OF_X, move_direction);
+ }
+
+ if (game.engine_version >= VERSION_IDENT(3,0,7,0))
+ {
+ TestIfPlayerTouchesBadThing(jx, jy);
+ TestIfPlayerTouchesCustomElement(jx, jy);
+
+ /* needed because pushed element has not yet reached its destination,
+ so it would trigger a change event at its previous field location */
+ if (!player->is_pushing)
+ TestIfElementTouchesCustomElement(jx, jy); /* for empty space */
+
+ if (!player->active)
+ RemovePlayer(player);
+ }
+
+ if (!local_player->LevelSolved && level.use_step_counter)
+ {
+ int i;
+
+ TimePlayed++;
+
+ if (TimeLeft > 0)
+ {
+ TimeLeft--;
+
+ if (TimeLeft <= 10 && setup.time_limit)
+ PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
+
+ game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
+
+ DisplayGameControlValues();
+
+ if (!TimeLeft && setup.time_limit)
+ for (i = 0; i < MAX_PLAYERS; i++)
+ KillPlayer(&stored_player[i]);
+ }
+ else if (game.no_time_limit && !AllPlayersGone) /* level w/o time limit */
+ {
+ game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
+
+ DisplayGameControlValues();
+ }
+ }
+
+ if (tape.single_step && tape.recording && !tape.pausing &&
+ !player->programmed_action)
+ TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
+
+ if (!player->programmed_action)
+ CheckSaveEngineSnapshot(player);
+ }
+}
+
+void ScrollScreen(struct PlayerInfo *player, int mode)
+{
+ static unsigned int screen_frame_counter = 0;
+
+ if (mode == SCROLL_INIT)
+ {
+ /* set scrolling step size according to actual player's moving speed */
+ ScrollStepSize = TILEX / player->move_delay_value;
+
+ screen_frame_counter = FrameCounter;
+ ScreenMovDir = player->MovDir;
+ ScreenMovPos = player->MovPos;
+ ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
+ return;
+ }
+ else if (!FrameReached(&screen_frame_counter, 1))
+ return;
+
+ if (ScreenMovPos)
+ {
+ ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
+ ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
+ redraw_mask |= REDRAW_FIELD;
+ }
+ else
+ ScreenMovDir = MV_NONE;
+}
+
+void TestIfPlayerTouchesCustomElement(int x, int y)
+{
+ static int xy[4][2] =
+ {
+ { 0, -1 },
+ { -1, 0 },
+ { +1, 0 },
+ { 0, +1 }
+ };
+ static int trigger_sides[4][2] =
+ {
+ /* center side border side */
+ { CH_SIDE_TOP, CH_SIDE_BOTTOM }, /* check top */
+ { CH_SIDE_LEFT, CH_SIDE_RIGHT }, /* check left */
+ { CH_SIDE_RIGHT, CH_SIDE_LEFT }, /* check right */
+ { CH_SIDE_BOTTOM, CH_SIDE_TOP } /* check bottom */
+ };
+ static int touch_dir[4] =
+ {
+ MV_LEFT | MV_RIGHT,
+ MV_UP | MV_DOWN,
+ MV_UP | MV_DOWN,
+ MV_LEFT | MV_RIGHT
+ };
+ int center_element = Feld[x][y]; /* should always be non-moving! */
+ int i;
+
+ for (i = 0; i < NUM_DIRECTIONS; i++)
+ {
+ int xx = x + xy[i][0];
+ int yy = y + xy[i][1];
+ int center_side = trigger_sides[i][0];
+ int border_side = trigger_sides[i][1];
+ int border_element;
+
+ if (!IN_LEV_FIELD(xx, yy))
+ continue;
+
+ if (IS_PLAYER(x, y)) /* player found at center element */
+ {
+ struct PlayerInfo *player = PLAYERINFO(x, y);
+
+ if (game.engine_version < VERSION_IDENT(3,0,7,0))
+ border_element = Feld[xx][yy]; /* may be moving! */
+ else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
+ border_element = Feld[xx][yy];
+ else if (MovDir[xx][yy] & touch_dir[i]) /* elements are touching */
+ border_element = MovingOrBlocked2Element(xx, yy);
+ else
+ continue; /* center and border element do not touch */
+
+ CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
+ player->index_bit, border_side);
+ CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
+ CE_PLAYER_TOUCHES_X,
+ player->index_bit, border_side);
+
+ {
+ /* use player element that is initially defined in the level playfield,
+ not the player element that corresponds to the runtime player number
+ (example: a level that contains EL_PLAYER_3 as the only player would
+ incorrectly give EL_PLAYER_1 for "player->element_nr") */
+ int player_element = PLAYERINFO(x, y)->initial_element;
+
+ CheckElementChangeBySide(xx, yy, border_element, player_element,
+ CE_TOUCHING_X, border_side);
+ }
+ }
+ else if (IS_PLAYER(xx, yy)) /* player found at border element */
+ {
+ struct PlayerInfo *player = PLAYERINFO(xx, yy);
+
+ if (game.engine_version >= VERSION_IDENT(3,0,7,0))
+ {
+ if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
+ continue; /* center and border element do not touch */
+ }
+
+ CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
+ player->index_bit, center_side);
+ CheckTriggeredElementChangeByPlayer(x, y, center_element,
+ CE_PLAYER_TOUCHES_X,
+ player->index_bit, center_side);
+
+ {
+ /* use player element that is initially defined in the level playfield,
+ not the player element that corresponds to the runtime player number
+ (example: a level that contains EL_PLAYER_3 as the only player would
+ incorrectly give EL_PLAYER_1 for "player->element_nr") */
+ int player_element = PLAYERINFO(xx, yy)->initial_element;
+
+ CheckElementChangeBySide(x, y, center_element, player_element,
+ CE_TOUCHING_X, center_side);
+ }
+
+ break;
+ }
+ }
+}
+
+void TestIfElementTouchesCustomElement(int x, int y)
+{
+ static int xy[4][2] =
+ {
+ { 0, -1 },
+ { -1, 0 },
+ { +1, 0 },
+ { 0, +1 }
+ };
+ static int trigger_sides[4][2] =
+ {
+ /* center side border side */
+ { CH_SIDE_TOP, CH_SIDE_BOTTOM }, /* check top */
+ { CH_SIDE_LEFT, CH_SIDE_RIGHT }, /* check left */
+ { CH_SIDE_RIGHT, CH_SIDE_LEFT }, /* check right */
+ { CH_SIDE_BOTTOM, CH_SIDE_TOP } /* check bottom */
+ };
+ static int touch_dir[4] =
+ {
+ MV_LEFT | MV_RIGHT,
+ MV_UP | MV_DOWN,
+ MV_UP | MV_DOWN,
+ MV_LEFT | MV_RIGHT
+ };
+ boolean change_center_element = FALSE;
+ int center_element = Feld[x][y]; /* should always be non-moving! */
+ int border_element_old[NUM_DIRECTIONS];
+ int i;
+
+ for (i = 0; i < NUM_DIRECTIONS; i++)
+ {
+ int xx = x + xy[i][0];
+ int yy = y + xy[i][1];
+ int border_element;
+
+ border_element_old[i] = -1;
+
+ if (!IN_LEV_FIELD(xx, yy))
+ continue;
+
+ if (game.engine_version < VERSION_IDENT(3,0,7,0))
+ border_element = Feld[xx][yy]; /* may be moving! */
+ else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
+ border_element = Feld[xx][yy];
+ else if (MovDir[xx][yy] & touch_dir[i]) /* elements are touching */
+ border_element = MovingOrBlocked2Element(xx, yy);
+ else
+ continue; /* center and border element do not touch */
+
+ border_element_old[i] = border_element;
+ }
+
+ for (i = 0; i < NUM_DIRECTIONS; i++)
+ {
+ int xx = x + xy[i][0];
+ int yy = y + xy[i][1];
+ int center_side = trigger_sides[i][0];
+ int border_element = border_element_old[i];
+
+ if (border_element == -1)
+ continue;
+
+ /* check for change of border element */
+ CheckElementChangeBySide(xx, yy, border_element, center_element,
+ CE_TOUCHING_X, center_side);
+
+ /* (center element cannot be player, so we dont have to check this here) */
+ }
+
+ for (i = 0; i < NUM_DIRECTIONS; i++)
+ {
+ int xx = x + xy[i][0];
+ int yy = y + xy[i][1];
+ int border_side = trigger_sides[i][1];
+ int border_element = border_element_old[i];
+
+ if (border_element == -1)
+ continue;
+
+ /* check for change of center element (but change it only once) */
+ if (!change_center_element)
+ change_center_element =
+ CheckElementChangeBySide(x, y, center_element, border_element,
+ CE_TOUCHING_X, border_side);
+
+ if (IS_PLAYER(xx, yy))
+ {
+ /* use player element that is initially defined in the level playfield,
+ not the player element that corresponds to the runtime player number
+ (example: a level that contains EL_PLAYER_3 as the only player would
+ incorrectly give EL_PLAYER_1 for "player->element_nr") */
+ int player_element = PLAYERINFO(xx, yy)->initial_element;
+
+ CheckElementChangeBySide(x, y, center_element, player_element,
+ CE_TOUCHING_X, border_side);
+ }
+ }
+}
+
+void TestIfElementHitsCustomElement(int x, int y, int direction)
+{
+ int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
+ int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
+ int hitx = x + dx, hity = y + dy;
+ int hitting_element = Feld[x][y];
+ int touched_element;
+
+ if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
+ return;
+
+ touched_element = (IN_LEV_FIELD(hitx, hity) ?
+ MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
+
+ if (IN_LEV_FIELD(hitx, hity))
+ {
+ int opposite_direction = MV_DIR_OPPOSITE(direction);
+ int hitting_side = direction;
+ int touched_side = opposite_direction;
+ boolean object_hit = (!IS_MOVING(hitx, hity) ||
+ MovDir[hitx][hity] != direction ||
+ ABS(MovPos[hitx][hity]) <= TILEY / 2);
+
+ object_hit = TRUE;
+
+ if (object_hit)
+ {
+ CheckElementChangeBySide(x, y, hitting_element, touched_element,
+ CE_HITTING_X, touched_side);
+
+ CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
+ CE_HIT_BY_X, hitting_side);
+
+ CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
+ CE_HIT_BY_SOMETHING, opposite_direction);
+
+ if (IS_PLAYER(hitx, hity))
+ {
+ /* use player element that is initially defined in the level playfield,
+ not the player element that corresponds to the runtime player number
+ (example: a level that contains EL_PLAYER_3 as the only player would
+ incorrectly give EL_PLAYER_1 for "player->element_nr") */
+ int player_element = PLAYERINFO(hitx, hity)->initial_element;
+
+ CheckElementChangeBySide(x, y, hitting_element, player_element,
+ CE_HITTING_X, touched_side);
+ }
+ }
+ }
+
+ /* "hitting something" is also true when hitting the playfield border */
+ CheckElementChangeBySide(x, y, hitting_element, touched_element,
+ CE_HITTING_SOMETHING, direction);
+}
+
+void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
+{
+ int i, kill_x = -1, kill_y = -1;
+
+ int bad_element = -1;
+ static int test_xy[4][2] =
+ {
+ { 0, -1 },
+ { -1, 0 },
+ { +1, 0 },
+ { 0, +1 }
+ };
+ static int test_dir[4] =
+ {
+ MV_UP,
+ MV_LEFT,
+ MV_RIGHT,
+ MV_DOWN
+ };
+
+ for (i = 0; i < NUM_DIRECTIONS; i++)
+ {
+ int test_x, test_y, test_move_dir, test_element;
+
+ test_x = good_x + test_xy[i][0];
+ test_y = good_y + test_xy[i][1];
+
+ if (!IN_LEV_FIELD(test_x, test_y))
+ continue;
+
+ test_move_dir =
+ (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
+
+ test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
+
+ /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
+ 2nd case: DONT_TOUCH style bad thing does not move away from good thing
+ */
+ if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
+ (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
+ {
+ kill_x = test_x;
+ kill_y = test_y;
+ bad_element = test_element;
+
+ break;
+ }
+ }
+
+ if (kill_x != -1 || kill_y != -1)
+ {
+ if (IS_PLAYER(good_x, good_y))
+ {
+ struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
+
+ if (player->shield_deadly_time_left > 0 &&
+ !IS_INDESTRUCTIBLE(bad_element))
+ Bang(kill_x, kill_y);
+ else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
+ KillPlayer(player);
+ }
+ else
+ Bang(good_x, good_y);
+ }
+}
+
+void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
+{
+ int i, kill_x = -1, kill_y = -1;
+ int bad_element = Feld[bad_x][bad_y];
+ static int test_xy[4][2] =
+ {
+ { 0, -1 },
+ { -1, 0 },
+ { +1, 0 },
+ { 0, +1 }
+ };
+ static int touch_dir[4] =
+ {
+ MV_LEFT | MV_RIGHT,
+ MV_UP | MV_DOWN,
+ MV_UP | MV_DOWN,
+ MV_LEFT | MV_RIGHT
+ };
+ static int test_dir[4] =
+ {
+ MV_UP,
+ MV_LEFT,
+ MV_RIGHT,
+ MV_DOWN
+ };
+
+ if (bad_element == EL_EXPLOSION) /* skip just exploding bad things */
+ return;
+
+ for (i = 0; i < NUM_DIRECTIONS; i++)
+ {
+ int test_x, test_y, test_move_dir, test_element;
+
+ test_x = bad_x + test_xy[i][0];
+ test_y = bad_y + test_xy[i][1];
+
+ if (!IN_LEV_FIELD(test_x, test_y))
+ continue;
+
+ test_move_dir =
+ (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
+
+ test_element = Feld[test_x][test_y];
+
+ /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
+ 2nd case: DONT_TOUCH style bad thing does not move away from good thing
+ */
+ if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
+ (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
+ {
+ /* good thing is player or penguin that does not move away */
+ if (IS_PLAYER(test_x, test_y))
+ {
+ struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
+
+ if (bad_element == EL_ROBOT && player->is_moving)
+ continue; /* robot does not kill player if he is moving */
+
+ if (game.engine_version >= VERSION_IDENT(3,0,7,0))
+ {
+ if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
+ continue; /* center and border element do not touch */
+ }
+
+ kill_x = test_x;
+ kill_y = test_y;
+
+ break;
+ }
+ else if (test_element == EL_PENGUIN)
+ {
+ kill_x = test_x;
+ kill_y = test_y;
+
+ break;
+ }
+ }
+ }
+
+ if (kill_x != -1 || kill_y != -1)
+ {
+ if (IS_PLAYER(kill_x, kill_y))
+ {
+ struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
+
+ if (player->shield_deadly_time_left > 0 &&
+ !IS_INDESTRUCTIBLE(bad_element))
+ Bang(bad_x, bad_y);
+ else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
+ KillPlayer(player);
+ }
+ else
+ Bang(kill_x, kill_y);
+ }
+}
+
+void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
+{
+ int bad_element = Feld[bad_x][bad_y];
+ int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
+ int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
+ int test_x = bad_x + dx, test_y = bad_y + dy;
+ int test_move_dir, test_element;
+ int kill_x = -1, kill_y = -1;
+
+ if (!IN_LEV_FIELD(test_x, test_y))
+ return;
+
+ test_move_dir =
+ (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
+
+ test_element = Feld[test_x][test_y];
+
+ if (test_move_dir != bad_move_dir)
+ {
+ /* good thing can be player or penguin that does not move away */
+ if (IS_PLAYER(test_x, test_y))
+ {
+ struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
+
+ /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
+ player as being hit when he is moving towards the bad thing, because
+ the "get hit by" condition would be lost after the player stops) */
+ if (player->MovPos != 0 && player->MovDir == bad_move_dir)
+ return; /* player moves away from bad thing */
+
+ kill_x = test_x;
+ kill_y = test_y;
+ }
+ else if (test_element == EL_PENGUIN)
+ {
+ kill_x = test_x;
+ kill_y = test_y;
+ }
+ }
+
+ if (kill_x != -1 || kill_y != -1)
+ {
+ if (IS_PLAYER(kill_x, kill_y))
+ {
+ struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
+
+ if (player->shield_deadly_time_left > 0 &&
+ !IS_INDESTRUCTIBLE(bad_element))
+ Bang(bad_x, bad_y);
+ else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
+ KillPlayer(player);
+ }
+ else
+ Bang(kill_x, kill_y);
+ }
+}
+
+void TestIfPlayerTouchesBadThing(int x, int y)
+{
+ TestIfGoodThingHitsBadThing(x, y, MV_NONE);
+}
+
+void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
+{
+ TestIfGoodThingHitsBadThing(x, y, move_dir);
+}
+
+void TestIfBadThingTouchesPlayer(int x, int y)
+{
+ TestIfBadThingHitsGoodThing(x, y, MV_NONE);
+}
+
+void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
+{
+ TestIfBadThingHitsGoodThing(x, y, move_dir);
+}
+
+void TestIfFriendTouchesBadThing(int x, int y)
+{
+ TestIfGoodThingHitsBadThing(x, y, MV_NONE);
+}
+
+void TestIfBadThingTouchesFriend(int x, int y)
+{
+ TestIfBadThingHitsGoodThing(x, y, MV_NONE);
+}
+
+void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
+{
+ int i, kill_x = bad_x, kill_y = bad_y;
+ static int xy[4][2] =
+ {
+ { 0, -1 },
+ { -1, 0 },
+ { +1, 0 },
+ { 0, +1 }
+ };
+
+ for (i = 0; i < NUM_DIRECTIONS; i++)
+ {
+ int x, y, element;
+
+ x = bad_x + xy[i][0];
+ y = bad_y + xy[i][1];
+ if (!IN_LEV_FIELD(x, y))
+ continue;
+
+ element = Feld[x][y];
+ if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
+ element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
+ {
+ kill_x = x;
+ kill_y = y;
+ break;
+ }
+ }
+
+ if (kill_x != bad_x || kill_y != bad_y)
+ Bang(bad_x, bad_y);
+}
+
+void KillPlayer(struct PlayerInfo *player)
+{
+ int jx = player->jx, jy = player->jy;
+
+ if (!player->active)
+ return;
+
+#if 0
+ printf("::: 0: killed == %d, active == %d, reanimated == %d\n",
+ player->killed, player->active, player->reanimated);
+#endif
+
+ /* the following code was introduced to prevent an infinite loop when calling
+ -> Bang()
+ -> CheckTriggeredElementChangeExt()
+ -> ExecuteCustomElementAction()
+ -> KillPlayer()
+ -> (infinitely repeating the above sequence of function calls)
+ which occurs when killing the player while having a CE with the setting
+ "kill player X when explosion of <player X>"; the solution using a new
+ field "player->killed" was chosen for backwards compatibility, although
+ clever use of the fields "player->active" etc. would probably also work */
+#if 1
+ if (player->killed)
+ return;
+#endif
+
+ player->killed = TRUE;
+
+ /* remove accessible field at the player's position */
+ Feld[jx][jy] = EL_EMPTY;
+
+ /* deactivate shield (else Bang()/Explode() would not work right) */
+ player->shield_normal_time_left = 0;
+ player->shield_deadly_time_left = 0;
+
+#if 0
+ printf("::: 1: killed == %d, active == %d, reanimated == %d\n",
+ player->killed, player->active, player->reanimated);
+#endif
+
+ Bang(jx, jy);
+
+#if 0
+ printf("::: 2: killed == %d, active == %d, reanimated == %d\n",
+ player->killed, player->active, player->reanimated);
+#endif
+
+ if (player->reanimated) /* killed player may have been reanimated */
+ player->killed = player->reanimated = FALSE;
+ else
+ BuryPlayer(player);
+}
+
+static void KillPlayerUnlessEnemyProtected(int x, int y)
+{
+ if (!PLAYER_ENEMY_PROTECTED(x, y))
+ KillPlayer(PLAYERINFO(x, y));
+}
+
+static void KillPlayerUnlessExplosionProtected(int x, int y)
+{
+ if (!PLAYER_EXPLOSION_PROTECTED(x, y))
+ KillPlayer(PLAYERINFO(x, y));
+}
+
+void BuryPlayer(struct PlayerInfo *player)
+{
+ int jx = player->jx, jy = player->jy;
+
+ if (!player->active)
+ return;
+
+ PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
+ PlayLevelSound(jx, jy, SND_GAME_LOSING);
+
+ player->GameOver = TRUE;
+ RemovePlayer(player);
+}
+
+void RemovePlayer(struct PlayerInfo *player)
+{
+ int jx = player->jx, jy = player->jy;
+ int i, found = FALSE;
+
+ player->present = FALSE;
+ player->active = FALSE;
+
+ if (!ExplodeField[jx][jy])
+ StorePlayer[jx][jy] = 0;
+
+ if (player->is_moving)
+ TEST_DrawLevelField(player->last_jx, player->last_jy);
+
+ for (i = 0; i < MAX_PLAYERS; i++)
+ if (stored_player[i].active)
+ found = TRUE;
+
+ if (!found)
+ AllPlayersGone = TRUE;
+
+ ExitX = ZX = jx;
+ ExitY = ZY = jy;
+}
+
+static void setFieldForSnapping(int x, int y, int element, int direction)
+{
+ struct ElementInfo *ei = &element_info[element];
+ int direction_bit = MV_DIR_TO_BIT(direction);
+ int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
+ int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
+ IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
+
+ Feld[x][y] = EL_ELEMENT_SNAPPING;
+ MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
+
+ ResetGfxAnimation(x, y);
+
+ GfxElement[x][y] = element;
+ GfxAction[x][y] = action;
+ GfxDir[x][y] = direction;
+ GfxFrame[x][y] = -1;
+}
+
+/*
+ =============================================================================
+ checkDiagonalPushing()
+ -----------------------------------------------------------------------------
+ check if diagonal input device direction results in pushing of object
+ (by checking if the alternative direction is walkable, diggable, ...)
+ =============================================================================
+*/
+
+static boolean checkDiagonalPushing(struct PlayerInfo *player,
+ int x, int y, int real_dx, int real_dy)
+{
+ int jx, jy, dx, dy, xx, yy;
+
+ if (real_dx == 0 || real_dy == 0) /* no diagonal direction => push */
+ return TRUE;
+
+ /* diagonal direction: check alternative direction */
+ jx = player->jx;
+ jy = player->jy;
+ dx = x - jx;
+ dy = y - jy;
+ xx = jx + (dx == 0 ? real_dx : 0);
+ yy = jy + (dy == 0 ? real_dy : 0);
+
+ return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Feld[xx][yy]));
+}
+
+/*
+ =============================================================================
+ DigField()
+ -----------------------------------------------------------------------------
+ x, y: field next to player (non-diagonal) to try to dig to
+ real_dx, real_dy: direction as read from input device (can be diagonal)
+ =============================================================================
+*/
+
+static int DigField(struct PlayerInfo *player,
+ int oldx, int oldy, int x, int y,
+ int real_dx, int real_dy, int mode)
+{
+ boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
+ boolean player_was_pushing = player->is_pushing;
+ boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
+ boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
+ int jx = oldx, jy = oldy;
+ int dx = x - jx, dy = y - jy;
+ int nextx = x + dx, nexty = y + dy;
+ int move_direction = (dx == -1 ? MV_LEFT :
+ dx == +1 ? MV_RIGHT :
+ dy == -1 ? MV_UP :
+ dy == +1 ? MV_DOWN : MV_NONE);
+ int opposite_direction = MV_DIR_OPPOSITE(move_direction);
+ int dig_side = MV_DIR_OPPOSITE(move_direction);
+ int old_element = Feld[jx][jy];
+ int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
+ int collect_count;
+
+ if (is_player) /* function can also be called by EL_PENGUIN */
+ {
+ if (player->MovPos == 0)
+ {
+ player->is_digging = FALSE;
+ player->is_collecting = FALSE;
+ }
+
+ if (player->MovPos == 0) /* last pushing move finished */
+ player->is_pushing = FALSE;
+
+ if (mode == DF_NO_PUSH) /* player just stopped pushing */
+ {
+ player->is_switching = FALSE;
+ player->push_delay = -1;
+
+ return MP_NO_ACTION;
+ }
+ }
+
+ if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
+ old_element = Back[jx][jy];
+
+ /* in case of element dropped at player position, check background */
+ else if (Back[jx][jy] != EL_EMPTY &&
+ game.engine_version >= VERSION_IDENT(2,2,0,0))
+ old_element = Back[jx][jy];
+
+ if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
+ return MP_NO_ACTION; /* field has no opening in this direction */