+ }
+ }
+ }
+ 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;
+ 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;
+ 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;
+ 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;
+ 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)
+ {
+ 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;
+
+ 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_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_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 1
+ if (CAN_CHANGE_OR_HAS_ACTION(element))
+#else
+ if (CAN_CHANGE(element))
+#endif
+ {
+ /* 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 0
+#if USE_NEW_CUSTOM_VALUE
+ CustomValue[newx][newy] = CustomValue[x][y];
+#endif
+#endif
+ }
+
+#if 1
+#if USE_NEW_CUSTOM_VALUE
+ CustomValue[newx][newy] = CustomValue[x][y];
+#endif
+#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]))
+ 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;
+
+ DrawLevelField(x, y);
+ 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 (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);
+
+ /* 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 1
+ 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));
+#endif
+}
+
+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;
+
+#if 1
+ SCAN_PLAYFIELD(xx, yy)
+#else
+ for (yy = 0; yy < lev_fieldy; yy++) for (xx = 0; xx < lev_fieldx; xx++)
+#endif
+ {
+ 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
+
+#if 1
+ SCAN_PLAYFIELD(x, y)
+#else
+ for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
+#endif
+ {
+ 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
+
+#if 1
+ SCAN_PLAYFIELD(x, y)
+#else
+ for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
+#endif
+ {
+ 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);
+ 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;
+ 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;
+ 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;
+ 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)
+ {
+ 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)
+ {
+ 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;
+ 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;
+ }
+
+ DrawLevelField(newax, neway);
+}
+
+void Life(int ax, int ay)
+{
+ int x1, y1, x2, y2;
+#if 0
+ static int life[4] = { 2, 3, 3, 3 }; /* parameters for "game of life" */
+#endif
+ 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])
+ 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])
+ 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;
+}
+
+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_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;
+}
+
+static void InitDiagonalMovingElement(int x, int y)
+{
+#if 0
+ MovDelay[x][y] = level.android_move_time;
+#endif
+}
+
+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;
+
+#if 1
+ SCAN_PLAYFIELD(x, y)
+#else
+ for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
+#endif
+ {
+ 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 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_NONE;
+ 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))
+ 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 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];
+ 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))
+ 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 i;
+
+ if (!change->has_action)
+ return;
+
+ /* ---------- determine action paramater values -------------------------- */
+
+ int level_time_value =
+ (level.time > 0 ? TimeLeft :
+ TimePlayed);
+
+ int action_arg_element =
+ (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 :
+ EL_EMPTY);
+
+ 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 1
+ 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_CS_TARGET ? GET_CE_SCORE(target_element) :
+ action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
+ action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
+ action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_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);
+
+ 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);
+
+ 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 :
+ 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;
+
+ DrawGameValue_Time(TimeLeft);
+
+ 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;
+
+ DrawGameValue_Score(local_player->score);
+
+ break;
+ }
+
+ case CA_SET_LEVEL_GEMS:
+ {
+ local_player->gems_still_needed = action_arg_number_new;
+
+ DrawGameValue_Emeralds(local_player->gems_still_needed);
+
+ 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;
+ }
+
+ /* ---------- 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))
+ stored_player[i].LevelSolved = stored_player[i].GameOver = TRUE;
+
+ 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;
+
+#if 1
+ DrawGameDoorValues();
+#else
+ DrawGameValue_Keys(stored_player[i].key);
+#endif
+
+ redraw_mask |= REDRAW_DOOR_1;
+ }
+ }
+ }
+
+ break;
+ }
+
+ case CA_SET_PLAYER_SPEED:
+ {
+ 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);
+
+#if 1
+ SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
+#else
+ /* make sure that value is power of 2 */
+ move_stepsize = (1 << log_2(move_stepsize));
+
+ /* do no immediately change -- the player might just be moving */
+ stored_player[i].move_delay_value_next = TILEX / move_stepsize;
+
+ stored_player[i].cannot_move =
+ (action_arg == CA_ARG_SPEED_NOT_MOVING ? TRUE : FALSE);
+#endif
+ }
+ }
+
+ 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);
+
+ 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;
+ }
+
+ /* ---------- 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 0
+ printf("::: CE value == %d\n", CustomValue[x][y]);
+#endif
+
+ 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)
+ {
+#if 0
+ printf("::: CE_VALUE_GETS_ZERO\n");
+#endif
+
+ CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
+ CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
+
+#if 0
+ printf("::: RESULT: %d, %d\n", Feld[x][y], ChangePage[x][y]);
+#endif
+ }
+ }
+
+#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 0
+ printf("::: CE score == %d\n", ei->collect_score);
+#endif
+
+ 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;
+
+#if 0
+ printf("::: CE_SCORE_GETS_ZERO\n");
+#endif
+
+ CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
+ CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
+
+#if 0
+ printf("::: RESULT: %d, %d\n", Feld[x][y], ChangePage[x][y]);
+#endif
+
+#if 1
+ /*
+ 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
+ }
+ }
+
+#endif
+
+ 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 = get_element_from_group_element(element);
+ int previous_move_direction = MovDir[x][y];
+#if USE_NEW_CUSTOM_VALUE
+ int last_ce_value = CustomValue[x][y];
+#endif
+ 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
+
+ DrawLevelField(x, y);
+
+ if (GFX_CRUMBLED(new_element))
+ 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 (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y) &&
+ IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
+ {
+ Bang(x, y);
+
+ return;
+ }
+#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_PLAYER_1;
+ 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];
+
+#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)
+ {
+ 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 */
+ {
+ 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 (change->can_change)
+ {
+ if (ChangeElement(x, y, element, page))
+ {
+ if (change->post_change_function)
+ change->post_change_function(x, y);
+ }
+ }
+
+ if (change->has_action)
+ 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;
+
+ 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 = EL_PLAYER_1 + log_2(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 ((change->can_change && !change_done) || change->has_action)
+ {
+ int x, y;
+
+#if 1
+ SCAN_PLAYFIELD(x, y)
+#else
+ for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
+#endif
+ {
+ if (Feld[x][y] == element)
+ {
+ if (change->can_change && !change_done)
+ {
+ ChangeDelay[x][y] = 1;
+ ChangeEvent[x][y] = trigger_event;
+
+ HandleElementChange(x, y, p);
+ }
+#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
+ }
+ }
+
+ if (change->can_change)
+ {
+ change_done = TRUE;
+ change_done_any = TRUE;
+ }
+ }
+ }
+ }
+ }
+
+ 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
+
+ for (p = 0; p < element_info[element].num_change_pages; p++)
+ {
+ struct ElementChangeInfo *change = &element_info[element].change_page[p];
+
+ boolean check_trigger_element =
+ (trigger_event == CE_TOUCHING_X ||
+ trigger_event == CE_HITTING_X ||
+ trigger_event == CE_HIT_BY_X);
+
+ 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 = EL_PLAYER_1 + log_2(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
+ }
+ }
+
+ 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 +
+ SimpleRND(game.player_boring_delay_random);
+ player->frame_counter_sleeping =
+ FrameCounter +
+ game.player_sleeping_delay_fixed +
+ SimpleRND(game.player_sleeping_delay_random);
+
+#if 1
+ InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
+#else
+ InitPlayerGfxAnimation(player, ACTION_WAITING, player->MovDir);
+#endif
+ }
+
+ 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 1
+ 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;
+ }
+#endif
+
+ 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 +
+ 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->artwork_element, 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->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;
+
+ if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+ {
+ if (level.native_em_level->lev->home == 0) /* all players at home */
+ {
+ local_player->LevelSolved = TRUE;
+ 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;
+ }
+
+ 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)
+ {
+ 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]);
+ }
+ }
+ else if (level.time == 0 && !AllPlayersGone) /* level w/o time limit */
+ DrawGameValue_Time(TimePlayed);
+
+ level.native_em_level->lev->time =
+ (level.time == 0 ? TimePlayed : TimeLeft);
+ }
+
+ if (tape.recording || tape.playing)
+ DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
+ }
+}
+
+void AdvanceFrameAndPlayerCounters(int player_nr)
+{
+ int i;
+
+#if 0
+ Error(ERR_NETWORK_CLIENT, "advancing frame counter from %d to %d",
+ FrameCounter, FrameCounter + 1);
+#endif
+
+ /* 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
+
+ StopAnimation();
+
+ game_status = GAME_MODE_PLAYING;
+
+ 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;
+
+ if (game.restart_level)
+ StartGameActions(options.network, setup.autorecord, NEW_RANDOMIZE);
+
+ if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+ {
+ if (level.native_em_level->lev->home == 0) /* all players at home */
+ {
+ local_player->LevelSolved = TRUE;
+ 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;
+ }
+
+ if (local_player->LevelSolved)
+ 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 */
+
+#if 1
+ network_player_action_received = FALSE;
+#endif
+
+ recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
+
+ 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 (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+ {
+ GameActions_EM_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;
+
+#if 0
+ printf("::: %04d: %08x\n", FrameCounter, effective_action[0]);
+#endif
+
+ GameActions_EM(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 1
+ 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(sx, sy, MV_NONE, TRUE, setup.quick_switch);
+ DrawGameDoorValues();
+ }
+#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
+
+ /* overwrite programmed action with tape action */
+ if (stored_player[i].programmed_action)
+ actual_player_action = stored_player[i].programmed_action;
+
+#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
+ network_player_action_received = FALSE;
+#endif
+
+ 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;
+
+ 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))
+ {
+ ContinueMoving(x, y);
+
+ /* continue moving after pushing (this is actually a bug) */
+ if (!IS_MOVING(x, y))
+ {
+ Stop[x][y] = FALSE;
+ }
+ }
+ }
+ }
+
+#if 1
+ SCAN_PLAYFIELD(x, y)
+#else
+ for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
+#endif
+ {
+ ChangeCount[x][y] = 0;
+ ChangeEvent[x][y] = -1;
+
+ /* 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);
+ }
+
+#if USE_NEW_SNAP_DELAY
+ if (Feld[x][y] == EL_ELEMENT_SNAPPING)
+ {
+ MovDelay[x][y]--;
+ if (MovDelay[x][y] <= 0)
+ {
+ RemoveField(x, y);
+ DrawLevelField(x, y);
+
+ TestIfElementTouchesCustomElement(x, y); /* for empty space */
+ }
+ }
+#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]++;
+
+ /* reset finished pushing action (not done in ContinueMoving() to allow
+ continuous pushing animation for elements with zero push delay) */
+ if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
+ {
+ ResetGfxAnimation(x, y);
+ DrawLevelField(x, y);
+ }
+
+#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
+ }
+
+#if 1
+ SCAN_PLAYFIELD(x, y)
+#else
+ for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
+#endif
+ {
+ element = Feld[x][y];
+ graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
+
+#if 0
+ printf("::: %d,%d\n", x, y);
+
+ if (element == EL_ROCK)
+ printf("::: Yo man! Rocks can fall!\n");
+#endif
+
+#if 1
+ ResetGfxFrame(x, y, TRUE);
+#else
+ if (graphic_info[graphic].anim_global_sync)
+ GfxFrame[x][y] = FrameCounter;
+ else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
+ {
+ int old_gfx_frame = GfxFrame[x][y];
+
+ GfxFrame[x][y] = CustomValue[x][y];
+
+#if 1
+ if (GfxFrame[x][y] != old_gfx_frame)
+#endif
+ DrawLevelGraphicAnimation(x, y, graphic);
+ }
+ else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
+ {
+ int old_gfx_frame = GfxFrame[x][y];
+
+ GfxFrame[x][y] = element_info[element].collect_score;
+
+#if 1
+ if (GfxFrame[x][y] != old_gfx_frame)
+#endif
+ DrawLevelGraphicAnimation(x, y, graphic);
+ }
+ else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
+ {
+ int old_gfx_frame = GfxFrame[x][y];
+
+ GfxFrame[x][y] = ChangeDelay[x][y];
+
+#if 1
+ if (GfxFrame[x][y] != old_gfx_frame)
+#endif
+ DrawLevelGraphicAnimation(x, y, graphic);
+ }
+#endif
+
+ if (ANIM_MODE(graphic) == ANIM_RANDOM &&
+ IS_NEXT_FRAME(GfxFrame[x][y], graphic))
+ ResetRandomAnimationValue(x, y);
+
+ SetRandomAnimationValue(x, y);
+
+ PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
+
+ if (IS_INACTIVE(element))
+ {
+ if (IS_ANIMATED(graphic))
+ DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
+
+ continue;
+ }
+
+ /* this may take place after moving, so 'element' may have changed */
+ if (IS_CHANGING(x, y) &&
+ (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
+ {
+ int page = element_info[element].event_page_nr[CE_DELAY];
+#if 0
+ HandleElementChange(x, y, ChangePage[x][y] != -1 ? ChangePage[x][y] : page);
+#else
+
+#if 0
+ printf("::: ChangeDelay == %d\n", ChangeDelay[x][y]);
+#endif
+
+#if 0
+ if (element == EL_CUSTOM_255)
+ printf("::: ChangeDelay == %d\n", ChangeDelay[x][y]);
+#endif
+
+#if 1
+ HandleElementChange(x, y, page);
+#else
+ if (CAN_CHANGE(element))
+ HandleElementChange(x, y, page);
+
+ if (HAS_ACTION(element))
+ ExecuteCustomElementAction(x, y, element, page);
+#endif
+
+#endif
+
+ element = Feld[x][y];
+ graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
+ }
+
+ if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
+ {
+ StartMoving(x, y);
+
+ element = Feld[x][y];
+ graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
+
+ if (IS_ANIMATED(graphic) &&
+ !IS_MOVING(x, y) &&
+ !Stop[x][y])
+ DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
+
+ 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);
+ 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 ||
+ element == EL_BD_EXPANDABLE_WALL)
+ MauerAbleger(x, y);
+ else if (element == EL_FLAMES)
+ CheckForDragon(x, y);
+ else if (element == EL_EXPLOSION)
+ ; /* drawing of correct explosion animation is handled separately */
+ else if (element == EL_ELEMENT_SNAPPING ||
+ element == EL_DIAGONAL_SHRINKING ||
+ element == EL_DIAGONAL_GROWING)
+ {
+#if 1
+ graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]);
+
+ DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
+#endif
+ }
+ else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
+ DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
+
+#if 0
+ if (element == EL_CUSTOM_255 ||
+ element == EL_CUSTOM_256)
+ DrawLevelGraphicAnimation(x, y, graphic);
+#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 (!(FrameCounter % 8))
+ {
+ static unsigned long random = 1684108901;
+
+ for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
+ {
+ x = RND(lev_fieldx);
+ y = RND(lev_fieldy);
+ element = Feld[x][y];
+
+ 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;
+ }
+
+ random = random * 129 + 1;
+ }
+ }
+#endif
+
+#if 0
+ if (game.explosions_delayed)
+#endif
+ {
+ game.explosions_delayed = FALSE;
+
+#if 1
+ SCAN_PLAYFIELD(x, y)
+#else
+ for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
+#endif
+ {
+ 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)
+ {
+#if 1
+ SCAN_PLAYFIELD(x, y)
+#else
+ for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
+#endif
+ {
+ 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();
+ }
+
+ if (game.lenses_time_left > 0)
+ {
+ game.lenses_time_left--;
+
+ if (game.lenses_time_left == 0)
+ RedrawAllInvisibleElementsForLenses();
+ }
+
+ if (game.magnify_time_left > 0)
+ {
+ game.magnify_time_left--;
+
+ if (game.magnify_time_left == 0)
+ RedrawAllInvisibleElementsForMagnifier();
+ }
+
+ 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);
+ }
+ }
+
+ CheckLevelTime();
+
+ 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;
+ }
+
+ AdvanceFrameAndPlayerCounters(-1); /* advance counters for all players */
+
+ if (local_player->show_envelope != 0 && local_player->MovPos == 0)
+ {
+ ShowEnvelope(local_player->show_envelope - EL_ENVELOPE_1);
+
+ local_player->show_envelope = 0;
+ }
+
+ /* use random number generator in every frame to make it less predictable */
+ if (game.engine_version >= VERSION_IDENT(3,1,1,0))
+ RND(1);
+}
+
+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;
+}
+
+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) ||
+ (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;
+
+ 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)));
+}
+
+static void CheckGravityMovement(struct PlayerInfo *player)
+{
+#if USE_PLAYER_GRAVITY
+ if (player->gravity && !player->programmed_action)
+#else
+ if (game.gravity && !player->programmed_action)
+#endif
+ {
+ int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
+ int move_dir_vertical = player->effective_action & MV_VERTICAL;
+ boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
+ 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)));
+ boolean player_can_fall_down = canFallDown(player);
+
+ if (player_can_fall_down &&
+ !player_is_moving_to_valid_field)
+ player->programmed_action = MV_DOWN;
+ }
+}
+
+static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
+{
+ return CheckGravityMovement(player);
+
+#if USE_PLAYER_GRAVITY
+ if (player->gravity && !player->programmed_action)
+#else
+ if (game.gravity && !player->programmed_action)
+#endif
+ {
+ 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)
+{
+ int jx = player->jx, jy = player->jy;
+ int new_jx = jx + dx, new_jy = jy + dy;
+#if !USE_FIXED_DONT_RUN_INTO
+ int element;
+#endif
+ int can_move;
+ boolean player_can_move = !player->cannot_move;
+
+ if (!player->active || (!dx && !dy))
+ return MP_NO_ACTION;
+
+ player->MovDir = (dx < 0 ? MV_LEFT :
+ dx > 0 ? MV_RIGHT :
+ dy < 0 ? MV_UP :
+ dy > 0 ? MV_DOWN : MV_NONE);
+
+ if (!IN_LEV_FIELD(new_jx, new_jy))
+ return MP_NO_ACTION;
+
+ if (!player_can_move)
+ {
+#if 1
+ if (player->MovPos == 0)
+ {
+ player->is_moving = FALSE;
+ player->is_digging = FALSE;
+ player->is_collecting = FALSE;
+ player->is_snapping = FALSE;
+ player->is_pushing = FALSE;
+ }
+#else
+ DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
+ SnapField(player, 0, 0);
+#endif
+
+#if 0
+ return MP_NO_ACTION;
+#endif
+ }
+
+#if 1
+ if (!options.network && game.centered_player_nr == -1 &&
+ !AllPlayersInSight(player, new_jx, new_jy))
+ return MP_NO_ACTION;
+#else
+ if (!options.network && !AllPlayersInSight(player, new_jx, new_jy))
+ return MP_NO_ACTION;
+#endif
+
+#if !USE_FIXED_DONT_RUN_INTO
+ element = MovingOrBlocked2ElementIfNotLeaving(new_jx, new_jy);
+
+ /* (moved to DigField()) */
+ if (player_can_move && 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);
+ BuryPlayer(player);
+ }
+ else
+ TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
+
+ return MP_MOVING;
+ }
+#endif
+
+ can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
+#if 0
+#if USE_FIXED_DONT_RUN_INTO
+ if (can_move == MP_DONT_RUN_INTO)
+ return MP_MOVING;
+#endif
+#endif
+ if (can_move != MP_MOVING)
+ return can_move;
+
+#if USE_FIXED_DONT_RUN_INTO
+#endif
+
+ /* check if DigField() has caused relocation of the player */
+ if (player->jx != jx || player->jy != jy)
+ return MP_NO_ACTION; /* <-- !!! CHECK THIS [-> MP_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;