+ 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 void CheckSaveEngineSnapshot(struct PlayerInfo *player)
+{
+ static boolean player_was_moving = FALSE;
+ static boolean player_was_snapping = FALSE;
+ static boolean player_was_dropping = FALSE;
+
+ if ((!player->is_moving && player_was_moving) ||
+ (player->MovPos == 0 && player_was_moving) ||
+ (player->is_snapping && !player_was_snapping) ||
+ (player->is_dropping && !player_was_dropping))
+ {
+ if (!SaveEngineSnapshotToList())
+ return;
+
+ player_was_moving = FALSE;
+ player_was_snapping = TRUE;
+ player_was_dropping = TRUE;
+ }
+ else
+ {
+ if (player->is_moving)
+ player_was_moving = TRUE;
+
+ if (!player->is_snapping)
+ player_was_snapping = FALSE;
+
+ if (!player->is_dropping)
+ player_was_dropping = FALSE;
+ }
+}
+
+static void CheckSingleStepMode(struct PlayerInfo *player)
+{
+ if (tape.single_step && tape.recording && !tape.pausing)
+ {
+ /* as it is called "single step mode", just return to pause mode when the
+ player stopped moving after one tile (or never starts moving at all) */
+ if (!player->is_moving && !player->is_pushing)
+ {
+ TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
+ SnapField(player, 0, 0); /* stop snapping */
+ }
+ }
+
+ CheckSaveEngineSnapshot(player);
+}
+
+static byte PlayerActions(struct PlayerInfo *player, byte player_action)
+{
+ 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)
+ SnapField(player, dx, dy);
+ else
+ {
+ if (button2)
+ DropElement(player);
+
+ MovePlayer(player, dx, dy);
+ }
+
+ CheckSingleStepMode(player);
+
+ 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;
+
+ CheckSingleStepMode(player);
+
+ return 0;
+ }
+}
+
+static void CheckLevelTime()
+{
+ int i;
+
+ /* !!! SAME CODE AS IN "GameActions()" -- FIX THIS !!! */
+ if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+ {
+ if (level.native_em_level->lev->home == 0) /* all players at home */
+ {
+ PlayerWins(local_player);
+
+ AllPlayersGone = TRUE;
+
+ level.native_em_level->lev->home = -1;
+ }
+
+ if (level.native_em_level->ply[0]->alive == 0 &&
+ level.native_em_level->ply[1]->alive == 0 &&
+ level.native_em_level->ply[2]->alive == 0 &&
+ level.native_em_level->ply[3]->alive == 0) /* all dead */
+ AllPlayersGone = TRUE;
+ }
+ else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
+ {
+ if (game_sp.LevelSolved &&
+ !game_sp.GameOver) /* game won */
+ {
+ PlayerWins(local_player);
+
+ game_sp.GameOver = TRUE;
+
+ AllPlayersGone = TRUE;
+ }
+
+ if (game_sp.GameOver) /* game lost */
+ AllPlayersGone = TRUE;
+ }
+
+ if (TimeFrames >= FRAMES_PER_SECOND)
+ {
+ TimeFrames = 0;
+ TapeTime++;
+
+ for (i = 0; i < MAX_PLAYERS; i++)
+ {
+ struct PlayerInfo *player = &stored_player[i];
+
+ if (SHIELD_ON(player))
+ {
+ player->shield_normal_time_left--;
+
+ if (player->shield_deadly_time_left > 0)
+ player->shield_deadly_time_left--;
+ }
+ }
+
+ if (!local_player->LevelSolved && !level.use_step_counter)
+ {
+ TimePlayed++;
+
+ if (TimeLeft > 0)
+ {
+ TimeLeft--;
+
+ if (TimeLeft <= 10 && setup.time_limit)
+ PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
+
+ /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
+ is reset from other values in UpdateGameDoorValues() -- FIX THIS */
+
+ game_panel_controls[GAME_PANEL_TIME].value = 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 (game.no_time_limit && !AllPlayersGone) /* level w/o time limit */
+ {
+ game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
+ }
+
+ level.native_em_level->lev->time =
+ (game.no_time_limit ? TimePlayed : TimeLeft);
+ }
+
+ if (tape.recording || tape.playing)
+ DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
+ }
+
+ if (tape.recording || tape.playing)
+ DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
+
+ UpdateAndDisplayGameControlValues();
+}
+
+void AdvanceFrameAndPlayerCounters(int player_nr)
+{
+ int i;
+
+ /* advance frame counters (global frame counter and time frame counter) */
+ FrameCounter++;
+ TimeFrames++;
+
+ /* advance player counters (counters for move delay, move animation etc.) */
+ for (i = 0; i < MAX_PLAYERS; i++)
+ {
+ boolean advance_player_counters = (player_nr == -1 || player_nr == i);
+ int move_delay_value = stored_player[i].move_delay_value;
+ int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
+
+ if (!advance_player_counters) /* not all players may be affected */
+ continue;
+
+ if (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;
+ }
+
+ 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,
+ int random_seed)
+{
+ unsigned int new_random_seed = InitRND(random_seed);
+
+ if (record_tape)
+ TapeStartRecording(new_random_seed);
+
+#if defined(NETWORK_AVALIABLE)
+ if (init_network_game)
+ {
+ SendToServer_StartPlaying();
+
+ return;
+ }
+#endif
+
+ InitGame();
+}
+
+void GameActions()
+{
+ static unsigned int game_frame_delay = 0;
+ unsigned int game_frame_delay_value;
+ byte *recorded_player_action;
+ byte summarized_player_action = 0;
+ byte tape_action[MAX_PLAYERS];
+ int i;
+
+ for (i = 0; i < MAX_PLAYERS; i++)
+ {
+ struct PlayerInfo *player = &stored_player[i];
+
+ // allow engine snapshot if movement attempt was stopped
+ if ((game.snapshot.last_action[i] & KEY_MOTION) != 0 &&
+ (player->action & KEY_MOTION) == 0)
+ game.snapshot.changed_action = TRUE;
+
+ // allow engine snapshot in case of snapping/dropping attempt
+ if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
+ (player->action & KEY_BUTTON) != 0)
+ game.snapshot.changed_action = TRUE;
+
+ game.snapshot.last_action[i] = player->action;
+ }
+
+ /* detect endless loops, caused by custom element programming */
+ if (recursion_loop_detected && recursion_loop_depth == 0)
+ {
+ char *message = getStringCat3("Internal Error! Element ",
+ EL_NAME(recursion_loop_element),
+ " caused endless loop! Quit the game?");
+
+ Error(ERR_WARN, "element '%s' caused endless loop in game engine",
+ EL_NAME(recursion_loop_element));
+
+ RequestQuitGameExt(FALSE, level_editor_test_game, message);
+
+ recursion_loop_detected = FALSE; /* if game should be continued */
+
+ free(message);
+
+ return;
+ }
+
+ if (game.restart_level)
+ StartGameActions(options.network, setup.autorecord, level.random_seed);
+
+ /* !!! SAME CODE AS IN "CheckLevelTime()" -- FIX THIS !!! */
+ if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+ {
+ if (level.native_em_level->lev->home == 0) /* all players at home */
+ {
+ PlayerWins(local_player);
+
+ AllPlayersGone = TRUE;
+
+ level.native_em_level->lev->home = -1;
+ }
+
+ if (level.native_em_level->ply[0]->alive == 0 &&
+ level.native_em_level->ply[1]->alive == 0 &&
+ level.native_em_level->ply[2]->alive == 0 &&
+ level.native_em_level->ply[3]->alive == 0) /* all dead */
+ AllPlayersGone = TRUE;
+ }
+ else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
+ {
+ if (game_sp.LevelSolved &&
+ !game_sp.GameOver) /* game won */
+ {
+ PlayerWins(local_player);
+
+ game_sp.GameOver = TRUE;
+
+ AllPlayersGone = TRUE;
+ }
+
+ if (game_sp.GameOver) /* game lost */
+ AllPlayersGone = TRUE;
+ }
+
+ if (local_player->LevelSolved && !local_player->LevelSolved_GameEnd)
+ GameWon();
+
+ if (AllPlayersGone && !TAPE_IS_STOPPED(tape))
+ TapeStop();
+
+ if (game_status != GAME_MODE_PLAYING) /* status might have changed */
+ return;
+
+ game_frame_delay_value =
+ (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
+
+ if (tape.playing && tape.warp_forward && !tape.pausing)
+ game_frame_delay_value = 0;
+
+ /* ---------- main game synchronization point ---------- */
+
+ WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
+
+ if (network_playing && !network_player_action_received)
+ {
+ /* try to get network player actions in time */
+
+#if defined(NETWORK_AVALIABLE)
+ /* last chance to get network player actions without main loop delay */
+ HandleNetworking();
+#endif
+
+ /* game was quit by network peer */
+ if (game_status != GAME_MODE_PLAYING)
+ return;
+
+ if (!network_player_action_received)
+ return; /* failed to get network player actions in time */
+
+ /* do not yet reset "network_player_action_received" (for tape.pausing) */
+ }
+
+ if (tape.pausing)
+ return;
+
+ /* at this point we know that we really continue executing the game */
+
+ network_player_action_received = FALSE;
+
+ /* when playing tape, read previously recorded player input from tape data */
+ recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
+
+ /* TapePlayAction() may return NULL when toggling to "pause before death" */
+ if (tape.pausing)
+ return;
+
+ 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 && (game.team_mode || tape.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 && !game.team_mode)
+ local_player->effective_action = summarized_player_action;
+
+ if (tape.recording &&
+ 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 may happen in the RND game engine if a player was not present on
+ the playfield on level start, but appeared later from a custom element */
+ if (setup.team_mode &&
+ tape.recording &&
+ tape_action[i] &&
+ !tape.player_participates[i])
+ tape.player_participates[i] = TRUE;
+ }
+
+ /* only record actions from input devices, but not programmed actions */
+ if (tape.recording)
+ TapeRecordAction(tape_action);
+
+#if USE_NEW_PLAYER_ASSIGNMENTS
+ // !!! also map player actions in single player mode !!!
+ // if (game.team_mode)
+ {
+ byte mapped_action[MAX_PLAYERS];
+
+#if DEBUG_PLAYER_ACTIONS
+ printf(":::");
+ for (i = 0; i < MAX_PLAYERS; i++)
+ printf(" %d, ", stored_player[i].effective_action);
+#endif
+
+ for (i = 0; i < MAX_PLAYERS; i++)
+ mapped_action[i] = stored_player[map_player_action[i]].effective_action;
+
+ for (i = 0; i < MAX_PLAYERS; i++)
+ stored_player[i].effective_action = mapped_action[i];
+
+#if DEBUG_PLAYER_ACTIONS
+ printf(" =>");
+ for (i = 0; i < MAX_PLAYERS; i++)
+ printf(" %d, ", stored_player[i].effective_action);
+ printf("\n");
+#endif
+ }
+#if DEBUG_PLAYER_ACTIONS
+ else
+ {
+ printf(":::");
+ for (i = 0; i < MAX_PLAYERS; i++)
+ printf(" %d, ", stored_player[i].effective_action);
+ printf("\n");
+ }
+#endif
+#endif
+
+ if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+ {
+ GameActions_EM_Main();
+ }
+ else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
+ {
+ GameActions_SP_Main();
+ }
+ else
+ {
+ GameActions_RND();
+ }
+}
+
+void GameActions_EM_Main()
+{
+ byte effective_action[MAX_PLAYERS];
+ boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
+ int i;
+
+ for (i = 0; i < MAX_PLAYERS; i++)
+ effective_action[i] = stored_player[i].effective_action;
+
+ GameActions_EM(effective_action, warp_mode);
+
+ CheckLevelTime();
+
+ AdvanceFrameAndPlayerCounters(-1); /* advance counters for all players */
+}
+
+void GameActions_SP_Main()
+{
+ byte effective_action[MAX_PLAYERS];
+ boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
+ int i;
+
+ for (i = 0; i < MAX_PLAYERS; i++)
+ effective_action[i] = stored_player[i].effective_action;
+
+ GameActions_SP(effective_action, warp_mode);
+
+ CheckLevelTime();
+
+ AdvanceFrameAndPlayerCounters(-1); /* advance counters for all players */
+}
+
+void GameActions_RND()
+{
+ int magic_wall_x = 0, magic_wall_y = 0;
+ int i, x, y, element, graphic;
+
+ InitPlayfieldScanModeVars();
+
+ if (game.engine_version >= VERSION_IDENT(3,2,0,7))
+ {
+ SCAN_PLAYFIELD(x, y)
+ {
+ ChangeCount[x][y] = 0;
+ ChangeEvent[x][y] = -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(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch);
+ DrawGameDoorValues();
+ }
+
+ for (i = 0; i < MAX_PLAYERS; i++)
+ {
+ int actual_player_action = stored_player[i].effective_action;
+
+#if 1
+ /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
+ - rnd_equinox_tetrachloride 048
+ - rnd_equinox_tetrachloride_ii 096
+ - rnd_emanuel_schmieg 002
+ - doctor_sloan_ww 001, 020
+ */
+ if (stored_player[i].MovPos == 0)
+ CheckGravityMovement(&stored_player[i]);
+#endif
+
+ /* overwrite programmed action with tape action */
+ if (stored_player[i].programmed_action)
+ actual_player_action = stored_player[i].programmed_action;
+
+ PlayerActions(&stored_player[i], actual_player_action);
+
+ ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
+ }
+
+ ScrollScreen(NULL, SCROLL_GO_ON);
+
+ /* for backwards compatibility, the following code emulates a fixed bug that
+ occured when pushing elements (causing elements that just made their last
+ pushing step to already (if possible) make their first falling step in the
+ same game frame, which is bad); this code is also needed to use the famous
+ "spring push bug" which is used in older levels and might be wanted to be
+ used also in newer levels, but in this case the buggy pushing code is only
+ affecting the "spring" element and no other elements */
+
+ if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
+ {
+ for (i = 0; i < MAX_PLAYERS; i++)
+ {
+ struct PlayerInfo *player = &stored_player[i];
+ int x = player->jx;
+ int y = player->jy;
+
+ 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;
+ }
+ }
+ }
+
+ SCAN_PLAYFIELD(x, y)
+ {
+ 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 (Feld[x][y] == EL_ELEMENT_SNAPPING)
+ {
+ MovDelay[x][y]--;
+ if (MovDelay[x][y] <= 0)
+ {
+ RemoveField(x, y);
+ TEST_DrawLevelField(x, y);
+
+ TestIfElementTouchesCustomElement(x, y); /* for empty space */
+ }
+ }
+
+#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]--;
+ if (CheckImpact[x][y] > 0)
+ CheckImpact[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);
+ TEST_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
+ }
+
+ SCAN_PLAYFIELD(x, y)
+ {
+ element = Feld[x][y];
+ graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
+
+ ResetGfxFrame(x, y, TRUE);
+
+ 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];
+
+ HandleElementChange(x, y, page);
+
+ 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)
+ TEST_DrawTwinkleOnField(x, y);
+ }