+void InitGame()
+{
+ boolean emulate_bd = TRUE; /* unless non-BOULDERDASH elements found */
+ boolean emulate_sb = TRUE; /* unless non-SOKOBAN elements found */
+ boolean emulate_sp = TRUE; /* unless non-SUPAPLEX elements found */
+ int i, j, x, y;
+
+ InitGameEngine();
+
+#if 0
+#if DEBUG
+#if USE_NEW_AMOEBA_CODE
+ printf("Using new amoeba code.\n");
+#else
+ printf("Using old amoeba code.\n");
+#endif
+#endif
+#endif
+
+ /* don't play tapes over network */
+ network_playing = (options.network && !tape.playing);
+
+ for (i=0; i<MAX_PLAYERS; i++)
+ {
+ struct PlayerInfo *player = &stored_player[i];
+
+ player->index_nr = i;
+ player->element_nr = EL_PLAYER_1 + i;
+
+ player->present = FALSE;
+ player->active = FALSE;
+
+ player->action = 0;
+ player->effective_action = 0;
+ player->programmed_action = 0;
+
+ player->score = 0;
+ player->gems_still_needed = level.gems_needed;
+ player->sokobanfields_still_needed = 0;
+ player->lights_still_needed = 0;
+ player->friends_still_needed = 0;
+
+ for (j=0; j<4; j++)
+ player->key[j] = FALSE;
+
+ player->dynabomb_count = 0;
+ player->dynabomb_size = 1;
+ player->dynabombs_left = 0;
+ player->dynabomb_xl = FALSE;
+
+ player->MovDir = MV_NO_MOVING;
+ player->MovPos = 0;
+ player->Pushing = FALSE;
+ player->Switching = FALSE;
+ player->GfxPos = 0;
+ player->GfxDir = MV_NO_MOVING;
+ player->GfxAction = ACTION_DEFAULT;
+ player->Frame = 0;
+ player->StepFrame = 0;
+
+ player->use_murphy_graphic = FALSE;
+ player->use_disk_red_graphic = FALSE;
+
+ player->actual_frame_counter = 0;
+
+ player->last_move_dir = MV_NO_MOVING;
+
+ player->is_moving = FALSE;
+ player->is_waiting = FALSE;
+ player->is_digging = FALSE;
+ player->is_collecting = FALSE;
+
+ player->move_delay = game.initial_move_delay;
+ player->move_delay_value = game.initial_move_delay_value;
+
+ player->push_delay = 0;
+ player->push_delay_value = 5;
+
+ player->snapped = FALSE;
+
+ player->last_jx = player->last_jy = 0;
+ player->jx = player->jy = 0;
+
+ player->shield_normal_time_left = 0;
+ player->shield_deadly_time_left = 0;
+
+ player->inventory_size = 0;
+
+ DigField(player, 0, 0, 0, 0, DF_NO_PUSH);
+ SnapField(player, 0, 0);
+
+ player->LevelSolved = FALSE;
+ player->GameOver = FALSE;
+ }
+
+ network_player_action_received = FALSE;
+
+#if defined(PLATFORM_UNIX)
+ /* initial null action */
+ if (network_playing)
+ SendToServer_MovePlayer(MV_NO_MOVING);
+#endif
+
+ ZX = ZY = -1;
+
+ FrameCounter = 0;
+ TimeFrames = 0;
+ TimePlayed = 0;
+ TimeLeft = level.time;
+
+ ScreenMovDir = MV_NO_MOVING;
+ ScreenMovPos = 0;
+ ScreenGfxPos = 0;
+
+ ScrollStepSize = 0; /* will be correctly initialized by ScrollScreen() */
+
+ AllPlayersGone = FALSE;
+
+ game.yamyam_content_nr = 0;
+ game.magic_wall_active = FALSE;
+ game.magic_wall_time_left = 0;
+ game.light_time_left = 0;
+ game.timegate_time_left = 0;
+ game.switchgate_pos = 0;
+ game.balloon_dir = MV_NO_MOVING;
+ game.explosions_delayed = TRUE;
+
+ for (i=0; i<4; i++)
+ {
+ game.belt_dir[i] = MV_NO_MOVING;
+ game.belt_dir_nr[i] = 3; /* not moving, next moving left */
+ }
+
+ for (i=0; i<MAX_NUM_AMOEBA; i++)
+ AmoebaCnt[i] = AmoebaCnt2[i] = 0;
+
+ for (x=0; x<lev_fieldx; x++)
+ {
+ for (y=0; y<lev_fieldy; y++)
+ {
+ Feld[x][y] = level.field[x][y];
+ MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
+ ChangeDelay[x][y] = 0;
+ Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
+ AmoebaNr[x][y] = 0;
+ JustStopped[x][y] = 0;
+ Stop[x][y] = FALSE;
+ Pushed[x][y] = FALSE;
+ Changed[x][y] = FALSE;
+ ExplodePhase[x][y] = 0;
+ ExplodeField[x][y] = EX_NO_EXPLOSION;
+
+ GfxFrame[x][y] = 0;
+ GfxAction[x][y] = ACTION_DEFAULT;
+ GfxRandom[x][y] = INIT_GFX_RANDOM();
+ GfxElement[x][y] = EL_UNDEFINED;
+ }
+ }
+
+ for(y=0; y<lev_fieldy; y++)
+ {
+ for(x=0; x<lev_fieldx; x++)
+ {
+ if (emulate_bd && !IS_BD_ELEMENT(Feld[x][y]))
+ emulate_bd = FALSE;
+ if (emulate_sb && !IS_SB_ELEMENT(Feld[x][y]))
+ emulate_sb = FALSE;
+ if (emulate_sp && !IS_SP_ELEMENT(Feld[x][y]))
+ emulate_sp = FALSE;
+
+ InitField(x, y, TRUE);
+ }
+ }
+
+ InitBeltMovement();
+
+ game.emulation = (emulate_bd ? EMU_BOULDERDASH :
+ emulate_sb ? EMU_SOKOBAN :
+ emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
+
+ /* correct non-moving belts to start moving left */
+ for (i=0; i<4; i++)
+ if (game.belt_dir[i] == MV_NO_MOVING)
+ game.belt_dir_nr[i] = 3; /* not moving, next moving left */
+
+ /* check if any connected player was not found in playfield */
+ for (i=0; i<MAX_PLAYERS; i++)
+ {
+ struct PlayerInfo *player = &stored_player[i];
+
+ if (player->connected && !player->present)
+ {
+ for (j=0; j<MAX_PLAYERS; j++)
+ {
+ struct PlayerInfo *some_player = &stored_player[j];
+ int jx = some_player->jx, jy = some_player->jy;
+
+ /* assign first free player found that is present in the playfield */
+ if (some_player->present && !some_player->connected)
+ {
+ player->present = TRUE;
+ player->active = TRUE;
+ some_player->present = FALSE;
+
+ StorePlayer[jx][jy] = player->element_nr;
+ player->jx = player->last_jx = jx;
+ player->jy = player->last_jy = jy;
+
+ break;
+ }
+ }
+ }
+ }
+
+ if (tape.playing)
+ {
+ /* when playing a tape, eliminate all players who do not participate */
+
+ for (i=0; i<MAX_PLAYERS; i++)
+ {
+ if (stored_player[i].active && !tape.player_participates[i])
+ {
+ struct PlayerInfo *player = &stored_player[i];
+ int jx = player->jx, jy = player->jy;
+
+ player->active = FALSE;
+ StorePlayer[jx][jy] = 0;
+ Feld[jx][jy] = EL_EMPTY;
+ }
+ }
+ }
+ else if (!options.network && !setup.team_mode) /* && !tape.playing */
+ {
+ /* when in single player mode, eliminate all but the first active player */
+
+ for (i=0; i<MAX_PLAYERS; i++)
+ {
+ if (stored_player[i].active)
+ {
+ for (j=i+1; j<MAX_PLAYERS; j++)
+ {
+ if (stored_player[j].active)
+ {
+ struct PlayerInfo *player = &stored_player[j];
+ int jx = player->jx, jy = player->jy;
+
+ player->active = FALSE;
+ StorePlayer[jx][jy] = 0;
+ Feld[jx][jy] = EL_EMPTY;
+ }
+ }
+ }
+ }
+ }
+
+ /* when recording the game, store which players take part in the game */
+ if (tape.recording)
+ {
+ for (i=0; i<MAX_PLAYERS; i++)
+ if (stored_player[i].active)
+ tape.player_participates[i] = TRUE;
+ }
+
+ if (options.debug)
+ {
+ for (i=0; i<MAX_PLAYERS; i++)
+ {
+ struct PlayerInfo *player = &stored_player[i];
+
+ printf("Player %d: present == %d, connected == %d, active == %d.\n",
+ i+1,
+ player->present,
+ player->connected,
+ player->active);
+ if (local_player == player)
+ printf("Player %d is local player.\n", i+1);
+ }
+ }
+
+ if (BorderElement == EL_EMPTY)
+ {
+ SBX_Left = 0;
+ SBX_Right = lev_fieldx - SCR_FIELDX;
+ SBY_Upper = 0;
+ SBY_Lower = lev_fieldy - SCR_FIELDY;
+ }
+ else
+ {
+ SBX_Left = -1;
+ SBX_Right = lev_fieldx - SCR_FIELDX + 1;
+ SBY_Upper = -1;
+ SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
+ }
+
+ if (lev_fieldx + (SBX_Left == -1 ? 2 : 0) <= SCR_FIELDX)
+ SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
+
+ if (lev_fieldy + (SBY_Upper == -1 ? 2 : 0) <= SCR_FIELDY)
+ SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
+
+ /* if local player not found, look for custom element that might create
+ the player (make some assumptions about the right custom element) */
+ if (!local_player->present)
+ {
+ int start_x = 0, start_y = 0;
+ int found_rating = 0;
+ int found_element = EL_UNDEFINED;
+
+ for(y=0; y < lev_fieldy; y++) for(x=0; x < lev_fieldx; x++)
+ {
+ int element = Feld[x][y];
+ int content;
+ int xx, yy;
+ boolean is_player;
+
+ if (!IS_CUSTOM_ELEMENT(element))
+ continue;
+
+ if (CAN_CHANGE(element))
+ {
+ for (i=0; i < element_info[element].num_change_pages; i++)
+ {
+ content = element_info[element].change_page[i].target_element;
+ is_player = ELEM_IS_PLAYER(content);
+
+ if (is_player && (found_rating < 3 || element < found_element))
+ {
+ start_x = x;
+ start_y = y;
+
+ found_rating = 3;
+ found_element = element;
+ }
+ }
+ }
+
+ for(yy=0; yy < 3; yy++) for(xx=0; xx < 3; xx++)
+ {
+ content = element_info[element].content[xx][yy];
+ is_player = ELEM_IS_PLAYER(content);
+
+ if (is_player && (found_rating < 2 || element < found_element))
+ {
+ start_x = x + xx - 1;
+ start_y = y + yy - 1;
+
+ found_rating = 2;
+ found_element = element;
+ }
+
+ if (!CAN_CHANGE(element))
+ continue;
+
+ for (i=0; i < element_info[element].num_change_pages; i++)
+ {
+ content = element_info[element].change_page[i].content[xx][yy];
+ is_player = ELEM_IS_PLAYER(content);
+
+ if (is_player && (found_rating < 1 || element < found_element))
+ {
+ start_x = x + xx - 1;
+ start_y = y + yy - 1;
+
+ found_rating = 1;
+ found_element = element;
+ }
+ }
+ }
+ }
+
+ scroll_x = (start_x < SBX_Left + MIDPOSX ? SBX_Left :
+ start_x > SBX_Right + MIDPOSX ? SBX_Right :
+ start_x - MIDPOSX);
+
+ scroll_y = (start_y < SBY_Upper + MIDPOSY ? SBY_Upper :
+ start_y > SBY_Lower + MIDPOSY ? SBY_Lower :
+ start_y - MIDPOSY);
+ }
+ else
+ {
+#if 1
+ scroll_x = (local_player->jx < SBX_Left + MIDPOSX ? SBX_Left :
+ local_player->jx > SBX_Right + MIDPOSX ? SBX_Right :
+ local_player->jx - MIDPOSX);
+
+ scroll_y = (local_player->jy < SBY_Upper + MIDPOSY ? SBY_Upper :
+ local_player->jy > SBY_Lower + MIDPOSY ? SBY_Lower :
+ local_player->jy - MIDPOSY);
+#else
+ scroll_x = SBX_Left;
+ scroll_y = SBY_Upper;
+ if (local_player->jx >= SBX_Left + MIDPOSX)
+ scroll_x = (local_player->jx <= SBX_Right + MIDPOSX ?
+ local_player->jx - MIDPOSX :
+ SBX_Right);
+ if (local_player->jy >= SBY_Upper + MIDPOSY)
+ scroll_y = (local_player->jy <= SBY_Lower + MIDPOSY ?
+ local_player->jy - MIDPOSY :
+ SBY_Lower);
+#endif
+ }
+
+ CloseDoor(DOOR_CLOSE_1);
+
+ DrawLevel();
+ DrawAllPlayers();
+
+ /* after drawing the level, correct some elements */
+ if (game.timegate_time_left == 0)
+ CloseAllOpenTimegates();
+
+ if (setup.soft_scrolling)
+ BlitBitmap(fieldbuffer, backbuffer, FX, FY, SXSIZE, SYSIZE, SX, SY);
+
+ redraw_mask |= REDRAW_FROM_BACKBUFFER;
+ FadeToFront();
+
+ /* copy default game door content to main double buffer */
+ BlitBitmap(graphic_info[IMG_GLOBAL_DOOR].bitmap, drawto,
+ DOOR_GFX_PAGEX5, DOOR_GFX_PAGEY1, DXSIZE, DYSIZE, DX, DY);
+
+ if (level_nr < 100)
+ DrawText(DX + XX_LEVEL, DY + YY_LEVEL, int2str(level_nr, 2), FONT_TEXT_2);
+ else
+ {
+ DrawTextExt(drawto, DX + XX_EMERALDS, DY + YY_EMERALDS,
+ int2str(level_nr, 3), FONT_LEVEL_NUMBER, BLIT_OPAQUE);
+ BlitBitmap(drawto, drawto,
+ DX + XX_EMERALDS, DY + YY_EMERALDS + 1,
+ getFontWidth(FONT_LEVEL_NUMBER) * 3,
+ getFontHeight(FONT_LEVEL_NUMBER) - 1,
+ DX + XX_LEVEL - 1, DY + YY_LEVEL + 1);
+ }
+
+ DrawGameDoorValues();
+
+ UnmapGameButtons();
+ UnmapTapeButtons();
+ game_gadget[SOUND_CTRL_ID_MUSIC]->checked = setup.sound_music;
+ game_gadget[SOUND_CTRL_ID_LOOPS]->checked = setup.sound_loops;
+ game_gadget[SOUND_CTRL_ID_SIMPLE]->checked = setup.sound_simple;
+ MapGameButtons();
+ MapTapeButtons();
+
+ /* copy actual game door content to door double buffer for OpenDoor() */
+ BlitBitmap(drawto, bitmap_db_door,
+ DX, DY, DXSIZE, DYSIZE, DOOR_GFX_PAGEX1, DOOR_GFX_PAGEY1);
+
+ OpenDoor(DOOR_OPEN_ALL);
+
+ PlaySoundStereo(SND_GAME_STARTING, SOUND_MIDDLE);
+ if (setup.sound_music)
+ PlayMusic(level_nr);
+
+ KeyboardAutoRepeatOffUnlessAutoplay();
+
+ if (options.debug)
+ {
+ for (i=0; i<4; i++)
+ printf("Player %d %sactive.\n",
+ i + 1, (stored_player[i].active ? "" : "not "));
+ }
+}
+
+void InitMovDir(int x, int y)
+{
+ int i, element = Feld[x][y];
+ static int xy[4][2] =
+ {
+ { 0, +1 },
+ { +1, 0 },
+ { 0, -1 },
+ { -1, 0 }
+ };
+ static int direction[3][4] =
+ {
+ { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
+ { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
+ { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
+ };
+
+ switch(element)
+ {
+ case EL_BUG_RIGHT:
+ case EL_BUG_UP:
+ case EL_BUG_LEFT:
+ case EL_BUG_DOWN:
+ Feld[x][y] = EL_BUG;
+ MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
+ break;
+
+ case EL_SPACESHIP_RIGHT:
+ case EL_SPACESHIP_UP:
+ case EL_SPACESHIP_LEFT:
+ case EL_SPACESHIP_DOWN:
+ Feld[x][y] = EL_SPACESHIP;
+ MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
+ break;
+
+ case EL_BD_BUTTERFLY_RIGHT:
+ case EL_BD_BUTTERFLY_UP:
+ case EL_BD_BUTTERFLY_LEFT:
+ case EL_BD_BUTTERFLY_DOWN:
+ Feld[x][y] = EL_BD_BUTTERFLY;
+ MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
+ break;
+
+ case EL_BD_FIREFLY_RIGHT:
+ case EL_BD_FIREFLY_UP:
+ case EL_BD_FIREFLY_LEFT:
+ case EL_BD_FIREFLY_DOWN:
+ Feld[x][y] = EL_BD_FIREFLY;
+ MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
+ break;
+
+ case EL_PACMAN_RIGHT:
+ case EL_PACMAN_UP:
+ case EL_PACMAN_LEFT:
+ case EL_PACMAN_DOWN:
+ Feld[x][y] = EL_PACMAN;
+ MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
+ break;
+
+ case EL_SP_SNIKSNAK:
+ MovDir[x][y] = MV_UP;
+ break;
+
+ case EL_SP_ELECTRON:
+ MovDir[x][y] = MV_LEFT;
+ break;
+
+ case EL_MOLE_LEFT:
+ case EL_MOLE_RIGHT:
+ case EL_MOLE_UP:
+ case EL_MOLE_DOWN:
+ Feld[x][y] = EL_MOLE;
+ MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
+ break;
+
+ default:
+ if (IS_CUSTOM_ELEMENT(element))
+ {
+ if (element_info[element].move_direction_initial != MV_NO_MOVING)
+ MovDir[x][y] = element_info[element].move_direction_initial;
+ else if (element_info[element].move_pattern == MV_ALL_DIRECTIONS ||
+ element_info[element].move_pattern == MV_TURNING_LEFT ||
+ element_info[element].move_pattern == MV_TURNING_RIGHT)
+ MovDir[x][y] = 1 << RND(4);
+ else if (element_info[element].move_pattern == MV_HORIZONTAL)
+ MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
+ else if (element_info[element].move_pattern == MV_VERTICAL)
+ MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
+ else if (element_info[element].move_pattern & MV_ANY_DIRECTION)
+ MovDir[x][y] = element_info[element].move_pattern;
+ else if (element_info[element].move_pattern == MV_ALONG_LEFT_SIDE ||
+ element_info[element].move_pattern == MV_ALONG_RIGHT_SIDE)
+ {
+ for (i=0; i<4; i++)
+ {
+ int x1 = x + xy[i][0];
+ int y1 = y + xy[i][1];
+
+ if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
+ {
+ if (element_info[element].move_pattern == MV_ALONG_RIGHT_SIDE)
+ MovDir[x][y] = direction[0][i];
+ else
+ MovDir[x][y] = direction[1][i];
+
+ break;
+ }
+ }
+ }
+ }
+ else
+ {
+ MovDir[x][y] = 1 << RND(4);
+
+ if (element != EL_BUG &&
+ element != EL_SPACESHIP &&
+ element != EL_BD_BUTTERFLY &&
+ element != EL_BD_FIREFLY)
+ break;
+
+ for (i=0; i<4; i++)
+ {
+ int x1 = x + xy[i][0];
+ int y1 = y + xy[i][1];
+
+ if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
+ {
+ if (element == EL_BUG || element == EL_BD_BUTTERFLY)
+ {
+ MovDir[x][y] = direction[0][i];
+ break;
+ }
+ else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
+ element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
+ {
+ MovDir[x][y] = direction[1][i];
+ break;
+ }
+ }
+ }
+ }
+ break;
+ }
+}
+
+void InitAmoebaNr(int x, int y)
+{
+ int i;
+ int group_nr = AmoebeNachbarNr(x, y);
+
+ if (group_nr == 0)
+ {
+ for (i=1; i<MAX_NUM_AMOEBA; i++)
+ {
+ if (AmoebaCnt[i] == 0)
+ {
+ group_nr = i;
+ break;
+ }
+ }
+ }
+
+ AmoebaNr[x][y] = group_nr;
+ AmoebaCnt[group_nr]++;
+ AmoebaCnt2[group_nr]++;
+}
+
+void GameWon()
+{
+ int hi_pos;
+ boolean raise_level = FALSE;
+
+ if (local_player->MovPos)
+ return;
+
+#if 1
+ if (tape.auto_play) /* tape might already be stopped here */
+ tape.auto_play_level_solved = TRUE;
+#else
+ if (tape.playing && tape.auto_play)
+ tape.auto_play_level_solved = TRUE;
+#endif
+
+ local_player->LevelSolved = FALSE;
+
+ PlaySoundStereo(SND_GAME_WINNING, SOUND_MIDDLE);
+
+ if (TimeLeft)
+ {
+ if (!tape.playing && setup.sound_loops)
+ PlaySoundExt(SND_GAME_LEVELTIME_BONUS, SOUND_MAX_VOLUME, SOUND_MIDDLE,
+ SND_CTRL_PLAY_LOOP);
+
+ while (TimeLeft > 0)
+ {
+ if (!tape.playing && !setup.sound_loops)
+ PlaySoundStereo(SND_GAME_LEVELTIME_BONUS, SOUND_MIDDLE);
+ if (TimeLeft > 0 && !(TimeLeft % 10))
+ RaiseScore(level.score[SC_TIME_BONUS]);
+ if (TimeLeft > 100 && !(TimeLeft % 10))
+ TimeLeft -= 10;
+ else
+ TimeLeft--;
+ DrawText(DX_TIME, DY_TIME, int2str(TimeLeft, 3), FONT_TEXT_2);
+ BackToFront();
+
+ if (!tape.playing)
+ Delay(10);
+ }
+
+ if (!tape.playing && setup.sound_loops)
+ StopSound(SND_GAME_LEVELTIME_BONUS);
+ }
+ else if (level.time == 0) /* level without time limit */
+ {
+ if (!tape.playing && setup.sound_loops)
+ PlaySoundExt(SND_GAME_LEVELTIME_BONUS, SOUND_MAX_VOLUME, SOUND_MIDDLE,
+ SND_CTRL_PLAY_LOOP);
+
+ while (TimePlayed < 999)
+ {
+ if (!tape.playing && !setup.sound_loops)
+ PlaySoundStereo(SND_GAME_LEVELTIME_BONUS, SOUND_MIDDLE);
+ if (TimePlayed < 999 && !(TimePlayed % 10))
+ RaiseScore(level.score[SC_TIME_BONUS]);
+ if (TimePlayed < 900 && !(TimePlayed % 10))
+ TimePlayed += 10;
+ else
+ TimePlayed++;
+ DrawText(DX_TIME, DY_TIME, int2str(TimePlayed, 3), FONT_TEXT_2);
+ BackToFront();
+
+ if (!tape.playing)
+ Delay(10);
+ }
+
+ if (!tape.playing && setup.sound_loops)
+ StopSound(SND_GAME_LEVELTIME_BONUS);
+ }
+
+ /* close exit door after last player */
+ if (Feld[ExitX][ExitY] == EL_EXIT_OPEN && AllPlayersGone)
+ {
+ Feld[ExitX][ExitY] = EL_EXIT_CLOSING;
+
+ PlaySoundLevelElementAction(ExitX, ExitY, EL_EXIT_OPEN, ACTION_CLOSING);
+ }
+
+ /* Hero disappears */
+ DrawLevelField(ExitX, ExitY);
+ BackToFront();
+
+ if (tape.playing)
+ return;
+
+ CloseDoor(DOOR_CLOSE_1);
+
+ if (tape.recording)
+ {
+ TapeStop();
+ SaveTape(tape.level_nr); /* Ask to save tape */
+ }
+
+ if (level_nr == leveldir_current->handicap_level)
+ {
+ leveldir_current->handicap_level++;
+ SaveLevelSetup_SeriesInfo();
+ }
+
+ if (level_editor_test_game)
+ local_player->score = -1; /* no highscore when playing from editor */
+ else if (level_nr < leveldir_current->last_level)
+ raise_level = TRUE; /* advance to next level */
+
+ if ((hi_pos = NewHiScore()) >= 0)
+ {
+ game_status = GAME_MODE_SCORES;
+ DrawHallOfFame(hi_pos);
+ if (raise_level)
+ {
+ level_nr++;
+ TapeErase();
+ }
+ }
+ else
+ {
+ game_status = GAME_MODE_MAIN;
+ if (raise_level)
+ {
+ level_nr++;
+ TapeErase();
+ }
+ DrawMainMenu();
+ }
+
+ BackToFront();
+}
+
+int NewHiScore()
+{
+ int k, l;
+ int position = -1;
+
+ LoadScore(level_nr);
+
+ if (strcmp(setup.player_name, EMPTY_PLAYER_NAME) == 0 ||
+ local_player->score < highscore[MAX_SCORE_ENTRIES - 1].Score)
+ return -1;
+
+ for (k=0; k<MAX_SCORE_ENTRIES; k++)
+ {
+ if (local_player->score > highscore[k].Score)
+ {
+ /* player has made it to the hall of fame */
+
+ if (k < MAX_SCORE_ENTRIES - 1)
+ {
+ int m = MAX_SCORE_ENTRIES - 1;
+
+#ifdef ONE_PER_NAME
+ for (l=k; l<MAX_SCORE_ENTRIES; l++)
+ if (!strcmp(setup.player_name, highscore[l].Name))
+ m = l;
+ if (m == k) /* player's new highscore overwrites his old one */
+ goto put_into_list;
+#endif
+
+ for (l=m; l>k; l--)
+ {
+ strcpy(highscore[l].Name, highscore[l - 1].Name);
+ highscore[l].Score = highscore[l - 1].Score;
+ }
+ }
+
+#ifdef ONE_PER_NAME
+ put_into_list:
+#endif
+ strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
+ highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
+ highscore[k].Score = local_player->score;
+ position = k;
+ break;
+ }
+
+#ifdef ONE_PER_NAME
+ else if (!strncmp(setup.player_name, highscore[k].Name,
+ MAX_PLAYER_NAME_LEN))
+ break; /* player already there with a higher score */
+#endif
+
+ }
+
+ if (position >= 0)
+ SaveScore(level_nr);
+
+ return position;
+}
+
+void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
+{
+ if (player->GfxAction != action || player->GfxDir != dir)
+ {
+#if 0
+ printf("Player frame reset! (%d => %d, %d => %d)\n",
+ player->GfxAction, action, player->GfxDir, dir);
+#endif
+
+ player->GfxAction = action;
+ player->GfxDir = dir;
+ player->Frame = 0;
+ player->StepFrame = 0;
+ }
+}
+
+static void ResetRandomAnimationValue(int x, int y)
+{
+ GfxRandom[x][y] = INIT_GFX_RANDOM();
+}
+
+static void ResetGfxAnimation(int x, int y)
+{
+ GfxFrame[x][y] = 0;
+ GfxAction[x][y] = ACTION_DEFAULT;
+}
+
+void InitMovingField(int x, int y, int direction)
+{
+ int element = Feld[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;
+ int newy = y + dy;
+
+ if (!JustStopped[x][y] || direction != MovDir[x][y])
+ ResetGfxAnimation(x, y);
+
+ MovDir[newx][newy] = MovDir[x][y] = direction;
+
+ if (Feld[newx][newy] == EL_EMPTY)
+ Feld[newx][newy] = EL_BLOCKED;
+
+ if (direction == MV_DOWN && CAN_FALL(element))
+ GfxAction[x][y] = ACTION_FALLING;
+ else
+ GfxAction[x][y] = ACTION_MOVING;
+
+ GfxFrame[newx][newy] = GfxFrame[x][y];
+ GfxAction[newx][newy] = GfxAction[x][y];
+ GfxRandom[newx][newy] = GfxRandom[x][y];
+}
+
+void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
+{
+ int direction = MovDir[x][y];
+ int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
+ int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
+
+ *goes_to_x = newx;
+ *goes_to_y = newy;
+}
+
+void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
+{
+ int oldx = x, oldy = y;
+ int direction = MovDir[x][y];
+
+ if (direction == MV_LEFT)
+ oldx++;
+ else if (direction == MV_RIGHT)
+ oldx--;
+ else if (direction == MV_UP)
+ oldy++;
+ else if (direction == MV_DOWN)
+ oldy--;
+
+ *comes_from_x = oldx;
+ *comes_from_y = oldy;
+}
+
+int MovingOrBlocked2Element(int x, int y)
+{
+ int element = Feld[x][y];
+
+ if (element == EL_BLOCKED)
+ {
+ int oldx, oldy;
+
+ Blocked2Moving(x, y, &oldx, &oldy);
+ return Feld[oldx][oldy];
+ }
+ else
+ return element;
+}
+
+static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
+{
+ /* like MovingOrBlocked2Element(), but if element is moving
+ and (x,y) is the field the moving element is just leaving,
+ return EL_BLOCKED instead of the element value */
+ int element = Feld[x][y];
+
+ if (IS_MOVING(x, y))
+ {
+ if (element == EL_BLOCKED)
+ {
+ int oldx, oldy;
+
+ Blocked2Moving(x, y, &oldx, &oldy);
+ return Feld[oldx][oldy];
+ }
+ else
+ return EL_BLOCKED;
+ }
+ else
+ return element;
+}
+
+static void RemoveField(int x, int y)
+{
+ Feld[x][y] = EL_EMPTY;
+
+ MovPos[x][y] = 0;
+ MovDir[x][y] = 0;
+ MovDelay[x][y] = 0;
+
+ AmoebaNr[x][y] = 0;
+ ChangeDelay[x][y] = 0;
+ Pushed[x][y] = FALSE;
+
+ GfxElement[x][y] = EL_UNDEFINED;
+ GfxAction[x][y] = ACTION_DEFAULT;
+}
+
+void RemoveMovingField(int x, int y)
+{
+ int oldx = x, oldy = y, newx = x, newy = y;
+ int element = Feld[x][y];
+ int next_element = EL_UNDEFINED;
+
+ if (element != EL_BLOCKED && !IS_MOVING(x, y))
+ return;
+
+ if (IS_MOVING(x, y))
+ {
+ Moving2Blocked(x, y, &newx, &newy);
+ if (Feld[newx][newy] != EL_BLOCKED)
+ return;
+ }
+ else if (element == EL_BLOCKED)
+ {
+ Blocked2Moving(x, y, &oldx, &oldy);
+ if (!IS_MOVING(oldx, oldy))
+ return;
+ }
+
+ if (element == EL_BLOCKED &&
+ (Feld[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
+ Feld[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
+ Feld[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
+ Feld[oldx][oldy] == EL_AMOEBA_DROPPING))
+ next_element = get_next_element(Feld[oldx][oldy]);
+
+ RemoveField(oldx, oldy);
+ RemoveField(newx, newy);
+
+ Store[oldx][oldy] = Store2[oldx][oldy] = 0;
+
+ if (next_element != EL_UNDEFINED)
+ Feld[oldx][oldy] = next_element;
+
+ DrawLevelField(oldx, oldy);
+ DrawLevelField(newx, newy);
+}
+
+void DrawDynamite(int x, int y)
+{
+ int sx = SCREENX(x), sy = SCREENY(y);
+ int graphic = el2img(Feld[x][y]);
+ int frame;
+
+ if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
+ return;
+
+ if (IS_WALKABLE_INSIDE(Back[x][y]))
+ return;
+
+ if (Back[x][y])
+ DrawGraphic(sx, sy, el2img(Back[x][y]), 0);
+ else if (Store[x][y])
+ DrawGraphic(sx, sy, el2img(Store[x][y]), 0);
+
+ frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
+
+#if 1
+ if (Back[x][y] || Store[x][y])
+ DrawGraphicThruMask(sx, sy, graphic, frame);
+ else
+ DrawGraphic(sx, sy, graphic, frame);
+#else
+ if (game.emulation == EMU_SUPAPLEX)
+ DrawGraphic(sx, sy, IMG_SP_DISK_RED, frame);
+ else if (Store[x][y])
+ DrawGraphicThruMask(sx, sy, graphic, frame);
+ else
+ DrawGraphic(sx, sy, graphic, frame);
+#endif
+}
+
+void CheckDynamite(int x, int y)
+{
+ if (MovDelay[x][y] != 0) /* dynamite is still waiting to explode */
+ {
+ MovDelay[x][y]--;
+
+ if (MovDelay[x][y] != 0)
+ {
+ DrawDynamite(x, y);
+ PlaySoundLevelActionIfLoop(x, y, ACTION_ACTIVE);
+
+ return;
+ }
+ }
+
+#if 1
+ StopSoundLevelActionIfLoop(x, y, ACTION_ACTIVE);
+#else
+ if (Feld[x][y] == EL_DYNAMITE_ACTIVE ||
+ Feld[x][y] == EL_SP_DISK_RED_ACTIVE)
+ StopSound(SND_DYNAMITE_ACTIVE);
+ else
+ StopSound(SND_DYNABOMB_ACTIVE);
+#endif
+
+ Bang(x, y);
+}
+
+void RelocatePlayer(int x, int y, int element)
+{
+ struct PlayerInfo *player = &stored_player[element - EL_PLAYER_1];
+
+ if (player->present)
+ {
+ while (player->MovPos)
+ {
+ ScrollFigure(player, SCROLL_GO_ON);
+ ScrollScreen(NULL, SCROLL_GO_ON);
+ FrameCounter++;
+ DrawAllPlayers();
+ BackToFront();
+ }
+
+ RemoveField(player->jx, player->jy);
+ DrawLevelField(player->jx, player->jy);
+ }
+
+ InitPlayerField(x, y, element, TRUE);
+
+ if (player == local_player)
+ {
+ int scroll_xx = -999, scroll_yy = -999;
+
+ while (scroll_xx != scroll_x || scroll_yy != scroll_y)
+ {
+ int dx = 0, dy = 0;
+ int fx = FX, fy = FY;
+
+ scroll_xx = (local_player->jx < SBX_Left + MIDPOSX ? SBX_Left :
+ local_player->jx > SBX_Right + MIDPOSX ? SBX_Right :
+ local_player->jx - MIDPOSX);
+
+ scroll_yy = (local_player->jy < SBY_Upper + MIDPOSY ? SBY_Upper :
+ local_player->jy > SBY_Lower + MIDPOSY ? SBY_Lower :
+ local_player->jy - MIDPOSY);
+
+ dx = (scroll_xx < scroll_x ? +1 : scroll_xx > scroll_x ? -1 : 0);
+ dy = (scroll_yy < scroll_y ? +1 : scroll_yy > scroll_y ? -1 : 0);
+
+ scroll_x -= dx;
+ scroll_y -= dy;
+
+ fx += dx * TILEX / 2;
+ fy += dy * TILEY / 2;
+
+ ScrollLevel(dx, dy);
+ DrawAllPlayers();
+
+ /* scroll in to steps of half tile size to make things smoother */
+ BlitBitmap(drawto_field, window, fx, fy, SXSIZE, SYSIZE, SX, SY);
+ FlushDisplay();
+ Delay(GAME_FRAME_DELAY);
+
+ /* scroll second step to align at full tile size */
+ BackToFront();
+ Delay(GAME_FRAME_DELAY);
+ }
+ }
+}
+
+void Explode(int ex, int ey, int phase, int mode)
+{
+ int x, y;
+ int num_phase = 9;
+ int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
+ int last_phase = num_phase * delay;
+ int half_phase = (num_phase / 2) * delay;
+ int first_phase_after_start = EX_PHASE_START + 1;
+
+ if (game.explosions_delayed)
+ {
+ ExplodeField[ex][ey] = mode;
+ return;
+ }
+
+ if (phase == EX_PHASE_START) /* initialize 'Store[][]' field */
+ {
+ int center_element = Feld[ex][ey];
+
+#if 0
+ /* --- This is only really needed (and now handled) in "Impact()". --- */
+ /* do not explode moving elements that left the explode field in time */
+ if (game.engine_version >= RELEASE_IDENT(2,2,0,7) &&
+ center_element == EL_EMPTY && (mode == EX_NORMAL || mode == EX_CENTER))
+ return;
+#endif
+
+ if (mode == EX_NORMAL || mode == EX_CENTER)
+ PlaySoundLevelAction(ex, ey, ACTION_EXPLODING);
+
+ /* remove things displayed in background while burning dynamite */
+ if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
+ Back[ex][ey] = 0;
+
+ if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
+ {
+ /* put moving element to center field (and let it explode there) */
+ center_element = MovingOrBlocked2Element(ex, ey);
+ RemoveMovingField(ex, ey);
+ Feld[ex][ey] = center_element;
+ }
+
+ for (y = ey - 1; y <= ey + 1; y++) for(x = ex - 1; x <= ex + 1; x++)
+ {
+ int xx = x - ex + 1;
+ int yy = y - ey + 1;
+ int element;
+
+ if (!IN_LEV_FIELD(x, y) ||
+ ((mode != EX_NORMAL || center_element == EL_AMOEBA_TO_DIAMOND) &&
+ (x != ex || y != ey)))
+ continue;
+
+ element = Feld[x][y];
+
+ if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
+ {
+ element = MovingOrBlocked2Element(x, y);
+
+ if (!IS_EXPLOSION_PROOF(element))
+ RemoveMovingField(x, y);
+ }
+
+#if 1
+
+#if 0
+ if (IS_EXPLOSION_PROOF(element))
+ continue;
+#else
+ /* indestructible elements can only explode in center (but not flames) */
+ if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey)) ||
+ element == EL_FLAMES)
+ continue;
+#endif
+
+#else
+ if ((IS_INDESTRUCTIBLE(element) &&
+ (game.engine_version < VERSION_IDENT(2,2,0) ||
+ (!IS_WALKABLE_OVER(element) && !IS_WALKABLE_UNDER(element)))) ||
+ element == EL_FLAMES)
+ continue;
+#endif
+
+ if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
+ {
+ if (IS_ACTIVE_BOMB(element))
+ {
+ /* re-activate things under the bomb like gate or penguin */
+ Feld[x][y] = (Store[x][y] ? Store[x][y] : EL_EMPTY);
+ Store[x][y] = 0;
+ }
+
+ continue;
+ }
+
+ /* save walkable background elements while explosion on same tile */
+#if 0
+ if (IS_INDESTRUCTIBLE(element))
+ Back[x][y] = element;
+#else
+ if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element))
+ Back[x][y] = element;
+#endif
+
+ /* ignite explodable elements reached by other explosion */
+ if (element == EL_EXPLOSION)
+ element = Store2[x][y];
+
+#if 1
+ if (AmoebaNr[x][y] &&
+ (element == EL_AMOEBA_FULL ||
+ element == EL_BD_AMOEBA ||
+ element == EL_AMOEBA_GROWING))
+ {
+ AmoebaCnt[AmoebaNr[x][y]]--;
+ AmoebaCnt2[AmoebaNr[x][y]]--;
+ }
+
+ RemoveField(x, y);
+#endif
+
+ if (IS_PLAYER(ex, ey) && !PLAYER_PROTECTED(ex, ey))
+ {
+ switch(StorePlayer[ex][ey])
+ {
+ case EL_PLAYER_2:
+ Store[x][y] = EL_EMERALD_RED;
+ break;
+ case EL_PLAYER_3:
+ Store[x][y] = EL_EMERALD;
+ break;
+ case EL_PLAYER_4:
+ Store[x][y] = EL_EMERALD_PURPLE;
+ break;
+ case EL_PLAYER_1:
+ default:
+ Store[x][y] = EL_EMERALD_YELLOW;
+ break;
+ }
+
+ if (game.emulation == EMU_SUPAPLEX)
+ Store[x][y] = EL_EMPTY;
+ }
+ else if (center_element == EL_MOLE)
+ Store[x][y] = EL_EMERALD_RED;
+ else if (center_element == EL_PENGUIN)
+ Store[x][y] = EL_EMERALD_PURPLE;
+ else if (center_element == EL_BUG)
+ Store[x][y] = ((x == ex && y == ey) ? EL_DIAMOND : EL_EMERALD);
+ else if (center_element == EL_BD_BUTTERFLY)
+ Store[x][y] = EL_BD_DIAMOND;
+ else if (center_element == EL_SP_ELECTRON)
+ Store[x][y] = EL_SP_INFOTRON;
+ else if (center_element == EL_AMOEBA_TO_DIAMOND)
+ Store[x][y] = level.amoeba_content;
+ else if (center_element == EL_YAMYAM)
+ Store[x][y] = level.yamyam_content[game.yamyam_content_nr][xx][yy];
+ else if (IS_CUSTOM_ELEMENT(center_element) &&
+ element_info[center_element].content[xx][yy] != EL_EMPTY)
+ Store[x][y] = element_info[center_element].content[xx][yy];
+ else if (element == EL_WALL_EMERALD)
+ Store[x][y] = EL_EMERALD;
+ else if (element == EL_WALL_DIAMOND)
+ Store[x][y] = EL_DIAMOND;
+ else if (element == EL_WALL_BD_DIAMOND)
+ Store[x][y] = EL_BD_DIAMOND;
+ else if (element == EL_WALL_EMERALD_YELLOW)
+ Store[x][y] = EL_EMERALD_YELLOW;
+ else if (element == EL_WALL_EMERALD_RED)
+ Store[x][y] = EL_EMERALD_RED;
+ else if (element == EL_WALL_EMERALD_PURPLE)
+ Store[x][y] = EL_EMERALD_PURPLE;
+ else if (element == EL_WALL_PEARL)
+ Store[x][y] = EL_PEARL;
+ else if (element == EL_WALL_CRYSTAL)
+ Store[x][y] = EL_CRYSTAL;
+ else if (IS_CUSTOM_ELEMENT(element) && !CAN_EXPLODE(element))
+ Store[x][y] = element_info[element].content[1][1];
+ else
+ Store[x][y] = EL_EMPTY;
+
+ if (x != ex || y != ey ||
+ center_element == EL_AMOEBA_TO_DIAMOND || mode == EX_BORDER)
+ Store2[x][y] = element;
+
+#if 0
+ if (AmoebaNr[x][y] &&
+ (element == EL_AMOEBA_FULL ||
+ element == EL_BD_AMOEBA ||
+ element == EL_AMOEBA_GROWING))
+ {
+ AmoebaCnt[AmoebaNr[x][y]]--;
+ AmoebaCnt2[AmoebaNr[x][y]]--;
+ }
+
+#if 1
+ RemoveField(x, y);
+#else
+ MovDir[x][y] = MovPos[x][y] = 0;
+ AmoebaNr[x][y] = 0;
+#endif
+#endif
+
+ Feld[x][y] = EL_EXPLOSION;
+#if 1
+ GfxElement[x][y] = center_element;
+#else
+ GfxElement[x][y] = EL_UNDEFINED;
+#endif
+
+ ExplodePhase[x][y] = 1;
+ Stop[x][y] = TRUE;