+ {
+ /* 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 */
+
+ if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
+ return MP_NO_ACTION; /* field has no opening in this direction */
+
+ if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
+ {
+ SplashAcid(x, y);
+
+ Feld[jx][jy] = player->artwork_element;
+ InitMovingField(jx, jy, MV_DOWN);
+ Store[jx][jy] = EL_ACID;
+ ContinueMoving(jx, jy);
+ BuryPlayer(player);
+
+ return MP_DONT_RUN_INTO;