+ for (i = 0; i < NUM_DIRECTIONS; i++)
+ {
+ int ex = x + xy[i][0];
+ int ey = y + xy[i][1];
+
+ if (IN_LEV_FIELD(ex, ey) && (Feld[ex][ey] == EL_EXIT_OPEN ||
+ Feld[ex][ey] == EL_EM_EXIT_OPEN ||
+ Feld[ex][ey] == EL_STEEL_EXIT_OPEN ||
+ Feld[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
+ {
+ attr_x = ex;
+ attr_y = ey;
+ break;
+ }
+ }
+ }
+
+ MovDir[x][y] = MV_NONE;
+ if (attr_x < x)
+ MovDir[x][y] |= (AllPlayersGone ? MV_RIGHT : MV_LEFT);
+ else if (attr_x > x)
+ MovDir[x][y] |= (AllPlayersGone ? MV_LEFT : MV_RIGHT);
+ if (attr_y < y)
+ MovDir[x][y] |= (AllPlayersGone ? MV_DOWN : MV_UP);
+ else if (attr_y > y)
+ MovDir[x][y] |= (AllPlayersGone ? MV_UP : MV_DOWN);
+
+ if (element == EL_ROBOT)
+ {
+ int newx, newy;
+
+ if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
+ MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
+ Moving2Blocked(x, y, &newx, &newy);
+
+ if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
+ MovDelay[x][y] = 8 + 8 * !RND(3);
+ else
+ MovDelay[x][y] = 16;
+ }
+ else if (element == EL_PENGUIN)
+ {
+ int newx, newy;
+
+ MovDelay[x][y] = 1;
+
+ if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
+ {
+ boolean first_horiz = RND(2);
+ int new_move_dir = MovDir[x][y];
+
+ MovDir[x][y] =
+ new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
+ Moving2Blocked(x, y, &newx, &newy);
+
+ if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
+ return;
+
+ MovDir[x][y] =
+ new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
+ Moving2Blocked(x, y, &newx, &newy);
+
+ if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
+ return;
+
+ MovDir[x][y] = old_move_dir;
+ return;
+ }
+ }
+ else if (element == EL_SATELLITE)
+ {
+ int newx, newy;
+
+ MovDelay[x][y] = 1;
+
+ if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
+ {
+ boolean first_horiz = RND(2);
+ int new_move_dir = MovDir[x][y];
+
+ MovDir[x][y] =
+ new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
+ Moving2Blocked(x, y, &newx, &newy);
+
+ if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
+ return;
+
+ MovDir[x][y] =
+ new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
+ Moving2Blocked(x, y, &newx, &newy);
+
+ if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
+ return;
+
+ MovDir[x][y] = old_move_dir;
+ return;
+ }
+ }
+ else if (element == EL_EMC_ANDROID)
+ {
+ static int check_pos[16] =
+ {
+ -1, /* 0 => (invalid) */
+ 7, /* 1 => MV_LEFT */
+ 3, /* 2 => MV_RIGHT */
+ -1, /* 3 => (invalid) */
+ 1, /* 4 => MV_UP */
+ 0, /* 5 => MV_LEFT | MV_UP */
+ 2, /* 6 => MV_RIGHT | MV_UP */
+ -1, /* 7 => (invalid) */
+ 5, /* 8 => MV_DOWN */
+ 6, /* 9 => MV_LEFT | MV_DOWN */
+ 4, /* 10 => MV_RIGHT | MV_DOWN */
+ -1, /* 11 => (invalid) */
+ -1, /* 12 => (invalid) */
+ -1, /* 13 => (invalid) */
+ -1, /* 14 => (invalid) */
+ -1, /* 15 => (invalid) */
+ };
+ static struct
+ {
+ int dx, dy;
+ int dir;
+ } check_xy[8] =
+ {
+ { -1, -1, MV_LEFT | MV_UP },
+ { 0, -1, MV_UP },
+ { +1, -1, MV_RIGHT | MV_UP },
+ { +1, 0, MV_RIGHT },
+ { +1, +1, MV_RIGHT | MV_DOWN },
+ { 0, +1, MV_DOWN },
+ { -1, +1, MV_LEFT | MV_DOWN },
+ { -1, 0, MV_LEFT },
+ };
+ int start_pos, check_order;
+ boolean can_clone = FALSE;
+ int i;
+
+ /* check if there is any free field around current position */
+ for (i = 0; i < 8; i++)
+ {
+ int newx = x + check_xy[i].dx;
+ int newy = y + check_xy[i].dy;
+
+ if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
+ {
+ can_clone = TRUE;
+
+ break;
+ }
+ }
+
+ if (can_clone) /* randomly find an element to clone */
+ {
+ can_clone = FALSE;
+
+ start_pos = check_pos[RND(8)];
+ check_order = (RND(2) ? -1 : +1);
+
+ for (i = 0; i < 8; i++)
+ {
+ int pos_raw = start_pos + i * check_order;
+ int pos = (pos_raw + 8) % 8;
+ int newx = x + check_xy[pos].dx;
+ int newy = y + check_xy[pos].dy;
+
+ if (ANDROID_CAN_CLONE_FIELD(newx, newy))
+ {
+ element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
+ element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
+
+ Store[x][y] = Feld[newx][newy];
+
+ can_clone = TRUE;
+
+ break;
+ }
+ }
+ }
+
+ if (can_clone) /* randomly find a direction to move */
+ {
+ can_clone = FALSE;
+
+ start_pos = check_pos[RND(8)];
+ check_order = (RND(2) ? -1 : +1);
+
+ for (i = 0; i < 8; i++)
+ {
+ int pos_raw = start_pos + i * check_order;
+ int pos = (pos_raw + 8) % 8;
+ int newx = x + check_xy[pos].dx;
+ int newy = y + check_xy[pos].dy;
+ int new_move_dir = check_xy[pos].dir;
+
+ if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
+ {
+ MovDir[x][y] = new_move_dir;
+ MovDelay[x][y] = level.android_clone_time * 8 + 1;
+
+ can_clone = TRUE;
+
+ break;
+ }
+ }
+ }
+
+ if (can_clone) /* cloning and moving successful */
+ return;
+
+ /* cannot clone -- try to move towards player */
+
+ start_pos = check_pos[MovDir[x][y] & 0x0f];
+ check_order = (RND(2) ? -1 : +1);
+
+ for (i = 0; i < 3; i++)
+ {
+ /* first check start_pos, then previous/next or (next/previous) pos */
+ int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
+ int pos = (pos_raw + 8) % 8;
+ int newx = x + check_xy[pos].dx;
+ int newy = y + check_xy[pos].dy;
+ int new_move_dir = check_xy[pos].dir;
+
+ if (IS_PLAYER(newx, newy))
+ break;
+
+ if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
+ {
+ MovDir[x][y] = new_move_dir;
+ MovDelay[x][y] = level.android_move_time * 8 + 1;
+
+ break;
+ }
+ }
+ }
+ }
+ else if (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 ||
+ move_pattern == MV_ALL_DIRECTIONS)
+ {
+ boolean can_turn_left =
+ CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
+ boolean can_turn_right =
+ CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
+
+ if (element_info[element].move_stepsize == 0) /* "not moving" */
+ return;
+
+ if (move_pattern == MV_TURNING_LEFT)
+ MovDir[x][y] = left_dir;
+ else if (move_pattern == MV_TURNING_RIGHT)
+ MovDir[x][y] = right_dir;
+ else if (move_pattern == MV_TURNING_LEFT_RIGHT)
+ MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
+ else if (move_pattern == MV_TURNING_RIGHT_LEFT)
+ MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
+ else if (move_pattern == MV_TURNING_RANDOM)
+ MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
+ can_turn_right && !can_turn_left ? right_dir :
+ RND(2) ? left_dir : right_dir);
+ else if (can_turn_left && can_turn_right)
+ MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
+ else if (can_turn_left)
+ MovDir[x][y] = (RND(2) ? left_dir : back_dir);
+ else if (can_turn_right)
+ MovDir[x][y] = (RND(2) ? right_dir : back_dir);
+ else
+ MovDir[x][y] = back_dir;
+
+ MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
+ }
+ else if (move_pattern == MV_HORIZONTAL ||
+ move_pattern == MV_VERTICAL)
+ {
+ if (move_pattern & old_move_dir)
+ MovDir[x][y] = back_dir;
+ else if (move_pattern == MV_HORIZONTAL)
+ MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
+ else if (move_pattern == MV_VERTICAL)
+ MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
+
+ MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
+ }
+ else if (move_pattern & MV_ANY_DIRECTION)
+ {
+ MovDir[x][y] = move_pattern;
+ MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
+ }
+ else if (move_pattern & MV_WIND_DIRECTION)
+ {
+ MovDir[x][y] = game.wind_direction;
+ MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
+ }
+ else if (move_pattern == MV_ALONG_LEFT_SIDE)
+ {
+ if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
+ MovDir[x][y] = left_dir;
+ else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
+ MovDir[x][y] = right_dir;
+
+ if (MovDir[x][y] != old_move_dir)
+ MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
+ }
+ else if (move_pattern == MV_ALONG_RIGHT_SIDE)
+ {
+ if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
+ MovDir[x][y] = right_dir;
+ else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
+ MovDir[x][y] = left_dir;
+
+ if (MovDir[x][y] != old_move_dir)
+ MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
+ }
+ else if (move_pattern == MV_TOWARDS_PLAYER ||
+ move_pattern == MV_AWAY_FROM_PLAYER)
+ {
+ int attr_x = -1, attr_y = -1;
+ int newx, newy;
+ boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
+
+ if (AllPlayersGone)
+ {
+ attr_x = ExitX;
+ attr_y = ExitY;
+ }
+ else
+ {
+ int i;
+
+ for (i = 0; i < MAX_PLAYERS; i++)
+ {
+ struct PlayerInfo *player = &stored_player[i];
+ int jx = player->jx, jy = player->jy;
+
+ if (!player->active)
+ continue;
+
+ if (attr_x == -1 ||
+ ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
+ {
+ attr_x = jx;
+ attr_y = jy;
+ }
+ }
+ }
+
+ MovDir[x][y] = MV_NONE;
+ if (attr_x < x)
+ MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
+ else if (attr_x > x)
+ MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
+ if (attr_y < y)
+ MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
+ else if (attr_y > y)
+ MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
+
+ MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
+
+ if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
+ {
+ boolean first_horiz = RND(2);
+ int new_move_dir = MovDir[x][y];
+
+ if (element_info[element].move_stepsize == 0) /* "not moving" */
+ {
+ first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
+ MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
+
+ return;
+ }
+
+ MovDir[x][y] =
+ new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
+ Moving2Blocked(x, y, &newx, &newy);
+
+ if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
+ return;
+
+ MovDir[x][y] =
+ new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
+ Moving2Blocked(x, y, &newx, &newy);
+
+ if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
+ return;
+
+ MovDir[x][y] = old_move_dir;
+ }
+ }
+ else if (move_pattern == MV_WHEN_PUSHED ||
+ move_pattern == MV_WHEN_DROPPED)
+ {
+ if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
+ MovDir[x][y] = MV_NONE;
+
+ MovDelay[x][y] = 0;
+ }
+ else if (move_pattern & MV_MAZE_RUNNER_STYLE)
+ {
+ static int test_xy[7][2] =
+ {
+ { 0, -1 },
+ { -1, 0 },
+ { +1, 0 },
+ { 0, +1 },
+ { 0, -1 },
+ { -1, 0 },
+ { +1, 0 },
+ };
+ static int test_dir[7] =
+ {
+ MV_UP,
+ MV_LEFT,
+ MV_RIGHT,
+ MV_DOWN,
+ MV_UP,
+ MV_LEFT,
+ MV_RIGHT,
+ };
+ boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
+ int move_preference = -1000000; /* start with very low preference */
+ int new_move_dir = MV_NONE;
+ int start_test = RND(4);
+ int i;
+
+ for (i = 0; i < NUM_DIRECTIONS; i++)
+ {
+ int move_dir = test_dir[start_test + i];
+ int move_dir_preference;
+
+ xx = x + test_xy[start_test + i][0];
+ yy = y + test_xy[start_test + i][1];
+
+ if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
+ (IS_PLAYER(xx, yy) || Feld[xx][yy] == EL_PLAYER_IS_LEAVING))
+ {
+ new_move_dir = move_dir;
+
+ break;
+ }
+
+ if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
+ continue;
+
+ move_dir_preference = -1 * RunnerVisit[xx][yy];
+ if (hunter_mode && PlayerVisit[xx][yy] > 0)
+ move_dir_preference = PlayerVisit[xx][yy];
+
+ if (move_dir_preference > move_preference)
+ {
+ /* prefer field that has not been visited for the longest time */
+ move_preference = move_dir_preference;
+ new_move_dir = move_dir;
+ }
+ else if (move_dir_preference == move_preference &&
+ move_dir == old_move_dir)
+ {
+ /* prefer last direction when all directions are preferred equally */
+ move_preference = move_dir_preference;
+ new_move_dir = move_dir;
+ }
+ }
+
+ MovDir[x][y] = new_move_dir;
+ if (old_move_dir != new_move_dir)
+ MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
+ }
+}
+
+static void TurnRound(int x, int y)
+{
+ int direction = MovDir[x][y];
+
+ TurnRoundExt(x, y);
+
+ GfxDir[x][y] = MovDir[x][y];
+
+ if (direction != MovDir[x][y])
+ GfxFrame[x][y] = 0;
+
+ if (MovDelay[x][y])
+ GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
+
+ ResetGfxFrame(x, y, FALSE);
+}
+
+static boolean JustBeingPushed(int x, int y)
+{
+ int i;
+
+ for (i = 0; i < MAX_PLAYERS; i++)
+ {
+ struct PlayerInfo *player = &stored_player[i];
+
+ if (player->active && player->is_pushing && player->MovPos)
+ {
+ int next_jx = player->jx + (player->jx - player->last_jx);
+ int next_jy = player->jy + (player->jy - player->last_jy);
+
+ if (x == next_jx && y == next_jy)
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+void StartMoving(int x, int y)
+{
+ boolean started_moving = FALSE; /* some elements can fall _and_ move */
+ int element = Feld[x][y];
+
+ if (Stop[x][y])
+ return;
+
+ if (MovDelay[x][y] == 0)
+ GfxAction[x][y] = ACTION_DEFAULT;
+
+ if (CAN_FALL(element) && y < lev_fieldy - 1)
+ {
+ if ((x > 0 && IS_PLAYER(x - 1, y)) ||
+ (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
+ if (JustBeingPushed(x, y))
+ return;
+
+ if (element == EL_QUICKSAND_FULL)
+ {
+ if (IS_FREE(x, y + 1))
+ {
+ InitMovingField(x, y, MV_DOWN);
+ started_moving = TRUE;
+
+ Feld[x][y] = EL_QUICKSAND_EMPTYING;
+#if USE_QUICKSAND_BD_ROCK_BUGFIX
+ if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
+ Store[x][y] = EL_ROCK;
+#else
+ Store[x][y] = EL_ROCK;
+#endif
+
+ PlayLevelSoundAction(x, y, ACTION_EMPTYING);
+ }
+ else if (Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
+ {
+ if (!MovDelay[x][y])
+ {
+ MovDelay[x][y] = TILEY + 1;
+
+ ResetGfxAnimation(x, y);
+ ResetGfxAnimation(x, y + 1);
+ }
+
+ if (MovDelay[x][y])
+ {
+ DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
+ DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
+
+ MovDelay[x][y]--;
+ if (MovDelay[x][y])
+ return;
+ }
+
+ Feld[x][y] = EL_QUICKSAND_EMPTY;
+ Feld[x][y + 1] = EL_QUICKSAND_FULL;
+ Store[x][y + 1] = Store[x][y];
+ Store[x][y] = 0;
+
+ PlayLevelSoundAction(x, y, ACTION_FILLING);
+ }
+ else if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
+ {
+ if (!MovDelay[x][y])
+ {
+ MovDelay[x][y] = TILEY + 1;
+
+ ResetGfxAnimation(x, y);
+ ResetGfxAnimation(x, y + 1);
+ }
+
+ if (MovDelay[x][y])
+ {
+ DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
+ DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
+
+ MovDelay[x][y]--;
+ if (MovDelay[x][y])
+ return;
+ }
+
+ Feld[x][y] = EL_QUICKSAND_EMPTY;
+ Feld[x][y + 1] = EL_QUICKSAND_FAST_FULL;
+ Store[x][y + 1] = Store[x][y];
+ Store[x][y] = 0;
+
+ PlayLevelSoundAction(x, y, ACTION_FILLING);
+ }
+ }
+ else if (element == EL_QUICKSAND_FAST_FULL)
+ {
+ if (IS_FREE(x, y + 1))
+ {
+ InitMovingField(x, y, MV_DOWN);
+ started_moving = TRUE;
+
+ Feld[x][y] = EL_QUICKSAND_FAST_EMPTYING;
+#if USE_QUICKSAND_BD_ROCK_BUGFIX
+ if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
+ Store[x][y] = EL_ROCK;
+#else
+ Store[x][y] = EL_ROCK;
+#endif
+
+ PlayLevelSoundAction(x, y, ACTION_EMPTYING);
+ }
+ else if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
+ {
+ if (!MovDelay[x][y])
+ {
+ MovDelay[x][y] = TILEY + 1;
+
+ ResetGfxAnimation(x, y);
+ ResetGfxAnimation(x, y + 1);
+ }
+
+ if (MovDelay[x][y])
+ {
+ DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
+ DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
+
+ MovDelay[x][y]--;
+ if (MovDelay[x][y])
+ return;
+ }
+
+ Feld[x][y] = EL_QUICKSAND_FAST_EMPTY;
+ Feld[x][y + 1] = EL_QUICKSAND_FAST_FULL;
+ Store[x][y + 1] = Store[x][y];
+ Store[x][y] = 0;
+
+ PlayLevelSoundAction(x, y, ACTION_FILLING);
+ }
+ else if (Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
+ {
+ if (!MovDelay[x][y])
+ {
+ MovDelay[x][y] = TILEY + 1;
+
+ ResetGfxAnimation(x, y);
+ ResetGfxAnimation(x, y + 1);
+ }
+
+ if (MovDelay[x][y])
+ {
+ DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
+ DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
+
+ MovDelay[x][y]--;
+ if (MovDelay[x][y])
+ return;
+ }
+
+ Feld[x][y] = EL_QUICKSAND_FAST_EMPTY;
+ Feld[x][y + 1] = EL_QUICKSAND_FULL;
+ Store[x][y + 1] = Store[x][y];
+ Store[x][y] = 0;
+
+ PlayLevelSoundAction(x, y, ACTION_FILLING);
+ }
+ }
+ else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
+ Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
+ {
+ InitMovingField(x, y, MV_DOWN);
+ started_moving = TRUE;
+
+ Feld[x][y] = EL_QUICKSAND_FILLING;
+ Store[x][y] = element;
+
+ PlayLevelSoundAction(x, y, ACTION_FILLING);
+ }
+ else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
+ Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
+ {
+ InitMovingField(x, y, MV_DOWN);
+ started_moving = TRUE;
+
+ Feld[x][y] = EL_QUICKSAND_FAST_FILLING;
+ Store[x][y] = element;
+
+ PlayLevelSoundAction(x, y, ACTION_FILLING);
+ }
+ else if (element == EL_MAGIC_WALL_FULL)
+ {
+ if (IS_FREE(x, y + 1))
+ {
+ InitMovingField(x, y, MV_DOWN);
+ started_moving = TRUE;
+
+ Feld[x][y] = EL_MAGIC_WALL_EMPTYING;
+ Store[x][y] = EL_CHANGED(Store[x][y]);
+ }
+ else if (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
+ {
+ if (!MovDelay[x][y])
+ MovDelay[x][y] = TILEY/4 + 1;
+
+ if (MovDelay[x][y])
+ {
+ MovDelay[x][y]--;
+ if (MovDelay[x][y])
+ return;
+ }
+
+ Feld[x][y] = EL_MAGIC_WALL_ACTIVE;
+ Feld[x][y + 1] = EL_MAGIC_WALL_FULL;
+ Store[x][y + 1] = EL_CHANGED(Store[x][y]);
+ Store[x][y] = 0;
+ }
+ }
+ else if (element == EL_BD_MAGIC_WALL_FULL)
+ {
+ if (IS_FREE(x, y + 1))
+ {
+ InitMovingField(x, y, MV_DOWN);
+ started_moving = TRUE;
+
+ Feld[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
+ Store[x][y] = EL_CHANGED_BD(Store[x][y]);
+ }
+ else if (Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
+ {
+ if (!MovDelay[x][y])
+ MovDelay[x][y] = TILEY/4 + 1;
+
+ if (MovDelay[x][y])
+ {
+ MovDelay[x][y]--;
+ if (MovDelay[x][y])
+ return;
+ }
+
+ Feld[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
+ Feld[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
+ Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
+ Store[x][y] = 0;
+ }
+ }
+ else if (element == EL_DC_MAGIC_WALL_FULL)
+ {
+ if (IS_FREE(x, y + 1))
+ {
+ InitMovingField(x, y, MV_DOWN);
+ started_moving = TRUE;
+
+ Feld[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
+ Store[x][y] = EL_CHANGED_DC(Store[x][y]);
+ }
+ else if (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
+ {
+ if (!MovDelay[x][y])
+ MovDelay[x][y] = TILEY/4 + 1;
+
+ if (MovDelay[x][y])
+ {
+ MovDelay[x][y]--;
+ if (MovDelay[x][y])
+ return;
+ }
+
+ Feld[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
+ Feld[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
+ Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
+ Store[x][y] = 0;
+ }
+ }
+ else if ((CAN_PASS_MAGIC_WALL(element) &&
+ (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
+ Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
+ (CAN_PASS_DC_MAGIC_WALL(element) &&
+ (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
+
+ {
+ InitMovingField(x, y, MV_DOWN);
+ started_moving = TRUE;
+
+ Feld[x][y] =
+ (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
+ Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
+ EL_DC_MAGIC_WALL_FILLING);
+ Store[x][y] = element;
+ }
+ else if (CAN_FALL(element) && Feld[x][y + 1] == EL_ACID)
+ {
+ SplashAcid(x, y + 1);
+
+ InitMovingField(x, y, MV_DOWN);
+ started_moving = TRUE;
+
+ Store[x][y] = EL_ACID;
+ }
+ else if (
+#if USE_FIX_IMPACT_COLLISION
+ (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
+ CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
+#else
+ (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
+ CheckCollision[x][y] && !IS_FREE(x, y + 1)) ||
+#endif
+ (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
+ CAN_FALL(element) && WasJustFalling[x][y] &&
+ (Feld[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
+
+ (game.engine_version < VERSION_IDENT(2,2,0,7) &&
+ CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
+ (Feld[x][y + 1] == EL_BLOCKED)))
+ {
+ /* this is needed for a special case not covered by calling "Impact()"
+ from "ContinueMoving()": if an element moves to a tile directly below
+ another element which was just falling on that tile (which was empty
+ in the previous frame), the falling element above would just stop
+ instead of smashing the element below (in previous version, the above
+ element was just checked for "moving" instead of "falling", resulting
+ in incorrect smashes caused by horizontal movement of the above
+ element; also, the case of the player being the element to smash was
+ simply not covered here... :-/ ) */
+
+ CheckCollision[x][y] = 0;
+ CheckImpact[x][y] = 0;
+
+ Impact(x, y);
+ }
+ else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
+ {
+ if (MovDir[x][y] == MV_NONE)
+ {
+ InitMovingField(x, y, MV_DOWN);
+ started_moving = TRUE;
+ }
+ }
+ else if (IS_FREE(x, y + 1) || Feld[x][y + 1] == EL_DIAMOND_BREAKING)
+ {
+ if (WasJustFalling[x][y]) /* prevent animation from being restarted */
+ MovDir[x][y] = MV_DOWN;
+
+ InitMovingField(x, y, MV_DOWN);
+ started_moving = TRUE;
+ }
+ else if (element == EL_AMOEBA_DROP)
+ {
+ Feld[x][y] = EL_AMOEBA_GROWING;
+ Store[x][y] = EL_AMOEBA_WET;
+ }
+ else if (((IS_SLIPPERY(Feld[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
+ (IS_EM_SLIPPERY_WALL(Feld[x][y + 1]) && IS_GEM(element))) &&
+ !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
+ element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
+ {
+ boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
+ (IS_FREE(x - 1, y + 1) ||
+ Feld[x - 1][y + 1] == EL_ACID));
+ boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
+ (IS_FREE(x + 1, y + 1) ||
+ Feld[x + 1][y + 1] == EL_ACID));
+ boolean can_fall_any = (can_fall_left || can_fall_right);
+ boolean can_fall_both = (can_fall_left && can_fall_right);
+ int slippery_type = element_info[Feld[x][y + 1]].slippery_type;
+
+#if USE_NEW_ALL_SLIPPERY
+ if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
+ {
+ if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
+ can_fall_right = FALSE;
+ else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
+ can_fall_left = FALSE;
+ else if (slippery_type == SLIPPERY_ONLY_LEFT)
+ can_fall_right = FALSE;
+ else if (slippery_type == SLIPPERY_ONLY_RIGHT)
+ can_fall_left = FALSE;
+
+ can_fall_any = (can_fall_left || can_fall_right);
+ can_fall_both = FALSE;
+ }
+#else
+ if (can_fall_any && IS_CUSTOM_ELEMENT(Feld[x][y + 1]))
+ {
+ if (slippery_type == SLIPPERY_ONLY_LEFT)
+ can_fall_right = FALSE;
+ else if (slippery_type == SLIPPERY_ONLY_RIGHT)
+ can_fall_left = FALSE;
+ else if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
+ can_fall_right = FALSE;
+ else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
+ can_fall_left = FALSE;
+
+ can_fall_any = (can_fall_left || can_fall_right);
+ can_fall_both = (can_fall_left && can_fall_right);
+ }
+#endif
+
+#if USE_NEW_ALL_SLIPPERY
+#else
+#if USE_NEW_SP_SLIPPERY
+ /* !!! better use the same properties as for custom elements here !!! */
+ else if (game.engine_version >= VERSION_IDENT(3,1,1,0) &&
+ can_fall_both && IS_SP_ELEMENT(Feld[x][y + 1]))
+ {
+ can_fall_right = FALSE; /* slip down on left side */
+ can_fall_both = FALSE;
+ }
+#endif
+#endif
+
+#if USE_NEW_ALL_SLIPPERY
+ if (can_fall_both)
+ {
+ if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
+ can_fall_right = FALSE; /* slip down on left side */
+ else
+ can_fall_left = !(can_fall_right = RND(2));
+
+ can_fall_both = FALSE;
+ }
+#else
+ if (can_fall_both)
+ {
+ if (game.emulation == EMU_BOULDERDASH ||
+ element == EL_BD_ROCK || element == EL_BD_DIAMOND)
+ can_fall_right = FALSE; /* slip down on left side */
+ else
+ can_fall_left = !(can_fall_right = RND(2));
+
+ can_fall_both = FALSE;
+ }
+#endif
+
+ if (can_fall_any)
+ {
+ /* if not determined otherwise, prefer left side for slipping down */
+ InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
+ started_moving = TRUE;
+ }
+ }
+#if 0
+ else if (IS_BELT_ACTIVE(Feld[x][y + 1]) && !CAN_MOVE(element))
+#else
+ else if (IS_BELT_ACTIVE(Feld[x][y + 1]))
+#endif
+ {
+ boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
+ boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
+ int belt_nr = getBeltNrFromBeltActiveElement(Feld[x][y + 1]);
+ int belt_dir = game.belt_dir[belt_nr];
+
+ if ((belt_dir == MV_LEFT && left_is_free) ||
+ (belt_dir == MV_RIGHT && right_is_free))
+ {
+ int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
+
+ InitMovingField(x, y, belt_dir);
+ started_moving = TRUE;
+
+ Pushed[x][y] = TRUE;
+ Pushed[nextx][y] = TRUE;
+
+ GfxAction[x][y] = ACTION_DEFAULT;
+ }
+ else
+ {
+ MovDir[x][y] = 0; /* if element was moving, stop it */
+ }
+ }
+ }
+
+ /* not "else if" because of elements that can fall and move (EL_SPRING) */
+#if 0
+ if (CAN_MOVE(element) && !started_moving && MovDir[x][y] != MV_NONE)
+#else
+ if (CAN_MOVE(element) && !started_moving)
+#endif
+ {
+ int move_pattern = element_info[element].move_pattern;
+ int newx, newy;
+
+#if 0
+#if DEBUG
+ if (MovDir[x][y] == MV_NONE)
+ {
+ printf("StartMoving(): %d,%d: element %d ['%s'] not moving\n",
+ x, y, element, element_info[element].token_name);
+ printf("StartMoving(): This should never happen!\n");
+ }
+#endif
+#endif
+
+ Moving2Blocked(x, y, &newx, &newy);
+
+ if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
+ return;
+
+ if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
+ CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
+ {
+ WasJustMoving[x][y] = 0;
+ CheckCollision[x][y] = 0;
+
+ TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
+
+ if (Feld[x][y] != element) /* element has changed */
+ return;
+ }
+
+ if (!MovDelay[x][y]) /* start new movement phase */
+ {
+ /* all objects that can change their move direction after each step
+ (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall */
+
+ if (element != EL_YAMYAM &&
+ element != EL_DARK_YAMYAM &&
+ element != EL_PACMAN &&
+ !(move_pattern & MV_ANY_DIRECTION) &&
+ 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)
+ {
+ TurnRound(x, y);
+
+ if (MovDelay[x][y] && (element == EL_BUG ||
+ element == EL_SPACESHIP ||
+ element == EL_SP_SNIKSNAK ||
+ element == EL_SP_ELECTRON ||
+ element == EL_MOLE))
+ TEST_DrawLevelField(x, y);
+ }
+ }
+
+ if (MovDelay[x][y]) /* wait some time before next movement */
+ {
+ MovDelay[x][y]--;
+
+ if (element == EL_ROBOT ||
+ element == EL_YAMYAM ||
+ element == EL_DARK_YAMYAM)
+ {
+ DrawLevelElementAnimationIfNeeded(x, y, element);
+ PlayLevelSoundAction(x, y, ACTION_WAITING);
+ }
+ else if (element == EL_SP_ELECTRON)
+ DrawLevelElementAnimationIfNeeded(x, y, element);
+ else if (element == EL_DRAGON)
+ {
+ int i;
+ int dir = MovDir[x][y];
+ int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
+ int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
+ int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
+ dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
+ dir == MV_UP ? IMG_FLAMES_1_UP :
+ dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
+ int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
+
+ GfxAction[x][y] = ACTION_ATTACKING;
+
+ if (IS_PLAYER(x, y))
+ DrawPlayerField(x, y);
+ else
+ TEST_DrawLevelField(x, y);
+
+ PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
+
+ for (i = 1; i <= 3; i++)
+ {
+ int xx = x + i * dx;
+ int yy = y + i * dy;
+ int sx = SCREENX(xx);
+ int sy = SCREENY(yy);
+ int flame_graphic = graphic + (i - 1);
+
+ if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Feld[xx][yy]))
+ break;
+
+ if (MovDelay[x][y])
+ {
+ int flamed = MovingOrBlocked2Element(xx, yy);
+
+ /* !!! */
+#if 0
+ if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
+ Bang(xx, yy);
+ else if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
+ RemoveMovingField(xx, yy);
+ else
+ RemoveField(xx, yy);
+#else
+ if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
+ Bang(xx, yy);
+ else
+ RemoveMovingField(xx, yy);
+#endif
+
+ ChangeDelay[xx][yy] = 0;
+
+ Feld[xx][yy] = EL_FLAMES;
+
+ if (IN_SCR_FIELD(sx, sy))
+ {
+ TEST_DrawLevelFieldCrumbledSand(xx, yy);
+ DrawGraphic(sx, sy, flame_graphic, frame);
+ }
+ }
+ else
+ {
+ if (Feld[xx][yy] == EL_FLAMES)
+ Feld[xx][yy] = EL_EMPTY;
+ TEST_DrawLevelField(xx, yy);
+ }
+ }
+ }
+
+ if (MovDelay[x][y]) /* element still has to wait some time */
+ {
+ PlayLevelSoundAction(x, y, ACTION_WAITING);
+
+ return;
+ }
+ }
+
+ /* now make next step */
+
+ Moving2Blocked(x, y, &newx, &newy); /* get next screen position */
+
+ if (DONT_COLLIDE_WITH(element) &&
+ IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
+ !PLAYER_ENEMY_PROTECTED(newx, newy))
+ {
+ TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
+
+ return;
+ }
+
+ else if (CAN_MOVE_INTO_ACID(element) &&
+ IN_LEV_FIELD(newx, newy) && Feld[newx][newy] == EL_ACID &&
+ !IS_MV_DIAGONAL(MovDir[x][y]) &&
+ (MovDir[x][y] == MV_DOWN ||
+ game.engine_version >= VERSION_IDENT(3,1,0,0)))
+ {
+ SplashAcid(newx, newy);
+ Store[x][y] = EL_ACID;
+ }
+ else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
+ {
+ if (Feld[newx][newy] == EL_EXIT_OPEN ||
+ Feld[newx][newy] == EL_EM_EXIT_OPEN ||
+ Feld[newx][newy] == EL_STEEL_EXIT_OPEN ||
+ Feld[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
+ {
+ RemoveField(x, y);
+ TEST_DrawLevelField(x, y);
+
+ PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
+ if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
+ DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0);
+
+ local_player->friends_still_needed--;
+ if (!local_player->friends_still_needed &&
+ !local_player->GameOver && AllPlayersGone)
+ PlayerWins(local_player);
+
+ return;
+ }
+ else if (IS_FOOD_PENGUIN(Feld[newx][newy]))
+ {
+ if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING)
+ TEST_DrawLevelField(newx, newy);
+ else
+ GfxDir[x][y] = MovDir[x][y] = MV_NONE;
+ }
+ else if (!IS_FREE(newx, newy))
+ {
+ GfxAction[x][y] = ACTION_WAITING;
+
+ if (IS_PLAYER(x, y))
+ DrawPlayerField(x, y);
+ else
+ TEST_DrawLevelField(x, y);
+
+ return;
+ }
+ }
+ else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
+ {
+ if (IS_FOOD_PIG(Feld[newx][newy]))
+ {
+ if (IS_MOVING(newx, newy))
+ RemoveMovingField(newx, newy);
+ else
+ {
+ Feld[newx][newy] = EL_EMPTY;
+ TEST_DrawLevelField(newx, newy);
+ }
+
+ PlayLevelSound(x, y, SND_PIG_DIGGING);
+ }
+ else if (!IS_FREE(newx, newy))
+ {
+ if (IS_PLAYER(x, y))
+ DrawPlayerField(x, y);
+ else
+ TEST_DrawLevelField(x, y);
+
+ return;
+ }
+ }
+ else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
+ {
+ if (Store[x][y] != EL_EMPTY)
+ {
+ boolean can_clone = FALSE;
+ int xx, yy;
+
+ /* check if element to clone is still there */
+ for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
+ {
+ if (IN_LEV_FIELD(xx, yy) && Feld[xx][yy] == Store[x][y])
+ {
+ can_clone = TRUE;
+
+ break;
+ }
+ }
+
+ /* cannot clone or target field not free anymore -- do not clone */
+ if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
+ Store[x][y] = EL_EMPTY;
+ }
+
+ if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
+ {
+ if (IS_MV_DIAGONAL(MovDir[x][y]))
+ {
+ int diagonal_move_dir = MovDir[x][y];
+ int stored = Store[x][y];
+ int change_delay = 8;
+ int graphic;
+
+ /* android is moving diagonally */
+
+ CreateField(x, y, EL_DIAGONAL_SHRINKING);
+
+ Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
+ GfxElement[x][y] = EL_EMC_ANDROID;
+ GfxAction[x][y] = ACTION_SHRINKING;
+ GfxDir[x][y] = diagonal_move_dir;
+ ChangeDelay[x][y] = change_delay;
+
+ graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
+ GfxDir[x][y]);
+
+ DrawLevelGraphicAnimation(x, y, graphic);
+ PlayLevelSoundAction(x, y, ACTION_SHRINKING);
+
+ if (Feld[newx][newy] == EL_ACID)
+ {
+ SplashAcid(newx, newy);
+
+ return;
+ }
+
+ CreateField(newx, newy, EL_DIAGONAL_GROWING);
+
+ Store[newx][newy] = EL_EMC_ANDROID;
+ GfxElement[newx][newy] = EL_EMC_ANDROID;
+ GfxAction[newx][newy] = ACTION_GROWING;
+ GfxDir[newx][newy] = diagonal_move_dir;
+ ChangeDelay[newx][newy] = change_delay;
+
+ graphic = el_act_dir2img(GfxElement[newx][newy],
+ GfxAction[newx][newy], GfxDir[newx][newy]);
+
+ DrawLevelGraphicAnimation(newx, newy, graphic);
+ PlayLevelSoundAction(newx, newy, ACTION_GROWING);
+
+ return;
+ }
+ else
+ {
+ Feld[newx][newy] = EL_EMPTY;
+ TEST_DrawLevelField(newx, newy);
+
+ PlayLevelSoundAction(x, y, ACTION_DIGGING);
+ }
+ }
+ else if (!IS_FREE(newx, newy))
+ {
+#if 0
+ if (IS_PLAYER(x, y))
+ DrawPlayerField(x, y);
+ else
+ TEST_DrawLevelField(x, y);
+#endif
+
+ return;
+ }
+ }
+ else if (IS_CUSTOM_ELEMENT(element) &&
+ CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
+ {
+#if 1
+ if (!DigFieldByCE(newx, newy, element))
+ return;
+#else
+ int new_element = Feld[newx][newy];
+
+ if (!IS_FREE(newx, newy))
+ {
+ int action = (IS_DIGGABLE(new_element) ? ACTION_DIGGING :
+ IS_COLLECTIBLE(new_element) ? ACTION_COLLECTING :
+ ACTION_BREAKING);
+
+ /* no element can dig solid indestructible elements */
+ if (IS_INDESTRUCTIBLE(new_element) &&
+ !IS_DIGGABLE(new_element) &&
+ !IS_COLLECTIBLE(new_element))
+ return;
+
+ if (AmoebaNr[newx][newy] &&
+ (new_element == EL_AMOEBA_FULL ||
+ new_element == EL_BD_AMOEBA ||
+ new_element == EL_AMOEBA_GROWING))
+ {
+ AmoebaCnt[AmoebaNr[newx][newy]]--;
+ AmoebaCnt2[AmoebaNr[newx][newy]]--;
+ }
+
+ if (IS_MOVING(newx, newy))
+ RemoveMovingField(newx, newy);
+ else
+ {
+ RemoveField(newx, newy);
+ TEST_DrawLevelField(newx, newy);
+ }
+
+ /* if digged element was about to explode, prevent the explosion */
+ ExplodeField[newx][newy] = EX_TYPE_NONE;
+
+ PlayLevelSoundAction(x, y, action);
+ }
+
+ Store[newx][newy] = EL_EMPTY;
+
+#if 1
+ /* this makes it possible to leave the removed element again */
+ if (IS_EQUAL_OR_IN_GROUP(new_element, MOVE_ENTER_EL(element)))
+ Store[newx][newy] = new_element;
+#else
+ if (IS_EQUAL_OR_IN_GROUP(new_element, MOVE_ENTER_EL(element)))
+ {
+ int move_leave_element = element_info[element].move_leave_element;
+
+ /* this makes it possible to leave the removed element again */
+ Store[newx][newy] = (move_leave_element == EL_TRIGGER_ELEMENT ?
+ new_element : move_leave_element);
+ }
+#endif
+
+#endif
+
+ if (move_pattern & MV_MAZE_RUNNER_STYLE)
+ {
+ RunnerVisit[x][y] = FrameCounter;
+ PlayerVisit[x][y] /= 8; /* expire player visit path */
+ }
+ }
+ else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
+ {
+ if (!IS_FREE(newx, newy))
+ {
+ if (IS_PLAYER(x, y))
+ DrawPlayerField(x, y);
+ else
+ TEST_DrawLevelField(x, y);
+
+ return;
+ }
+ else
+ {
+ boolean wanna_flame = !RND(10);
+ int dx = newx - x, dy = newy - y;
+ int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
+ int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
+ int element1 = (IN_LEV_FIELD(newx1, newy1) ?
+ MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
+ int element2 = (IN_LEV_FIELD(newx2, newy2) ?
+ MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
+
+ if ((wanna_flame ||
+ IS_CLASSIC_ENEMY(element1) ||
+ IS_CLASSIC_ENEMY(element2)) &&
+ element1 != EL_DRAGON && element2 != EL_DRAGON &&
+ element1 != EL_FLAMES && element2 != EL_FLAMES)
+ {
+ ResetGfxAnimation(x, y);
+ GfxAction[x][y] = ACTION_ATTACKING;
+
+ if (IS_PLAYER(x, y))
+ DrawPlayerField(x, y);
+ else
+ TEST_DrawLevelField(x, y);
+
+ PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
+
+ MovDelay[x][y] = 50;
+
+ /* !!! */
+#if 0
+ RemoveField(newx, newy);
+#endif
+ Feld[newx][newy] = EL_FLAMES;
+ if (IN_LEV_FIELD(newx1, newy1) && Feld[newx1][newy1] == EL_EMPTY)
+ {
+#if 0
+ RemoveField(newx1, newy1);
+#endif
+ Feld[newx1][newy1] = EL_FLAMES;
+ }
+ if (IN_LEV_FIELD(newx2, newy2) && Feld[newx2][newy2] == EL_EMPTY)
+ {
+#if 0
+ RemoveField(newx2, newy2);
+#endif
+ Feld[newx2][newy2] = EL_FLAMES;
+ }
+
+ return;
+ }
+ }
+ }
+ else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
+ Feld[newx][newy] == EL_DIAMOND)
+ {
+ if (IS_MOVING(newx, newy))
+ RemoveMovingField(newx, newy);
+ else
+ {
+ Feld[newx][newy] = EL_EMPTY;
+ TEST_DrawLevelField(newx, newy);
+ }
+
+ PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
+ }
+ else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
+ IS_FOOD_DARK_YAMYAM(Feld[newx][newy]))
+ {
+ if (AmoebaNr[newx][newy])
+ {
+ AmoebaCnt2[AmoebaNr[newx][newy]]--;
+ if (Feld[newx][newy] == EL_AMOEBA_FULL ||
+ Feld[newx][newy] == EL_BD_AMOEBA)
+ AmoebaCnt[AmoebaNr[newx][newy]]--;
+ }
+
+#if 0
+ /* !!! test !!! */
+ if (IS_MOVING(newx, newy) || IS_BLOCKED(newx, newy))
+ {
+ RemoveMovingField(newx, newy);
+ }
+#else
+ if (IS_MOVING(newx, newy))
+ {
+ RemoveMovingField(newx, newy);
+ }
+#endif
+ else
+ {
+ Feld[newx][newy] = EL_EMPTY;
+ TEST_DrawLevelField(newx, newy);
+ }
+
+ PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
+ }
+ else if ((element == EL_PACMAN || element == EL_MOLE)
+ && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Feld[newx][newy]))
+ {
+ if (AmoebaNr[newx][newy])
+ {
+ AmoebaCnt2[AmoebaNr[newx][newy]]--;
+ if (Feld[newx][newy] == EL_AMOEBA_FULL ||
+ Feld[newx][newy] == EL_BD_AMOEBA)
+ AmoebaCnt[AmoebaNr[newx][newy]]--;
+ }
+
+ if (element == EL_MOLE)
+ {
+ Feld[newx][newy] = EL_AMOEBA_SHRINKING;
+ PlayLevelSound(x, y, SND_MOLE_DIGGING);
+
+ ResetGfxAnimation(x, y);
+ GfxAction[x][y] = ACTION_DIGGING;
+ TEST_DrawLevelField(x, y);
+
+ MovDelay[newx][newy] = 0; /* start amoeba shrinking delay */
+
+ return; /* wait for shrinking amoeba */
+ }
+ else /* element == EL_PACMAN */
+ {
+ Feld[newx][newy] = EL_EMPTY;
+ TEST_DrawLevelField(newx, newy);
+ PlayLevelSound(x, y, SND_PACMAN_DIGGING);
+ }
+ }
+ else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
+ (Feld[newx][newy] == EL_AMOEBA_SHRINKING ||
+ (Feld[newx][newy] == EL_EMPTY && Stop[newx][newy])))
+ {
+ /* wait for shrinking amoeba to completely disappear */
+ return;
+ }
+ else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
+ {
+ /* object was running against a wall */
+
+ TurnRound(x, y);
+
+#if 0
+ /* !!! NEW "CE_BLOCKED" STUFF !!! -- DOES NOT WORK YET... !!! */
+ if (move_pattern & MV_ANY_DIRECTION &&
+ move_pattern == MovDir[x][y])
+ {
+ int blocking_element =
+ (IN_LEV_FIELD(newx, newy) ? Feld[newx][newy] : BorderElement);
+
+ CheckElementChangeBySide(x, y, element, blocking_element, CE_BLOCKED,
+ MovDir[x][y]);
+
+ element = Feld[x][y]; /* element might have changed */
+ }
+#endif
+
+ if (GFX_ELEMENT(element) != EL_SAND) /* !!! FIX THIS (crumble) !!! */
+ DrawLevelElementAnimation(x, y, element);
+
+ if (DONT_TOUCH(element))
+ TestIfBadThingTouchesPlayer(x, y);
+
+ return;
+ }
+
+ InitMovingField(x, y, MovDir[x][y]);
+
+ PlayLevelSoundAction(x, y, ACTION_MOVING);
+ }
+
+ if (MovDir[x][y])
+ ContinueMoving(x, 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);
+ int newx = x + dx, newy = y + dy;
+ int stored = Store[x][y];
+ int stored_new = Store[newx][newy];
+ boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
+ boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
+ boolean last_line = (newy == lev_fieldy - 1);
+
+ MovPos[x][y] += getElementMoveStepsize(x, y);
+
+ if (pushed_by_player) /* special case: moving object pushed by player */
+ MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
+
+ if (ABS(MovPos[x][y]) < TILEX)
+ {
+#if 0
+ int ee = Feld[x][y];
+ int gg = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
+ int ff = getGraphicAnimationFrame(gg, GfxFrame[x][y]);
+
+ printf("::: %d.%d: moving %d ... [%d, %d, %d] [%d, %d, %d]\n",
+ x, y, ABS(MovPos[x][y]),
+ ee, gg, ff,
+ GfxAction[x][y], GfxDir[x][y], GfxFrame[x][y]);
+#endif
+
+ TEST_DrawLevelField(x, y);
+
+ return; /* element is still moving */
+ }
+
+ /* element reached destination field */
+
+ Feld[x][y] = EL_EMPTY;
+ Feld[newx][newy] = element;
+ MovPos[x][y] = 0; /* force "not moving" for "crumbled sand" */
+
+ if (Store[x][y] == EL_ACID) /* element is moving into acid pool */
+ {
+ element = Feld[newx][newy] = EL_ACID;
+ }
+ else if (element == EL_MOLE)
+ {
+ Feld[x][y] = EL_SAND;
+
+ TEST_DrawLevelFieldCrumbledSandNeighbours(x, y);
+ }
+ else if (element == EL_QUICKSAND_FILLING)
+ {
+ element = Feld[newx][newy] = get_next_element(element);
+ Store[newx][newy] = Store[x][y];
+ }
+ else if (element == EL_QUICKSAND_EMPTYING)
+ {
+ Feld[x][y] = get_next_element(element);
+ element = Feld[newx][newy] = Store[x][y];
+ }
+ else if (element == EL_QUICKSAND_FAST_FILLING)
+ {
+ element = Feld[newx][newy] = get_next_element(element);
+ Store[newx][newy] = Store[x][y];
+ }
+ else if (element == EL_QUICKSAND_FAST_EMPTYING)
+ {
+ Feld[x][y] = get_next_element(element);
+ element = Feld[newx][newy] = Store[x][y];
+ }
+ else if (element == EL_MAGIC_WALL_FILLING)
+ {
+ element = Feld[newx][newy] = get_next_element(element);
+ if (!game.magic_wall_active)
+ element = Feld[newx][newy] = EL_MAGIC_WALL_DEAD;
+ Store[newx][newy] = Store[x][y];
+ }
+ else if (element == EL_MAGIC_WALL_EMPTYING)
+ {
+ Feld[x][y] = get_next_element(element);
+ if (!game.magic_wall_active)
+ Feld[x][y] = EL_MAGIC_WALL_DEAD;
+ element = Feld[newx][newy] = Store[x][y];
+
+#if USE_NEW_CUSTOM_VALUE
+ InitField(newx, newy, FALSE);
+#endif
+ }
+ else if (element == EL_BD_MAGIC_WALL_FILLING)
+ {
+ element = Feld[newx][newy] = get_next_element(element);
+ if (!game.magic_wall_active)
+ element = Feld[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
+ Store[newx][newy] = Store[x][y];
+ }
+ else if (element == EL_BD_MAGIC_WALL_EMPTYING)
+ {
+ Feld[x][y] = get_next_element(element);
+ if (!game.magic_wall_active)
+ Feld[x][y] = EL_BD_MAGIC_WALL_DEAD;
+ element = Feld[newx][newy] = Store[x][y];
+
+#if USE_NEW_CUSTOM_VALUE
+ InitField(newx, newy, FALSE);
+#endif
+ }
+ else if (element == EL_DC_MAGIC_WALL_FILLING)
+ {
+ element = Feld[newx][newy] = get_next_element(element);
+ if (!game.magic_wall_active)
+ element = Feld[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
+ Store[newx][newy] = Store[x][y];
+ }
+ else if (element == EL_DC_MAGIC_WALL_EMPTYING)
+ {
+ Feld[x][y] = get_next_element(element);
+ if (!game.magic_wall_active)
+ Feld[x][y] = EL_DC_MAGIC_WALL_DEAD;
+ element = Feld[newx][newy] = Store[x][y];
+
+#if USE_NEW_CUSTOM_VALUE
+ InitField(newx, newy, FALSE);
+#endif
+ }
+ else if (element == EL_AMOEBA_DROPPING)
+ {
+ Feld[x][y] = get_next_element(element);
+ element = Feld[newx][newy] = Store[x][y];
+ }
+ else if (element == EL_SOKOBAN_OBJECT)
+ {
+ if (Back[x][y])
+ Feld[x][y] = Back[x][y];
+
+ if (Back[newx][newy])
+ Feld[newx][newy] = EL_SOKOBAN_FIELD_FULL;
+
+ Back[x][y] = Back[newx][newy] = 0;
+ }
+
+ Store[x][y] = EL_EMPTY;
+ MovPos[x][y] = 0;
+ MovDir[x][y] = 0;
+ MovDelay[x][y] = 0;
+
+ MovDelay[newx][newy] = 0;
+
+ if (CAN_CHANGE_OR_HAS_ACTION(element))
+ {
+ /* copy element change control values to new field */
+ ChangeDelay[newx][newy] = ChangeDelay[x][y];
+ ChangePage[newx][newy] = ChangePage[x][y];
+ ChangeCount[newx][newy] = ChangeCount[x][y];
+ ChangeEvent[newx][newy] = ChangeEvent[x][y];
+ }
+
+#if USE_NEW_CUSTOM_VALUE
+ CustomValue[newx][newy] = CustomValue[x][y];
+#endif
+
+ ChangeDelay[x][y] = 0;
+ ChangePage[x][y] = -1;
+ ChangeCount[x][y] = 0;
+ ChangeEvent[x][y] = -1;
+
+#if USE_NEW_CUSTOM_VALUE
+ CustomValue[x][y] = 0;
+#endif
+
+ /* copy animation control values to new field */
+ GfxFrame[newx][newy] = GfxFrame[x][y];
+ GfxRandom[newx][newy] = GfxRandom[x][y]; /* keep same random value */
+ GfxAction[newx][newy] = GfxAction[x][y]; /* keep action one frame */
+ GfxDir[newx][newy] = GfxDir[x][y]; /* keep element direction */
+
+ Pushed[x][y] = Pushed[newx][newy] = FALSE;
+
+ /* some elements can leave other elements behind after moving */
+#if 1
+ if (ei->move_leave_element != EL_EMPTY &&
+ (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
+ (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
+#else
+ if (IS_CUSTOM_ELEMENT(element) && ei->move_leave_element != EL_EMPTY &&
+ (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
+ (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
+#endif
+ {
+ int move_leave_element = ei->move_leave_element;
+
+#if 1
+#if 1
+ /* this makes it possible to leave the removed element again */
+ if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
+ move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
+#else
+ /* this makes it possible to leave the removed element again */
+ if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
+ move_leave_element = stored;
+#endif
+#else
+ /* this makes it possible to leave the removed element again */
+ if (ei->move_leave_type == LEAVE_TYPE_LIMITED &&
+ ei->move_leave_element == EL_TRIGGER_ELEMENT)
+ move_leave_element = stored;
+#endif
+
+ Feld[x][y] = move_leave_element;
+
+ if (element_info[Feld[x][y]].move_direction_initial == MV_START_PREVIOUS)
+ MovDir[x][y] = direction;
+
+ InitField(x, y, FALSE);
+
+ if (GFX_CRUMBLED(Feld[x][y]))
+ TEST_DrawLevelFieldCrumbledSandNeighbours(x, y);
+
+ if (ELEM_IS_PLAYER(move_leave_element))
+ RelocatePlayer(x, y, move_leave_element);
+ }
+
+ /* do this after checking for left-behind element */
+ ResetGfxAnimation(x, y); /* reset animation values for old field */
+
+ if (!CAN_MOVE(element) ||
+ (CAN_FALL(element) && direction == MV_DOWN &&
+ (element == EL_SPRING ||
+ element_info[element].move_pattern == MV_WHEN_PUSHED ||
+ element_info[element].move_pattern == MV_WHEN_DROPPED)))
+ GfxDir[x][y] = MovDir[newx][newy] = 0;
+
+ TEST_DrawLevelField(x, y);
+ TEST_DrawLevelField(newx, newy);
+
+ Stop[newx][newy] = TRUE; /* ignore this element until the next frame */
+
+ /* prevent pushed element from moving on in pushed direction */
+ if (pushed_by_player && CAN_MOVE(element) &&
+ element_info[element].move_pattern & MV_ANY_DIRECTION &&
+ !(element_info[element].move_pattern & direction))
+ TurnRound(newx, newy);
+
+ /* prevent elements on conveyor belt from moving on in last direction */
+ if (pushed_by_conveyor && CAN_FALL(element) &&
+ direction & MV_HORIZONTAL)
+ MovDir[newx][newy] = 0;
+
+ if (!pushed_by_player)
+ {
+ int nextx = newx + dx, nexty = newy + dy;
+ boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
+
+ WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
+
+ if (CAN_FALL(element) && direction == MV_DOWN)
+ WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
+
+ if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
+ CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
+
+#if USE_FIX_IMPACT_COLLISION
+ if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
+ CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
+#endif
+ }
+
+ if (DONT_TOUCH(element)) /* object may be nasty to player or others */
+ {
+ TestIfBadThingTouchesPlayer(newx, newy);
+ TestIfBadThingTouchesFriend(newx, newy);
+
+ if (!IS_CUSTOM_ELEMENT(element))
+ TestIfBadThingTouchesOtherBadThing(newx, newy);
+ }
+ else if (element == EL_PENGUIN)
+ TestIfFriendTouchesBadThing(newx, newy);
+
+ if (DONT_GET_HIT_BY(element))
+ {
+ TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
+ }
+
+ /* give the player one last chance (one more frame) to move away */
+ if (CAN_FALL(element) && direction == MV_DOWN &&
+ (last_line || (!IS_FREE(x, newy + 1) &&
+ (!IS_PLAYER(x, newy + 1) ||
+ game.engine_version < VERSION_IDENT(3,1,1,0)))))
+ Impact(x, newy);
+
+ if (pushed_by_player && !game.use_change_when_pushing_bug)
+ {
+ int push_side = MV_DIR_OPPOSITE(direction);
+ struct PlayerInfo *player = PLAYERINFO(x, y);
+
+ CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
+ player->index_bit, push_side);
+ CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
+ player->index_bit, push_side);
+ }
+
+ if (element == EL_EMC_ANDROID && pushed_by_player) /* make another move */
+ MovDelay[newx][newy] = 1;
+
+ CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
+
+ TestIfElementTouchesCustomElement(x, y); /* empty or new element */
+
+#if 0
+ if (ChangePage[newx][newy] != -1) /* delayed change */
+ {
+ int page = ChangePage[newx][newy];
+ struct ElementChangeInfo *change = &ei->change_page[page];
+
+ ChangePage[newx][newy] = -1;
+
+ if (change->can_change)
+ {
+ if (ChangeElement(newx, newy, element, page))
+ {
+ if (change->post_change_function)
+ change->post_change_function(newx, newy);
+ }
+ }
+
+ if (change->has_action)
+ ExecuteCustomElementAction(newx, newy, element, page);
+ }
+#endif
+
+ TestIfElementHitsCustomElement(newx, newy, direction);
+ TestIfPlayerTouchesCustomElement(newx, newy);
+ TestIfElementTouchesCustomElement(newx, newy);
+
+ if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
+ IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
+ CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
+ MV_DIR_OPPOSITE(direction));
+}
+
+int AmoebeNachbarNr(int ax, int ay)
+{
+ int i;
+ int element = Feld[ax][ay];
+ int group_nr = 0;
+ static int xy[4][2] =
+ {
+ { 0, -1 },
+ { -1, 0 },
+ { +1, 0 },
+ { 0, +1 }
+ };
+
+ for (i = 0; i < NUM_DIRECTIONS; i++)
+ {
+ int x = ax + xy[i][0];
+ int y = ay + xy[i][1];
+
+ if (!IN_LEV_FIELD(x, y))
+ continue;
+
+ if (Feld[x][y] == element && AmoebaNr[x][y] > 0)
+ group_nr = AmoebaNr[x][y];
+ }
+
+ return group_nr;
+}
+
+void AmoebenVereinigen(int ax, int ay)
+{
+ int i, x, y, xx, yy;
+ int new_group_nr = AmoebaNr[ax][ay];
+ static int xy[4][2] =
+ {
+ { 0, -1 },
+ { -1, 0 },
+ { +1, 0 },
+ { 0, +1 }
+ };
+
+ if (new_group_nr == 0)
+ return;
+
+ for (i = 0; i < NUM_DIRECTIONS; i++)
+ {
+ x = ax + xy[i][0];
+ y = ay + xy[i][1];
+
+ if (!IN_LEV_FIELD(x, y))
+ continue;
+
+ if ((Feld[x][y] == EL_AMOEBA_FULL ||
+ Feld[x][y] == EL_BD_AMOEBA ||
+ Feld[x][y] == EL_AMOEBA_DEAD) &&
+ AmoebaNr[x][y] != new_group_nr)
+ {
+ int old_group_nr = AmoebaNr[x][y];
+
+ if (old_group_nr == 0)
+ return;
+
+ AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
+ AmoebaCnt[old_group_nr] = 0;
+ AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
+ AmoebaCnt2[old_group_nr] = 0;
+
+ SCAN_PLAYFIELD(xx, yy)
+ {
+ if (AmoebaNr[xx][yy] == old_group_nr)
+ AmoebaNr[xx][yy] = new_group_nr;
+ }
+ }
+ }
+}
+
+void AmoebeUmwandeln(int ax, int ay)
+{
+ int i, x, y;
+
+ if (Feld[ax][ay] == EL_AMOEBA_DEAD)
+ {
+ int group_nr = AmoebaNr[ax][ay];
+
+#ifdef DEBUG
+ if (group_nr == 0)
+ {
+ printf("AmoebeUmwandeln(): ax = %d, ay = %d\n", ax, ay);
+ printf("AmoebeUmwandeln(): This should never happen!\n");
+ return;
+ }
+#endif
+
+ SCAN_PLAYFIELD(x, y)
+ {
+ if (Feld[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
+ {
+ AmoebaNr[x][y] = 0;
+ Feld[x][y] = EL_AMOEBA_TO_DIAMOND;
+ }
+ }
+
+ PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
+ SND_AMOEBA_TURNING_TO_GEM :
+ SND_AMOEBA_TURNING_TO_ROCK));
+ Bang(ax, ay);
+ }
+ else
+ {
+ static int xy[4][2] =
+ {
+ { 0, -1 },
+ { -1, 0 },
+ { +1, 0 },
+ { 0, +1 }
+ };
+
+ for (i = 0; i < NUM_DIRECTIONS; i++)
+ {
+ x = ax + xy[i][0];
+ y = ay + xy[i][1];
+
+ if (!IN_LEV_FIELD(x, y))
+ continue;
+
+ if (Feld[x][y] == EL_AMOEBA_TO_DIAMOND)
+ {
+ PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
+ SND_AMOEBA_TURNING_TO_GEM :
+ SND_AMOEBA_TURNING_TO_ROCK));
+ Bang(x, y);
+ }
+ }
+ }
+}
+
+void AmoebeUmwandelnBD(int ax, int ay, int new_element)
+{
+ int x, y;
+ int group_nr = AmoebaNr[ax][ay];
+ boolean done = FALSE;
+
+#ifdef DEBUG
+ if (group_nr == 0)
+ {
+ printf("AmoebeUmwandelnBD(): ax = %d, ay = %d\n", ax, ay);
+ printf("AmoebeUmwandelnBD(): This should never happen!\n");
+ return;
+ }
+#endif
+
+ SCAN_PLAYFIELD(x, y)
+ {
+ if (AmoebaNr[x][y] == group_nr &&
+ (Feld[x][y] == EL_AMOEBA_DEAD ||
+ Feld[x][y] == EL_BD_AMOEBA ||
+ Feld[x][y] == EL_AMOEBA_GROWING))
+ {
+ AmoebaNr[x][y] = 0;
+ Feld[x][y] = new_element;
+ InitField(x, y, FALSE);
+ TEST_DrawLevelField(x, y);
+ done = TRUE;
+ }
+ }
+
+ if (done)
+ PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
+ SND_BD_AMOEBA_TURNING_TO_ROCK :
+ SND_BD_AMOEBA_TURNING_TO_GEM));
+}
+
+void AmoebeWaechst(int x, int y)
+{
+ static unsigned long sound_delay = 0;
+ static unsigned long sound_delay_value = 0;
+
+ if (!MovDelay[x][y]) /* start new growing cycle */
+ {
+ MovDelay[x][y] = 7;
+
+ if (DelayReached(&sound_delay, sound_delay_value))
+ {
+ PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
+ sound_delay_value = 30;
+ }
+ }
+
+ if (MovDelay[x][y]) /* wait some time before growing bigger */
+ {
+ MovDelay[x][y]--;
+ if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
+ {
+ int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
+ 6 - MovDelay[x][y]);
+
+ DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_GROWING, frame);
+ }
+
+ if (!MovDelay[x][y])
+ {
+ Feld[x][y] = Store[x][y];
+ Store[x][y] = 0;
+ TEST_DrawLevelField(x, y);
+ }
+ }
+}
+
+void AmoebaDisappearing(int x, int y)
+{
+ static unsigned long sound_delay = 0;
+ static unsigned long sound_delay_value = 0;
+
+ if (!MovDelay[x][y]) /* start new shrinking cycle */
+ {
+ MovDelay[x][y] = 7;
+
+ if (DelayReached(&sound_delay, sound_delay_value))
+ sound_delay_value = 30;
+ }
+
+ if (MovDelay[x][y]) /* wait some time before shrinking */
+ {
+ MovDelay[x][y]--;
+ if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
+ {
+ int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
+ 6 - MovDelay[x][y]);
+
+ DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_SHRINKING, frame);
+ }
+
+ if (!MovDelay[x][y])
+ {
+ Feld[x][y] = EL_EMPTY;
+ TEST_DrawLevelField(x, y);
+
+ /* don't let mole enter this field in this cycle;
+ (give priority to objects falling to this field from above) */
+ Stop[x][y] = TRUE;
+ }
+ }
+}
+
+void AmoebeAbleger(int ax, int ay)
+{
+ int i;
+ int element = Feld[ax][ay];
+ int graphic = el2img(element);
+ int newax = ax, neway = ay;
+ boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
+ static int xy[4][2] =
+ {
+ { 0, -1 },
+ { -1, 0 },
+ { +1, 0 },
+ { 0, +1 }
+ };
+
+ if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
+ {
+ Feld[ax][ay] = EL_AMOEBA_DEAD;
+ TEST_DrawLevelField(ax, ay);
+ return;
+ }
+
+ if (IS_ANIMATED(graphic))
+ DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
+
+ if (!MovDelay[ax][ay]) /* start making new amoeba field */
+ MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
+
+ if (MovDelay[ax][ay]) /* wait some time before making new amoeba */
+ {
+ MovDelay[ax][ay]--;
+ if (MovDelay[ax][ay])
+ return;
+ }
+
+ if (can_drop) /* EL_AMOEBA_WET or EL_EMC_DRIPPER */
+ {
+ int start = RND(4);
+ int x = ax + xy[start][0];
+ int y = ay + xy[start][1];
+
+ if (!IN_LEV_FIELD(x, y))
+ return;
+
+ if (IS_FREE(x, y) ||
+ CAN_GROW_INTO(Feld[x][y]) ||
+ Feld[x][y] == EL_QUICKSAND_EMPTY ||
+ Feld[x][y] == EL_QUICKSAND_FAST_EMPTY)
+ {
+ newax = x;
+ neway = y;
+ }
+
+ if (newax == ax && neway == ay)
+ return;
+ }
+ else /* normal or "filled" (BD style) amoeba */
+ {
+ int start = RND(4);
+ boolean waiting_for_player = FALSE;
+
+ for (i = 0; i < NUM_DIRECTIONS; i++)
+ {
+ int j = (start + i) % 4;
+ int x = ax + xy[j][0];
+ int y = ay + xy[j][1];
+
+ if (!IN_LEV_FIELD(x, y))
+ continue;
+
+ if (IS_FREE(x, y) ||
+ CAN_GROW_INTO(Feld[x][y]) ||
+ Feld[x][y] == EL_QUICKSAND_EMPTY ||
+ Feld[x][y] == EL_QUICKSAND_FAST_EMPTY)
+ {
+ newax = x;
+ neway = y;
+ break;
+ }
+ else if (IS_PLAYER(x, y))
+ waiting_for_player = TRUE;
+ }
+
+ if (newax == ax && neway == ay) /* amoeba cannot grow */
+ {
+ if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
+ {
+ Feld[ax][ay] = EL_AMOEBA_DEAD;
+ TEST_DrawLevelField(ax, ay);
+ AmoebaCnt[AmoebaNr[ax][ay]]--;
+
+ if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) /* amoeba is completely dead */
+ {
+ if (element == EL_AMOEBA_FULL)
+ AmoebeUmwandeln(ax, ay);
+ else if (element == EL_BD_AMOEBA)
+ AmoebeUmwandelnBD(ax, ay, level.amoeba_content);
+ }
+ }
+ return;
+ }
+ else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
+ {
+ /* amoeba gets larger by growing in some direction */
+
+ int new_group_nr = AmoebaNr[ax][ay];
+
+#ifdef DEBUG
+ if (new_group_nr == 0)
+ {
+ printf("AmoebeAbleger(): newax = %d, neway = %d\n", newax, neway);
+ printf("AmoebeAbleger(): This should never happen!\n");
+ return;
+ }
+#endif
+
+ AmoebaNr[newax][neway] = new_group_nr;
+ AmoebaCnt[new_group_nr]++;
+ AmoebaCnt2[new_group_nr]++;
+
+ /* if amoeba touches other amoeba(s) after growing, unify them */
+ AmoebenVereinigen(newax, neway);
+
+ if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
+ {
+ AmoebeUmwandelnBD(newax, neway, EL_BD_ROCK);
+ return;
+ }
+ }
+ }
+
+ if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
+ (neway == lev_fieldy - 1 && newax != ax))
+ {
+ Feld[newax][neway] = EL_AMOEBA_GROWING; /* creation of new amoeba */
+ Store[newax][neway] = element;
+ }
+ else if (neway == ay || element == EL_EMC_DRIPPER)
+ {
+ Feld[newax][neway] = EL_AMOEBA_DROP; /* drop left/right of amoeba */
+
+ PlayLevelSoundAction(newax, neway, ACTION_GROWING);
+ }
+ else
+ {
+ InitMovingField(ax, ay, MV_DOWN); /* drop dripping from amoeba */
+ Feld[ax][ay] = EL_AMOEBA_DROPPING;
+ Store[ax][ay] = EL_AMOEBA_DROP;
+ ContinueMoving(ax, ay);
+ return;
+ }
+
+ TEST_DrawLevelField(newax, neway);
+}
+
+void Life(int ax, int ay)
+{
+ int x1, y1, x2, y2;
+ int life_time = 40;
+ int element = Feld[ax][ay];
+ int graphic = el2img(element);
+ int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
+ level.biomaze);
+ boolean changed = FALSE;
+
+ if (IS_ANIMATED(graphic))
+ DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
+
+ if (Stop[ax][ay])
+ return;
+
+ if (!MovDelay[ax][ay]) /* start new "game of life" cycle */
+ MovDelay[ax][ay] = life_time;
+
+ if (MovDelay[ax][ay]) /* wait some time before next cycle */
+ {
+ MovDelay[ax][ay]--;
+ if (MovDelay[ax][ay])
+ return;
+ }
+
+ for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
+ {
+ int xx = ax+x1, yy = ay+y1;
+ int nachbarn = 0;
+
+ if (!IN_LEV_FIELD(xx, yy))
+ continue;
+
+ for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
+ {
+ int x = xx+x2, y = yy+y2;
+
+ if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
+ continue;
+
+ if (((Feld[x][y] == element ||
+ (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y))) &&
+ !Stop[x][y]) ||
+ (IS_FREE(x, y) && Stop[x][y]))
+ nachbarn++;
+ }
+
+ if (xx == ax && yy == ay) /* field in the middle */
+ {
+ if (nachbarn < life_parameter[0] ||
+ nachbarn > life_parameter[1])
+ {
+ Feld[xx][yy] = EL_EMPTY;
+ if (!Stop[xx][yy])
+ TEST_DrawLevelField(xx, yy);
+ Stop[xx][yy] = TRUE;
+ changed = TRUE;
+ }
+ }
+ else if (IS_FREE(xx, yy) || CAN_GROW_INTO(Feld[xx][yy]))
+ { /* free border field */
+ if (nachbarn >= life_parameter[2] &&
+ nachbarn <= life_parameter[3])
+ {
+ Feld[xx][yy] = element;
+ MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1);
+ if (!Stop[xx][yy])
+ TEST_DrawLevelField(xx, yy);
+ Stop[xx][yy] = TRUE;
+ changed = TRUE;
+ }
+ }
+ }
+
+ if (changed)
+ PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
+ SND_GAME_OF_LIFE_GROWING);
+}
+
+static void InitRobotWheel(int x, int y)
+{
+ ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
+}
+
+static void RunRobotWheel(int x, int y)
+{
+ PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
+}
+
+static void StopRobotWheel(int x, int y)
+{
+ if (ZX == x && ZY == y)
+ {
+ ZX = ZY = -1;
+
+ game.robot_wheel_active = FALSE;
+ }
+}
+
+static void InitTimegateWheel(int x, int y)
+{
+ ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
+}
+
+static void RunTimegateWheel(int x, int y)
+{
+ PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
+}
+
+static void InitMagicBallDelay(int x, int y)
+{
+#if 1
+ ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
+#else
+ ChangeDelay[x][y] = level.ball_time * FRAMES_PER_SECOND + 1;
+#endif
+}
+
+static void ActivateMagicBall(int bx, int by)
+{
+ int x, y;
+
+ if (level.ball_random)
+ {
+ int pos_border = RND(8); /* select one of the eight border elements */
+ int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
+ int xx = pos_content % 3;
+ int yy = pos_content / 3;
+
+ x = bx - 1 + xx;
+ y = by - 1 + yy;
+
+ if (IN_LEV_FIELD(x, y) && Feld[x][y] == EL_EMPTY)
+ CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
+ }
+ else
+ {
+ for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
+ {
+ int xx = x - bx + 1;
+ int yy = y - by + 1;
+
+ if (IN_LEV_FIELD(x, y) && Feld[x][y] == EL_EMPTY)
+ CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
+ }
+ }
+
+ game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
+}
+
+void CheckExit(int x, int y)
+{
+ if (local_player->gems_still_needed > 0 ||
+ local_player->sokobanfields_still_needed > 0 ||
+ local_player->lights_still_needed > 0)
+ {
+ int element = Feld[x][y];
+ int graphic = el2img(element);
+
+ if (IS_ANIMATED(graphic))
+ DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
+
+ return;
+ }
+
+ if (AllPlayersGone) /* do not re-open exit door closed after last player */
+ return;
+
+ Feld[x][y] = EL_EXIT_OPENING;
+
+ PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
+}
+
+void CheckExitEM(int x, int y)
+{
+ if (local_player->gems_still_needed > 0 ||
+ local_player->sokobanfields_still_needed > 0 ||
+ local_player->lights_still_needed > 0)
+ {
+ int element = Feld[x][y];
+ int graphic = el2img(element);
+
+ if (IS_ANIMATED(graphic))
+ DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
+
+ return;
+ }
+
+ if (AllPlayersGone) /* do not re-open exit door closed after last player */
+ return;
+
+ Feld[x][y] = EL_EM_EXIT_OPENING;
+
+ PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
+}
+
+void CheckExitSteel(int x, int y)
+{
+ if (local_player->gems_still_needed > 0 ||
+ local_player->sokobanfields_still_needed > 0 ||
+ local_player->lights_still_needed > 0)
+ {
+ int element = Feld[x][y];
+ int graphic = el2img(element);
+
+ if (IS_ANIMATED(graphic))
+ DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
+
+ return;
+ }
+
+ if (AllPlayersGone) /* do not re-open exit door closed after last player */
+ return;
+
+ Feld[x][y] = EL_STEEL_EXIT_OPENING;
+
+ PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
+}
+
+void CheckExitSteelEM(int x, int y)
+{
+ if (local_player->gems_still_needed > 0 ||
+ local_player->sokobanfields_still_needed > 0 ||
+ local_player->lights_still_needed > 0)
+ {
+ int element = Feld[x][y];
+ int graphic = el2img(element);
+
+ if (IS_ANIMATED(graphic))
+ DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
+
+ return;
+ }
+
+ if (AllPlayersGone) /* do not re-open exit door closed after last player */
+ return;
+
+ Feld[x][y] = EL_EM_STEEL_EXIT_OPENING;
+
+ PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
+}
+
+void CheckExitSP(int x, int y)
+{
+ if (local_player->gems_still_needed > 0)
+ {
+ int element = Feld[x][y];
+ int graphic = el2img(element);
+
+ if (IS_ANIMATED(graphic))
+ DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
+
+ return;
+ }
+
+ if (AllPlayersGone) /* do not re-open exit door closed after last player */
+ return;
+
+ Feld[x][y] = EL_SP_EXIT_OPENING;
+
+ PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
+}
+
+static void CloseAllOpenTimegates()
+{
+ int x, y;
+
+ SCAN_PLAYFIELD(x, y)
+ {
+ int element = Feld[x][y];
+
+ if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
+ {
+ Feld[x][y] = EL_TIMEGATE_CLOSING;
+
+ PlayLevelSoundAction(x, y, ACTION_CLOSING);
+ }
+ }
+}
+
+void DrawTwinkleOnField(int x, int y)
+{
+ if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
+ return;
+
+ if (Feld[x][y] == EL_BD_DIAMOND)
+ return;
+
+ if (MovDelay[x][y] == 0) /* next animation frame */
+ MovDelay[x][y] = 11 * !GetSimpleRandom(500);
+
+ if (MovDelay[x][y] != 0) /* wait some time before next frame */
+ {
+ MovDelay[x][y]--;
+
+ DrawLevelElementAnimation(x, y, Feld[x][y]);
+
+ if (MovDelay[x][y] != 0)
+ {
+ int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
+ 10 - MovDelay[x][y]);
+
+ DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
+ }
+ }
+}
+
+void MauerWaechst(int x, int y)
+{
+ int delay = 6;
+
+ if (!MovDelay[x][y]) /* next animation frame */
+ MovDelay[x][y] = 3 * delay;
+
+ if (MovDelay[x][y]) /* wait some time before next frame */
+ {
+ MovDelay[x][y]--;
+
+ if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
+ {
+ int graphic = el_dir2img(Feld[x][y], GfxDir[x][y]);
+ int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
+
+ DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
+ }
+
+ if (!MovDelay[x][y])
+ {
+ if (MovDir[x][y] == MV_LEFT)
+ {
+ if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Feld[x - 1][y]))
+ TEST_DrawLevelField(x - 1, y);
+ }
+ else if (MovDir[x][y] == MV_RIGHT)
+ {
+ if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Feld[x + 1][y]))
+ TEST_DrawLevelField(x + 1, y);
+ }
+ else if (MovDir[x][y] == MV_UP)
+ {
+ if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Feld[x][y - 1]))
+ TEST_DrawLevelField(x, y - 1);
+ }
+ else
+ {
+ if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Feld[x][y + 1]))
+ TEST_DrawLevelField(x, y + 1);
+ }
+
+ Feld[x][y] = Store[x][y];
+ Store[x][y] = 0;
+ GfxDir[x][y] = MovDir[x][y] = MV_NONE;
+ TEST_DrawLevelField(x, y);
+ }
+ }
+}
+
+void MauerAbleger(int ax, int ay)
+{
+ int element = Feld[ax][ay];
+ int graphic = el2img(element);
+ boolean oben_frei = FALSE, unten_frei = FALSE;
+ boolean links_frei = FALSE, rechts_frei = FALSE;
+ boolean oben_massiv = FALSE, unten_massiv = FALSE;
+ boolean links_massiv = FALSE, rechts_massiv = FALSE;
+ boolean new_wall = FALSE;
+
+ if (IS_ANIMATED(graphic))
+ DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
+
+ if (!MovDelay[ax][ay]) /* start building new wall */
+ MovDelay[ax][ay] = 6;
+
+ if (MovDelay[ax][ay]) /* wait some time before building new wall */
+ {
+ MovDelay[ax][ay]--;
+ if (MovDelay[ax][ay])
+ return;
+ }
+
+ if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
+ oben_frei = TRUE;
+ if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
+ unten_frei = TRUE;
+ if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
+ links_frei = TRUE;
+ if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
+ rechts_frei = TRUE;
+
+ if (element == EL_EXPANDABLE_WALL_VERTICAL ||
+ element == EL_EXPANDABLE_WALL_ANY)
+ {
+ if (oben_frei)
+ {
+ Feld[ax][ay-1] = EL_EXPANDABLE_WALL_GROWING;
+ Store[ax][ay-1] = element;
+ GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
+ if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
+ DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
+ IMG_EXPANDABLE_WALL_GROWING_UP, 0);
+ new_wall = TRUE;
+ }
+ if (unten_frei)
+ {
+ Feld[ax][ay+1] = EL_EXPANDABLE_WALL_GROWING;
+ Store[ax][ay+1] = element;
+ GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
+ if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
+ DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
+ IMG_EXPANDABLE_WALL_GROWING_DOWN, 0);
+ new_wall = TRUE;
+ }
+ }
+
+ if (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
+ element == EL_EXPANDABLE_WALL_ANY ||
+ element == EL_EXPANDABLE_WALL ||
+ element == EL_BD_EXPANDABLE_WALL)
+ {
+ if (links_frei)
+ {
+ Feld[ax-1][ay] = EL_EXPANDABLE_WALL_GROWING;
+ Store[ax-1][ay] = element;
+ GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
+ if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
+ DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
+ IMG_EXPANDABLE_WALL_GROWING_LEFT, 0);
+ new_wall = TRUE;
+ }
+
+ if (rechts_frei)
+ {
+ Feld[ax+1][ay] = EL_EXPANDABLE_WALL_GROWING;
+ Store[ax+1][ay] = element;
+ GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
+ if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
+ DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
+ IMG_EXPANDABLE_WALL_GROWING_RIGHT, 0);
+ new_wall = TRUE;
+ }
+ }
+
+ if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei))
+ TEST_DrawLevelField(ax, ay);
+
+ if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Feld[ax][ay-1]))
+ oben_massiv = TRUE;
+ if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Feld[ax][ay+1]))
+ unten_massiv = TRUE;
+ if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Feld[ax-1][ay]))
+ links_massiv = TRUE;
+ if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Feld[ax+1][ay]))
+ rechts_massiv = TRUE;
+
+ if (((oben_massiv && unten_massiv) ||
+ element == EL_EXPANDABLE_WALL_HORIZONTAL ||
+ element == EL_EXPANDABLE_WALL) &&
+ ((links_massiv && rechts_massiv) ||
+ element == EL_EXPANDABLE_WALL_VERTICAL))
+ Feld[ax][ay] = EL_WALL;
+
+ if (new_wall)
+ PlayLevelSoundAction(ax, ay, ACTION_GROWING);
+}
+
+void MauerAblegerStahl(int ax, int ay)
+{
+ int element = Feld[ax][ay];
+ int graphic = el2img(element);
+ boolean oben_frei = FALSE, unten_frei = FALSE;
+ boolean links_frei = FALSE, rechts_frei = FALSE;
+ boolean oben_massiv = FALSE, unten_massiv = FALSE;
+ boolean links_massiv = FALSE, rechts_massiv = FALSE;
+ boolean new_wall = FALSE;
+
+ if (IS_ANIMATED(graphic))
+ DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
+
+ if (!MovDelay[ax][ay]) /* start building new wall */
+ MovDelay[ax][ay] = 6;
+
+ if (MovDelay[ax][ay]) /* wait some time before building new wall */
+ {
+ MovDelay[ax][ay]--;
+ if (MovDelay[ax][ay])
+ return;
+ }
+
+ if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
+ oben_frei = TRUE;
+ if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
+ unten_frei = TRUE;
+ if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
+ links_frei = TRUE;
+ if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
+ rechts_frei = TRUE;
+
+ if (element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
+ element == EL_EXPANDABLE_STEELWALL_ANY)
+ {
+ if (oben_frei)
+ {
+ Feld[ax][ay-1] = EL_EXPANDABLE_STEELWALL_GROWING;
+ Store[ax][ay-1] = element;
+ GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
+ if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
+ DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
+ IMG_EXPANDABLE_STEELWALL_GROWING_UP, 0);
+ new_wall = TRUE;
+ }
+ if (unten_frei)
+ {
+ Feld[ax][ay+1] = EL_EXPANDABLE_STEELWALL_GROWING;
+ Store[ax][ay+1] = element;
+ GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
+ if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
+ DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
+ IMG_EXPANDABLE_STEELWALL_GROWING_DOWN, 0);
+ new_wall = TRUE;
+ }
+ }
+
+ if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
+ element == EL_EXPANDABLE_STEELWALL_ANY)
+ {
+ if (links_frei)
+ {
+ Feld[ax-1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
+ Store[ax-1][ay] = element;
+ GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
+ if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
+ DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
+ IMG_EXPANDABLE_STEELWALL_GROWING_LEFT, 0);
+ new_wall = TRUE;
+ }
+
+ if (rechts_frei)
+ {
+ Feld[ax+1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
+ Store[ax+1][ay] = element;
+ GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
+ if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
+ DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
+ IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT, 0);
+ new_wall = TRUE;
+ }
+ }
+
+ if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Feld[ax][ay-1]))
+ oben_massiv = TRUE;
+ if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Feld[ax][ay+1]))
+ unten_massiv = TRUE;
+ if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Feld[ax-1][ay]))
+ links_massiv = TRUE;
+ if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Feld[ax+1][ay]))
+ rechts_massiv = TRUE;
+
+ if (((oben_massiv && unten_massiv) ||
+ element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) &&
+ ((links_massiv && rechts_massiv) ||
+ element == EL_EXPANDABLE_STEELWALL_VERTICAL))
+ Feld[ax][ay] = EL_STEELWALL;
+
+ if (new_wall)
+ PlayLevelSoundAction(ax, ay, ACTION_GROWING);
+}
+
+void CheckForDragon(int x, int y)
+{
+ int i, j;
+ boolean dragon_found = FALSE;
+ static int xy[4][2] =
+ {
+ { 0, -1 },
+ { -1, 0 },
+ { +1, 0 },
+ { 0, +1 }
+ };
+
+ for (i = 0; i < NUM_DIRECTIONS; i++)
+ {
+ for (j = 0; j < 4; j++)
+ {
+ int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
+
+ if (IN_LEV_FIELD(xx, yy) &&
+ (Feld[xx][yy] == EL_FLAMES || Feld[xx][yy] == EL_DRAGON))
+ {
+ if (Feld[xx][yy] == EL_DRAGON)
+ dragon_found = TRUE;
+ }
+ else
+ break;
+ }
+ }
+
+ if (!dragon_found)
+ {
+ for (i = 0; i < NUM_DIRECTIONS; i++)
+ {
+ for (j = 0; j < 3; j++)
+ {
+ int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
+
+ if (IN_LEV_FIELD(xx, yy) && Feld[xx][yy] == EL_FLAMES)
+ {
+ Feld[xx][yy] = EL_EMPTY;
+ TEST_DrawLevelField(xx, yy);
+ }
+ else
+ break;
+ }
+ }
+ }
+}
+
+static void InitBuggyBase(int x, int y)
+{
+ int element = Feld[x][y];
+ int activating_delay = FRAMES_PER_SECOND / 4;
+
+ ChangeDelay[x][y] =
+ (element == EL_SP_BUGGY_BASE ?
+ 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
+ element == EL_SP_BUGGY_BASE_ACTIVATING ?
+ activating_delay :
+ element == EL_SP_BUGGY_BASE_ACTIVE ?
+ 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
+}
+
+static void WarnBuggyBase(int x, int y)
+{
+ int i;
+ static int xy[4][2] =
+ {
+ { 0, -1 },
+ { -1, 0 },
+ { +1, 0 },
+ { 0, +1 }
+ };
+
+ for (i = 0; i < NUM_DIRECTIONS; i++)
+ {
+ int xx = x + xy[i][0];
+ int yy = y + xy[i][1];
+
+ if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
+ {
+ PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
+
+ break;
+ }
+ }
+}
+
+static void InitTrap(int x, int y)
+{
+ ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
+}
+
+static void ActivateTrap(int x, int y)
+{
+ PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
+}
+
+static void ChangeActiveTrap(int x, int y)
+{
+ int graphic = IMG_TRAP_ACTIVE;
+
+ /* if new animation frame was drawn, correct crumbled sand border */
+ if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
+ TEST_DrawLevelFieldCrumbledSand(x, y);
+}
+
+static int getSpecialActionElement(int element, int number, int base_element)
+{
+ return (element != EL_EMPTY ? element :
+ number != -1 ? base_element + number - 1 :
+ EL_EMPTY);
+}
+
+static int getModifiedActionNumber(int value_old, int operator, int operand,
+ int value_min, int value_max)
+{
+ int value_new = (operator == CA_MODE_SET ? operand :
+ operator == CA_MODE_ADD ? value_old + operand :
+ operator == CA_MODE_SUBTRACT ? value_old - operand :
+ operator == CA_MODE_MULTIPLY ? value_old * operand :
+ operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
+ operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
+ value_old);
+
+ return (value_new < value_min ? value_min :
+ value_new > value_max ? value_max :
+ value_new);
+}
+
+static void ExecuteCustomElementAction(int x, int y, int element, int page)
+{
+ struct ElementInfo *ei = &element_info[element];
+ struct ElementChangeInfo *change = &ei->change_page[page];
+ int target_element = change->target_element;
+ int action_type = change->action_type;
+ int action_mode = change->action_mode;
+ int action_arg = change->action_arg;
+ int action_element = change->action_element;
+ int i;
+
+ if (!change->has_action)
+ return;
+
+ /* ---------- determine action paramater values -------------------------- */
+
+ int level_time_value =
+ (level.time > 0 ? TimeLeft :
+ TimePlayed);
+
+ int action_arg_element_raw =
+ (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
+ action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
+ action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
+ action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
+ action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
+ action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
+ action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
+ EL_EMPTY);
+ int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
+
+#if 0
+ if (action_arg_element_raw == EL_GROUP_START)
+ printf("::: %d,%d: %d ('%s')\n", x, y, element, EL_NAME(element));
+#endif
+
+ int action_arg_direction =
+ (action_arg >= CA_ARG_DIRECTION_LEFT &&
+ action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
+ action_arg == CA_ARG_DIRECTION_TRIGGER ?
+ change->actual_trigger_side :
+ action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
+ MV_DIR_OPPOSITE(change->actual_trigger_side) :
+ MV_NONE);
+
+ int action_arg_number_min =
+ (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
+ CA_ARG_MIN);
+
+ int action_arg_number_max =
+ (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
+ action_type == CA_SET_LEVEL_GEMS ? 999 :
+ action_type == CA_SET_LEVEL_TIME ? 9999 :
+ action_type == CA_SET_LEVEL_SCORE ? 99999 :
+ action_type == CA_SET_CE_VALUE ? 9999 :
+ action_type == CA_SET_CE_SCORE ? 9999 :
+ CA_ARG_MAX);
+
+ int action_arg_number_reset =
+ (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
+ action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
+ action_type == CA_SET_LEVEL_TIME ? level.time :
+ action_type == CA_SET_LEVEL_SCORE ? 0 :
+#if USE_NEW_CUSTOM_VALUE
+ action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
+#else
+ action_type == CA_SET_CE_VALUE ? ei->custom_value_initial :
+#endif
+ action_type == CA_SET_CE_SCORE ? 0 :
+ 0);
+
+ int action_arg_number =
+ (action_arg <= CA_ARG_MAX ? action_arg :
+ action_arg >= CA_ARG_SPEED_NOT_MOVING &&
+ action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
+ action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
+ action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
+ action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
+ action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
+#if USE_NEW_CUSTOM_VALUE
+ action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
+#else
+ action_arg == CA_ARG_NUMBER_CE_VALUE ? ei->custom_value_initial :
+#endif
+ action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
+ action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
+ action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
+ action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? local_player->gems_still_needed :
+ action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? local_player->score :
+ action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
+ action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
+ action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
+ action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
+ action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
+ action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
+ action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
+ action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
+ action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
+ -1);
+
+ int action_arg_number_old =
+ (action_type == CA_SET_LEVEL_GEMS ? local_player->gems_still_needed :
+ action_type == CA_SET_LEVEL_TIME ? TimeLeft :
+ action_type == CA_SET_LEVEL_SCORE ? local_player->score :
+ action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
+ action_type == CA_SET_CE_SCORE ? ei->collect_score :
+ 0);
+
+ int action_arg_number_new =
+ getModifiedActionNumber(action_arg_number_old,
+ action_mode, action_arg_number,
+ action_arg_number_min, action_arg_number_max);
+
+#if 1
+ int trigger_player_bits =
+ (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
+ change->actual_trigger_player_bits : change->trigger_player);
+#else
+ int trigger_player_bits =
+ (change->actual_trigger_player >= EL_PLAYER_1 &&
+ change->actual_trigger_player <= EL_PLAYER_4 ?
+ (1 << (change->actual_trigger_player - EL_PLAYER_1)) :
+ PLAYER_BITS_ANY);
+#endif
+
+ int action_arg_player_bits =
+ (action_arg >= CA_ARG_PLAYER_1 &&
+ action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
+ action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
+ action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
+ PLAYER_BITS_ANY);
+
+ /* ---------- execute action -------------------------------------------- */
+
+ switch (action_type)
+ {
+ case CA_NO_ACTION:
+ {
+ return;
+ }
+
+ /* ---------- level actions ------------------------------------------- */
+
+ case CA_RESTART_LEVEL:
+ {
+ game.restart_level = TRUE;
+
+ break;
+ }
+
+ case CA_SHOW_ENVELOPE:
+ {
+ int element = getSpecialActionElement(action_arg_element,
+ action_arg_number, EL_ENVELOPE_1);
+
+ if (IS_ENVELOPE(element))
+ local_player->show_envelope = element;
+
+ break;
+ }
+
+ case CA_SET_LEVEL_TIME:
+ {
+ if (level.time > 0) /* only modify limited time value */
+ {
+ TimeLeft = action_arg_number_new;
+
+#if 1
+ game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
+
+ DisplayGameControlValues();
+#else
+ DrawGameValue_Time(TimeLeft);
+#endif
+
+ if (!TimeLeft && setup.time_limit)
+ for (i = 0; i < MAX_PLAYERS; i++)
+ KillPlayer(&stored_player[i]);
+ }
+
+ break;
+ }
+
+ case CA_SET_LEVEL_SCORE:
+ {
+ local_player->score = action_arg_number_new;
+
+#if 1
+ game_panel_controls[GAME_PANEL_SCORE].value = local_player->score;
+
+ DisplayGameControlValues();
+#else
+ DrawGameValue_Score(local_player->score);
+#endif
+
+ break;
+ }
+
+ case CA_SET_LEVEL_GEMS:
+ {
+ local_player->gems_still_needed = action_arg_number_new;
+
+#if 1
+ game_panel_controls[GAME_PANEL_GEMS].value = local_player->gems_still_needed;
+
+ DisplayGameControlValues();
+#else
+ DrawGameValue_Emeralds(local_player->gems_still_needed);
+#endif
+
+ break;
+ }
+
+#if !USE_PLAYER_GRAVITY
+ case CA_SET_LEVEL_GRAVITY:
+ {
+ game.gravity = (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
+ action_arg == CA_ARG_GRAVITY_ON ? TRUE :
+ action_arg == CA_ARG_GRAVITY_TOGGLE ? !game.gravity :
+ game.gravity);
+ break;
+ }
+#endif
+
+ case CA_SET_LEVEL_WIND:
+ {
+ game.wind_direction = action_arg_direction;
+
+ break;
+ }
+
+ case CA_SET_LEVEL_RANDOM_SEED:
+ {
+#if 1
+ /* ensure that setting a new random seed while playing is predictable */
+ InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
+#else
+ InitRND(action_arg_number_new);
+#endif
+
+#if 0
+ printf("::: %d -> %d\n", action_arg_number_new, RND(10));
+#endif
+
+#if 0
+ {
+ int i;
+
+ printf("::: ");
+ for (i = 0; i < 9; i++)
+ printf("%d, ", RND(2));
+ printf("\n");
+ }
+#endif
+
+ break;
+ }
+
+ /* ---------- player actions ------------------------------------------ */
+
+ case CA_MOVE_PLAYER:
+ {
+ /* automatically move to the next field in specified direction */
+ for (i = 0; i < MAX_PLAYERS; i++)
+ if (trigger_player_bits & (1 << i))
+ stored_player[i].programmed_action = action_arg_direction;
+
+ break;
+ }
+
+ case CA_EXIT_PLAYER:
+ {
+ for (i = 0; i < MAX_PLAYERS; i++)
+ if (action_arg_player_bits & (1 << i))
+ PlayerWins(&stored_player[i]);
+
+ break;
+ }
+
+ case CA_KILL_PLAYER:
+ {
+ for (i = 0; i < MAX_PLAYERS; i++)
+ if (action_arg_player_bits & (1 << i))
+ KillPlayer(&stored_player[i]);
+
+ break;
+ }
+
+ case CA_SET_PLAYER_KEYS:
+ {
+ int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
+ int element = getSpecialActionElement(action_arg_element,
+ action_arg_number, EL_KEY_1);
+
+ if (IS_KEY(element))
+ {
+ for (i = 0; i < MAX_PLAYERS; i++)
+ {
+ if (trigger_player_bits & (1 << i))
+ {
+ stored_player[i].key[KEY_NR(element)] = key_state;
+
+ DrawGameDoorValues();
+ }
+ }
+ }
+
+ break;
+ }
+
+ case CA_SET_PLAYER_SPEED:
+ {
+#if 0
+ printf("::: trigger_player_bits == %d\n", trigger_player_bits);
+#endif
+
+ for (i = 0; i < MAX_PLAYERS; i++)
+ {
+ if (trigger_player_bits & (1 << i))
+ {
+ int move_stepsize = TILEX / stored_player[i].move_delay_value;
+
+ if (action_arg == CA_ARG_SPEED_FASTER &&
+ stored_player[i].cannot_move)
+ {
+ action_arg_number = STEPSIZE_VERY_SLOW;
+ }
+ else if (action_arg == CA_ARG_SPEED_SLOWER ||
+ action_arg == CA_ARG_SPEED_FASTER)
+ {
+ action_arg_number = 2;
+ action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
+ CA_MODE_MULTIPLY);
+ }
+ else if (action_arg == CA_ARG_NUMBER_RESET)
+ {
+ action_arg_number = level.initial_player_stepsize[i];
+ }
+
+ move_stepsize =
+ getModifiedActionNumber(move_stepsize,
+ action_mode,
+ action_arg_number,
+ action_arg_number_min,
+ action_arg_number_max);
+
+ SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
+ }
+ }
+
+ break;
+ }
+
+ case CA_SET_PLAYER_SHIELD:
+ {
+ for (i = 0; i < MAX_PLAYERS; i++)
+ {
+ if (trigger_player_bits & (1 << i))
+ {
+ if (action_arg == CA_ARG_SHIELD_OFF)
+ {
+ stored_player[i].shield_normal_time_left = 0;
+ stored_player[i].shield_deadly_time_left = 0;
+ }
+ else if (action_arg == CA_ARG_SHIELD_NORMAL)
+ {
+ stored_player[i].shield_normal_time_left = 999999;
+ }
+ else if (action_arg == CA_ARG_SHIELD_DEADLY)
+ {
+ stored_player[i].shield_normal_time_left = 999999;
+ stored_player[i].shield_deadly_time_left = 999999;
+ }
+ }
+ }
+
+ break;
+ }
+
+#if USE_PLAYER_GRAVITY
+ case CA_SET_PLAYER_GRAVITY:
+ {
+ for (i = 0; i < MAX_PLAYERS; i++)
+ {
+ if (trigger_player_bits & (1 << i))
+ {
+ stored_player[i].gravity =
+ (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
+ action_arg == CA_ARG_GRAVITY_ON ? TRUE :
+ action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
+ stored_player[i].gravity);
+ }
+ }
+
+ break;
+ }
+#endif
+
+ case CA_SET_PLAYER_ARTWORK:
+ {
+ for (i = 0; i < MAX_PLAYERS; i++)
+ {
+ if (trigger_player_bits & (1 << i))
+ {
+ int artwork_element = action_arg_element;
+
+ if (action_arg == CA_ARG_ELEMENT_RESET)
+ artwork_element =
+ (level.use_artwork_element[i] ? level.artwork_element[i] :
+ stored_player[i].element_nr);
+
+#if USE_GFX_RESET_PLAYER_ARTWORK
+ if (stored_player[i].artwork_element != artwork_element)
+ stored_player[i].Frame = 0;
+#endif
+
+ stored_player[i].artwork_element = artwork_element;
+
+ SetPlayerWaiting(&stored_player[i], FALSE);
+
+ /* set number of special actions for bored and sleeping animation */
+ stored_player[i].num_special_action_bored =
+ get_num_special_action(artwork_element,
+ ACTION_BORING_1, ACTION_BORING_LAST);
+ stored_player[i].num_special_action_sleeping =
+ get_num_special_action(artwork_element,
+ ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
+ }
+ }
+
+ break;
+ }
+
+ case CA_SET_PLAYER_INVENTORY:
+ {
+ for (i = 0; i < MAX_PLAYERS; i++)
+ {
+ struct PlayerInfo *player = &stored_player[i];
+ int j, k;
+
+ if (trigger_player_bits & (1 << i))
+ {
+ int inventory_element = action_arg_element;
+
+ if (action_arg == CA_ARG_ELEMENT_TARGET ||
+ action_arg == CA_ARG_ELEMENT_TRIGGER ||
+ action_arg == CA_ARG_ELEMENT_ACTION)
+ {
+ int element = inventory_element;
+ int collect_count = element_info[element].collect_count_initial;
+
+ if (!IS_CUSTOM_ELEMENT(element))
+ collect_count = 1;
+
+ if (collect_count == 0)
+ player->inventory_infinite_element = element;
+ else
+ for (k = 0; k < collect_count; k++)
+ if (player->inventory_size < MAX_INVENTORY_SIZE)
+ player->inventory_element[player->inventory_size++] =
+ element;
+ }
+ else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
+ action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
+ action_arg == CA_ARG_INVENTORY_RM_ACTION)
+ {
+ if (player->inventory_infinite_element != EL_UNDEFINED &&
+ IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
+ action_arg_element_raw))
+ player->inventory_infinite_element = EL_UNDEFINED;
+
+ for (k = 0, j = 0; j < player->inventory_size; j++)
+ {
+ if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
+ action_arg_element_raw))
+ player->inventory_element[k++] = player->inventory_element[j];
+ }
+
+ player->inventory_size = k;
+ }
+ else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
+ {
+ if (player->inventory_size > 0)
+ {
+ for (j = 0; j < player->inventory_size - 1; j++)
+ player->inventory_element[j] = player->inventory_element[j + 1];
+
+ player->inventory_size--;
+ }
+ }
+ else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
+ {
+ if (player->inventory_size > 0)
+ player->inventory_size--;
+ }
+ else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
+ {
+ player->inventory_infinite_element = EL_UNDEFINED;
+ player->inventory_size = 0;
+ }
+ else if (action_arg == CA_ARG_INVENTORY_RESET)
+ {
+ player->inventory_infinite_element = EL_UNDEFINED;
+ player->inventory_size = 0;
+
+ if (level.use_initial_inventory[i])
+ {
+ for (j = 0; j < level.initial_inventory_size[i]; j++)
+ {
+ int element = level.initial_inventory_content[i][j];
+ int collect_count = element_info[element].collect_count_initial;
+
+ if (!IS_CUSTOM_ELEMENT(element))
+ collect_count = 1;
+
+ if (collect_count == 0)
+ player->inventory_infinite_element = element;
+ else
+ for (k = 0; k < collect_count; k++)
+ if (player->inventory_size < MAX_INVENTORY_SIZE)
+ player->inventory_element[player->inventory_size++] =
+ element;
+ }
+ }
+ }
+ }
+ }
+
+ break;
+ }
+
+ /* ---------- CE actions ---------------------------------------------- */
+
+ case CA_SET_CE_VALUE:
+ {
+#if USE_NEW_CUSTOM_VALUE
+ int last_ce_value = CustomValue[x][y];
+
+ CustomValue[x][y] = action_arg_number_new;
+
+ if (CustomValue[x][y] != last_ce_value)
+ {
+ CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
+ CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
+
+ if (CustomValue[x][y] == 0)
+ {
+ CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
+ CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
+ }
+ }
+#endif
+
+ break;
+ }
+
+ case CA_SET_CE_SCORE:
+ {
+#if USE_NEW_CUSTOM_VALUE
+ int last_ce_score = ei->collect_score;
+
+ ei->collect_score = action_arg_number_new;
+
+ if (ei->collect_score != last_ce_score)
+ {
+ CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
+ CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
+
+ if (ei->collect_score == 0)
+ {
+ int xx, yy;
+
+ CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
+ CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
+
+ /*
+ This is a very special case that seems to be a mixture between
+ CheckElementChange() and CheckTriggeredElementChange(): while
+ the first one only affects single elements that are triggered
+ directly, the second one affects multiple elements in the playfield
+ that are triggered indirectly by another element. This is a third
+ case: Changing the CE score always affects multiple identical CEs,
+ so every affected CE must be checked, not only the single CE for
+ which the CE score was changed in the first place (as every instance
+ of that CE shares the same CE score, and therefore also can change)!
+ */
+ SCAN_PLAYFIELD(xx, yy)
+ {
+ if (Feld[xx][yy] == element)
+ CheckElementChange(xx, yy, element, EL_UNDEFINED,
+ CE_SCORE_GETS_ZERO);
+ }
+ }
+ }
+#endif
+
+ break;
+ }
+
+ case CA_SET_CE_ARTWORK:
+ {
+ int artwork_element = action_arg_element;
+ boolean reset_frame = FALSE;
+ int xx, yy;
+
+ if (action_arg == CA_ARG_ELEMENT_RESET)
+ artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
+ element);
+
+ if (ei->gfx_element != artwork_element)
+ reset_frame = TRUE;
+
+ ei->gfx_element = artwork_element;
+
+ SCAN_PLAYFIELD(xx, yy)
+ {
+ if (Feld[xx][yy] == element)
+ {
+ if (reset_frame)
+ {
+ ResetGfxAnimation(xx, yy);
+ ResetRandomAnimationValue(xx, yy);
+ }
+
+ TEST_DrawLevelField(xx, yy);
+ }
+ }
+
+ break;
+ }
+
+ /* ---------- engine actions ------------------------------------------ */
+
+ case CA_SET_ENGINE_SCAN_MODE:
+ {
+ InitPlayfieldScanMode(action_arg);
+
+ break;
+ }
+
+ default:
+ break;
+ }
+}
+
+static void CreateFieldExt(int x, int y, int element, boolean is_change)
+{
+ int old_element = Feld[x][y];
+ int new_element = GetElementFromGroupElement(element);
+ int previous_move_direction = MovDir[x][y];
+#if USE_NEW_CUSTOM_VALUE
+ int last_ce_value = CustomValue[x][y];
+#endif
+ boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
+ boolean new_element_is_player = ELEM_IS_PLAYER(new_element);
+ boolean add_player_onto_element = (new_element_is_player &&
+#if USE_CODE_THAT_BREAKS_SNAKE_BITE
+ /* this breaks SnakeBite when a snake is
+ halfway through a door that closes */
+ /* NOW FIXED AT LEVEL INIT IN files.c */
+ new_element != EL_SOKOBAN_FIELD_PLAYER &&
+#endif
+ IS_WALKABLE(old_element));
+
+#if 0
+ /* check if element under the player changes from accessible to unaccessible
+ (needed for special case of dropping element which then changes) */
+ if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y) &&
+ IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
+ {
+ Bang(x, y);
+
+ return;
+ }
+#endif
+
+ if (!add_player_onto_element)
+ {
+ if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
+ RemoveMovingField(x, y);
+ else
+ RemoveField(x, y);
+
+ Feld[x][y] = new_element;
+
+#if !USE_GFX_RESET_GFX_ANIMATION
+ ResetGfxAnimation(x, y);
+ ResetRandomAnimationValue(x, y);
+#endif
+
+ if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
+ MovDir[x][y] = previous_move_direction;
+
+#if USE_NEW_CUSTOM_VALUE
+ if (element_info[new_element].use_last_ce_value)
+ CustomValue[x][y] = last_ce_value;
+#endif
+
+ InitField_WithBug1(x, y, FALSE);
+
+ new_element = Feld[x][y]; /* element may have changed */
+
+#if USE_GFX_RESET_GFX_ANIMATION
+ ResetGfxAnimation(x, y);
+ ResetRandomAnimationValue(x, y);
+#endif
+
+ TEST_DrawLevelField(x, y);
+
+ if (GFX_CRUMBLED(new_element))
+ TEST_DrawLevelFieldCrumbledSandNeighbours(x, y);
+ }
+
+#if 1
+ /* check if element under the player changes from accessible to unaccessible
+ (needed for special case of dropping element which then changes) */
+ /* (must be checked after creating new element for walkable group elements) */
+#if USE_FIX_KILLED_BY_NON_WALKABLE
+ if (IS_PLAYER(x, y) && !player_explosion_protected &&
+ IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
+ {
+ Bang(x, y);
+
+ return;
+ }
+#else
+ if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y) &&
+ IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
+ {
+ Bang(x, y);
+
+ return;
+ }
+#endif
+#endif
+
+ /* "ChangeCount" not set yet to allow "entered by player" change one time */
+ if (new_element_is_player)
+ RelocatePlayer(x, y, new_element);
+
+ if (is_change)
+ ChangeCount[x][y]++; /* count number of changes in the same frame */
+
+ TestIfBadThingTouchesPlayer(x, y);
+ TestIfPlayerTouchesCustomElement(x, y);
+ TestIfElementTouchesCustomElement(x, y);
+}
+
+static void CreateField(int x, int y, int element)
+{
+ CreateFieldExt(x, y, element, FALSE);
+}
+
+static void CreateElementFromChange(int x, int y, int element)
+{
+ element = GET_VALID_RUNTIME_ELEMENT(element);
+
+#if USE_STOP_CHANGED_ELEMENTS
+ if (game.engine_version >= VERSION_IDENT(3,2,0,7))
+ {
+ int old_element = Feld[x][y];
+
+ /* prevent changed element from moving in same engine frame
+ unless both old and new element can either fall or move */
+ if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
+ (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
+ Stop[x][y] = TRUE;
+ }
+#endif
+
+ CreateFieldExt(x, y, element, TRUE);
+}
+
+static boolean ChangeElement(int x, int y, int element, int page)
+{
+ struct ElementInfo *ei = &element_info[element];
+ struct ElementChangeInfo *change = &ei->change_page[page];
+ int ce_value = CustomValue[x][y];
+ int ce_score = ei->collect_score;
+ int target_element;
+ int old_element = Feld[x][y];
+
+ /* always use default change event to prevent running into a loop */
+ if (ChangeEvent[x][y] == -1)
+ ChangeEvent[x][y] = CE_DELAY;
+
+ if (ChangeEvent[x][y] == CE_DELAY)
+ {
+ /* reset actual trigger element, trigger player and action element */
+ change->actual_trigger_element = EL_EMPTY;
+ change->actual_trigger_player = EL_EMPTY;
+ change->actual_trigger_player_bits = CH_PLAYER_NONE;
+ change->actual_trigger_side = CH_SIDE_NONE;
+ change->actual_trigger_ce_value = 0;
+ change->actual_trigger_ce_score = 0;
+ }
+
+ /* do not change elements more than a specified maximum number of changes */
+ if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
+ return FALSE;
+
+ ChangeCount[x][y]++; /* count number of changes in the same frame */
+
+ if (change->explode)
+ {
+ Bang(x, y);
+
+ return TRUE;
+ }
+
+ if (change->use_target_content)
+ {
+ boolean complete_replace = TRUE;
+ boolean can_replace[3][3];
+ int xx, yy;
+
+ for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
+ {
+ boolean is_empty;
+ boolean is_walkable;
+ boolean is_diggable;
+ boolean is_collectible;
+ boolean is_removable;
+ boolean is_destructible;
+ int ex = x + xx - 1;
+ int ey = y + yy - 1;
+ int content_element = change->target_content.e[xx][yy];
+ int e;
+
+ can_replace[xx][yy] = TRUE;
+
+ if (ex == x && ey == y) /* do not check changing element itself */
+ continue;
+
+ if (content_element == EL_EMPTY_SPACE)
+ {
+ can_replace[xx][yy] = FALSE; /* do not replace border with space */
+
+ continue;
+ }
+
+ if (!IN_LEV_FIELD(ex, ey))
+ {
+ can_replace[xx][yy] = FALSE;
+ complete_replace = FALSE;
+
+ continue;
+ }
+
+ e = Feld[ex][ey];
+
+ if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
+ e = MovingOrBlocked2Element(ex, ey);
+
+ is_empty = (IS_FREE(ex, ey) ||
+ (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
+
+ is_walkable = (is_empty || IS_WALKABLE(e));
+ is_diggable = (is_empty || IS_DIGGABLE(e));
+ is_collectible = (is_empty || IS_COLLECTIBLE(e));
+ is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
+ is_removable = (is_diggable || is_collectible);
+
+ can_replace[xx][yy] =
+ (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
+ (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
+ (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
+ (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
+ (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
+ (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
+ !(IS_PLAYER(ex, ey) && ELEM_IS_PLAYER(content_element)));
+
+ if (!can_replace[xx][yy])
+ complete_replace = FALSE;
+ }
+
+ if (!change->only_if_complete || complete_replace)
+ {
+ boolean something_has_changed = FALSE;
+
+ if (change->only_if_complete && change->use_random_replace &&
+ RND(100) < change->random_percentage)
+ return FALSE;
+
+ for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
+ {
+ int ex = x + xx - 1;
+ int ey = y + yy - 1;
+ int content_element;
+
+ if (can_replace[xx][yy] && (!change->use_random_replace ||
+ RND(100) < change->random_percentage))
+ {
+ if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
+ RemoveMovingField(ex, ey);
+
+ ChangeEvent[ex][ey] = ChangeEvent[x][y];
+
+ content_element = change->target_content.e[xx][yy];
+ target_element = GET_TARGET_ELEMENT(element, content_element, change,
+ ce_value, ce_score);
+
+ CreateElementFromChange(ex, ey, target_element);
+
+ something_has_changed = TRUE;
+
+ /* for symmetry reasons, freeze newly created border elements */
+ if (ex != x || ey != y)
+ Stop[ex][ey] = TRUE; /* no more moving in this frame */
+ }
+ }
+
+ if (something_has_changed)
+ {
+ PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
+ PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
+ }
+ }
+ }
+ else
+ {
+ target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
+ ce_value, ce_score);
+
+ if (element == EL_DIAGONAL_GROWING ||
+ element == EL_DIAGONAL_SHRINKING)
+ {
+ target_element = Store[x][y];
+
+ Store[x][y] = EL_EMPTY;
+ }
+
+ CreateElementFromChange(x, y, target_element);
+
+ PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
+ PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
+ }
+
+ /* this uses direct change before indirect change */
+ CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
+
+ return TRUE;
+}
+
+#if USE_NEW_DELAYED_ACTION
+
+static void HandleElementChange(int x, int y, int page)
+{
+ int element = MovingOrBlocked2Element(x, y);
+ struct ElementInfo *ei = &element_info[element];
+ struct ElementChangeInfo *change = &ei->change_page[page];
+ boolean handle_action_before_change = FALSE;
+
+#ifdef DEBUG
+ if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
+ !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
+ {
+ printf("\n\n");
+ printf("HandleElementChange(): %d,%d: element = %d ('%s')\n",
+ x, y, element, element_info[element].token_name);
+ printf("HandleElementChange(): This should never happen!\n");
+ printf("\n\n");
+ }
+#endif
+
+ /* this can happen with classic bombs on walkable, changing elements */
+ if (!CAN_CHANGE_OR_HAS_ACTION(element))
+ {
+#if 0
+ if (!CAN_CHANGE(Back[x][y])) /* prevent permanent repetition */
+ ChangeDelay[x][y] = 0;
+#endif
+
+ return;
+ }
+
+ if (ChangeDelay[x][y] == 0) /* initialize element change */
+ {
+ ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
+
+ if (change->can_change)
+ {
+#if 1
+ /* !!! not clear why graphic animation should be reset at all here !!! */
+ /* !!! UPDATE: but is needed for correct Snake Bite tail animation !!! */
+#if USE_GFX_RESET_WHEN_NOT_MOVING
+ /* when a custom element is about to change (for example by change delay),
+ do not reset graphic animation when the custom element is moving */
+ if (!IS_MOVING(x, y))
+#endif
+ {
+ ResetGfxAnimation(x, y);
+ ResetRandomAnimationValue(x, y);
+ }
+#endif
+
+ if (change->pre_change_function)
+ change->pre_change_function(x, y);
+ }
+ }
+
+ ChangeDelay[x][y]--;
+
+ if (ChangeDelay[x][y] != 0) /* continue element change */
+ {
+ if (change->can_change)
+ {
+ int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
+
+ if (IS_ANIMATED(graphic))
+ DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
+
+ if (change->change_function)
+ change->change_function(x, y);
+ }
+ }
+ else /* finish element change */
+ {
+ if (ChangePage[x][y] != -1) /* remember page from delayed change */
+ {
+ page = ChangePage[x][y];
+ ChangePage[x][y] = -1;
+
+ change = &ei->change_page[page];
+ }
+
+ if (IS_MOVING(x, y)) /* never change a running system ;-) */
+ {
+ ChangeDelay[x][y] = 1; /* try change after next move step */
+ ChangePage[x][y] = page; /* remember page to use for change */
+
+ return;
+ }
+
+#if 1
+ /* special case: set new level random seed before changing element */
+ if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
+ handle_action_before_change = TRUE;
+
+ if (change->has_action && handle_action_before_change)
+ ExecuteCustomElementAction(x, y, element, page);
+#endif
+
+ if (change->can_change)
+ {
+ if (ChangeElement(x, y, element, page))
+ {
+ if (change->post_change_function)
+ change->post_change_function(x, y);
+ }
+ }
+
+ if (change->has_action && !handle_action_before_change)
+ ExecuteCustomElementAction(x, y, element, page);
+ }
+}
+
+#else
+
+static void HandleElementChange(int x, int y, int page)
+{
+ int element = MovingOrBlocked2Element(x, y);
+ struct ElementInfo *ei = &element_info[element];
+ struct ElementChangeInfo *change = &ei->change_page[page];
+
+#ifdef DEBUG
+ if (!CAN_CHANGE(element) && !CAN_CHANGE(Back[x][y]))
+ {
+ printf("\n\n");
+ printf("HandleElementChange(): %d,%d: element = %d ('%s')\n",
+ x, y, element, element_info[element].token_name);
+ printf("HandleElementChange(): This should never happen!\n");
+ printf("\n\n");
+ }
+#endif
+
+ /* this can happen with classic bombs on walkable, changing elements */
+ if (!CAN_CHANGE(element))
+ {
+#if 0
+ if (!CAN_CHANGE(Back[x][y])) /* prevent permanent repetition */
+ ChangeDelay[x][y] = 0;
+#endif
+
+ return;
+ }
+
+ if (ChangeDelay[x][y] == 0) /* initialize element change */
+ {
+ ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
+
+ ResetGfxAnimation(x, y);
+ ResetRandomAnimationValue(x, y);
+
+ if (change->pre_change_function)
+ change->pre_change_function(x, y);
+ }
+
+ ChangeDelay[x][y]--;
+
+ if (ChangeDelay[x][y] != 0) /* continue element change */
+ {
+ int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
+
+ if (IS_ANIMATED(graphic))
+ DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
+
+ if (change->change_function)
+ change->change_function(x, y);
+ }
+ else /* finish element change */
+ {
+ if (ChangePage[x][y] != -1) /* remember page from delayed change */
+ {
+ page = ChangePage[x][y];
+ ChangePage[x][y] = -1;
+
+ change = &ei->change_page[page];
+ }
+
+ if (IS_MOVING(x, y)) /* never change a running system ;-) */
+ {
+ ChangeDelay[x][y] = 1; /* try change after next move step */
+ ChangePage[x][y] = page; /* remember page to use for change */
+
+ return;
+ }
+
+ if (ChangeElement(x, y, element, page))
+ {
+ if (change->post_change_function)
+ change->post_change_function(x, y);
+ }
+ }
+}
+
+#endif
+
+static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
+ int trigger_element,
+ int trigger_event,
+ int trigger_player,
+ int trigger_side,
+ int trigger_page)
+{
+ boolean change_done_any = FALSE;
+ int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
+ int i;
+
+ if (!(trigger_events[trigger_element][trigger_event]))
+ return FALSE;
+
+#if 0
+ printf("::: CheckTriggeredElementChangeExt %d ... [%d, %d, %d, '%s']\n",
+ trigger_event, recursion_loop_depth, recursion_loop_detected,
+ recursion_loop_element, EL_NAME(recursion_loop_element));
+#endif
+
+ RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
+
+ for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
+ {
+ int element = EL_CUSTOM_START + i;
+ boolean change_done = FALSE;
+ int p;
+
+ if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
+ !HAS_ANY_CHANGE_EVENT(element, trigger_event))
+ continue;
+
+ for (p = 0; p < element_info[element].num_change_pages; p++)
+ {
+ struct ElementChangeInfo *change = &element_info[element].change_page[p];
+
+ if (change->can_change_or_has_action &&
+ change->has_event[trigger_event] &&
+ change->trigger_side & trigger_side &&
+ change->trigger_player & trigger_player &&
+ change->trigger_page & trigger_page_bits &&
+ IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
+ {
+ change->actual_trigger_element = trigger_element;
+ change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
+ change->actual_trigger_player_bits = trigger_player;
+ change->actual_trigger_side = trigger_side;
+ change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
+ change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
+
+#if 0
+ printf("::: TRIGGERED CHANGE FOUND: %d ['%s'], %d\n",
+ element, EL_NAME(element), p);
+#endif
+
+ if ((change->can_change && !change_done) || change->has_action)
+ {
+ int x, y;
+
+ SCAN_PLAYFIELD(x, y)
+ {
+ if (Feld[x][y] == element)
+ {
+ if (change->can_change && !change_done)
+ {
+#if USE_FIX_NO_ACTION_AFTER_CHANGE
+ /* if element already changed in this frame, not only prevent
+ another element change (checked in ChangeElement()), but
+ also prevent additional element actions for this element */
+
+ if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
+ !level.use_action_after_change_bug)
+ continue;
+#endif
+
+#if 0
+ printf("::: TRIGGERED CHANGE FOUND: %d ['%s'], %d -- CHANGE\n",
+ element, EL_NAME(element), p);
+#endif
+
+ ChangeDelay[x][y] = 1;
+ ChangeEvent[x][y] = trigger_event;
+
+ HandleElementChange(x, y, p);
+ }
+#if USE_NEW_DELAYED_ACTION
+ else if (change->has_action)
+ {
+#if USE_FIX_NO_ACTION_AFTER_CHANGE
+ /* if element already changed in this frame, not only prevent
+ another element change (checked in ChangeElement()), but
+ also prevent additional element actions for this element */
+
+ if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
+ !level.use_action_after_change_bug)
+ continue;
+#endif
+
+
+#if 0
+ printf("::: TRIGGERED CHANGE FOUND: %d ['%s'], %d -- ACTION\n",
+ element, EL_NAME(element), p);
+#endif
+
+ ExecuteCustomElementAction(x, y, element, p);
+ PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
+ }
+#else
+ if (change->has_action)
+ {
+ ExecuteCustomElementAction(x, y, element, p);
+ PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
+ }
+#endif
+ }
+ }
+
+ if (change->can_change)
+ {
+ change_done = TRUE;
+ change_done_any = TRUE;
+
+#if 0
+ printf("::: TRIGGERED CHANGE FOUND: %d ['%s'], %d -- DONE\n",
+ element, EL_NAME(element), p);
+#endif
+
+ }
+ }
+ }
+ }
+ }
+
+ RECURSION_LOOP_DETECTION_END();
+
+ return change_done_any;
+}
+
+static boolean CheckElementChangeExt(int x, int y,
+ int element,
+ int trigger_element,
+ int trigger_event,
+ int trigger_player,
+ int trigger_side)
+{
+ boolean change_done = FALSE;
+ int p;
+
+ if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
+ !HAS_ANY_CHANGE_EVENT(element, trigger_event))
+ return FALSE;
+
+ if (Feld[x][y] == EL_BLOCKED)
+ {
+ Blocked2Moving(x, y, &x, &y);
+ element = Feld[x][y];
+ }
+
+#if 0
+ /* check if element has already changed */
+ if (Feld[x][y] != element)
+ return FALSE;
+#else
+ /* check if element has already changed or is about to change after moving */
+ if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
+ Feld[x][y] != element) ||
+
+ (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
+ (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
+ ChangePage[x][y] != -1)))
+ return FALSE;
+#endif
+
+#if 0
+ printf("::: CheckElementChangeExt %d ... [%d, %d, %d, '%s']\n",
+ trigger_event, recursion_loop_depth, recursion_loop_detected,
+ recursion_loop_element, EL_NAME(recursion_loop_element));
+#endif
+
+ RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
+
+#if 0
+ printf("::: X: trigger_player_bits == %d\n", trigger_player);
+#endif
+
+ for (p = 0; p < element_info[element].num_change_pages; p++)
+ {
+ struct ElementChangeInfo *change = &element_info[element].change_page[p];
+
+ /* check trigger element for all events where the element that is checked
+ for changing interacts with a directly adjacent element -- this is
+ different to element changes that affect other elements to change on the
+ whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
+ boolean check_trigger_element =
+ (trigger_event == CE_TOUCHING_X ||
+ trigger_event == CE_HITTING_X ||
+ trigger_event == CE_HIT_BY_X ||
+#if 1
+ /* this one was forgotten until 3.2.3 */
+ trigger_event == CE_DIGGING_X);
+#endif
+
+ if (change->can_change_or_has_action &&
+ change->has_event[trigger_event] &&
+ change->trigger_side & trigger_side &&
+ change->trigger_player & trigger_player &&
+ (!check_trigger_element ||
+ IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
+ {
+ change->actual_trigger_element = trigger_element;
+ change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
+ change->actual_trigger_player_bits = trigger_player;
+ change->actual_trigger_side = trigger_side;
+ change->actual_trigger_ce_value = CustomValue[x][y];
+ change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
+
+ /* special case: trigger element not at (x,y) position for some events */
+ if (check_trigger_element)
+ {
+ static struct
+ {
+ int dx, dy;
+ } move_xy[] =
+ {
+ { 0, 0 },
+ { -1, 0 },
+ { +1, 0 },
+ { 0, 0 },
+ { 0, -1 },
+ { 0, 0 }, { 0, 0 }, { 0, 0 },
+ { 0, +1 }
+ };
+
+ int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
+ int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
+
+ change->actual_trigger_ce_value = CustomValue[xx][yy];
+ change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
+ }
+
+ if (change->can_change && !change_done)
+ {
+ ChangeDelay[x][y] = 1;
+ ChangeEvent[x][y] = trigger_event;
+
+ HandleElementChange(x, y, p);
+
+ change_done = TRUE;
+ }
+#if USE_NEW_DELAYED_ACTION
+ else if (change->has_action)
+ {
+ ExecuteCustomElementAction(x, y, element, p);
+ PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
+ }
+#else
+ if (change->has_action)
+ {
+ ExecuteCustomElementAction(x, y, element, p);
+ PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
+ }
+#endif
+ }
+ }
+
+ RECURSION_LOOP_DETECTION_END();
+
+ return change_done;
+}
+
+static void PlayPlayerSound(struct PlayerInfo *player)
+{
+ int jx = player->jx, jy = player->jy;
+ int sound_element = player->artwork_element;
+ int last_action = player->last_action_waiting;
+ int action = player->action_waiting;
+
+ if (player->is_waiting)
+ {
+ if (action != last_action)
+ PlayLevelSoundElementAction(jx, jy, sound_element, action);
+ else
+ PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
+ }
+ else
+ {
+ if (action != last_action)
+ StopSound(element_info[sound_element].sound[last_action]);
+
+ if (last_action == ACTION_SLEEPING)
+ PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
+ }
+}
+
+static void PlayAllPlayersSound()
+{
+ int i;
+
+ for (i = 0; i < MAX_PLAYERS; i++)
+ if (stored_player[i].active)
+ PlayPlayerSound(&stored_player[i]);
+}
+
+static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
+{
+ boolean last_waiting = player->is_waiting;
+ int move_dir = player->MovDir;
+
+ player->dir_waiting = move_dir;
+ player->last_action_waiting = player->action_waiting;
+
+ if (is_waiting)
+ {
+ if (!last_waiting) /* not waiting -> waiting */
+ {
+ player->is_waiting = TRUE;
+
+ player->frame_counter_bored =
+ FrameCounter +
+ game.player_boring_delay_fixed +
+ GetSimpleRandom(game.player_boring_delay_random);
+ player->frame_counter_sleeping =
+ FrameCounter +
+ game.player_sleeping_delay_fixed +
+ GetSimpleRandom(game.player_sleeping_delay_random);
+
+ InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
+ }
+
+ if (game.player_sleeping_delay_fixed +
+ game.player_sleeping_delay_random > 0 &&
+ player->anim_delay_counter == 0 &&
+ player->post_delay_counter == 0 &&
+ FrameCounter >= player->frame_counter_sleeping)
+ player->is_sleeping = TRUE;
+ else if (game.player_boring_delay_fixed +
+ game.player_boring_delay_random > 0 &&
+ FrameCounter >= player->frame_counter_bored)
+ player->is_bored = TRUE;
+
+ player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
+ player->is_bored ? ACTION_BORING :
+ ACTION_WAITING);
+
+ if (player->is_sleeping && player->use_murphy)
+ {
+ /* special case for sleeping Murphy when leaning against non-free tile */
+
+ if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
+ (Feld[player->jx - 1][player->jy] != EL_EMPTY &&
+ !IS_MOVING(player->jx - 1, player->jy)))
+ move_dir = MV_LEFT;
+ else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
+ (Feld[player->jx + 1][player->jy] != EL_EMPTY &&
+ !IS_MOVING(player->jx + 1, player->jy)))
+ move_dir = MV_RIGHT;
+ else
+ player->is_sleeping = FALSE;
+
+ player->dir_waiting = move_dir;
+ }
+
+ if (player->is_sleeping)
+ {
+ if (player->num_special_action_sleeping > 0)
+ {
+ if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
+ {
+ int last_special_action = player->special_action_sleeping;
+ int num_special_action = player->num_special_action_sleeping;
+ int special_action =
+ (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
+ last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
+ last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
+ last_special_action + 1 : ACTION_SLEEPING);
+ int special_graphic =
+ el_act_dir2img(player->artwork_element, special_action, move_dir);
+
+ player->anim_delay_counter =
+ graphic_info[special_graphic].anim_delay_fixed +
+ GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
+ player->post_delay_counter =
+ graphic_info[special_graphic].post_delay_fixed +
+ GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
+
+ player->special_action_sleeping = special_action;
+ }
+
+ if (player->anim_delay_counter > 0)
+ {
+ player->action_waiting = player->special_action_sleeping;
+ player->anim_delay_counter--;
+ }
+ else if (player->post_delay_counter > 0)
+ {
+ player->post_delay_counter--;
+ }
+ }
+ }
+ else if (player->is_bored)
+ {
+ if (player->num_special_action_bored > 0)
+ {
+ if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
+ {
+ int special_action =
+ ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
+ int special_graphic =
+ el_act_dir2img(player->artwork_element, special_action, move_dir);
+
+ player->anim_delay_counter =
+ graphic_info[special_graphic].anim_delay_fixed +
+ GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
+ player->post_delay_counter =
+ graphic_info[special_graphic].post_delay_fixed +
+ GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
+
+ player->special_action_bored = special_action;
+ }
+
+ if (player->anim_delay_counter > 0)
+ {
+ player->action_waiting = player->special_action_bored;
+ player->anim_delay_counter--;
+ }
+ else if (player->post_delay_counter > 0)
+ {
+ player->post_delay_counter--;
+ }
+ }
+ }
+ }
+ else if (last_waiting) /* waiting -> not waiting */
+ {
+ player->is_waiting = FALSE;
+ player->is_bored = FALSE;
+ player->is_sleeping = FALSE;
+
+ player->frame_counter_bored = -1;
+ player->frame_counter_sleeping = -1;
+
+ player->anim_delay_counter = 0;
+ player->post_delay_counter = 0;
+
+ player->dir_waiting = player->MovDir;
+ player->action_waiting = ACTION_DEFAULT;
+
+ player->special_action_bored = ACTION_DEFAULT;
+ player->special_action_sleeping = ACTION_DEFAULT;
+ }
+}
+
+static byte PlayerActions(struct PlayerInfo *player, byte player_action)
+{
+ boolean moved = FALSE, snapped = FALSE, dropped = FALSE;
+ int left = player_action & JOY_LEFT;
+ int right = player_action & JOY_RIGHT;
+ int up = player_action & JOY_UP;
+ int down = player_action & JOY_DOWN;
+ int button1 = player_action & JOY_BUTTON_1;
+ int button2 = player_action & JOY_BUTTON_2;
+ int dx = (left ? -1 : right ? 1 : 0);
+ int dy = (up ? -1 : down ? 1 : 0);
+
+ if (!player->active || tape.pausing)
+ return 0;
+
+ if (player_action)
+ {
+ if (button1)
+ snapped = SnapField(player, dx, dy);
+ else
+ {
+ if (button2)
+ dropped = DropElement(player);
+
+ moved = MovePlayer(player, dx, dy);
+ }
+
+ if (tape.single_step && tape.recording && !tape.pausing)
+ {
+ if (button1 || (dropped && !moved))
+ {
+ TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
+ SnapField(player, 0, 0); /* stop snapping */
+ }
+ }
+
+ SetPlayerWaiting(player, FALSE);
+
+ return player_action;
+ }
+ else
+ {
+ /* no actions for this player (no input at player's configured device) */
+
+ DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
+ SnapField(player, 0, 0);
+ CheckGravityMovementWhenNotMoving(player);
+
+ if (player->MovPos == 0)
+ SetPlayerWaiting(player, TRUE);
+
+ if (player->MovPos == 0) /* needed for tape.playing */
+ player->is_moving = FALSE;
+
+ player->is_dropping = FALSE;
+ player->is_dropping_pressed = FALSE;
+ player->drop_pressed_delay = 0;
+
+ return 0;
+ }
+}
+
+static void CheckLevelTime()
+{
+ int i;
+
+ /* !!! SAME CODE AS IN "GameActions()" -- FIX THIS !!! */
+ if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+ {
+ if (level.native_em_level->lev->home == 0) /* all players at home */
+ {
+ PlayerWins(local_player);
+
+ AllPlayersGone = TRUE;
+
+ level.native_em_level->lev->home = -1;
+ }
+
+ if (level.native_em_level->ply[0]->alive == 0 &&
+ level.native_em_level->ply[1]->alive == 0 &&
+ level.native_em_level->ply[2]->alive == 0 &&
+ level.native_em_level->ply[3]->alive == 0) /* all dead */
+ AllPlayersGone = TRUE;
+ }
+ else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
+ {
+ if (game_sp_info.LevelSolved &&
+ !game_sp_info.GameOver) /* game won */
+ {
+ PlayerWins(local_player);
+
+ game_sp_info.GameOver = TRUE;
+
+ AllPlayersGone = TRUE;
+ }
+
+ if (game_sp_info.GameOver) /* game lost */
+ AllPlayersGone = TRUE;
+ }
+
+ if (TimeFrames >= FRAMES_PER_SECOND)
+ {
+ TimeFrames = 0;
+ TapeTime++;
+
+ for (i = 0; i < MAX_PLAYERS; i++)
+ {
+ struct PlayerInfo *player = &stored_player[i];
+
+ if (SHIELD_ON(player))
+ {
+ player->shield_normal_time_left--;
+
+ if (player->shield_deadly_time_left > 0)
+ player->shield_deadly_time_left--;
+ }
+ }
+
+ if (!local_player->LevelSolved && !level.use_step_counter)
+ {
+ TimePlayed++;
+
+ if (TimeLeft > 0)
+ {
+ TimeLeft--;
+
+ if (TimeLeft <= 10 && setup.time_limit)
+ PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
+
+#if 1
+ game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
+
+ DisplayGameControlValues();
+#else
+ DrawGameValue_Time(TimeLeft);
+#endif
+
+ if (!TimeLeft && setup.time_limit)
+ {
+ if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+ level.native_em_level->lev->killed_out_of_time = TRUE;
+ else
+ for (i = 0; i < MAX_PLAYERS; i++)
+ KillPlayer(&stored_player[i]);
+ }
+ }
+#if 1
+ else if (level.time == 0 && !AllPlayersGone) /* level w/o time limit */
+ {
+ game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
+
+ DisplayGameControlValues();
+ }
+#else
+ else if (level.time == 0 && !AllPlayersGone) /* level w/o time limit */
+ DrawGameValue_Time(TimePlayed);
+#endif
+
+ level.native_em_level->lev->time =
+ (level.time == 0 ? TimePlayed : TimeLeft);
+ }
+
+ if (tape.recording || tape.playing)
+ DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
+ }
+
+#if 1
+ UpdateAndDisplayGameControlValues();
+#else
+ UpdateGameDoorValues();
+ DrawGameDoorValues();
+#endif
+}
+
+void AdvanceFrameAndPlayerCounters(int player_nr)
+{
+ int i;
+
+ /* advance frame counters (global frame counter and time frame counter) */
+ FrameCounter++;
+ TimeFrames++;
+
+ /* advance player counters (counters for move delay, move animation etc.) */
+ for (i = 0; i < MAX_PLAYERS; i++)
+ {
+ boolean advance_player_counters = (player_nr == -1 || player_nr == i);
+ int move_delay_value = stored_player[i].move_delay_value;
+ int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
+
+ if (!advance_player_counters) /* not all players may be affected */
+ continue;
+
+#if USE_NEW_PLAYER_ANIM
+ if (move_frames == 0) /* less than one move per game frame */
+ {
+ int stepsize = TILEX / move_delay_value;
+ int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
+ int count = (stored_player[i].is_moving ?
+ ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
+
+ if (count % delay == 0)
+ move_frames = 1;
+ }
+#endif
+
+ stored_player[i].Frame += move_frames;
+
+ if (stored_player[i].MovPos != 0)
+ stored_player[i].StepFrame += move_frames;
+
+ if (stored_player[i].move_delay > 0)
+ stored_player[i].move_delay--;
+
+ /* due to bugs in previous versions, counter must count up, not down */
+ if (stored_player[i].push_delay != -1)
+ stored_player[i].push_delay++;
+
+ if (stored_player[i].drop_delay > 0)
+ stored_player[i].drop_delay--;
+
+ if (stored_player[i].is_dropping_pressed)
+ stored_player[i].drop_pressed_delay++;
+ }
+}
+
+void StartGameActions(boolean init_network_game, boolean record_tape,
+ long random_seed)
+{
+ unsigned long new_random_seed = InitRND(random_seed);
+
+ if (record_tape)
+ TapeStartRecording(new_random_seed);
+
+#if defined(NETWORK_AVALIABLE)
+ if (init_network_game)
+ {
+ SendToServer_StartPlaying();
+
+ return;
+ }
+#endif
+
+ InitGame();
+}
+
+void GameActions()
+{
+ static unsigned long game_frame_delay = 0;
+ unsigned long game_frame_delay_value;
+ byte *recorded_player_action;
+ byte summarized_player_action = 0;
+ byte tape_action[MAX_PLAYERS];
+ int i;
+
+ /* detect endless loops, caused by custom element programming */
+ if (recursion_loop_detected && recursion_loop_depth == 0)
+ {
+ char *message = getStringCat3("Internal Error ! Element ",
+ EL_NAME(recursion_loop_element),
+ " caused endless loop ! Quit the game ?");
+
+ Error(ERR_WARN, "element '%s' caused endless loop in game engine",
+ EL_NAME(recursion_loop_element));
+
+ RequestQuitGameExt(FALSE, level_editor_test_game, message);
+
+ recursion_loop_detected = FALSE; /* if game should be continued */
+
+ free(message);
+
+ return;
+ }
+
+ if (game.restart_level)
+ StartGameActions(options.network, setup.autorecord, level.random_seed);
+
+ /* !!! SAME CODE AS IN "CheckLevelTime()" -- FIX THIS !!! */
+ if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+ {
+ if (level.native_em_level->lev->home == 0) /* all players at home */
+ {
+ PlayerWins(local_player);
+
+ AllPlayersGone = TRUE;
+
+ level.native_em_level->lev->home = -1;
+ }
+
+ if (level.native_em_level->ply[0]->alive == 0 &&
+ level.native_em_level->ply[1]->alive == 0 &&
+ level.native_em_level->ply[2]->alive == 0 &&
+ level.native_em_level->ply[3]->alive == 0) /* all dead */
+ AllPlayersGone = TRUE;
+ }
+ else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
+ {
+ if (game_sp_info.LevelSolved &&
+ !game_sp_info.GameOver) /* game won */
+ {
+ PlayerWins(local_player);
+
+ game_sp_info.GameOver = TRUE;
+
+ AllPlayersGone = TRUE;
+ }
+
+ if (game_sp_info.GameOver) /* game lost */
+ AllPlayersGone = TRUE;
+ }
+
+ if (local_player->LevelSolved && !local_player->LevelSolved_GameEnd)
+ GameWon();
+
+ if (AllPlayersGone && !TAPE_IS_STOPPED(tape))
+ TapeStop();
+
+ if (game_status != GAME_MODE_PLAYING) /* status might have changed */
+ return;
+
+ game_frame_delay_value =
+ (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
+
+ if (tape.playing && tape.warp_forward && !tape.pausing)
+ game_frame_delay_value = 0;
+
+ /* ---------- main game synchronization point ---------- */
+
+ WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
+
+ if (network_playing && !network_player_action_received)
+ {
+ /* try to get network player actions in time */
+
+#if defined(NETWORK_AVALIABLE)
+ /* last chance to get network player actions without main loop delay */
+ HandleNetworking();
+#endif
+
+ /* game was quit by network peer */
+ if (game_status != GAME_MODE_PLAYING)
+ return;
+
+ if (!network_player_action_received)
+ return; /* failed to get network player actions in time */
+
+ /* do not yet reset "network_player_action_received" (for tape.pausing) */
+ }
+
+ if (tape.pausing)
+ return;
+
+ /* at this point we know that we really continue executing the game */
+
+ network_player_action_received = FALSE;
+
+ /* when playing tape, read previously recorded player input from tape data */
+ recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
+
+#if 1
+ /* TapePlayAction() may return NULL when toggling to "pause before death" */
+ if (tape.pausing)
+ return;
+#endif
+
+ if (tape.set_centered_player)
+ {
+ game.centered_player_nr_next = tape.centered_player_nr_next;
+ game.set_centered_player = TRUE;
+ }
+
+ for (i = 0; i < MAX_PLAYERS; i++)
+ {
+ summarized_player_action |= stored_player[i].action;
+
+ if (!network_playing)
+ stored_player[i].effective_action = stored_player[i].action;
+ }
+
+#if defined(NETWORK_AVALIABLE)
+ if (network_playing)
+ SendToServer_MovePlayer(summarized_player_action);
+#endif
+
+ if (!options.network && !setup.team_mode)
+ local_player->effective_action = summarized_player_action;
+
+ if (setup.team_mode && setup.input_on_focus && game.centered_player_nr != -1)
+ {
+ for (i = 0; i < MAX_PLAYERS; i++)
+ stored_player[i].effective_action =
+ (i == game.centered_player_nr ? summarized_player_action : 0);
+ }
+
+ if (recorded_player_action != NULL)
+ for (i = 0; i < MAX_PLAYERS; i++)
+ stored_player[i].effective_action = recorded_player_action[i];
+
+ for (i = 0; i < MAX_PLAYERS; i++)
+ {
+ tape_action[i] = stored_player[i].effective_action;
+
+ /* (this can only happen in the R'n'D game engine) */
+ if (tape.recording && tape_action[i] && !tape.player_participates[i])
+ tape.player_participates[i] = TRUE; /* player just appeared from CE */
+ }
+
+ /* only record actions from input devices, but not programmed actions */
+ if (tape.recording)
+ TapeRecordAction(tape_action);
+
+#if USE_NEW_PLAYER_ASSIGNMENTS
+ {
+ byte mapped_action[MAX_PLAYERS];
+
+ for (i = 0; i < MAX_PLAYERS; i++)
+ mapped_action[i] = stored_player[map_player_action[i]].effective_action;
+
+ for (i = 0; i < MAX_PLAYERS; i++)
+ stored_player[i].effective_action = mapped_action[i];
+ }
+#endif
+
+ if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+ {
+ GameActions_EM_Main();
+ }
+ else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
+ {
+ GameActions_SP_Main();
+ }
+ else
+ {
+ GameActions_RND();
+ }
+}
+
+void GameActions_EM_Main()
+{
+ byte effective_action[MAX_PLAYERS];
+ boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
+ int i;
+
+ for (i = 0; i < MAX_PLAYERS; i++)
+ effective_action[i] = stored_player[i].effective_action;
+
+ GameActions_EM(effective_action, warp_mode);
+
+ CheckLevelTime();
+
+ AdvanceFrameAndPlayerCounters(-1); /* advance counters for all players */
+}
+
+void GameActions_SP_Main()
+{
+ byte effective_action[MAX_PLAYERS];
+ boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
+ int i;
+
+ for (i = 0; i < MAX_PLAYERS; i++)
+ effective_action[i] = stored_player[i].effective_action;
+
+ GameActions_SP(effective_action, warp_mode);
+
+ CheckLevelTime();
+
+ AdvanceFrameAndPlayerCounters(-1); /* advance counters for all players */
+}
+
+void GameActions_RND()
+{
+ int magic_wall_x = 0, magic_wall_y = 0;
+ int i, x, y, element, graphic;
+
+ InitPlayfieldScanModeVars();
+
+#if USE_ONE_MORE_CHANGE_PER_FRAME
+ if (game.engine_version >= VERSION_IDENT(3,2,0,7))
+ {
+ SCAN_PLAYFIELD(x, y)
+ {
+ ChangeCount[x][y] = 0;
+ ChangeEvent[x][y] = -1;
+ }
+ }
+#endif
+
+ if (game.set_centered_player)
+ {
+ boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
+
+ /* switching to "all players" only possible if all players fit to screen */
+ if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
+ {
+ game.centered_player_nr_next = game.centered_player_nr;
+ game.set_centered_player = FALSE;
+ }
+
+ /* do not switch focus to non-existing (or non-active) player */
+ if (game.centered_player_nr_next >= 0 &&
+ !stored_player[game.centered_player_nr_next].active)
+ {
+ game.centered_player_nr_next = game.centered_player_nr;
+ game.set_centered_player = FALSE;
+ }
+ }
+
+ if (game.set_centered_player &&
+ ScreenMovPos == 0) /* screen currently aligned at tile position */
+ {
+ int sx, sy;
+
+ if (game.centered_player_nr_next == -1)
+ {
+ setScreenCenteredToAllPlayers(&sx, &sy);
+ }
+ else
+ {
+ sx = stored_player[game.centered_player_nr_next].jx;
+ sy = stored_player[game.centered_player_nr_next].jy;
+ }
+
+ game.centered_player_nr = game.centered_player_nr_next;
+ game.set_centered_player = FALSE;
+
+ DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch);
+ DrawGameDoorValues();
+ }
+
+ for (i = 0; i < MAX_PLAYERS; i++)
+ {
+ int actual_player_action = stored_player[i].effective_action;
+
+#if 1
+ /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
+ - rnd_equinox_tetrachloride 048
+ - rnd_equinox_tetrachloride_ii 096
+ - rnd_emanuel_schmieg 002
+ - doctor_sloan_ww 001, 020
+ */
+ if (stored_player[i].MovPos == 0)
+ CheckGravityMovement(&stored_player[i]);
+#endif
+
+ /* overwrite programmed action with tape action */
+ if (stored_player[i].programmed_action)
+ actual_player_action = stored_player[i].programmed_action;
+
+ PlayerActions(&stored_player[i], actual_player_action);
+
+ ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
+ }
+
+ ScrollScreen(NULL, SCROLL_GO_ON);
+
+ /* for backwards compatibility, the following code emulates a fixed bug that
+ occured when pushing elements (causing elements that just made their last
+ pushing step to already (if possible) make their first falling step in the
+ same game frame, which is bad); this code is also needed to use the famous
+ "spring push bug" which is used in older levels and might be wanted to be
+ used also in newer levels, but in this case the buggy pushing code is only
+ affecting the "spring" element and no other elements */
+
+ if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
+ {
+ for (i = 0; i < MAX_PLAYERS; i++)
+ {
+ struct PlayerInfo *player = &stored_player[i];
+ int x = player->jx;
+ int y = player->jy;