+ static byte stored_player_action[MAX_PLAYERS];
+ static int num_stored_actions = 0;
+ static boolean save_tape_entry = FALSE;
+ boolean moved = FALSE, snapped = FALSE, bombed = FALSE;
+ int jx = player->jx, jy = player->jy;
+ int left = player_action & JOY_LEFT;
+ int right = player_action & JOY_RIGHT;
+ int up = player_action & JOY_UP;
+ int down = player_action & JOY_DOWN;
+ int button1 = player_action & JOY_BUTTON_1;
+ int button2 = player_action & JOY_BUTTON_2;
+ int dx = (left ? -1 : right ? 1 : 0);
+ int dy = (up ? -1 : down ? 1 : 0);
+
+ stored_player_action[player->index_nr] = 0;
+ num_stored_actions++;
+
+ if (!player->active || tape.pausing)
+ return;
+
+ if (player_action)
+ {
+ save_tape_entry = TRUE;
+ player->frame_reset_delay = 0;
+
+ if (button1)
+ snapped = SnapField(player, dx, dy);
+ else
+ {
+ if (button2)
+ bombed = PlaceBomb(player);
+ moved = MoveFigure(player, dx, dy);
+ }
+
+ if (tape.recording && (moved || snapped || bombed))
+ {
+ if (bombed && !moved)
+ player_action &= JOY_BUTTON;
+
+ stored_player_action[player->index_nr] = player_action;
+ }
+ else if (tape.playing && snapped)
+ SnapField(player, 0, 0); /* stop snapping */
+ }
+ else
+ {
+ /* no actions for this player (no input at player's configured device) */
+
+ DigField(player, 0, 0, 0, 0, DF_NO_PUSH);
+ SnapField(player, 0, 0);
+ CheckGravityMovement(player);
+
+ if (++player->frame_reset_delay > player->move_delay_value)
+ player->Frame = 0;
+ }
+
+ if (tape.recording && num_stored_actions >= MAX_PLAYERS && save_tape_entry)
+ {
+ TapeRecordAction(stored_player_action);
+ num_stored_actions = 0;
+ save_tape_entry = FALSE;
+ }
+
+ if (tape.playing && !tape.pausing && !player_action &&
+ tape.counter < tape.length)
+ {
+ int next_joy =
+ tape.pos[tape.counter].action[player->index_nr] & (JOY_LEFT|JOY_RIGHT);
+
+ if ((next_joy == JOY_LEFT || next_joy == JOY_RIGHT) &&
+ (player->MovDir != JOY_UP && player->MovDir != JOY_DOWN))
+ {
+ int dx = (next_joy == JOY_LEFT ? -1 : +1);
+
+ if (IN_LEV_FIELD(jx+dx, jy) && IS_PUSHABLE(Feld[jx+dx][jy]))
+ {
+ int el = Feld[jx+dx][jy];
+ int push_delay = (IS_SB_ELEMENT(el) || el == EL_SONDE ? 2 : 10);
+
+ if (tape.delay_played + push_delay >= tape.pos[tape.counter].delay)
+ {
+ player->MovDir = next_joy;
+ player->Frame = FrameCounter % 4;
+ player->Pushing = TRUE;
+ }
+ }
+ }
+ }
+}
+
+void GameActions()
+{
+ static unsigned long action_delay = 0;
+ unsigned long action_delay_value;
+ int sieb_x = 0, sieb_y = 0;
+ int i, x, y, element;
+ byte *recorded_player_action;
+ byte summarized_player_action = 0;
+
+ if (game_status != PLAYING)
+ return;
+
+ action_delay_value =
+ (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
+
+ /* ---------- main game synchronization point ---------- */
+
+ WaitUntilDelayReached(&action_delay, action_delay_value);
+
+ if (network_playing && !network_player_action_received)
+ {
+ /*
+#ifdef DEBUG
+ printf("DEBUG: try to get network player actions in time\n");
+#endif
+ */
+
+#ifndef MSDOS
+ /* last chance to get network player actions without main loop delay */
+ HandleNetworking();
+#endif
+
+ if (game_status != PLAYING)
+ return;
+
+ if (!network_player_action_received)
+ {
+ /*
+#ifdef DEBUG
+ printf("DEBUG: failed to get network player actions in time\n");
+#endif
+ */
+ return;
+ }
+ }
+
+ if (tape.pausing)
+ return;
+
+ if (tape.playing)
+ TapePlayDelay();
+ else if (tape.recording)
+ TapeRecordDelay();
+
+ recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
+
+ 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;
+ }
+
+#ifndef MSDOS
+ if (network_playing)
+ SendToServer_MovePlayer(summarized_player_action);
+#endif
+
+ if (!options.network && !setup.team_mode)
+ local_player->effective_action = summarized_player_action;
+
+ for (i=0; i<MAX_PLAYERS; i++)
+ {
+ int actual_player_action = stored_player[i].effective_action;
+
+ if (stored_player[i].programmed_action)
+ actual_player_action = stored_player[i].programmed_action;
+
+ if (recorded_player_action)
+ actual_player_action = recorded_player_action[i];
+
+ PlayerActions(&stored_player[i], actual_player_action);
+ ScrollFigure(&stored_player[i], SCROLL_GO_ON);
+ }
+
+ network_player_action_received = FALSE;
+
+ ScrollScreen(NULL, SCROLL_GO_ON);
+
+
+
+#ifdef DEBUG
+#if 0
+ if (TimeFrames == 0 && local_player->active)
+ {
+ extern unsigned int last_RND();
+
+ printf("DEBUG: %03d last RND was %d \t [state checksum is %d]\n",
+ TimePlayed, last_RND(), getStateCheckSum(TimePlayed));
+ }
+#endif
+#endif
+
+#ifdef DEBUG
+#if 0
+ if (GameFrameDelay >= 500)
+ printf("FrameCounter == %d\n", FrameCounter);
+#endif
+#endif
+
+
+
+ FrameCounter++;
+ TimeFrames++;
+
+ for (y=0; y<lev_fieldy; y++) for (x=0; x<lev_fieldx; x++)
+ {
+ Stop[x][y] = FALSE;
+ if (JustStopped[x][y] > 0)
+ JustStopped[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
+ }
+
+ for (y=0; y<lev_fieldy; y++) for (x=0; x<lev_fieldx; x++)
+ {
+ element = Feld[x][y];
+
+ if (IS_INACTIVE(element))
+ continue;
+
+ if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
+ {
+ StartMoving(x, y);
+
+ if (IS_GEM(element))
+ EdelsteinFunkeln(x, y);
+ }
+ else if (IS_MOVING(x, y))
+ ContinueMoving(x, y);
+ else if (IS_ACTIVE_BOMB(element))
+ CheckDynamite(x, y);
+ else if (element == EL_EXPLODING)
+ Explode(x, y, Frame[x][y], EX_NORMAL);
+ else if (element == EL_AMOEBING)
+ AmoebeWaechst(x, y);
+ else if (IS_AMOEBALIVE(element))
+ AmoebeAbleger(x, y);
+ else if (element == EL_LIFE || element == EL_LIFE_ASYNC)
+ Life(x, y);
+ else if (element == EL_ABLENK_EIN)
+ Ablenk(x, y);
+ else if (element == EL_SALZSAEURE)
+ Blubber(x, y);
+ else if (element == EL_BLURB_LEFT || element == EL_BLURB_RIGHT)
+ Blurb(x, y);
+ else if (element == EL_CRACKINGNUT)
+ NussKnacken(x, y);
+ else if (element == EL_AUSGANG_ZU)
+ AusgangstuerPruefen(x, y);
+ else if (element == EL_AUSGANG_ACT)
+ AusgangstuerOeffnen(x, y);
+ else if (element == EL_AUSGANG_AUF)
+ AusgangstuerBlinken(x, y);
+ else if (element == EL_MAUERND)
+ MauerWaechst(x, y);
+ else if (element == EL_MAUER_LEBT ||
+ element == EL_MAUER_X ||
+ element == EL_MAUER_Y ||
+ element == EL_MAUER_XY)
+ MauerAbleger(x, y);
+ else if (element == EL_BURNING)
+ CheckForDragon(x, y);
+ else if (element == EL_SP_BUG || element == EL_SP_BUG_ACTIVE)
+ CheckBuggyBase(x, y);
+ else if (element == EL_SP_TERMINAL)
+ DrawGraphicAnimation(x, y, GFX2_SP_TERMINAL, 7, 12, ANIM_NORMAL);
+ else if (element == EL_SP_TERMINAL_ACTIVE)
+ DrawGraphicAnimation(x, y, GFX2_SP_TERMINAL_ACTIVE, 7, 4, ANIM_NORMAL);
+ else if (IS_BELT(element))
+ DrawBeltAnimation(x, y, element);
+ else if (element == EL_SWITCHGATE_OPENING)
+ OpenSwitchgate(x, y);
+ else if (element == EL_SWITCHGATE_CLOSING)
+ CloseSwitchgate(x, y);
+
+ if (game.magic_wall_active)
+ {
+ boolean sieb = FALSE;
+ int jx = local_player->jx, jy = local_player->jy;
+
+ if (element == EL_SIEB_LEER || element == EL_SIEB_VOLL ||
+ Store[x][y] == EL_SIEB_LEER)
+ {
+ SiebAktivieren(x, y, 1);
+ sieb = TRUE;
+ }
+ else if (element == EL_SIEB2_LEER || element == EL_SIEB2_VOLL ||
+ Store[x][y] == EL_SIEB2_LEER)
+ {
+ SiebAktivieren(x, y, 2);
+ sieb = TRUE;
+ }
+
+ /* play the element sound at the position nearest to the player */
+ if (sieb && ABS(x-jx)+ABS(y-jy) < ABS(sieb_x-jx)+ABS(sieb_y-jy))
+ {
+ sieb_x = x;
+ sieb_y = y;
+ }
+ }
+ }
+
+ if (game.magic_wall_active)
+ {
+ if (!(game.magic_wall_time_left % 4))
+ PlaySoundLevel(sieb_x, sieb_y, SND_MIEP);
+
+ if (game.magic_wall_time_left > 0)
+ {
+ game.magic_wall_time_left--;
+ if (!game.magic_wall_time_left)
+ {
+ for (y=0; y<lev_fieldy; y++) for (x=0; x<lev_fieldx; x++)
+ {
+ element = Feld[x][y];
+
+ if (element == EL_SIEB_LEER || element == EL_SIEB_VOLL)
+ {
+ Feld[x][y] = EL_SIEB_TOT;
+ DrawLevelField(x, y);
+ }
+ else if (element == EL_SIEB2_LEER || element == EL_SIEB2_VOLL)
+ {
+ Feld[x][y] = EL_SIEB2_TOT;
+ DrawLevelField(x, y);
+ }
+ }
+
+ game.magic_wall_active = FALSE;
+ }
+ }
+ }
+
+ if (game.light_time_left > 0)
+ {
+ game.light_time_left--;
+
+ if (game.light_time_left == 0)
+ {
+ for (y=0; y<lev_fieldy; y++) for (x=0; x<lev_fieldx; x++)
+ {
+ element = Feld[x][y];
+
+ if (element == EL_LIGHT_SWITCH_ON)
+ {
+ Feld[x][y] = EL_LIGHT_SWITCH_OFF;
+ DrawLevelField(x, y);
+ }
+ else if (element == EL_INVISIBLE_STEEL ||
+ element == EL_UNSICHTBAR ||
+ element == EL_SAND_INVISIBLE)
+ DrawLevelField(x, y);
+ }
+ }
+ }
+
+ if (TimeFrames >= (1000 / GameFrameDelay) && !tape.pausing)
+ {
+ TimeFrames = 0;
+ TimePlayed++;
+
+ if (tape.recording || tape.playing)
+ DrawVideoDisplay(VIDEO_STATE_TIME_ON, TimePlayed);
+
+ if (TimeLeft > 0)
+ {
+ TimeLeft--;
+
+ if (TimeLeft <= 10)
+ PlaySoundStereo(SND_GONG, PSND_MAX_RIGHT);
+
+ DrawText(DX_TIME, DY_TIME, int2str(TimeLeft, 3), FS_SMALL, FC_YELLOW);
+
+ if (!TimeLeft)
+ for (i=0; i<MAX_PLAYERS; i++)
+ KillHero(&stored_player[i]);
+ }
+ else if (level.time == 0) /* level without time limit */
+ DrawText(DX_TIME, DY_TIME, int2str(TimePlayed, 3), FS_SMALL, FC_YELLOW);
+ }
+
+ DrawAllPlayers();
+}
+
+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;
+
+ XCopyArea(display, drawto_field, drawto_field, gc,
+ 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 void CheckGravityMovement(struct PlayerInfo *player)
+{
+ if (level.gravity && !player->programmed_action)
+ {
+ int move_dir_vertical = player->action & (MV_UP | MV_DOWN);
+ int move_dir_horizontal = player->action & (MV_LEFT | MV_RIGHT);
+ int move_dir =
+ (player->last_move_dir & (MV_LEFT | MV_RIGHT) ?
+ (move_dir_vertical ? move_dir_vertical : move_dir_horizontal) :
+ (move_dir_horizontal ? move_dir_horizontal : move_dir_vertical));
+ int jx = player->jx, jy = player->jy;
+ int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
+ int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
+ int new_jx = jx + dx, new_jy = jy + dy;
+ boolean field_under_player_is_free =
+ (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
+ boolean player_is_moving_to_valid_field =
+ (IN_LEV_FIELD(new_jx, new_jy) &&
+ (Feld[new_jx][new_jy] == EL_SP_BASE ||
+ Feld[new_jx][new_jy] == EL_ERDREICH));
+
+ if (field_under_player_is_free && !player_is_moving_to_valid_field)
+ player->programmed_action = MV_DOWN;
+ }
+}
+
+boolean MoveFigureOneStep(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;