+ /* 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;
+ static int xy[4][2] =
+ {
+ { 0, -1 },
+ { -1, 0 },
+ { +1, 0 },
+ { 0, +1 }
+ };
+
+ if (!level.amoeba_speed)
+ {
+ Feld[ax][ay] = EL_AMOEBA_DEAD;
+ 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 (element == EL_AMOEBA_WET) /* object is an acid / amoeba drop */
+ {
+ int start = RND(4);
+ int x = ax + xy[start][0];
+ int y = ay + xy[start][1];
+
+ if (!IN_LEV_FIELD(x, y))
+ return;
+
+#if 1
+ if (IS_FREE(x, y) ||
+ CAN_GROW_INTO(Feld[x][y]) ||
+ Feld[x][y] == EL_QUICKSAND_EMPTY)
+ {
+ newax = x;
+ neway = y;
+ }
+#else
+ /* !!! extend EL_SAND to anything diggable (but maybe not SP_BASE) !!! */
+ if (IS_FREE(x, y) ||
+ Feld[x][y] == EL_SAND || Feld[x][y] == EL_QUICKSAND_EMPTY)
+ {
+ newax = x;
+ neway = y;
+ }
+#endif
+
+ 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 1
+ if (IS_FREE(x, y) ||
+ CAN_GROW_INTO(Feld[x][y]) ||
+ Feld[x][y] == EL_QUICKSAND_EMPTY)
+ {
+ newax = x;
+ neway = y;
+ break;
+ }
+#else
+ /* !!! extend EL_SAND to anything diggable (but maybe not SP_BASE) !!! */
+ if (IS_FREE(x, y) ||
+ Feld[x][y] == EL_SAND || Feld[x][y] == EL_QUICKSAND_EMPTY)
+ {
+ newax = x;
+ neway = y;
+ break;
+ }
+#endif
+ else if (IS_PLAYER(x, y))
+ waiting_for_player = TRUE;
+ }
+
+ if (newax == ax && neway == ay) /* amoeba cannot grow */
+ {
+#if 1
+ if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
+#else
+ if (i == 4 && (!waiting_for_player || game.emulation == EMU_BOULDERDASH))
+#endif
+ {
+ Feld[ax][ay] = EL_AMOEBA_DEAD;
+ 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 (element != EL_AMOEBA_WET || 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)
+ {
+ Feld[newax][neway] = EL_AMOEBA_DROP; /* drop left/right of amoeba */
+#if 1
+ PlayLevelSoundAction(newax, neway, ACTION_GROWING);
+#else
+ PlayLevelSound(newax, neway, SND_AMOEBA_GROWING);
+#endif
+ }
+ 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;
+ }
+
+ DrawLevelField(newax, neway);
+}
+
+void Life(int ax, int ay)
+{
+ int x1, y1, x2, y2;
+ static int life[4] = { 2, 3, 3, 3 }; /* parameters for "game of life" */
+ int life_time = 40;
+ int element = Feld[ax][ay];
+ int graphic = el2img(element);
+ 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[0] || nachbarn > life[1])
+ {
+ Feld[xx][yy] = EL_EMPTY;
+ if (!Stop[xx][yy])
+ DrawLevelField(xx, yy);
+ Stop[xx][yy] = TRUE;
+ changed = TRUE;
+ }
+ }
+#if 1
+ else if (IS_FREE(xx, yy) || CAN_GROW_INTO(Feld[xx][yy]))
+ { /* free border field */
+ if (nachbarn >= life[2] && nachbarn <= life[3])
+ {
+ Feld[xx][yy] = element;
+ MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1);
+ if (!Stop[xx][yy])
+ DrawLevelField(xx, yy);
+ Stop[xx][yy] = TRUE;
+ changed = TRUE;
+ }
+ }
+#else
+ /* !!! extend EL_SAND to anything diggable (but maybe not SP_BASE) !!! */
+ else if (IS_FREE(xx, yy) || Feld[xx][yy] == EL_SAND)
+ { /* free border field */
+ if (nachbarn >= life[2] && nachbarn <= life[3])
+ {
+ Feld[xx][yy] = element;
+ MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1);
+ if (!Stop[xx][yy])
+ DrawLevelField(xx, yy);
+ Stop[xx][yy] = TRUE;
+ changed = TRUE;
+ }
+ }
+#endif
+ }
+
+ 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;
+}
+
+static void InitTimegateWheel(int x, int y)
+{
+#if 1
+ ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
+#else
+ /* another brainless, "type style" bug ... :-( */
+ ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
+#endif
+}
+
+static void RunTimegateWheel(int x, int y)
+{
+ PlayLevelSound(x, y, SND_TIMEGATE_SWITCH_ACTIVE);
+}
+
+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 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;
+
+ for (y = 0; y < lev_fieldy; y++)
+ {
+ for (x = 0; x < lev_fieldx; x++)
+ {
+ int element = Feld[x][y];
+
+ if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
+ {
+ Feld[x][y] = EL_TIMEGATE_CLOSING;
+#if 1
+ PlayLevelSoundAction(x, y, ACTION_CLOSING);
+#else
+ PlayLevelSound(x, y, SND_TIMEGATE_CLOSING);
+#endif
+ }
+ }
+ }
+}
+
+void EdelsteinFunkeln(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 * !SimpleRND(500);
+
+ if (MovDelay[x][y] != 0) /* wait some time before next frame */
+ {
+ MovDelay[x][y]--;
+
+ if (setup.direct_draw && MovDelay[x][y])
+ SetDrawtoField(DRAW_BUFFERED);
+
+ 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);
+
+ if (setup.direct_draw)
+ {
+ int dest_x, dest_y;
+
+ dest_x = FX + SCREENX(x) * TILEX;
+ dest_y = FY + SCREENY(y) * TILEY;
+
+ BlitBitmap(drawto_field, window,
+ dest_x, dest_y, TILEX, TILEY, dest_x, dest_y);
+ SetDrawtoField(DRAW_DIRECT);
+ }
+ }
+ }
+}
+
+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]))
+ DrawLevelField(x - 1, y);
+ }
+ else if (MovDir[x][y] == MV_RIGHT)
+ {
+ if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Feld[x + 1][y]))
+ DrawLevelField(x + 1, y);
+ }
+ else if (MovDir[x][y] == MV_UP)
+ {
+ if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Feld[x][y - 1]))
+ DrawLevelField(x, y - 1);
+ }
+ else
+ {
+ if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Feld[x][y + 1]))
+ DrawLevelField(x, y + 1);
+ }
+
+ Feld[x][y] = Store[x][y];
+ Store[x][y] = 0;
+ GfxDir[x][y] = MovDir[x][y] = MV_NO_MOVING;
+ 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)
+ {
+ 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))
+ 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)
+#if 1
+ PlayLevelSoundAction(ax, ay, ACTION_GROWING);
+#else
+ PlayLevelSound(ax, ay, SND_EXPANDABLE_WALL_GROWING);
+#endif
+}
+
+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;
+ 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], yy = y + xy[i][1];
+
+ if (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))
+ DrawLevelFieldCrumbledSand(x, y);
+}
+
+static void ChangeElementNowExt(int x, int y, int target_element)
+{
+ int previous_move_direction = MovDir[x][y];
+#if 1
+ boolean add_player = (ELEM_IS_PLAYER(target_element) &&
+ IS_WALKABLE(Feld[x][y]));
+#else
+ boolean add_player = (ELEM_IS_PLAYER(target_element) &&
+ IS_WALKABLE(Feld[x][y]) &&
+ !IS_MOVING(x, y));
+#endif
+
+ /* check if element under player changes from accessible to unaccessible
+ (needed for special case of dropping element which then changes) */
+ if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y) &&
+ IS_ACCESSIBLE(Feld[x][y]) && !IS_ACCESSIBLE(target_element))
+ {
+#if 0
+ printf("::: BOOOM! [%d, '%s']\n", target_element,
+ element_info[target_element].token_name);
+#endif
+
+ Bang(x, y);
+ return;
+ }
+
+#if 1
+ if (!add_player)
+#endif
+ {
+#if 1
+ if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
+ RemoveMovingField(x, y);
+ else
+ RemoveField(x, y);
+
+ Feld[x][y] = target_element;
+#else
+ RemoveField(x, y);
+ Feld[x][y] = target_element;
+#endif
+
+ ResetGfxAnimation(x, y);
+ ResetRandomAnimationValue(x, y);
+
+ if (element_info[Feld[x][y]].move_direction_initial == MV_START_PREVIOUS)
+ MovDir[x][y] = previous_move_direction;
+
+#if 1
+ InitField_WithBug1(x, y, FALSE);
+#else
+ InitField(x, y, FALSE);
+ if (CAN_MOVE(Feld[x][y]))
+ InitMovDir(x, y);
+#endif
+
+ DrawLevelField(x, y);
+
+ if (GFX_CRUMBLED(Feld[x][y]))
+ DrawLevelFieldCrumbledSandNeighbours(x, y);
+ }
+
+#if 0
+ Changed[x][y] |= ChangeEvent[x][y]; /* ignore same changes in this frame */
+#endif
+
+#if 0
+ TestIfBadThingTouchesHero(x, y);
+ TestIfPlayerTouchesCustomElement(x, y);
+ TestIfElementTouchesCustomElement(x, y);
+#endif
+
+ /* "Changed[][]" not set yet to allow "entered by player" change one time */
+ if (ELEM_IS_PLAYER(target_element))
+ RelocatePlayer(x, y, target_element);
+
+#if 1
+ Changed[x][y] = TRUE; /* ignore all further changes in this frame */
+#else
+ Changed[x][y] |= ChangeEvent[x][y]; /* ignore same changes in this frame */
+#endif
+
+#if 1
+ TestIfBadThingTouchesHero(x, y);
+ TestIfPlayerTouchesCustomElement(x, y);
+ TestIfElementTouchesCustomElement(x, y);
+#endif
+}
+
+static boolean ChangeElementNow(int x, int y, int element, int page)
+{
+ struct ElementChangeInfo *change = &element_info[element].change_page[page];
+ 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 and player */
+ change->actual_trigger_element = EL_EMPTY;
+ change->actual_trigger_player = EL_PLAYER_1;
+ }
+
+#if 1
+ /* do not change any elements that have already changed in this frame */
+ if (Changed[x][y])
+ return FALSE;
+#else
+ /* do not change already changed elements with same change event */
+ if (Changed[x][y] & ChangeEvent[x][y])
+ return FALSE;
+#endif
+
+#if 1
+ Changed[x][y] = TRUE; /* ignore all further changes in this frame */
+#else
+ Changed[x][y] |= ChangeEvent[x][y]; /* ignore same changes in this frame */
+#endif
+
+#if 0
+ /* !!! indirect change before direct change !!! */
+ CheckTriggeredElementChangeByPage(x, y, Feld[x][y], CE_CHANGE_OF_X, page);
+#endif
+
+ 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[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;
+ }
+
+#if 0
+ if (Changed[ex][ey]) /* do not change already changed elements */
+ {
+ can_replace[xx][yy] = FALSE;
+ complete_replace = FALSE;
+
+ continue;
+ }
+#endif
+
+ e = Feld[ex][ey];
+
+ if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
+ e = MovingOrBlocked2Element(ex, ey);
+
+#if 1
+
+#if 0
+ is_empty = (IS_FREE(ex, ey) ||
+ (IS_PLAYER(ex, ey) && IS_WALKABLE(content_element)) ||
+ (IS_WALKABLE(e) && ELEM_IS_PLAYER(content_element) &&
+ !IS_MOVING(ex, ey) && !IS_BLOCKED(ex, ey)));
+#else
+
+#if 0
+ is_empty = (IS_FREE(ex, ey) ||
+ (IS_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
+#else
+ is_empty = (IS_FREE(ex, ey) ||
+ (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
+#endif
+
+#endif
+
+ 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;
+#else
+ empty_for_element = (IS_FREE(ex, ey) || (IS_FREE_OR_PLAYER(ex, ey) &&
+ IS_WALKABLE(content_element)));
+#if 1
+ half_destructible = (empty_for_element || IS_DIGGABLE(e));
+#else
+ half_destructible = (IS_FREE(ex, ey) || IS_DIGGABLE(e));
+#endif
+
+ if ((change->replace_when <= CP_WHEN_EMPTY && !empty_for_element) ||
+ (change->replace_when <= CP_WHEN_DIGGABLE && !half_destructible) ||
+ (change->replace_when <= CP_WHEN_DESTRUCTIBLE && IS_INDESTRUCTIBLE(e)))
+ {
+ can_replace[xx][yy] = FALSE;
+ complete_replace = FALSE;
+ }
+#endif
+ }
+
+ 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[xx][yy];
+ target_element = GET_TARGET_ELEMENT(content_element, change);
+
+ ChangeElementNowExt(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);
+ }
+ }
+ else
+ {
+ target_element = GET_TARGET_ELEMENT(change->target_element, change);
+
+ ChangeElementNowExt(x, y, target_element);
+
+ PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
+ }
+
+#if 1
+ /* this uses direct change before indirect change */
+ CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
+#endif
+
+ return TRUE;
+}
+
+static void ChangeElement(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("ChangeElement(): %d,%d: element = %d ('%s')\n",
+ x, y, element, element_info[element].token_name);
+ printf("ChangeElement(): 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] = ( change->delay_fixed * change->delay_frames +
+ RND(change->delay_random * change->delay_frames)) + 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 0
+ if (IS_MOVING(x, y) && !change->explode)
+#else
+ if (IS_MOVING(x, y)) /* never change a running system ;-) */
+#endif
+ {
+ ChangeDelay[x][y] = 1; /* try change after next move step */
+ ChangePage[x][y] = page; /* remember page to use for change */
+
+ return;
+ }
+
+ if (ChangeElementNow(x, y, element, page))
+ {
+ if (change->post_change_function)
+ change->post_change_function(x, y);
+ }
+ }
+}
+
+static boolean CheckTriggeredElementChangeExt(int lx, int ly,
+ int trigger_element,
+ int trigger_event,
+ int trigger_player,
+ int trigger_side,
+ int trigger_page)
+{
+ int i, j, x, y;
+ int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
+
+ if (!(trigger_events[trigger_element][trigger_event]))
+ return FALSE;
+
+ for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
+ {
+ int element = EL_CUSTOM_START + i;
+
+ boolean change_element = FALSE;
+ int page = 0;
+
+ if (!CAN_CHANGE(element) || !HAS_ANY_CHANGE_EVENT(element, trigger_event))
+ continue;
+
+ for (j = 0; j < element_info[element].num_change_pages; j++)
+ {
+ struct ElementChangeInfo *change = &element_info[element].change_page[j];
+
+ if (change->can_change &&
+ 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))
+ {
+#if 0
+ if (!(change->has_event[trigger_event]))
+ printf("::: !!! %d triggers %d: using wrong page %d [event %d]\n",
+ trigger_element-EL_CUSTOM_START+1, i+1, j, trigger_event);
+#endif
+
+ change_element = TRUE;
+ page = j;
+
+ change->actual_trigger_element = trigger_element;
+ change->actual_trigger_player = EL_PLAYER_1 + log_2(trigger_player);
+
+ break;
+ }
+ }
+
+ if (!change_element)
+ continue;
+
+ for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
+ {
+#if 0
+ if (x == lx && y == ly) /* do not change trigger element itself */
+ continue;
+#endif
+
+ if (Feld[x][y] == element)
+ {
+ ChangeDelay[x][y] = 1;
+ ChangeEvent[x][y] = trigger_event;
+ ChangeElement(x, y, page);
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+static boolean CheckElementChangeExt(int x, int y,
+ int element,
+ int trigger_element,
+ int trigger_event,
+ int trigger_player,
+ int trigger_side,
+ int trigger_page)
+{
+ if (!CAN_CHANGE(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 1
+ if (Feld[x][y] != element) /* check if element has already changed */
+ {
+#if 0
+ printf("::: %d ('%s') != %d ('%s') [%d]\n",
+ Feld[x][y], element_info[Feld[x][y]].token_name,
+ element, element_info[element].token_name,
+ trigger_event);
+#endif
+
+ return FALSE;
+ }
+#endif
+
+#if 1
+ if (trigger_page < 0)
+ {
+ boolean change_element = FALSE;
+ int i;
+
+ for (i = 0; i < element_info[element].num_change_pages; i++)
+ {
+ struct ElementChangeInfo *change = &element_info[element].change_page[i];
+
+ if (change->can_change &&
+ change->has_event[trigger_event] &&
+ change->trigger_side & trigger_side &&
+ change->trigger_player & trigger_player)
+ {
+ change_element = TRUE;
+ trigger_page = i;
+
+ change->actual_trigger_element = trigger_element;
+ change->actual_trigger_player = EL_PLAYER_1 + log_2(trigger_player);
+
+ break;
+ }
+ }
+
+ if (!change_element)
+ return FALSE;
+ }
+ else
+ {
+ struct ElementInfo *ei = &element_info[element];
+ struct ElementChangeInfo *change = &ei->change_page[trigger_page];
+
+ change->actual_trigger_element = trigger_element;
+ change->actual_trigger_player = EL_PLAYER_1; /* unused */
+ }
+
+#else
+
+ /* !!! this check misses pages with same event, but different side !!! */
+
+ if (trigger_page < 0)
+ trigger_page = element_info[element].event_page_nr[trigger_event];
+
+ if (!(element_info[element].change_page[trigger_page].trigger_side & trigger_side))
+ return FALSE;
+#endif
+
+ ChangeDelay[x][y] = 1;
+ ChangeEvent[x][y] = trigger_event;
+ ChangeElement(x, y, trigger_page);
+
+ return TRUE;
+}
+
+static void PlayPlayerSound(struct PlayerInfo *player)
+{
+ int jx = player->jx, jy = player->jy;
+ int element = player->element_nr;
+ int last_action = player->last_action_waiting;
+ int action = player->action_waiting;
+
+ if (player->is_waiting)
+ {
+ if (action != last_action)
+ PlayLevelSoundElementAction(jx, jy, element, action);
+ else
+ PlayLevelSoundElementActionIfLoop(jx, jy, element, action);
+ }
+ else
+ {
+ if (action != last_action)
+ StopSound(element_info[element].sound[last_action]);
+
+ if (last_action == ACTION_SLEEPING)
+ PlayLevelSoundElementAction(jx, jy, 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->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 +
+ SimpleRND(game.player_boring_delay_random);
+ player->frame_counter_sleeping =
+ FrameCounter +
+ game.player_sleeping_delay_fixed +
+ SimpleRND(game.player_sleeping_delay_random);
+
+ InitPlayerGfxAnimation(player, ACTION_WAITING, player->MovDir);
+ }
+
+ 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)
+ {
+ 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->element_nr, special_action, move_dir);
+
+ player->anim_delay_counter =
+ graphic_info[special_graphic].anim_delay_fixed +
+ SimpleRND(graphic_info[special_graphic].anim_delay_random);
+ player->post_delay_counter =
+ graphic_info[special_graphic].post_delay_fixed +
+ SimpleRND(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 + SimpleRND(player->num_special_action_bored);
+ int special_graphic =
+ el_act_dir2img(player->element_nr, special_action, move_dir);
+
+ player->anim_delay_counter =
+ graphic_info[special_graphic].anim_delay_fixed +
+ SimpleRND(graphic_info[special_graphic].anim_delay_random);
+ player->post_delay_counter =
+ graphic_info[special_graphic].post_delay_fixed +
+ SimpleRND(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->action_waiting = ACTION_DEFAULT;
+
+ player->special_action_bored = ACTION_DEFAULT;
+ player->special_action_sleeping = ACTION_DEFAULT;
+ }
+}
+
+#if 1
+static byte PlayerActions(struct PlayerInfo *player, byte player_action)
+{
+#if 0
+ static byte stored_player_action[MAX_PLAYERS];
+ static int num_stored_actions = 0;
+#endif
+ 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 0
+ stored_player_action[player->index_nr] = 0;
+ num_stored_actions++;
+#endif
+
+#if 0
+ printf("::: player %d [%d]\n", player->index_nr, FrameCounter);
+#endif
+
+ if (!player->active || tape.pausing)
+ return 0;
+
+#if 0
+ printf("::: [%d %d %d %d] [%d %d]\n",
+ left, right, up, down, button1, button2);
+#endif
+
+ if (player_action)
+ {
+#if 0
+ printf("::: player %d acts [%d]\n", player->index_nr, FrameCounter);
+#endif
+
+#if 0
+ /* !!! TEST !!! */
+ if (player->MovPos == 0)
+ CheckGravityMovement(player);
+#endif
+ 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);
+
+#if 1
+ return player_action;
+#else
+ stored_player_action[player->index_nr] = player_action;
+#endif
+ }
+ else
+ {
+#if 0
+ printf("::: player %d waits [%d]\n", player->index_nr, FrameCounter);
+#endif
+
+ /* 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;
+
+ return 0;
+ }
+
+#if 0
+ if (tape.recording && num_stored_actions >= MAX_PLAYERS)
+ {
+ printf("::: player %d recorded [%d]\n", player->index_nr, FrameCounter);
+
+ TapeRecordAction(stored_player_action);
+ num_stored_actions = 0;
+ }
+#endif
+}
+
+#else
+
+static void PlayerActions(struct PlayerInfo *player, byte player_action)
+{
+ static byte stored_player_action[MAX_PLAYERS];
+ static int num_stored_actions = 0;
+ 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);
+
+ stored_player_action[player->index_nr] = 0;
+ num_stored_actions++;
+
+ printf("::: player %d [%d]\n", player->index_nr, FrameCounter);
+
+ if (!player->active || tape.pausing)
+ return;
+
+ if (player_action)
+ {
+ printf("::: player %d acts [%d]\n", player->index_nr, FrameCounter);
+
+ 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 */
+ }
+ }
+
+ stored_player_action[player->index_nr] = player_action;
+ }
+ else
+ {
+ printf("::: player %d waits [%d]\n", player->index_nr, FrameCounter);
+
+ /* 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)
+ InitPlayerGfxAnimation(player, ACTION_DEFAULT, player->MovDir);
+
+ if (player->MovPos == 0) /* needed for tape.playing */
+ player->is_moving = FALSE;
+ }
+
+ if (tape.recording && num_stored_actions >= MAX_PLAYERS)
+ {
+ printf("::: player %d recorded [%d]\n", player->index_nr, FrameCounter);
+
+ TapeRecordAction(stored_player_action);
+ num_stored_actions = 0;
+ }
+}
+#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_frames =
+ MOVE_DELAY_NORMAL_SPEED / stored_player[i].move_delay_value;
+
+ if (!advance_player_counters) /* not all players may be affected */
+ continue;
+
+ stored_player[i].Frame += move_frames;
+
+ if (stored_player[i].MovPos != 0)
+ stored_player[i].StepFrame += move_frames;
+
+#if USE_NEW_MOVE_DELAY
+ if (stored_player[i].move_delay > 0)
+ stored_player[i].move_delay--;
+#endif
+
+#if USE_NEW_PUSH_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++;
+#endif
+
+ if (stored_player[i].drop_delay > 0)
+ stored_player[i].drop_delay--;
+ }
+}
+
+void GameActions()
+{
+ static unsigned long game_frame_delay = 0;
+ unsigned long game_frame_delay_value;
+ int magic_wall_x = 0, magic_wall_y = 0;
+ int i, x, y, element, graphic;
+ byte *recorded_player_action;
+ byte summarized_player_action = 0;
+#if 1
+ byte tape_action[MAX_PLAYERS];
+#endif
+
+ if (game_status != GAME_MODE_PLAYING)
+ 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)
+ {
+ /*
+#ifdef DEBUG
+ printf("DEBUG: try to get network player actions in time\n");
+#endif
+ */
+
+#if defined(NETWORK_AVALIABLE)
+ /* last chance to get network player actions without main loop delay */
+ HandleNetworking();
+#endif
+
+ if (game_status != GAME_MODE_PLAYING)
+ return;
+
+ if (!network_player_action_received)
+ {
+ /*
+#ifdef DEBUG
+ printf("DEBUG: failed to get network player actions in time\n");
+#endif
+ */
+ return;
+ }
+ }
+
+ if (tape.pausing)
+ return;
+
+#if 0
+ printf("::: getting new tape action [%d]\n", FrameCounter);
+#endif
+
+ recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
+
+#if 1
+ /* !!! CHECK THIS (tape.pausing is always FALSE here!) !!! */
+ if (recorded_player_action == NULL && tape.pausing)
+ return;
+#endif
+
+#if 0
+ printf("::: %d\n", stored_player[0].action);
+#endif
+
+#if 0
+ if (recorded_player_action != NULL)
+ for (i = 0; i < MAX_PLAYERS; i++)
+ stored_player[i].action = recorded_player_action[i];
+#endif
+
+ 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 1
+ if (recorded_player_action != NULL)
+ for (i = 0; i < MAX_PLAYERS; i++)
+ stored_player[i].effective_action = recorded_player_action[i];
+#endif
+
+#if 1
+ for (i = 0; i < MAX_PLAYERS; i++)
+ {
+ tape_action[i] = stored_player[i].effective_action;
+
+ if (tape.recording && tape_action[i] && !tape.player_participates[i])
+ tape.player_participates[i] = TRUE; /* player just appeared from CE */
+ }
+
+ /* only save actions from input devices, but not programmed actions */
+ if (tape.recording)
+ TapeRecordAction(tape_action);
+#endif
+
+ 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
+
+#if 1
+ /* overwrite programmed action with tape action */
+ if (stored_player[i].programmed_action)
+ actual_player_action = stored_player[i].programmed_action;
+#endif
+
+#if 0
+ if (stored_player[i].programmed_action)
+ printf("::: %d\n", stored_player[i].programmed_action);
+#endif
+
+ if (recorded_player_action)
+ {
+#if 0
+ if (stored_player[i].programmed_action &&
+ stored_player[i].programmed_action != recorded_player_action[i])
+ printf("::: %d: %d <-> %d\n", i,
+ stored_player[i].programmed_action, recorded_player_action[i]);
+#endif
+
+#if 0
+ actual_player_action = recorded_player_action[i];
+#endif
+ }
+
+#if 0
+ /* overwrite tape action with programmed action */
+ if (stored_player[i].programmed_action)
+ actual_player_action = stored_player[i].programmed_action;
+#endif
+
+#if 0
+ if (i == 0)
+ printf("::: action: %d: %x [%d]\n",
+ stored_player[i].MovPos, actual_player_action, FrameCounter);
+#endif
+
+#if 1
+ PlayerActions(&stored_player[i], actual_player_action);
+#else
+ tape_action[i] = PlayerActions(&stored_player[i], actual_player_action);
+
+ if (tape.recording && tape_action[i] && !tape.player_participates[i])
+ tape.player_participates[i] = TRUE; /* player just appeared from CE */
+#endif
+
+ ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
+ }
+
+#if 0
+ if (tape.recording)
+ TapeRecordAction(tape_action);
+#endif
+
+ network_player_action_received = FALSE;
+
+ ScrollScreen(NULL, SCROLL_GO_ON);
+
+#if 0
+ FrameCounter++;
+ TimeFrames++;
+
+ for (i = 0; i < MAX_PLAYERS; i++)
+ stored_player[i].Frame++;
+#endif
+
+#if 1
+ /* 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 1
+ if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
+#else
+ if (game.engine_version < VERSION_IDENT(2,2,0,7))
+#endif
+ {
+ for (i = 0; i < MAX_PLAYERS; i++)
+ {
+ struct PlayerInfo *player = &stored_player[i];
+ int x = player->jx;
+ int y = player->jy;
+
+#if 1
+ if (player->active && player->is_pushing && player->is_moving &&
+ IS_MOVING(x, y) &&
+ (game.engine_version < VERSION_IDENT(2,2,0,7) ||
+ Feld[x][y] == EL_SPRING))
+#else
+ if (player->active && player->is_pushing && player->is_moving &&
+ IS_MOVING(x, y))
+#endif
+ {
+ ContinueMoving(x, y);
+
+ /* continue moving after pushing (this is actually a bug) */
+ if (!IS_MOVING(x, y))
+ {
+ Stop[x][y] = FALSE;
+ }
+ }
+ }
+ }
+#endif
+
+ for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
+ {
+ Changed[x][y] = FALSE;
+ ChangeEvent[x][y] = -1;
+
+#if USE_NEW_BLOCK_STYLE
+ /* this must be handled before main playfield loop */
+ if (Feld[x][y] == EL_PLAYER_IS_LEAVING)
+ {
+ MovDelay[x][y]--;
+ if (MovDelay[x][y] <= 0)
+ RemoveField(x, y);
+ }
+#endif
+
+#if DEBUG
+ if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
+ {
+ printf("GameActions(): x = %d, y = %d: ChangePage != -1\n", x, y);
+ printf("GameActions(): This should never happen!\n");
+
+ ChangePage[x][y] = -1;
+ }
+#endif
+
+ Stop[x][y] = FALSE;
+ if (WasJustMoving[x][y] > 0)
+ WasJustMoving[x][y]--;
+ if (WasJustFalling[x][y] > 0)
+ WasJustFalling[x][y]--;
+ if (CheckCollision[x][y] > 0)
+ CheckCollision[x][y]--;
+
+ GfxFrame[x][y]++;
+
+#if 1
+ /* reset finished pushing action (not done in ContinueMoving() to allow
+ continous pushing animation for elements with zero push delay) */
+ if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
+ {
+ ResetGfxAnimation(x, y);
+ DrawLevelField(x, y);
+ }
+#endif
+
+#if DEBUG
+ if (IS_BLOCKED(x, y))
+ {
+ int oldx, oldy;
+
+ Blocked2Moving(x, y, &oldx, &oldy);
+ if (!IS_MOVING(oldx, oldy))
+ {
+ printf("GameActions(): (BLOCKED => MOVING) context corrupted!\n");
+ printf("GameActions(): BLOCKED: x = %d, y = %d\n", x, y);
+ printf("GameActions(): !MOVING: oldx = %d, oldy = %d\n", oldx, oldy);
+ printf("GameActions(): This should never happen!\n");
+ }
+ }
+#endif
+ }
+
+ for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
+ {
+ element = Feld[x][y];
+#if 1
+ graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
+#else
+ graphic = el2img(element);
+#endif
+
+#if 0
+ if (element == -1)
+ {
+ printf("::: %d,%d: %d [%d]\n", x, y, element, FrameCounter);
+
+ element = graphic = 0;
+ }
+#endif
+
+ if (graphic_info[graphic].anim_global_sync)
+ GfxFrame[x][y] = FrameCounter;
+
+ if (ANIM_MODE(graphic) == ANIM_RANDOM &&
+ IS_NEXT_FRAME(GfxFrame[x][y], graphic))
+ ResetRandomAnimationValue(x, y);
+
+ SetRandomAnimationValue(x, y);
+
+#if 1
+ PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
+#endif
+
+ if (IS_INACTIVE(element))
+ {
+ if (IS_ANIMATED(graphic))
+ DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
+
+ continue;
+ }
+
+#if 1
+ /* this may take place after moving, so 'element' may have changed */
+#if 0
+ if (IS_CHANGING(x, y))
+#else
+ if (IS_CHANGING(x, y) &&
+ (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
+#endif
+ {
+#if 0
+ ChangeElement(x, y, ChangePage[x][y] != -1 ? ChangePage[x][y] :
+ element_info[element].event_page_nr[CE_DELAY]);
+#else
+ ChangeElement(x, y, element_info[element].event_page_nr[CE_DELAY]);
+#endif
+
+ element = Feld[x][y];
+ graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
+ }
+#endif
+
+ if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
+ {
+ StartMoving(x, y);
+
+#if 1
+ element = Feld[x][y];
+ graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
+#if 0
+ if (element == EL_MOLE)
+ printf("::: %d, %d, %d [%d]\n",
+ IS_ANIMATED(graphic), IS_MOVING(x, y), Stop[x][y],
+ GfxAction[x][y]);
+#endif
+#if 0
+ if (element == EL_YAMYAM)
+ printf("::: %d, %d, %d\n",
+ IS_ANIMATED(graphic), IS_MOVING(x, y), Stop[x][y]);
+#endif
+#endif
+
+ if (IS_ANIMATED(graphic) &&
+ !IS_MOVING(x, y) &&
+ !Stop[x][y])
+ {
+ DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
+
+#if 0
+ if (element == EL_BUG)
+ printf("::: %d, %d\n", graphic, GfxFrame[x][y]);
+#endif
+
+#if 0
+ if (element == EL_MOLE)
+ printf("::: %d, %d\n", graphic, GfxFrame[x][y]);
+#endif
+ }
+
+ if (IS_GEM(element) || element == EL_SP_INFOTRON)
+ EdelsteinFunkeln(x, y);
+ }
+ else if ((element == EL_ACID ||
+ element == EL_EXIT_OPEN ||
+ element == EL_SP_EXIT_OPEN ||
+ element == EL_SP_TERMINAL ||
+ element == EL_SP_TERMINAL_ACTIVE ||
+ element == EL_EXTRA_TIME ||
+ element == EL_SHIELD_NORMAL ||
+ element == EL_SHIELD_DEADLY) &&
+ IS_ANIMATED(graphic))
+ DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
+ else if (IS_MOVING(x, y))
+ ContinueMoving(x, y);
+ else if (IS_ACTIVE_BOMB(element))
+ CheckDynamite(x, y);
+#if 0
+ else if (element == EL_EXPLOSION && !game.explosions_delayed)
+ Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
+#endif
+ else if (element == EL_AMOEBA_GROWING)
+ AmoebeWaechst(x, y);
+ else if (element == EL_AMOEBA_SHRINKING)
+ AmoebaDisappearing(x, y);
+
+#if !USE_NEW_AMOEBA_CODE
+ else if (IS_AMOEBALIVE(element))
+ AmoebeAbleger(x, y);
+#endif
+
+ else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
+ Life(x, y);
+ else if (element == EL_EXIT_CLOSED)
+ CheckExit(x, y);
+ else if (element == EL_SP_EXIT_CLOSED)
+ CheckExitSP(x, y);
+ else if (element == EL_EXPANDABLE_WALL_GROWING)
+ MauerWaechst(x, y);
+ else if (element == EL_EXPANDABLE_WALL ||
+ element == EL_EXPANDABLE_WALL_HORIZONTAL ||
+ element == EL_EXPANDABLE_WALL_VERTICAL ||
+ element == EL_EXPANDABLE_WALL_ANY)
+ MauerAbleger(x, y);
+ else if (element == EL_FLAMES)
+ CheckForDragon(x, y);
+#if 0
+ else if (IS_AUTO_CHANGING(element))
+ ChangeElement(x, y);
+#endif
+ else if (element == EL_EXPLOSION)
+ ; /* drawing of correct explosion animation is handled separately */
+ else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
+ DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
+
+#if 0
+ /* this may take place after moving, so 'element' may have changed */
+ if (IS_AUTO_CHANGING(Feld[x][y]))
+ ChangeElement(x, y);
+#endif
+
+ if (IS_BELT_ACTIVE(element))
+ PlayLevelSoundAction(x, y, ACTION_ACTIVE);
+
+ if (game.magic_wall_active)
+ {
+ int jx = local_player->jx, jy = local_player->jy;
+
+ /* play the element sound at the position nearest to the player */
+ if ((element == EL_MAGIC_WALL_FULL ||
+ element == EL_MAGIC_WALL_ACTIVE ||
+ element == EL_MAGIC_WALL_EMPTYING ||
+ element == EL_BD_MAGIC_WALL_FULL ||
+ element == EL_BD_MAGIC_WALL_ACTIVE ||
+ element == EL_BD_MAGIC_WALL_EMPTYING) &&
+ ABS(x-jx) + ABS(y-jy) < ABS(magic_wall_x-jx) + ABS(magic_wall_y-jy))
+ {
+ magic_wall_x = x;
+ magic_wall_y = y;
+ }
+ }
+ }
+
+#if USE_NEW_AMOEBA_CODE
+ /* new experimental amoeba growth stuff */
+#if 1
+ if (!(FrameCounter % 8))
+#endif
+ {
+ static unsigned long random = 1684108901;
+
+ for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
+ {
+#if 0
+ x = (random >> 10) % lev_fieldx;
+ y = (random >> 20) % lev_fieldy;
+#else
+ x = RND(lev_fieldx);
+ y = RND(lev_fieldy);
+#endif
+ element = Feld[x][y];
+
+#if 1
+ if (!IS_PLAYER(x,y) &&
+ (element == EL_EMPTY ||
+ CAN_GROW_INTO(element) ||
+ element == EL_QUICKSAND_EMPTY ||
+ element == EL_ACID_SPLASH_LEFT ||
+ element == EL_ACID_SPLASH_RIGHT))
+ {
+ if ((IN_LEV_FIELD(x, y-1) && Feld[x][y-1] == EL_AMOEBA_WET) ||
+ (IN_LEV_FIELD(x-1, y) && Feld[x-1][y] == EL_AMOEBA_WET) ||
+ (IN_LEV_FIELD(x+1, y) && Feld[x+1][y] == EL_AMOEBA_WET) ||
+ (IN_LEV_FIELD(x, y+1) && Feld[x][y+1] == EL_AMOEBA_WET))
+ Feld[x][y] = EL_AMOEBA_DROP;
+ }
+#else
+ /* !!! extend EL_SAND to anything diggable (but maybe not SP_BASE) !!! */
+ if (!IS_PLAYER(x,y) &&
+ (element == EL_EMPTY ||
+ element == EL_SAND ||
+ element == EL_QUICKSAND_EMPTY ||
+ element == EL_ACID_SPLASH_LEFT ||
+ element == EL_ACID_SPLASH_RIGHT))
+ {
+ if ((IN_LEV_FIELD(x, y-1) && Feld[x][y-1] == EL_AMOEBA_WET) ||
+ (IN_LEV_FIELD(x-1, y) && Feld[x-1][y] == EL_AMOEBA_WET) ||
+ (IN_LEV_FIELD(x+1, y) && Feld[x+1][y] == EL_AMOEBA_WET) ||
+ (IN_LEV_FIELD(x, y+1) && Feld[x][y+1] == EL_AMOEBA_WET))
+ Feld[x][y] = EL_AMOEBA_DROP;
+ }
+#endif
+
+ random = random * 129 + 1;
+ }
+ }
+#endif
+
+#if 0
+ if (game.explosions_delayed)
+#endif
+ {
+ game.explosions_delayed = FALSE;
+
+ for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
+ {
+ element = Feld[x][y];
+
+ if (ExplodeField[x][y])
+ Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
+ else if (element == EL_EXPLOSION)
+ Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
+
+ ExplodeField[x][y] = EX_TYPE_NONE;
+ }
+
+ game.explosions_delayed = TRUE;
+ }
+
+ if (game.magic_wall_active)
+ {
+ if (!(game.magic_wall_time_left % 4))
+ {
+ int element = Feld[magic_wall_x][magic_wall_y];
+
+ if (element == EL_BD_MAGIC_WALL_FULL ||
+ element == EL_BD_MAGIC_WALL_ACTIVE ||
+ element == EL_BD_MAGIC_WALL_EMPTYING)
+ PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
+ else
+ PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
+ }
+
+ if (game.magic_wall_time_left > 0)
+ {
+ game.magic_wall_time_left--;
+ if (!game.magic_wall_time_left)
+ {
+ for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
+ {
+ element = Feld[x][y];
+
+ if (element == EL_MAGIC_WALL_ACTIVE ||
+ element == EL_MAGIC_WALL_FULL)
+ {
+ Feld[x][y] = EL_MAGIC_WALL_DEAD;
+ DrawLevelField(x, y);
+ }
+ else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
+ element == EL_BD_MAGIC_WALL_FULL)
+ {
+ Feld[x][y] = EL_BD_MAGIC_WALL_DEAD;
+ DrawLevelField(x, y);
+ }
+ }
+
+ game.magic_wall_active = FALSE;
+ }
+ }
+ }
+
+ if (game.light_time_left > 0)
+ {
+ game.light_time_left--;
+
+ if (game.light_time_left == 0)
+ RedrawAllLightSwitchesAndInvisibleElements();
+ }
+
+ if (game.timegate_time_left > 0)
+ {
+ game.timegate_time_left--;
+
+ if (game.timegate_time_left == 0)
+ CloseAllOpenTimegates();
+ }
+
+ for (i = 0; i < MAX_PLAYERS; i++)
+ {
+ struct PlayerInfo *player = &stored_player[i];
+
+ if (SHIELD_ON(player))
+ {
+ if (player->shield_deadly_time_left)
+ PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
+ else if (player->shield_normal_time_left)
+ PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
+ }
+ }
+
+ 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 (!level.use_step_counter)
+ {
+ TimePlayed++;
+
+ if (TimeLeft > 0)
+ {
+ TimeLeft--;
+
+ if (TimeLeft <= 10 && setup.time_limit)
+ PlaySoundStereo(SND_GAME_RUNNING_OUT_OF_TIME, SOUND_MIDDLE);
+
+ DrawGameValue_Time(TimeLeft);
+
+ if (!TimeLeft && setup.time_limit)
+ for (i = 0; i < MAX_PLAYERS; i++)
+ KillHero(&stored_player[i]);
+ }
+ else if (level.time == 0 && !AllPlayersGone) /* level w/o time limit */
+ DrawGameValue_Time(TimePlayed);
+ }
+
+ if (tape.recording || tape.playing)
+ DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
+ }
+
+ DrawAllPlayers();
+ PlayAllPlayersSound();
+
+ if (options.debug) /* calculate frames per second */
+ {
+ static unsigned long fps_counter = 0;
+ static int fps_frames = 0;
+ unsigned long fps_delay_ms = Counter() - fps_counter;
+
+ fps_frames++;
+
+ if (fps_delay_ms >= 500) /* calculate fps every 0.5 seconds */
+ {
+ global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
+
+ fps_frames = 0;
+ fps_counter = Counter();
+ }
+
+ redraw_mask |= REDRAW_FPS;
+ }
+
+#if 0
+ if (stored_player[0].jx != stored_player[0].last_jx ||
+ stored_player[0].jy != stored_player[0].last_jy)
+ printf("::: %d, %d, %d, %d, %d\n",
+ stored_player[0].MovDir,
+ stored_player[0].MovPos,
+ stored_player[0].GfxPos,
+ stored_player[0].Frame,
+ stored_player[0].StepFrame);
+#endif
+
+#if USE_NEW_MOVE_DELAY
+ AdvanceFrameAndPlayerCounters(-1); /* advance counters for all players */
+#else
+ FrameCounter++;
+ TimeFrames++;
+
+ for (i = 0; i < MAX_PLAYERS; i++)
+ {
+ int move_frames =
+ MOVE_DELAY_NORMAL_SPEED / stored_player[i].move_delay_value;
+
+ stored_player[i].Frame += move_frames;
+
+ if (stored_player[i].MovPos != 0)
+ stored_player[i].StepFrame += move_frames;
+
+#if USE_NEW_MOVE_DELAY
+ if (stored_player[i].move_delay > 0)
+ stored_player[i].move_delay--;
+#endif
+
+ if (stored_player[i].drop_delay > 0)
+ stored_player[i].drop_delay--;
+ }
+#endif
+
+#if 1
+ if (local_player->show_envelope != 0 && local_player->MovPos == 0)
+ {
+ ShowEnvelope(local_player->show_envelope - EL_ENVELOPE_1);
+
+ local_player->show_envelope = 0;
+ }
+#endif
+
+#if USE_NEW_RANDOMIZE
+ /* use random number generator in every frame to make it less predictable */
+ if (game.engine_version >= VERSION_IDENT(3,1,1,0))
+ RND(1);
+#endif
+}
+
+static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
+{
+ int min_x = x, min_y = y, max_x = x, max_y = y;
+ int i;
+
+ for (i = 0; i < MAX_PLAYERS; i++)
+ {
+ int jx = stored_player[i].jx, jy = stored_player[i].jy;
+
+ if (!stored_player[i].active || &stored_player[i] == player)
+ continue;
+
+ min_x = MIN(min_x, jx);
+ min_y = MIN(min_y, jy);
+ max_x = MAX(max_x, jx);
+ max_y = MAX(max_y, jy);
+ }
+
+ return (max_x - min_x < SCR_FIELDX && max_y - min_y < SCR_FIELDY);
+}
+
+static boolean AllPlayersInVisibleScreen()
+{
+ int i;
+
+ for (i = 0; i < MAX_PLAYERS; i++)
+ {
+ int jx = stored_player[i].jx, jy = stored_player[i].jy;
+
+ if (!stored_player[i].active)
+ continue;
+
+ if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+void ScrollLevel(int dx, int dy)
+{
+ int softscroll_offset = (setup.soft_scrolling ? TILEX : 0);
+ int x, y;
+
+ BlitBitmap(drawto_field, drawto_field,
+ FX + TILEX * (dx == -1) - softscroll_offset,
+ FY + TILEY * (dy == -1) - softscroll_offset,
+ SXSIZE - TILEX * (dx!=0) + 2 * softscroll_offset,
+ SYSIZE - TILEY * (dy!=0) + 2 * softscroll_offset,
+ FX + TILEX * (dx == 1) - softscroll_offset,
+ FY + TILEY * (dy == 1) - softscroll_offset);
+
+ if (dx)
+ {
+ x = (dx == 1 ? BX1 : BX2);
+ for (y = BY1; y <= BY2; y++)
+ DrawScreenField(x, y);
+ }
+
+ if (dy)
+ {
+ y = (dy == 1 ? BY1 : BY2);
+ for (x = BX1; x <= BX2; x++)
+ DrawScreenField(x, y);
+ }
+
+ redraw_mask |= REDRAW_FIELD;
+}
+
+#if 0
+static boolean canEnterSupaplexPort(int x, int y, int dx, int dy)
+{
+ int nextx = x + dx, nexty = y + dy;
+ int element = Feld[x][y];
+
+ if ((dx == -1 &&
+ element != EL_SP_PORT_LEFT &&
+ element != EL_SP_GRAVITY_PORT_LEFT &&
+ element != EL_SP_PORT_HORIZONTAL &&
+ element != EL_SP_PORT_ANY) ||
+ (dx == +1 &&
+ element != EL_SP_PORT_RIGHT &&
+ element != EL_SP_GRAVITY_PORT_RIGHT &&
+ element != EL_SP_PORT_HORIZONTAL &&
+ element != EL_SP_PORT_ANY) ||
+ (dy == -1 &&
+ element != EL_SP_PORT_UP &&
+ element != EL_SP_GRAVITY_PORT_UP &&
+ element != EL_SP_PORT_VERTICAL &&
+ element != EL_SP_PORT_ANY) ||
+ (dy == +1 &&
+ element != EL_SP_PORT_DOWN &&
+ element != EL_SP_GRAVITY_PORT_DOWN &&
+ element != EL_SP_PORT_VERTICAL &&
+ element != EL_SP_PORT_ANY) ||
+ !IN_LEV_FIELD(nextx, nexty) ||
+ !IS_FREE(nextx, nexty))
+ return FALSE;
+
+ return TRUE;
+}
+#endif
+
+static boolean canFallDown(struct PlayerInfo *player)
+{
+ int jx = player->jx, jy = player->jy;
+
+ return (IN_LEV_FIELD(jx, jy + 1) &&
+ (IS_FREE(jx, jy + 1) ||
+#if USE_NEW_BLOCK_STYLE
+#if USE_GRAVITY_BUGFIX_OLD
+ Feld[jx][jy + 1] == EL_PLAYER_IS_LEAVING ||
+#endif
+#endif
+ (Feld[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
+ IS_WALKABLE_FROM(Feld[jx][jy], MV_DOWN) &&
+ !IS_WALKABLE_INSIDE(Feld[jx][jy]));
+}
+
+static boolean canPassField(int x, int y, int move_dir)
+{
+ int opposite_dir = MV_DIR_OPPOSITE(move_dir);
+ int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
+ int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
+ int nextx = x + dx;
+ int nexty = y + dy;
+ int element = Feld[x][y];
+
+ return (IS_PASSABLE_FROM(element, opposite_dir) &&
+ !CAN_MOVE(element) &&
+ IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
+ IS_WALKABLE_FROM(Feld[nextx][nexty], move_dir) &&
+ (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
+}
+
+static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
+{
+ int opposite_dir = MV_DIR_OPPOSITE(move_dir);
+ int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
+ int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
+ int newx = x + dx;
+ int newy = y + dy;
+#if 0
+ int nextx = newx + dx;
+ int nexty = newy + dy;
+#endif
+
+#if 1
+ return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
+ IS_GRAVITY_REACHABLE(Feld[newx][newy]) &&
+#if 0
+ (!IS_SP_PORT(Feld[newx][newy]) || move_dir == MV_UP) &&
+#endif
+ (IS_DIGGABLE(Feld[newx][newy]) ||
+ IS_WALKABLE_FROM(Feld[newx][newy], opposite_dir) ||
+ canPassField(newx, newy, move_dir)));
+#else
+#if 1
+ return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
+ IS_GRAVITY_REACHABLE(Feld[newx][newy]) &&
+ (IS_DIGGABLE(Feld[newx][newy]) ||
+ IS_WALKABLE_FROM(Feld[newx][newy], opposite_dir) ||
+ canPassField(newx, newy, move_dir)));
+#else
+#if 1
+ return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
+ (IS_DIGGABLE_WITH_GRAVITY(Feld[newx][newy]) ||
+ IS_WALKABLE_FROM(Feld[newx][newy], opposite_dir) ||
+ canPassField(newx, newy, move_dir)));
+#else
+ return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
+ (IS_DIGGABLE(Feld[newx][newy]) ||
+ IS_WALKABLE_FROM(Feld[newx][newy], opposite_dir) ||
+ (IS_PASSABLE_FROM(Feld[newx][newy], opposite_dir) &&
+ !CAN_MOVE(Feld[newx][newy]) &&
+ IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
+ IS_WALKABLE_FROM(Feld[nextx][nexty], move_dir) &&
+ (level.can_pass_to_walkable || IS_FREE(nextx, nexty)))));
+#endif
+#endif
+#endif
+}
+
+static void CheckGravityMovement(struct PlayerInfo *player)
+{
+ if (game.gravity && !player->programmed_action)
+ {
+#if 1
+ int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
+ int move_dir_vertical = player->effective_action & MV_VERTICAL;
+#else
+ int move_dir_horizontal = player->action & MV_HORIZONTAL;
+ int move_dir_vertical = player->action & MV_VERTICAL;
+#endif
+
+#if 1
+ boolean player_is_snapping = player->effective_action & JOY_BUTTON_1;
+#else
+ boolean player_is_snapping = player->action & JOY_BUTTON_1;
+#endif
+
+ int jx = player->jx, jy = player->jy;
+
+ boolean player_is_moving_to_valid_field =
+ (!player_is_snapping &&
+ (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
+ canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
+
+#if 0
+ int move_dir =
+ (player->last_move_dir & MV_HORIZONTAL ?
+ (move_dir_vertical ? move_dir_vertical : move_dir_horizontal) :
+ (move_dir_horizontal ? move_dir_horizontal : move_dir_vertical));
+#endif
+
+#if 0
+ int opposite_dir = MV_DIR_OPPOSITE(move_dir);
+ int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
+ int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
+ int new_jx = jx + dx, new_jy = jy + dy;
+ int nextx = new_jx + dx, nexty = new_jy + dy;
+#endif
+
+#if 1
+
+#if 1
+ boolean player_can_fall_down = canFallDown(player);
+#else
+ boolean player_can_fall_down =
+ (IN_LEV_FIELD(jx, jy + 1) &&
+ (IS_FREE(jx, jy + 1) ||
+ (Feld[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)));
+#endif
+
+#else
+ boolean player_can_fall_down =
+ (IN_LEV_FIELD(jx, jy + 1) &&
+ (IS_FREE(jx, jy + 1)));
+#endif
+
+#if 0
+ boolean player_is_moving_to_valid_field =
+ (
+#if 1
+ !player_is_snapping &&
+#endif
+
+#if 1
+ IN_LEV_FIELD(new_jx, new_jy) &&
+ (IS_DIGGABLE(Feld[new_jx][new_jy]) ||
+ (IS_SP_PORT(Feld[new_jx][new_jy]) &&
+ element_info[Feld[new_jx][new_jy]].access_direction & opposite_dir &&
+ IN_LEV_FIELD(nextx, nexty) &&
+ element_info[Feld[nextx][nexty]].access_direction & move_dir))
+#else
+ IN_LEV_FIELD(new_jx, new_jy) &&
+ (Feld[new_jx][new_jy] == EL_SP_BASE ||
+ Feld[new_jx][new_jy] == EL_SAND ||
+ (IS_SP_PORT(Feld[new_jx][new_jy]) &&
+ canEnterSupaplexPort(new_jx, new_jy, dx, dy)))
+ /* !!! extend EL_SAND to anything diggable !!! */
+#endif
+ );
+#endif
+
+#if 0
+ boolean player_is_standing_on_valid_field =
+ (IS_WALKABLE_INSIDE(Feld[jx][jy]) ||
+ (IS_WALKABLE(Feld[jx][jy]) && !ACCESS_FROM(Feld[jx][jy], MV_DOWN)));
+#endif
+
+#if 0
+ printf("::: checking gravity NOW [%d, %d, %d] [%d] [%d / %d] ...\n",
+ player_can_fall_down,
+ player_is_standing_on_valid_field,
+ player_is_moving_to_valid_field,
+ (player_is_moving_to_valid_field ? Feld[new_jx][new_jy] : -1),
+ player->effective_action,
+ player->can_fall_into_acid);
+#endif
+
+ if (player_can_fall_down &&
+#if 0
+ !player_is_standing_on_valid_field &&
+#endif
+ !player_is_moving_to_valid_field)
+ {
+#if 0
+ printf("::: setting programmed_action to MV_DOWN [%d,%d - %d] ...\n",
+ jx, jy, FrameCounter);
+#endif
+
+ player->programmed_action = MV_DOWN;
+ }
+ }
+}
+
+static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
+{
+#if 1
+ return CheckGravityMovement(player);
+#endif
+
+ if (game.gravity && !player->programmed_action)
+ {
+ int jx = player->jx, jy = player->jy;
+ boolean field_under_player_is_free =
+ (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
+ boolean player_is_standing_on_valid_field =
+ (IS_WALKABLE_INSIDE(Feld[jx][jy]) ||
+ (IS_WALKABLE(Feld[jx][jy]) &&
+ !(element_info[Feld[jx][jy]].access_direction & MV_DOWN)));
+
+ if (field_under_player_is_free && !player_is_standing_on_valid_field)
+ player->programmed_action = MV_DOWN;
+ }
+}
+
+/*
+ MovePlayerOneStep()
+ -----------------------------------------------------------------------------
+ dx, dy: direction (non-diagonal) to try to move the player to
+ real_dx, real_dy: direction as read from input device (can be diagonal)
+*/
+
+boolean MovePlayerOneStep(struct PlayerInfo *player,
+ int dx, int dy, int real_dx, int real_dy)
+{
+#if 0
+ static int trigger_sides[4][2] =
+ {
+ /* enter side leave side */
+ { CH_SIDE_RIGHT, CH_SIDE_LEFT }, /* moving left */
+ { CH_SIDE_LEFT, CH_SIDE_RIGHT }, /* moving right */
+ { CH_SIDE_BOTTOM, CH_SIDE_TOP }, /* moving up */
+ { CH_SIDE_TOP, CH_SIDE_BOTTOM } /* moving down */
+ };
+ int move_direction = (dx == -1 ? MV_LEFT :
+ dx == +1 ? MV_RIGHT :
+ dy == -1 ? MV_UP :
+ dy == +1 ? MV_DOWN : MV_NO_MOVING);
+ int enter_side = trigger_sides[MV_DIR_BIT(move_direction)][0];
+ int leave_side = trigger_sides[MV_DIR_BIT(move_direction)][1];
+#endif
+ int jx = player->jx, jy = player->jy;
+ int new_jx = jx + dx, new_jy = jy + dy;
+ int element;
+ int can_move;
+
+ if (!player->active || (!dx && !dy))
+ return MF_NO_ACTION;
+
+ player->MovDir = (dx < 0 ? MV_LEFT :
+ dx > 0 ? MV_RIGHT :
+ dy < 0 ? MV_UP :
+ dy > 0 ? MV_DOWN : MV_NO_MOVING);
+
+ if (!IN_LEV_FIELD(new_jx, new_jy))
+ return MF_NO_ACTION;
+
+ if (!options.network && !AllPlayersInSight(player, new_jx, new_jy))
+ return MF_NO_ACTION;
+
+#if 0
+ element = MovingOrBlocked2Element(new_jx, new_jy);
+#else
+ element = MovingOrBlocked2ElementIfNotLeaving(new_jx, new_jy);
+#endif
+
+ if (DONT_RUN_INTO(element))
+ {
+ if (element == EL_ACID && dx == 0 && dy == 1)
+ {
+ SplashAcid(new_jx, new_jy);
+ Feld[jx][jy] = EL_PLAYER_1;
+ InitMovingField(jx, jy, MV_DOWN);
+ Store[jx][jy] = EL_ACID;
+ ContinueMoving(jx, jy);
+ BuryHero(player);
+ }
+ else
+ TestIfHeroRunsIntoBadThing(jx, jy, player->MovDir);
+
+ return MF_MOVING;
+ }
+
+ can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
+ if (can_move != MF_MOVING)
+ return can_move;
+
+ /* check if DigField() has caused relocation of the player */
+ if (player->jx != jx || player->jy != jy)
+ return MF_NO_ACTION; /* <-- !!! CHECK THIS [-> MF_ACTION ?] !!! */
+
+ StorePlayer[jx][jy] = 0;
+ player->last_jx = jx;
+ player->last_jy = jy;
+ player->jx = new_jx;
+ player->jy = new_jy;
+ StorePlayer[new_jx][new_jy] = player->element_nr;
+
+ player->MovPos =
+ (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
+
+ player->step_counter++;
+
+#if 0
+ player->drop_delay = 0;
+#endif
+
+ PlayerVisit[jx][jy] = FrameCounter;
+
+ ScrollPlayer(player, SCROLL_INIT);
+
+#if 0
+ if (IS_CUSTOM_ELEMENT(Feld[jx][jy]))
+ {
+ CheckTriggeredElementChangeBySide(jx, jy, Feld[jx][jy], CE_PLAYER_LEAVES_X,
+ leave_side);
+ CheckElementChangeBySide(jx,jy, Feld[jx][jy],CE_LEFT_BY_PLAYER,leave_side);
+ }
+
+ if (IS_CUSTOM_ELEMENT(Feld[new_jx][new_jy]))
+ {
+ CheckTriggeredElementChangeBySide(new_jx, new_jy, Feld[new_jx][new_jy],
+ CE_PLAYER_ENTERS_X, enter_side);
+ CheckElementChangeBySide(new_jx, new_jy, Feld[new_jx][new_jy],
+ CE_ENTERED_BY_PLAYER, enter_side);
+ }
+#endif
+
+ return MF_MOVING;
+}
+
+boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
+{
+ int jx = player->jx, jy = player->jy;
+ int old_jx = jx, old_jy = jy;
+ int moved = MF_NO_ACTION;
+
+#if 1
+ if (!player->active)
+ return FALSE;
+
+ if (!dx && !dy)
+ {
+ if (player->MovPos == 0)
+ {
+ player->is_moving = FALSE;
+ player->is_digging = FALSE;
+ player->is_collecting = FALSE;
+ player->is_snapping = FALSE;
+ player->is_pushing = FALSE;
+ }
+
+ return FALSE;
+ }
+#else
+ if (!player->active || (!dx && !dy))
+ return FALSE;
+#endif
+
+#if 0
+ if (!FrameReached(&player->move_delay, player->move_delay_value) &&
+ !tape.playing)
+ return FALSE;
+#else
+
+#if 1
+
+#if 0
+ printf("::: %d <= %d < %d ?\n", player->move_delay, FrameCounter,
+ player->move_delay + player->move_delay_value);
+#endif
+
+#if USE_NEW_MOVE_DELAY
+ if (player->move_delay > 0)
+#else
+ if (!FrameReached(&player->move_delay, player->move_delay_value))
+#endif
+ {
+#if 0
+ printf("::: can NOT move\n");
+#endif
+
+ return FALSE;
+ }
+#else
+ if (!FrameReached(&player->move_delay, player->move_delay_value) &&
+ !(tape.playing && tape.file_version < FILE_VERSION_2_0))
+ return FALSE;
+#endif
+
+#endif
+
+#if 0
+ printf("::: COULD move now\n");
+#endif
+
+#if USE_NEW_MOVE_DELAY
+ player->move_delay = -1; /* set to "uninitialized" value */
+#endif
+
+ /* store if player is automatically moved to next field */
+ player->is_auto_moving = (player->programmed_action != MV_NO_MOVING);
+
+ /* remove the last programmed player action */
+ player->programmed_action = 0;
+
+ if (player->MovPos)
+ {
+ /* should only happen if pre-1.2 tape recordings are played */
+ /* this is only for backward compatibility */
+
+ int original_move_delay_value = player->move_delay_value;
+
+#if DEBUG
+ printf("THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%ld]\n",
+ tape.counter);
+#endif
+
+ /* scroll remaining steps with finest movement resolution */
+ player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
+
+ while (player->MovPos)
+ {
+ ScrollPlayer(player, SCROLL_GO_ON);
+ ScrollScreen(NULL, SCROLL_GO_ON);
+
+#if USE_NEW_MOVE_DELAY
+ AdvanceFrameAndPlayerCounters(player->index_nr);
+#else
+ FrameCounter++;
+#endif
+
+ DrawAllPlayers();
+ BackToFront();
+ }
+
+ player->move_delay_value = original_move_delay_value;
+ }
+
+ if (player->last_move_dir & MV_HORIZONTAL)
+ {
+ if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
+ moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
+ }
+ else
+ {
+ if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
+ moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
+ }
+
+ jx = player->jx;
+ jy = player->jy;
+
+ if (moved & MF_MOVING && !ScreenMovPos &&
+ (player == local_player || !options.network))
+ {
+ int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
+ int offset = (setup.scroll_delay ? 3 : 0);
+
+ if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
+ {
+ /* actual player has left the screen -- scroll in that direction */
+ if (jx != old_jx) /* player has moved horizontally */
+ scroll_x += (jx - old_jx);
+ else /* player has moved vertically */
+ scroll_y += (jy - old_jy);
+ }
+ else
+ {
+ if (jx != old_jx) /* player has moved horizontally */
+ {
+ if ((player->MovDir == MV_LEFT && scroll_x > jx - MIDPOSX + offset) ||
+ (player->MovDir == MV_RIGHT && scroll_x < jx - MIDPOSX - offset))
+ scroll_x = jx-MIDPOSX + (scroll_x < jx-MIDPOSX ? -offset : +offset);
+
+ /* don't scroll over playfield boundaries */
+ if (scroll_x < SBX_Left || scroll_x > SBX_Right)
+ scroll_x = (scroll_x < SBX_Left ? SBX_Left : SBX_Right);
+
+ /* don't scroll more than one field at a time */
+ scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
+
+ /* don't scroll against the player's moving direction */
+ if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||