+ { EL_DC_MAGIC_WALL_FILLING, 2 },
+ { EL_DC_MAGIC_WALL_EMPTYING, 2 },
+
+ { EL_UNDEFINED, 0 },
+};
+
+struct
+{
+ int element;
+ int count;
+}
+collect_count_list[] =
+{
+ { EL_EMERALD, 1 },
+ { EL_BD_DIAMOND, 1 },
+ { EL_EMERALD_YELLOW, 1 },
+ { EL_EMERALD_RED, 1 },
+ { EL_EMERALD_PURPLE, 1 },
+ { EL_DIAMOND, 3 },
+ { EL_SP_INFOTRON, 1 },
+ { EL_PEARL, 5 },
+ { EL_CRYSTAL, 8 },
+
+ { EL_UNDEFINED, 0 },
+};
+
+struct
+{
+ int element;
+ int direction;
+}
+access_direction_list[] =
+{
+ { EL_TUBE_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
+ { EL_TUBE_VERTICAL, MV_UP | MV_DOWN },
+ { EL_TUBE_HORIZONTAL, MV_LEFT | MV_RIGHT },
+ { EL_TUBE_VERTICAL_LEFT, MV_LEFT | MV_UP | MV_DOWN },
+ { EL_TUBE_VERTICAL_RIGHT, MV_RIGHT | MV_UP | MV_DOWN },
+ { EL_TUBE_HORIZONTAL_UP, MV_LEFT | MV_RIGHT | MV_UP },
+ { EL_TUBE_HORIZONTAL_DOWN, MV_LEFT | MV_RIGHT | MV_DOWN },
+ { EL_TUBE_LEFT_UP, MV_LEFT | MV_UP },
+ { EL_TUBE_LEFT_DOWN, MV_LEFT | MV_DOWN },
+ { EL_TUBE_RIGHT_UP, MV_RIGHT | MV_UP },
+ { EL_TUBE_RIGHT_DOWN, MV_RIGHT | MV_DOWN },
+
+ { EL_SP_PORT_LEFT, MV_RIGHT },
+ { EL_SP_PORT_RIGHT, MV_LEFT },
+ { EL_SP_PORT_UP, MV_DOWN },
+ { EL_SP_PORT_DOWN, MV_UP },
+ { EL_SP_PORT_HORIZONTAL, MV_LEFT | MV_RIGHT },
+ { EL_SP_PORT_VERTICAL, MV_UP | MV_DOWN },
+ { EL_SP_PORT_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
+ { EL_SP_GRAVITY_PORT_LEFT, MV_RIGHT },
+ { EL_SP_GRAVITY_PORT_RIGHT, MV_LEFT },
+ { EL_SP_GRAVITY_PORT_UP, MV_DOWN },
+ { EL_SP_GRAVITY_PORT_DOWN, MV_UP },
+ { EL_SP_GRAVITY_ON_PORT_LEFT, MV_RIGHT },
+ { EL_SP_GRAVITY_ON_PORT_RIGHT, MV_LEFT },
+ { EL_SP_GRAVITY_ON_PORT_UP, MV_DOWN },
+ { EL_SP_GRAVITY_ON_PORT_DOWN, MV_UP },
+ { EL_SP_GRAVITY_OFF_PORT_LEFT, MV_RIGHT },
+ { EL_SP_GRAVITY_OFF_PORT_RIGHT, MV_LEFT },
+ { EL_SP_GRAVITY_OFF_PORT_UP, MV_DOWN },
+ { EL_SP_GRAVITY_OFF_PORT_DOWN, MV_UP },
+
+ { EL_UNDEFINED, MV_NONE }
+};
+
+static boolean trigger_events[MAX_NUM_ELEMENTS][NUM_CHANGE_EVENTS];
+
+#define IS_AUTO_CHANGING(e) (element_info[e].has_change_event[CE_DELAY])
+#define IS_JUST_CHANGING(x, y) (ChangeDelay[x][y] != 0)
+#define IS_CHANGING(x, y) (IS_AUTO_CHANGING(Feld[x][y]) || \
+ IS_JUST_CHANGING(x, y))
+
+#define CE_PAGE(e, ce) (element_info[e].event_page[ce])
+
+/* static variables for playfield scan mode (scanning forward or backward) */
+static int playfield_scan_start_x = 0;
+static int playfield_scan_start_y = 0;
+static int playfield_scan_delta_x = 1;
+static int playfield_scan_delta_y = 1;
+
+#define SCAN_PLAYFIELD(x, y) for ((y) = playfield_scan_start_y; \
+ (y) >= 0 && (y) <= lev_fieldy - 1; \
+ (y) += playfield_scan_delta_y) \
+ for ((x) = playfield_scan_start_x; \
+ (x) >= 0 && (x) <= lev_fieldx - 1; \
+ (x) += playfield_scan_delta_x)
+
+#ifdef DEBUG
+void DEBUG_SetMaximumDynamite()
+{
+ int i;
+
+ for (i = 0; i < MAX_INVENTORY_SIZE; i++)
+ if (local_player->inventory_size < MAX_INVENTORY_SIZE)
+ local_player->inventory_element[local_player->inventory_size++] =
+ EL_DYNAMITE;
+}
+#endif
+
+static void InitPlayfieldScanModeVars()
+{
+ if (game.use_reverse_scan_direction)
+ {
+ playfield_scan_start_x = lev_fieldx - 1;
+ playfield_scan_start_y = lev_fieldy - 1;
+
+ playfield_scan_delta_x = -1;
+ playfield_scan_delta_y = -1;
+ }
+ else
+ {
+ playfield_scan_start_x = 0;
+ playfield_scan_start_y = 0;
+
+ playfield_scan_delta_x = 1;
+ playfield_scan_delta_y = 1;
+ }
+}
+
+static void InitPlayfieldScanMode(int mode)
+{
+ game.use_reverse_scan_direction =
+ (mode == CA_ARG_SCAN_MODE_REVERSE ? TRUE : FALSE);
+
+ InitPlayfieldScanModeVars();
+}
+
+static int get_move_delay_from_stepsize(int move_stepsize)
+{
+ move_stepsize =
+ MIN(MAX(MOVE_STEPSIZE_MIN, move_stepsize), MOVE_STEPSIZE_MAX);
+
+ /* make sure that stepsize value is always a power of 2 */
+ move_stepsize = (1 << log_2(move_stepsize));
+
+ return TILEX / move_stepsize;
+}
+
+static void SetPlayerMoveSpeed(struct PlayerInfo *player, int move_stepsize,
+ boolean init_game)
+{
+ int player_nr = player->index_nr;
+ int move_delay = get_move_delay_from_stepsize(move_stepsize);
+ boolean cannot_move = (move_stepsize == STEPSIZE_NOT_MOVING ? TRUE : FALSE);
+
+ /* do no immediately change move delay -- the player might just be moving */
+ player->move_delay_value_next = move_delay;
+
+ /* information if player can move must be set separately */
+ player->cannot_move = cannot_move;
+
+ if (init_game)
+ {
+ player->move_delay = game.initial_move_delay[player_nr];
+ player->move_delay_value = game.initial_move_delay_value[player_nr];
+
+ player->move_delay_value_next = -1;
+
+ player->move_delay_reset_counter = 0;
+ }
+}
+
+void GetPlayerConfig()
+{
+ GameFrameDelay = setup.game_frame_delay;
+
+ if (!audio.sound_available)
+ setup.sound_simple = FALSE;
+
+ if (!audio.loops_available)
+ setup.sound_loops = FALSE;
+
+ if (!audio.music_available)
+ setup.sound_music = FALSE;
+
+ if (!video.fullscreen_available)
+ setup.fullscreen = FALSE;
+
+ setup.sound = (setup.sound_simple || setup.sound_loops || setup.sound_music);
+
+ SetAudioMode(setup.sound);
+}
+
+int GetElementFromGroupElement(int element)
+{
+ if (IS_GROUP_ELEMENT(element))
+ {
+ struct ElementGroupInfo *group = element_info[element].group;
+ int last_anim_random_frame = gfx.anim_random_frame;
+ int element_pos;
+
+ if (group->choice_mode == ANIM_RANDOM)
+ gfx.anim_random_frame = RND(group->num_elements_resolved);
+
+ element_pos = getAnimationFrame(group->num_elements_resolved, 1,
+ group->choice_mode, 0,
+ group->choice_pos);
+
+ if (group->choice_mode == ANIM_RANDOM)
+ gfx.anim_random_frame = last_anim_random_frame;
+
+ group->choice_pos++;
+
+ element = group->element_resolved[element_pos];
+ }
+
+ return element;
+}
+
+static void InitPlayerField(int x, int y, int element, boolean init_game)
+{
+ if (element == EL_SP_MURPHY)
+ {
+ if (init_game)
+ {
+ if (stored_player[0].present)
+ {
+ Feld[x][y] = EL_SP_MURPHY_CLONE;
+
+ return;
+ }
+ else
+ {
+ stored_player[0].initial_element = element;
+ stored_player[0].use_murphy = TRUE;
+
+ if (!level.use_artwork_element[0])
+ stored_player[0].artwork_element = EL_SP_MURPHY;
+ }
+
+ Feld[x][y] = EL_PLAYER_1;
+ }
+ }
+
+ if (init_game)
+ {
+ struct PlayerInfo *player = &stored_player[Feld[x][y] - EL_PLAYER_1];
+ int jx = player->jx, jy = player->jy;
+
+ player->present = TRUE;
+
+ player->block_last_field = (element == EL_SP_MURPHY ?
+ level.sp_block_last_field :
+ level.block_last_field);
+
+ /* ---------- initialize player's last field block delay --------------- */
+
+ /* always start with reliable default value (no adjustment needed) */
+ player->block_delay_adjustment = 0;
+
+ /* special case 1: in Supaplex, Murphy blocks last field one more frame */
+ if (player->block_last_field && element == EL_SP_MURPHY)
+ player->block_delay_adjustment = 1;
+
+ /* special case 2: in game engines before 3.1.1, blocking was different */
+ if (game.use_block_last_field_bug)
+ player->block_delay_adjustment = (player->block_last_field ? -1 : 1);
+
+ if (!network.enabled || player->connected_network)
+ {
+ player->active = TRUE;
+
+ /* remove potentially duplicate players */
+ if (StorePlayer[jx][jy] == Feld[x][y])
+ StorePlayer[jx][jy] = 0;
+
+ StorePlayer[x][y] = Feld[x][y];
+
+#if DEBUG_INIT_PLAYER
+ if (options.debug)
+ {
+ printf("- player element %d activated", player->element_nr);
+ printf(" (local player is %d and currently %s)\n",
+ local_player->element_nr,
+ local_player->active ? "active" : "not active");
+ }
+ }
+#endif
+
+ Feld[x][y] = EL_EMPTY;
+
+ player->jx = player->last_jx = x;
+ player->jy = player->last_jy = y;
+ }
+
+ if (!init_game)
+ {
+ int player_nr = GET_PLAYER_NR(element);
+ struct PlayerInfo *player = &stored_player[player_nr];
+
+ if (player->active && player->killed)
+ player->reanimated = TRUE; /* if player was just killed, reanimate him */
+ }
+}
+
+static void InitField(int x, int y, boolean init_game)
+{
+ int element = Feld[x][y];
+
+ switch (element)
+ {
+ case EL_SP_MURPHY:
+ case EL_PLAYER_1:
+ case EL_PLAYER_2:
+ case EL_PLAYER_3:
+ case EL_PLAYER_4:
+ InitPlayerField(x, y, element, init_game);
+ break;
+
+ case EL_SOKOBAN_FIELD_PLAYER:
+ element = Feld[x][y] = EL_PLAYER_1;
+ InitField(x, y, init_game);
+
+ element = Feld[x][y] = EL_SOKOBAN_FIELD_EMPTY;
+ InitField(x, y, init_game);
+ break;
+
+ case EL_SOKOBAN_FIELD_EMPTY:
+ local_player->sokobanfields_still_needed++;
+ break;
+
+ case EL_STONEBLOCK:
+ if (x < lev_fieldx-1 && Feld[x+1][y] == EL_ACID)
+ Feld[x][y] = EL_ACID_POOL_TOPLEFT;
+ else if (x > 0 && Feld[x-1][y] == EL_ACID)
+ Feld[x][y] = EL_ACID_POOL_TOPRIGHT;
+ else if (y > 0 && Feld[x][y-1] == EL_ACID_POOL_TOPLEFT)
+ Feld[x][y] = EL_ACID_POOL_BOTTOMLEFT;
+ else if (y > 0 && Feld[x][y-1] == EL_ACID)
+ Feld[x][y] = EL_ACID_POOL_BOTTOM;
+ else if (y > 0 && Feld[x][y-1] == EL_ACID_POOL_TOPRIGHT)
+ Feld[x][y] = EL_ACID_POOL_BOTTOMRIGHT;
+ break;
+
+ case EL_BUG:
+ case EL_BUG_RIGHT:
+ case EL_BUG_UP:
+ case EL_BUG_LEFT:
+ case EL_BUG_DOWN:
+ case EL_SPACESHIP:
+ case EL_SPACESHIP_RIGHT:
+ case EL_SPACESHIP_UP:
+ case EL_SPACESHIP_LEFT:
+ case EL_SPACESHIP_DOWN:
+ case EL_BD_BUTTERFLY:
+ case EL_BD_BUTTERFLY_RIGHT:
+ case EL_BD_BUTTERFLY_UP:
+ case EL_BD_BUTTERFLY_LEFT:
+ case EL_BD_BUTTERFLY_DOWN:
+ case EL_BD_FIREFLY:
+ case EL_BD_FIREFLY_RIGHT:
+ case EL_BD_FIREFLY_UP:
+ case EL_BD_FIREFLY_LEFT:
+ case EL_BD_FIREFLY_DOWN:
+ case EL_PACMAN_RIGHT:
+ case EL_PACMAN_UP:
+ case EL_PACMAN_LEFT:
+ case EL_PACMAN_DOWN:
+ case EL_YAMYAM:
+ case EL_YAMYAM_LEFT:
+ case EL_YAMYAM_RIGHT:
+ case EL_YAMYAM_UP:
+ case EL_YAMYAM_DOWN:
+ case EL_DARK_YAMYAM:
+ case EL_ROBOT:
+ case EL_PACMAN:
+ case EL_SP_SNIKSNAK:
+ case EL_SP_ELECTRON:
+ case EL_MOLE:
+ case EL_MOLE_LEFT:
+ case EL_MOLE_RIGHT:
+ case EL_MOLE_UP:
+ case EL_MOLE_DOWN:
+ InitMovDir(x, y);
+ break;
+
+ case EL_AMOEBA_FULL:
+ case EL_BD_AMOEBA:
+ InitAmoebaNr(x, y);
+ break;
+
+ case EL_AMOEBA_DROP:
+ if (y == lev_fieldy - 1)
+ {
+ Feld[x][y] = EL_AMOEBA_GROWING;
+ Store[x][y] = EL_AMOEBA_WET;
+ }
+ break;
+
+ case EL_DYNAMITE_ACTIVE:
+ case EL_SP_DISK_RED_ACTIVE:
+ case EL_DYNABOMB_PLAYER_1_ACTIVE:
+ case EL_DYNABOMB_PLAYER_2_ACTIVE:
+ case EL_DYNABOMB_PLAYER_3_ACTIVE:
+ case EL_DYNABOMB_PLAYER_4_ACTIVE:
+ MovDelay[x][y] = 96;
+ break;
+
+ case EL_EM_DYNAMITE_ACTIVE:
+ MovDelay[x][y] = 32;
+ break;
+
+ case EL_LAMP:
+ local_player->lights_still_needed++;
+ break;
+
+ case EL_PENGUIN:
+ local_player->friends_still_needed++;
+ break;
+
+ case EL_PIG:
+ case EL_DRAGON:
+ GfxDir[x][y] = MovDir[x][y] = 1 << RND(4);
+ break;
+
+ case EL_CONVEYOR_BELT_1_SWITCH_LEFT:
+ case EL_CONVEYOR_BELT_1_SWITCH_MIDDLE:
+ case EL_CONVEYOR_BELT_1_SWITCH_RIGHT:
+ case EL_CONVEYOR_BELT_2_SWITCH_LEFT:
+ case EL_CONVEYOR_BELT_2_SWITCH_MIDDLE:
+ case EL_CONVEYOR_BELT_2_SWITCH_RIGHT:
+ case EL_CONVEYOR_BELT_3_SWITCH_LEFT:
+ case EL_CONVEYOR_BELT_3_SWITCH_MIDDLE:
+ case EL_CONVEYOR_BELT_3_SWITCH_RIGHT:
+ case EL_CONVEYOR_BELT_4_SWITCH_LEFT:
+ case EL_CONVEYOR_BELT_4_SWITCH_MIDDLE:
+ case EL_CONVEYOR_BELT_4_SWITCH_RIGHT:
+ if (init_game)
+ {
+ int belt_nr = getBeltNrFromBeltSwitchElement(Feld[x][y]);
+ int belt_dir = getBeltDirFromBeltSwitchElement(Feld[x][y]);
+ int belt_dir_nr = getBeltDirNrFromBeltSwitchElement(Feld[x][y]);
+
+ if (game.belt_dir_nr[belt_nr] == 3) /* initial value */
+ {
+ game.belt_dir[belt_nr] = belt_dir;
+ game.belt_dir_nr[belt_nr] = belt_dir_nr;
+ }
+ else /* more than one switch -- set it like the first switch */
+ {
+ Feld[x][y] = Feld[x][y] - belt_dir_nr + game.belt_dir_nr[belt_nr];
+ }
+ }
+ break;
+
+ case EL_LIGHT_SWITCH_ACTIVE:
+ if (init_game)
+ game.light_time_left = level.time_light * FRAMES_PER_SECOND;
+ break;
+
+ case EL_INVISIBLE_STEELWALL:
+ case EL_INVISIBLE_WALL:
+ case EL_INVISIBLE_SAND:
+ if (game.light_time_left > 0 ||
+ game.lenses_time_left > 0)
+ Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
+ break;
+
+ case EL_EMC_MAGIC_BALL:
+ if (game.ball_state)
+ Feld[x][y] = EL_EMC_MAGIC_BALL_ACTIVE;
+ break;
+
+ case EL_EMC_MAGIC_BALL_SWITCH:
+ if (game.ball_state)
+ Feld[x][y] = EL_EMC_MAGIC_BALL_SWITCH_ACTIVE;
+ break;
+
+ case EL_TRIGGER_PLAYER:
+ case EL_TRIGGER_ELEMENT:
+ case EL_TRIGGER_CE_VALUE:
+ case EL_TRIGGER_CE_SCORE:
+ case EL_SELF:
+ case EL_ANY_ELEMENT:
+ case EL_CURRENT_CE_VALUE:
+ case EL_CURRENT_CE_SCORE:
+ case EL_PREV_CE_1:
+ case EL_PREV_CE_2:
+ case EL_PREV_CE_3:
+ case EL_PREV_CE_4:
+ case EL_PREV_CE_5:
+ case EL_PREV_CE_6:
+ case EL_PREV_CE_7:
+ case EL_PREV_CE_8:
+ case EL_NEXT_CE_1:
+ case EL_NEXT_CE_2:
+ case EL_NEXT_CE_3:
+ case EL_NEXT_CE_4:
+ case EL_NEXT_CE_5:
+ case EL_NEXT_CE_6:
+ case EL_NEXT_CE_7:
+ case EL_NEXT_CE_8:
+ /* reference elements should not be used on the playfield */
+ Feld[x][y] = EL_EMPTY;
+ break;
+
+ default:
+ if (IS_CUSTOM_ELEMENT(element))
+ {
+ if (CAN_MOVE(element))
+ InitMovDir(x, y);
+
+ if (!element_info[element].use_last_ce_value || init_game)
+ CustomValue[x][y] = GET_NEW_CE_VALUE(Feld[x][y]);
+ }
+ else if (IS_GROUP_ELEMENT(element))
+ {
+ Feld[x][y] = GetElementFromGroupElement(element);
+
+ InitField(x, y, init_game);
+ }
+
+ break;
+ }
+
+ if (!init_game)
+ CheckTriggeredElementChange(x, y, element, CE_CREATION_OF_X);
+}
+
+inline static void InitField_WithBug1(int x, int y, boolean init_game)
+{
+ InitField(x, y, init_game);
+
+ /* not needed to call InitMovDir() -- already done by InitField()! */
+ if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
+ CAN_MOVE(Feld[x][y]))
+ InitMovDir(x, y);
+}
+
+inline static void InitField_WithBug2(int x, int y, boolean init_game)
+{
+ int old_element = Feld[x][y];
+
+ InitField(x, y, init_game);
+
+ /* not needed to call InitMovDir() -- already done by InitField()! */
+ if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
+ CAN_MOVE(old_element) &&
+ (old_element < EL_MOLE_LEFT || old_element > EL_MOLE_DOWN))
+ InitMovDir(x, y);
+
+ /* this case is in fact a combination of not less than three bugs:
+ first, it calls InitMovDir() for elements that can move, although this is
+ already done by InitField(); then, it checks the element that was at this
+ field _before_ the call to InitField() (which can change it); lastly, it
+ was not called for "mole with direction" elements, which were treated as
+ "cannot move" due to (fixed) wrong element initialization in "src/init.c"
+ */
+}
+
+static int get_key_element_from_nr(int key_nr)
+{
+ int key_base_element = (key_nr >= STD_NUM_KEYS ? EL_EMC_KEY_5 - STD_NUM_KEYS :
+ level.game_engine_type == GAME_ENGINE_TYPE_EM ?
+ EL_EM_KEY_1 : EL_KEY_1);
+
+ return key_base_element + key_nr;
+}
+
+static int get_next_dropped_element(struct PlayerInfo *player)
+{
+ return (player->inventory_size > 0 ?
+ player->inventory_element[player->inventory_size - 1] :
+ player->inventory_infinite_element != EL_UNDEFINED ?
+ player->inventory_infinite_element :
+ player->dynabombs_left > 0 ?
+ EL_DYNABOMB_PLAYER_1_ACTIVE + player->index_nr :
+ EL_UNDEFINED);
+}
+
+static int get_inventory_element_from_pos(struct PlayerInfo *player, int pos)
+{
+ /* pos >= 0: get element from bottom of the stack;
+ pos < 0: get element from top of the stack */
+
+ if (pos < 0)
+ {
+ int min_inventory_size = -pos;
+ int inventory_pos = player->inventory_size - min_inventory_size;
+ int min_dynabombs_left = min_inventory_size - player->inventory_size;
+
+ return (player->inventory_size >= min_inventory_size ?
+ player->inventory_element[inventory_pos] :
+ player->inventory_infinite_element != EL_UNDEFINED ?
+ player->inventory_infinite_element :
+ player->dynabombs_left >= min_dynabombs_left ?
+ EL_DYNABOMB_PLAYER_1 + player->index_nr :
+ EL_UNDEFINED);
+ }
+ else
+ {
+ int min_dynabombs_left = pos + 1;
+ int min_inventory_size = pos + 1 - player->dynabombs_left;
+ int inventory_pos = pos - player->dynabombs_left;
+
+ return (player->inventory_infinite_element != EL_UNDEFINED ?
+ player->inventory_infinite_element :
+ player->dynabombs_left >= min_dynabombs_left ?
+ EL_DYNABOMB_PLAYER_1 + player->index_nr :
+ player->inventory_size >= min_inventory_size ?
+ player->inventory_element[inventory_pos] :
+ EL_UNDEFINED);
+ }
+}
+
+static int compareGamePanelOrderInfo(const void *object1, const void *object2)
+{
+ const struct GamePanelOrderInfo *gpo1 = (struct GamePanelOrderInfo *)object1;
+ const struct GamePanelOrderInfo *gpo2 = (struct GamePanelOrderInfo *)object2;
+ int compare_result;
+
+ if (gpo1->sort_priority != gpo2->sort_priority)
+ compare_result = gpo1->sort_priority - gpo2->sort_priority;
+ else
+ compare_result = gpo1->nr - gpo2->nr;
+
+ return compare_result;
+}
+
+int getPlayerInventorySize(int player_nr)
+{
+ if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+ return level.native_em_level->ply[player_nr]->dynamite;
+ else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
+ return level.native_sp_level->game_sp->red_disk_count;
+ else
+ return stored_player[player_nr].inventory_size;
+}
+
+void InitGameControlValues()
+{
+ int i;
+
+ for (i = 0; game_panel_controls[i].nr != -1; i++)
+ {
+ struct GamePanelControlInfo *gpc = &game_panel_controls[i];
+ struct GamePanelOrderInfo *gpo = &game_panel_order[i];
+ struct TextPosInfo *pos = gpc->pos;
+ int nr = gpc->nr;
+ int type = gpc->type;
+
+ if (nr != i)
+ {
+ Error(ERR_INFO, "'game_panel_controls' structure corrupted at %d", i);
+ Error(ERR_EXIT, "this should not happen -- please debug");
+ }
+
+ /* force update of game controls after initialization */
+ gpc->value = gpc->last_value = -1;
+ gpc->frame = gpc->last_frame = -1;
+ gpc->gfx_frame = -1;
+
+ /* determine panel value width for later calculation of alignment */
+ if (type == TYPE_INTEGER || type == TYPE_STRING)
+ {
+ pos->width = pos->size * getFontWidth(pos->font);
+ pos->height = getFontHeight(pos->font);
+ }
+ else if (type == TYPE_ELEMENT)
+ {
+ pos->width = pos->size;
+ pos->height = pos->size;
+ }
+
+ /* fill structure for game panel draw order */
+ gpo->nr = gpc->nr;
+ gpo->sort_priority = pos->sort_priority;
+ }
+
+ /* sort game panel controls according to sort_priority and control number */
+ qsort(game_panel_order, NUM_GAME_PANEL_CONTROLS,
+ sizeof(struct GamePanelOrderInfo), compareGamePanelOrderInfo);
+}
+
+void UpdatePlayfieldElementCount()
+{
+ boolean use_element_count = FALSE;
+ int i, j, x, y;
+
+ /* first check if it is needed at all to calculate playfield element count */
+ for (i = GAME_PANEL_ELEMENT_COUNT_1; i <= GAME_PANEL_ELEMENT_COUNT_8; i++)
+ if (!PANEL_DEACTIVATED(game_panel_controls[i].pos))
+ use_element_count = TRUE;
+
+ if (!use_element_count)
+ return;
+
+ for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+ element_info[i].element_count = 0;
+
+ SCAN_PLAYFIELD(x, y)
+ {
+ element_info[Feld[x][y]].element_count++;
+ }
+
+ for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
+ for (j = 0; j < MAX_NUM_ELEMENTS; j++)
+ if (IS_IN_GROUP(j, i))
+ element_info[EL_GROUP_START + i].element_count +=
+ element_info[j].element_count;
+}
+
+void UpdateGameControlValues()
+{
+ int i, k;
+ int time = (local_player->LevelSolved ?
+ local_player->LevelSolved_CountingTime :
+ level.game_engine_type == GAME_ENGINE_TYPE_EM ?
+ level.native_em_level->lev->time :
+ level.game_engine_type == GAME_ENGINE_TYPE_SP ?
+ level.native_sp_level->game_sp->time_played :
+ level.game_engine_type == GAME_ENGINE_TYPE_MM ?
+ game_mm.energy_left :
+ game.no_time_limit ? TimePlayed : TimeLeft);
+ int score = (local_player->LevelSolved ?
+ local_player->LevelSolved_CountingScore :
+ level.game_engine_type == GAME_ENGINE_TYPE_EM ?
+ level.native_em_level->lev->score :
+ level.game_engine_type == GAME_ENGINE_TYPE_SP ?
+ level.native_sp_level->game_sp->score :
+ level.game_engine_type == GAME_ENGINE_TYPE_MM ?
+ game_mm.score :
+ local_player->score);
+ int gems = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
+ level.native_em_level->lev->required :
+ level.game_engine_type == GAME_ENGINE_TYPE_SP ?
+ level.native_sp_level->game_sp->infotrons_still_needed :
+ level.game_engine_type == GAME_ENGINE_TYPE_MM ?
+ game_mm.kettles_still_needed :
+ local_player->gems_still_needed);
+ int exit_closed = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
+ level.native_em_level->lev->required > 0 :
+ level.game_engine_type == GAME_ENGINE_TYPE_SP ?
+ level.native_sp_level->game_sp->infotrons_still_needed > 0 :
+ level.game_engine_type == GAME_ENGINE_TYPE_MM ?
+ game_mm.kettles_still_needed > 0 ||
+ game_mm.lights_still_needed > 0 :
+ local_player->gems_still_needed > 0 ||
+ local_player->sokobanfields_still_needed > 0 ||
+ local_player->lights_still_needed > 0);
+ int health = (local_player->LevelSolved ?
+ local_player->LevelSolved_CountingHealth :
+ level.game_engine_type == GAME_ENGINE_TYPE_MM ?
+ MM_HEALTH(game_mm.laser_overload_value) :
+ local_player->health);
+
+ UpdatePlayfieldElementCount();
+
+ /* update game panel control values */
+
+ game_panel_controls[GAME_PANEL_LEVEL_NUMBER].value = level_nr;
+ game_panel_controls[GAME_PANEL_GEMS].value = gems;
+
+ game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value = 0;
+ for (i = 0; i < MAX_NUM_KEYS; i++)
+ game_panel_controls[GAME_PANEL_KEY_1 + i].value = EL_EMPTY;
+ game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_EMPTY;
+ game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value = 0;
+
+ if (game.centered_player_nr == -1)
+ {
+ for (i = 0; i < MAX_PLAYERS; i++)
+ {
+ /* only one player in Supaplex game engine */
+ if (level.game_engine_type == GAME_ENGINE_TYPE_SP && i > 0)
+ break;
+
+ for (k = 0; k < MAX_NUM_KEYS; k++)
+ {
+ if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+ {
+ if (level.native_em_level->ply[i]->keys & (1 << k))
+ game_panel_controls[GAME_PANEL_KEY_1 + k].value =
+ get_key_element_from_nr(k);
+ }
+ else if (stored_player[i].key[k])
+ game_panel_controls[GAME_PANEL_KEY_1 + k].value =
+ get_key_element_from_nr(k);
+ }
+
+ game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
+ getPlayerInventorySize(i);
+
+ if (stored_player[i].num_white_keys > 0)
+ game_panel_controls[GAME_PANEL_KEY_WHITE].value =
+ EL_DC_KEY_WHITE;
+
+ game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
+ stored_player[i].num_white_keys;
+ }
+ }
+ else
+ {
+ int player_nr = game.centered_player_nr;
+
+ for (k = 0; k < MAX_NUM_KEYS; k++)
+ {
+ if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+ {
+ if (level.native_em_level->ply[player_nr]->keys & (1 << k))
+ game_panel_controls[GAME_PANEL_KEY_1 + k].value =
+ get_key_element_from_nr(k);
+ }
+ else if (stored_player[player_nr].key[k])
+ game_panel_controls[GAME_PANEL_KEY_1 + k].value =
+ get_key_element_from_nr(k);
+ }
+
+ game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
+ getPlayerInventorySize(player_nr);
+
+ if (stored_player[player_nr].num_white_keys > 0)
+ game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_DC_KEY_WHITE;
+
+ game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
+ stored_player[player_nr].num_white_keys;
+ }
+
+ for (i = 0; i < NUM_PANEL_INVENTORY; i++)
+ {
+ game_panel_controls[GAME_PANEL_INVENTORY_FIRST_1 + i].value =
+ get_inventory_element_from_pos(local_player, i);
+ game_panel_controls[GAME_PANEL_INVENTORY_LAST_1 + i].value =
+ get_inventory_element_from_pos(local_player, -i - 1);
+ }
+
+ game_panel_controls[GAME_PANEL_SCORE].value = score;
+ game_panel_controls[GAME_PANEL_HIGHSCORE].value = highscore[0].Score;
+
+ game_panel_controls[GAME_PANEL_TIME].value = time;
+
+ game_panel_controls[GAME_PANEL_TIME_HH].value = time / 3600;
+ game_panel_controls[GAME_PANEL_TIME_MM].value = (time / 60) % 60;
+ game_panel_controls[GAME_PANEL_TIME_SS].value = time % 60;
+
+ if (level.time == 0)
+ game_panel_controls[GAME_PANEL_TIME_ANIM].value = 100;
+ else
+ game_panel_controls[GAME_PANEL_TIME_ANIM].value = time * 100 / level.time;
+
+ game_panel_controls[GAME_PANEL_HEALTH].value = health;
+ game_panel_controls[GAME_PANEL_HEALTH_ANIM].value = health;
+
+ game_panel_controls[GAME_PANEL_FRAME].value = FrameCounter;
+
+ game_panel_controls[GAME_PANEL_SHIELD_NORMAL].value =
+ (local_player->shield_normal_time_left > 0 ? EL_SHIELD_NORMAL_ACTIVE :
+ EL_EMPTY);
+ game_panel_controls[GAME_PANEL_SHIELD_NORMAL_TIME].value =
+ local_player->shield_normal_time_left;
+ game_panel_controls[GAME_PANEL_SHIELD_DEADLY].value =
+ (local_player->shield_deadly_time_left > 0 ? EL_SHIELD_DEADLY_ACTIVE :
+ EL_EMPTY);
+ game_panel_controls[GAME_PANEL_SHIELD_DEADLY_TIME].value =
+ local_player->shield_deadly_time_left;
+
+ game_panel_controls[GAME_PANEL_EXIT].value =
+ (exit_closed ? EL_EXIT_CLOSED : EL_EXIT_OPEN);
+
+ game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL].value =
+ (game.ball_state ? EL_EMC_MAGIC_BALL_ACTIVE : EL_EMC_MAGIC_BALL);
+ game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL_SWITCH].value =
+ (game.ball_state ? EL_EMC_MAGIC_BALL_SWITCH_ACTIVE :
+ EL_EMC_MAGIC_BALL_SWITCH);
+
+ game_panel_controls[GAME_PANEL_LIGHT_SWITCH].value =
+ (game.light_time_left > 0 ? EL_LIGHT_SWITCH_ACTIVE : EL_LIGHT_SWITCH);
+ game_panel_controls[GAME_PANEL_LIGHT_SWITCH_TIME].value =
+ game.light_time_left;
+
+ game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH].value =
+ (game.timegate_time_left > 0 ? EL_TIMEGATE_OPEN : EL_TIMEGATE_CLOSED);
+ game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH_TIME].value =
+ game.timegate_time_left;
+
+ game_panel_controls[GAME_PANEL_SWITCHGATE_SWITCH].value =
+ EL_SWITCHGATE_SWITCH_UP + game.switchgate_pos;
+
+ game_panel_controls[GAME_PANEL_EMC_LENSES].value =
+ (game.lenses_time_left > 0 ? EL_EMC_LENSES : EL_EMPTY);
+ game_panel_controls[GAME_PANEL_EMC_LENSES_TIME].value =
+ game.lenses_time_left;
+
+ game_panel_controls[GAME_PANEL_EMC_MAGNIFIER].value =
+ (game.magnify_time_left > 0 ? EL_EMC_MAGNIFIER : EL_EMPTY);
+ game_panel_controls[GAME_PANEL_EMC_MAGNIFIER_TIME].value =
+ game.magnify_time_left;
+
+ game_panel_controls[GAME_PANEL_BALLOON_SWITCH].value =
+ (game.wind_direction == MV_LEFT ? EL_BALLOON_SWITCH_LEFT :
+ game.wind_direction == MV_RIGHT ? EL_BALLOON_SWITCH_RIGHT :
+ game.wind_direction == MV_UP ? EL_BALLOON_SWITCH_UP :
+ game.wind_direction == MV_DOWN ? EL_BALLOON_SWITCH_DOWN :
+ EL_BALLOON_SWITCH_NONE);
+
+ game_panel_controls[GAME_PANEL_DYNABOMB_NUMBER].value =
+ local_player->dynabomb_count;
+ game_panel_controls[GAME_PANEL_DYNABOMB_SIZE].value =
+ local_player->dynabomb_size;
+ game_panel_controls[GAME_PANEL_DYNABOMB_POWER].value =
+ (local_player->dynabomb_xl ? EL_DYNABOMB_INCREASE_POWER : EL_EMPTY);
+
+ game_panel_controls[GAME_PANEL_PENGUINS].value =
+ local_player->friends_still_needed;
+
+ game_panel_controls[GAME_PANEL_SOKOBAN_OBJECTS].value =
+ local_player->sokobanfields_still_needed;
+ game_panel_controls[GAME_PANEL_SOKOBAN_FIELDS].value =
+ local_player->sokobanfields_still_needed;
+
+ game_panel_controls[GAME_PANEL_ROBOT_WHEEL].value =
+ (game.robot_wheel_active ? EL_ROBOT_WHEEL_ACTIVE : EL_ROBOT_WHEEL);
+
+ for (i = 0; i < NUM_BELTS; i++)
+ {
+ game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1 + i].value =
+ (game.belt_dir[i] != MV_NONE ? EL_CONVEYOR_BELT_1_MIDDLE_ACTIVE :
+ EL_CONVEYOR_BELT_1_MIDDLE) + i;
+ game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1_SWITCH + i].value =
+ getBeltSwitchElementFromBeltNrAndBeltDir(i, game.belt_dir[i]);
+ }
+
+ game_panel_controls[GAME_PANEL_MAGIC_WALL].value =
+ (game.magic_wall_active ? EL_MAGIC_WALL_ACTIVE : EL_MAGIC_WALL);
+ game_panel_controls[GAME_PANEL_MAGIC_WALL_TIME].value =
+ game.magic_wall_time_left;
+
+ game_panel_controls[GAME_PANEL_GRAVITY_STATE].value =
+ local_player->gravity;
+
+ for (i = 0; i < NUM_PANEL_GRAPHICS; i++)
+ game_panel_controls[GAME_PANEL_GRAPHIC_1 + i].value = EL_GRAPHIC_1 + i;
+
+ for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
+ game_panel_controls[GAME_PANEL_ELEMENT_1 + i].value =
+ (IS_DRAWABLE_ELEMENT(game.panel.element[i].id) ?
+ game.panel.element[i].id : EL_UNDEFINED);
+
+ for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
+ game_panel_controls[GAME_PANEL_ELEMENT_COUNT_1 + i].value =
+ (IS_VALID_ELEMENT(game.panel.element_count[i].id) ?
+ element_info[game.panel.element_count[i].id].element_count : 0);
+
+ for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
+ game_panel_controls[GAME_PANEL_CE_SCORE_1 + i].value =
+ (IS_CUSTOM_ELEMENT(game.panel.ce_score[i].id) ?
+ element_info[game.panel.ce_score[i].id].collect_score : 0);
+
+ for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
+ game_panel_controls[GAME_PANEL_CE_SCORE_1_ELEMENT + i].value =
+ (IS_CUSTOM_ELEMENT(game.panel.ce_score_element[i].id) ?
+ element_info[game.panel.ce_score_element[i].id].collect_score :
+ EL_UNDEFINED);
+
+ game_panel_controls[GAME_PANEL_PLAYER_NAME].value = 0;
+ game_panel_controls[GAME_PANEL_LEVEL_NAME].value = 0;
+ game_panel_controls[GAME_PANEL_LEVEL_AUTHOR].value = 0;
+
+ /* update game panel control frames */
+
+ for (i = 0; game_panel_controls[i].nr != -1; i++)
+ {
+ struct GamePanelControlInfo *gpc = &game_panel_controls[i];
+
+ if (gpc->type == TYPE_ELEMENT)
+ {
+ if (gpc->value != EL_UNDEFINED && gpc->value != EL_EMPTY)
+ {
+ int last_anim_random_frame = gfx.anim_random_frame;
+ int element = gpc->value;
+ int graphic = el2panelimg(element);
+
+ if (gpc->value != gpc->last_value)
+ {
+ gpc->gfx_frame = 0;
+ gpc->gfx_random = INIT_GFX_RANDOM();
+ }
+ else
+ {
+ gpc->gfx_frame++;
+
+ if (ANIM_MODE(graphic) == ANIM_RANDOM &&
+ IS_NEXT_FRAME(gpc->gfx_frame, graphic))
+ gpc->gfx_random = INIT_GFX_RANDOM();
+ }
+
+ if (ANIM_MODE(graphic) == ANIM_RANDOM)
+ gfx.anim_random_frame = gpc->gfx_random;
+
+ if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
+ gpc->gfx_frame = element_info[element].collect_score;
+
+ gpc->frame = getGraphicAnimationFrame(el2panelimg(gpc->value),
+ gpc->gfx_frame);
+
+ if (ANIM_MODE(graphic) == ANIM_RANDOM)
+ gfx.anim_random_frame = last_anim_random_frame;
+ }
+ }
+ else if (gpc->type == TYPE_GRAPHIC)
+ {
+ if (gpc->graphic != IMG_UNDEFINED)
+ {
+ int last_anim_random_frame = gfx.anim_random_frame;
+ int graphic = gpc->graphic;
+
+ if (gpc->value != gpc->last_value)
+ {
+ gpc->gfx_frame = 0;
+ gpc->gfx_random = INIT_GFX_RANDOM();
+ }
+ else
+ {
+ gpc->gfx_frame++;
+
+ if (ANIM_MODE(graphic) == ANIM_RANDOM &&
+ IS_NEXT_FRAME(gpc->gfx_frame, graphic))
+ gpc->gfx_random = INIT_GFX_RANDOM();
+ }
+
+ if (ANIM_MODE(graphic) == ANIM_RANDOM)
+ gfx.anim_random_frame = gpc->gfx_random;
+
+ gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
+
+ if (ANIM_MODE(graphic) == ANIM_RANDOM)
+ gfx.anim_random_frame = last_anim_random_frame;
+ }
+ }
+ }
+}
+
+void DisplayGameControlValues()
+{
+ boolean redraw_panel = FALSE;
+ int i;
+
+ for (i = 0; game_panel_controls[i].nr != -1; i++)
+ {
+ struct GamePanelControlInfo *gpc = &game_panel_controls[i];
+
+ if (PANEL_DEACTIVATED(gpc->pos))
+ continue;
+
+ if (gpc->value == gpc->last_value &&
+ gpc->frame == gpc->last_frame)
+ continue;
+
+ redraw_panel = TRUE;
+ }
+
+ if (!redraw_panel)
+ return;
+
+ /* copy default game door content to main double buffer */
+
+ /* !!! CHECK AGAIN !!! */
+ SetPanelBackground();
+ // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
+ DrawBackground(DX, DY, DXSIZE, DYSIZE);
+
+ /* redraw game control buttons */
+ RedrawGameButtons();
+
+ SetGameStatus(GAME_MODE_PSEUDO_PANEL);
+
+ for (i = 0; i < NUM_GAME_PANEL_CONTROLS; i++)
+ {
+ int nr = game_panel_order[i].nr;
+ struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
+ struct TextPosInfo *pos = gpc->pos;
+ int type = gpc->type;
+ int value = gpc->value;
+ int frame = gpc->frame;
+ int size = pos->size;
+ int font = pos->font;
+ boolean draw_masked = pos->draw_masked;
+ int mask_mode = (draw_masked ? BLIT_MASKED : BLIT_OPAQUE);
+
+ if (PANEL_DEACTIVATED(pos))
+ continue;
+
+ gpc->last_value = value;
+ gpc->last_frame = frame;
+
+ if (type == TYPE_INTEGER)
+ {
+ if (nr == GAME_PANEL_LEVEL_NUMBER ||
+ nr == GAME_PANEL_TIME)
+ {
+ boolean use_dynamic_size = (size == -1 ? TRUE : FALSE);
+
+ if (use_dynamic_size) /* use dynamic number of digits */
+ {
+ int value_change = (nr == GAME_PANEL_LEVEL_NUMBER ? 100 : 1000);
+ int size1 = (nr == GAME_PANEL_LEVEL_NUMBER ? 2 : 3);
+ int size2 = size1 + 1;
+ int font1 = pos->font;
+ int font2 = pos->font_alt;
+
+ size = (value < value_change ? size1 : size2);
+ font = (value < value_change ? font1 : font2);
+ }
+ }
+
+ /* correct text size if "digits" is zero or less */
+ if (size <= 0)
+ size = strlen(int2str(value, size));
+
+ /* dynamically correct text alignment */
+ pos->width = size * getFontWidth(font);
+
+ DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
+ int2str(value, size), font, mask_mode);
+ }
+ else if (type == TYPE_ELEMENT)
+ {
+ int element, graphic;
+ Bitmap *src_bitmap;
+ int src_x, src_y;
+ int width, height;
+ int dst_x = PANEL_XPOS(pos);
+ int dst_y = PANEL_YPOS(pos);
+
+ if (value != EL_UNDEFINED && value != EL_EMPTY)
+ {
+ element = value;
+ graphic = el2panelimg(value);
+
+ // printf("::: %d, '%s' [%d]\n", element, EL_NAME(element), size);
+
+ if (element >= EL_GRAPHIC_1 && element <= EL_GRAPHIC_8 && size == 0)
+ size = TILESIZE;
+
+ getSizedGraphicSource(graphic, frame, size, &src_bitmap,
+ &src_x, &src_y);
+
+ width = graphic_info[graphic].width * size / TILESIZE;
+ height = graphic_info[graphic].height * size / TILESIZE;
+
+ if (draw_masked)
+ BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
+ dst_x, dst_y);
+ else
+ BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
+ dst_x, dst_y);
+ }
+ }
+ else if (type == TYPE_GRAPHIC)
+ {
+ int graphic = gpc->graphic;
+ int graphic_active = gpc->graphic_active;
+ Bitmap *src_bitmap;
+ int src_x, src_y;
+ int width, height;
+ int dst_x = PANEL_XPOS(pos);
+ int dst_y = PANEL_YPOS(pos);
+ boolean skip = (pos->class == get_hash_from_key("mm_engine_only") &&
+ level.game_engine_type != GAME_ENGINE_TYPE_MM);
+
+ if (graphic != IMG_UNDEFINED && !skip)
+ {
+ if (pos->style == STYLE_REVERSE)
+ value = 100 - value;
+
+ getGraphicSource(graphic_active, frame, &src_bitmap, &src_x, &src_y);
+
+ if (pos->direction & MV_HORIZONTAL)
+ {
+ width = graphic_info[graphic_active].width * value / 100;
+ height = graphic_info[graphic_active].height;
+
+ if (pos->direction == MV_LEFT)
+ {
+ src_x += graphic_info[graphic_active].width - width;
+ dst_x += graphic_info[graphic_active].width - width;
+ }
+ }
+ else
+ {
+ width = graphic_info[graphic_active].width;
+ height = graphic_info[graphic_active].height * value / 100;
+
+ if (pos->direction == MV_UP)
+ {
+ src_y += graphic_info[graphic_active].height - height;
+ dst_y += graphic_info[graphic_active].height - height;
+ }
+ }
+
+ if (draw_masked)
+ BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
+ dst_x, dst_y);
+ else
+ BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
+ dst_x, dst_y);
+
+ getGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
+
+ if (pos->direction & MV_HORIZONTAL)
+ {
+ if (pos->direction == MV_RIGHT)
+ {
+ src_x += width;
+ dst_x += width;
+ }
+ else
+ {
+ dst_x = PANEL_XPOS(pos);
+ }
+
+ width = graphic_info[graphic].width - width;
+ }
+ else
+ {
+ if (pos->direction == MV_DOWN)
+ {
+ src_y += height;
+ dst_y += height;
+ }
+ else
+ {
+ dst_y = PANEL_YPOS(pos);
+ }
+
+ height = graphic_info[graphic].height - height;
+ }
+
+ if (draw_masked)
+ BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
+ dst_x, dst_y);
+ else
+ BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
+ dst_x, dst_y);
+ }
+ }
+ else if (type == TYPE_STRING)
+ {
+ boolean active = (value != 0);
+ char *state_normal = "off";
+ char *state_active = "on";
+ char *state = (active ? state_active : state_normal);
+ char *s = (nr == GAME_PANEL_GRAVITY_STATE ? state :
+ nr == GAME_PANEL_PLAYER_NAME ? setup.player_name :
+ nr == GAME_PANEL_LEVEL_NAME ? level.name :
+ nr == GAME_PANEL_LEVEL_AUTHOR ? level.author : NULL);
+
+ if (nr == GAME_PANEL_GRAVITY_STATE)
+ {
+ int font1 = pos->font; /* (used for normal state) */
+ int font2 = pos->font_alt; /* (used for active state) */
+
+ font = (active ? font2 : font1);
+ }
+
+ if (s != NULL)
+ {
+ char *s_cut;
+
+ if (size <= 0)
+ {
+ /* don't truncate output if "chars" is zero or less */
+ size = strlen(s);
+
+ /* dynamically correct text alignment */
+ pos->width = size * getFontWidth(font);
+ }
+
+ s_cut = getStringCopyN(s, size);
+
+ DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
+ s_cut, font, mask_mode);
+
+ free(s_cut);
+ }
+ }
+
+ redraw_mask |= REDRAW_DOOR_1;
+ }
+
+ SetGameStatus(GAME_MODE_PLAYING);
+}
+
+void UpdateAndDisplayGameControlValues()
+{
+ if (tape.deactivate_display)
+ return;
+
+ UpdateGameControlValues();
+ DisplayGameControlValues();
+}
+
+void UpdateGameDoorValues()
+{
+ UpdateGameControlValues();
+}
+
+void DrawGameDoorValues()
+{
+ DisplayGameControlValues();
+}
+
+
+/*
+ =============================================================================
+ InitGameEngine()
+ -----------------------------------------------------------------------------
+ initialize game engine due to level / tape version number
+ =============================================================================
+*/
+
+static void InitGameEngine()
+{
+ int i, j, k, l, x, y;
+
+ /* set game engine from tape file when re-playing, else from level file */
+ game.engine_version = (tape.playing ? tape.engine_version :
+ level.game_version);
+
+ /* set single or multi-player game mode (needed for re-playing tapes) */
+ game.team_mode = setup.team_mode;
+
+ if (tape.playing)
+ {
+ int num_players = 0;
+
+ for (i = 0; i < MAX_PLAYERS; i++)
+ if (tape.player_participates[i])
+ num_players++;
+
+ /* multi-player tapes contain input data for more than one player */
+ game.team_mode = (num_players > 1);
+ }
+
+ /* ---------------------------------------------------------------------- */
+ /* set flags for bugs and changes according to active game engine version */
+ /* ---------------------------------------------------------------------- */
+
+ /*
+ Summary of bugfix/change:
+ Fixed handling for custom elements that change when pushed by the player.
+
+ Fixed/changed in version:
+ 3.1.0
+
+ Description:
+ Before 3.1.0, custom elements that "change when pushing" changed directly
+ after the player started pushing them (until then handled in "DigField()").
+ Since 3.1.0, these custom elements are not changed until the "pushing"
+ move of the element is finished (now handled in "ContinueMoving()").
+
+ Affected levels/tapes:
+ The first condition is generally needed for all levels/tapes before version
+ 3.1.0, which might use the old behaviour before it was changed; known tapes
+ that are affected are some tapes from the level set "Walpurgis Gardens" by
+ Jamie Cullen.
+ The second condition is an exception from the above case and is needed for
+ the special case of tapes recorded with game (not engine!) version 3.1.0 or
+ above (including some development versions of 3.1.0), but before it was
+ known that this change would break tapes like the above and was fixed in
+ 3.1.1, so that the changed behaviour was active although the engine version
+ while recording maybe was before 3.1.0. There is at least one tape that is
+ affected by this exception, which is the tape for the one-level set "Bug
+ Machine" by Juergen Bonhagen.
+ */
+
+ game.use_change_when_pushing_bug =
+ (game.engine_version < VERSION_IDENT(3,1,0,0) &&
+ !(tape.playing &&
+ tape.game_version >= VERSION_IDENT(3,1,0,0) &&
+ tape.game_version < VERSION_IDENT(3,1,1,0)));
+
+ /*
+ Summary of bugfix/change:
+ Fixed handling for blocking the field the player leaves when moving.
+
+ Fixed/changed in version:
+ 3.1.1
+
+ Description:
+ Before 3.1.1, when "block last field when moving" was enabled, the field
+ the player is leaving when moving was blocked for the time of the move,
+ and was directly unblocked afterwards. This resulted in the last field
+ being blocked for exactly one less than the number of frames of one player
+ move. Additionally, even when blocking was disabled, the last field was
+ blocked for exactly one frame.
+ Since 3.1.1, due to changes in player movement handling, the last field
+ is not blocked at all when blocking is disabled. When blocking is enabled,
+ the last field is blocked for exactly the number of frames of one player
+ move. Additionally, if the player is Murphy, the hero of Supaplex, the
+ last field is blocked for exactly one more than the number of frames of
+ one player move.
+
+ Affected levels/tapes:
+ (!!! yet to be determined -- probably many !!!)
+ */
+
+ game.use_block_last_field_bug =
+ (game.engine_version < VERSION_IDENT(3,1,1,0));
+
+ game_em.use_single_button =
+ (game.engine_version > VERSION_IDENT(4,0,0,2));
+
+ game_em.use_snap_key_bug =
+ (game.engine_version < VERSION_IDENT(4,0,1,0));
+
+ /* ---------------------------------------------------------------------- */
+
+ /* set maximal allowed number of custom element changes per game frame */
+ game.max_num_changes_per_frame = 1;
+
+ /* default scan direction: scan playfield from top/left to bottom/right */
+ InitPlayfieldScanMode(CA_ARG_SCAN_MODE_NORMAL);
+
+ /* dynamically adjust element properties according to game engine version */
+ InitElementPropertiesEngine(game.engine_version);
+
+#if 0
+ printf("level %d: level version == %06d\n", level_nr, level.game_version);
+ printf(" tape version == %06d [%s] [file: %06d]\n",
+ tape.engine_version, (tape.playing ? "PLAYING" : "RECORDING"),
+ tape.file_version);
+ printf(" => game.engine_version == %06d\n", game.engine_version);
+#endif
+
+ /* ---------- initialize player's initial move delay --------------------- */
+
+ /* dynamically adjust player properties according to level information */
+ for (i = 0; i < MAX_PLAYERS; i++)
+ game.initial_move_delay_value[i] =
+ get_move_delay_from_stepsize(level.initial_player_stepsize[i]);
+
+ /* dynamically adjust player properties according to game engine version */
+ for (i = 0; i < MAX_PLAYERS; i++)
+ game.initial_move_delay[i] =
+ (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
+ game.initial_move_delay_value[i] : 0);
+
+ /* ---------- initialize player's initial push delay --------------------- */
+
+ /* dynamically adjust player properties according to game engine version */
+ game.initial_push_delay_value =
+ (game.engine_version < VERSION_IDENT(3,0,7,1) ? 5 : -1);
+
+ /* ---------- initialize changing elements ------------------------------- */
+
+ /* initialize changing elements information */
+ for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+ {
+ struct ElementInfo *ei = &element_info[i];
+
+ /* this pointer might have been changed in the level editor */
+ ei->change = &ei->change_page[0];
+
+ if (!IS_CUSTOM_ELEMENT(i))
+ {
+ ei->change->target_element = EL_EMPTY_SPACE;
+ ei->change->delay_fixed = 0;
+ ei->change->delay_random = 0;
+ ei->change->delay_frames = 1;
+ }
+
+ for (j = 0; j < NUM_CHANGE_EVENTS; j++)
+ {
+ ei->has_change_event[j] = FALSE;
+
+ ei->event_page_nr[j] = 0;
+ ei->event_page[j] = &ei->change_page[0];
+ }
+ }
+
+ /* add changing elements from pre-defined list */
+ for (i = 0; change_delay_list[i].element != EL_UNDEFINED; i++)
+ {
+ struct ChangingElementInfo *ch_delay = &change_delay_list[i];
+ struct ElementInfo *ei = &element_info[ch_delay->element];
+
+ ei->change->target_element = ch_delay->target_element;
+ ei->change->delay_fixed = ch_delay->change_delay;
+
+ ei->change->pre_change_function = ch_delay->pre_change_function;
+ ei->change->change_function = ch_delay->change_function;
+ ei->change->post_change_function = ch_delay->post_change_function;
+
+ ei->change->can_change = TRUE;
+ ei->change->can_change_or_has_action = TRUE;
+
+ ei->has_change_event[CE_DELAY] = TRUE;
+
+ SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE, TRUE);
+ SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE);
+ }
+
+ /* ---------- initialize internal run-time variables --------------------- */
+
+ for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
+ {
+ struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
+
+ for (j = 0; j < ei->num_change_pages; j++)
+ {
+ ei->change_page[j].can_change_or_has_action =
+ (ei->change_page[j].can_change |
+ ei->change_page[j].has_action);
+ }
+ }
+
+ /* add change events from custom element configuration */
+ for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
+ {
+ struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
+
+ for (j = 0; j < ei->num_change_pages; j++)
+ {
+ if (!ei->change_page[j].can_change_or_has_action)
+ continue;
+
+ for (k = 0; k < NUM_CHANGE_EVENTS; k++)
+ {
+ /* only add event page for the first page found with this event */
+ if (ei->change_page[j].has_event[k] && !(ei->has_change_event[k]))
+ {
+ ei->has_change_event[k] = TRUE;
+
+ ei->event_page_nr[k] = j;
+ ei->event_page[k] = &ei->change_page[j];
+ }
+ }
+ }
+ }
+
+ /* ---------- initialize reference elements in change conditions --------- */
+
+ for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
+ {
+ int element = EL_CUSTOM_START + i;
+ struct ElementInfo *ei = &element_info[element];
+
+ for (j = 0; j < ei->num_change_pages; j++)
+ {
+ int trigger_element = ei->change_page[j].initial_trigger_element;
+
+ if (trigger_element >= EL_PREV_CE_8 &&
+ trigger_element <= EL_NEXT_CE_8)
+ trigger_element = RESOLVED_REFERENCE_ELEMENT(element, trigger_element);
+
+ ei->change_page[j].trigger_element = trigger_element;
+ }
+ }
+
+ /* ---------- initialize run-time trigger player and element ------------- */
+
+ for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
+ {
+ struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
+
+ for (j = 0; j < ei->num_change_pages; j++)
+ {
+ ei->change_page[j].actual_trigger_element = EL_EMPTY;
+ ei->change_page[j].actual_trigger_player = EL_EMPTY;
+ ei->change_page[j].actual_trigger_player_bits = CH_PLAYER_NONE;
+ ei->change_page[j].actual_trigger_side = CH_SIDE_NONE;
+ ei->change_page[j].actual_trigger_ce_value = 0;
+ ei->change_page[j].actual_trigger_ce_score = 0;
+ }
+ }
+
+ /* ---------- initialize trigger events ---------------------------------- */
+
+ /* initialize trigger events information */
+ for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+ for (j = 0; j < NUM_CHANGE_EVENTS; j++)
+ trigger_events[i][j] = FALSE;
+
+ /* add trigger events from element change event properties */
+ for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+ {
+ struct ElementInfo *ei = &element_info[i];
+
+ for (j = 0; j < ei->num_change_pages; j++)
+ {
+ if (!ei->change_page[j].can_change_or_has_action)
+ continue;
+
+ if (ei->change_page[j].has_event[CE_BY_OTHER_ACTION])
+ {
+ int trigger_element = ei->change_page[j].trigger_element;
+
+ for (k = 0; k < NUM_CHANGE_EVENTS; k++)
+ {
+ if (ei->change_page[j].has_event[k])
+ {
+ if (IS_GROUP_ELEMENT(trigger_element))
+ {
+ struct ElementGroupInfo *group =
+ element_info[trigger_element].group;
+
+ for (l = 0; l < group->num_elements_resolved; l++)
+ trigger_events[group->element_resolved[l]][k] = TRUE;
+ }
+ else if (trigger_element == EL_ANY_ELEMENT)
+ for (l = 0; l < MAX_NUM_ELEMENTS; l++)
+ trigger_events[l][k] = TRUE;
+ else
+ trigger_events[trigger_element][k] = TRUE;
+ }
+ }
+ }
+ }
+ }
+
+ /* ---------- initialize push delay -------------------------------------- */
+
+ /* initialize push delay values to default */
+ for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+ {
+ if (!IS_CUSTOM_ELEMENT(i))
+ {
+ /* set default push delay values (corrected since version 3.0.7-1) */
+ if (game.engine_version < VERSION_IDENT(3,0,7,1))
+ {
+ element_info[i].push_delay_fixed = 2;
+ element_info[i].push_delay_random = 8;
+ }
+ else
+ {
+ element_info[i].push_delay_fixed = 8;
+ element_info[i].push_delay_random = 8;
+ }
+ }
+ }
+
+ /* set push delay value for certain elements from pre-defined list */
+ for (i = 0; push_delay_list[i].element != EL_UNDEFINED; i++)
+ {
+ int e = push_delay_list[i].element;
+
+ element_info[e].push_delay_fixed = push_delay_list[i].push_delay_fixed;
+ element_info[e].push_delay_random = push_delay_list[i].push_delay_random;
+ }
+
+ /* set push delay value for Supaplex elements for newer engine versions */
+ if (game.engine_version >= VERSION_IDENT(3,1,0,0))
+ {
+ for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+ {
+ if (IS_SP_ELEMENT(i))
+ {
+ /* set SP push delay to just enough to push under a falling zonk */
+ int delay = (game.engine_version >= VERSION_IDENT(3,1,1,0) ? 8 : 6);
+
+ element_info[i].push_delay_fixed = delay;
+ element_info[i].push_delay_random = 0;
+ }
+ }
+ }
+
+ /* ---------- initialize move stepsize ----------------------------------- */
+
+ /* initialize move stepsize values to default */
+ for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+ if (!IS_CUSTOM_ELEMENT(i))
+ element_info[i].move_stepsize = MOVE_STEPSIZE_NORMAL;
+
+ /* set move stepsize value for certain elements from pre-defined list */
+ for (i = 0; move_stepsize_list[i].element != EL_UNDEFINED; i++)
+ {
+ int e = move_stepsize_list[i].element;
+
+ element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
+ }
+
+ /* ---------- initialize collect score ----------------------------------- */
+
+ /* initialize collect score values for custom elements from initial value */
+ for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+ if (IS_CUSTOM_ELEMENT(i))
+ element_info[i].collect_score = element_info[i].collect_score_initial;
+
+ /* ---------- initialize collect count ----------------------------------- */
+
+ /* initialize collect count values for non-custom elements */
+ for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+ if (!IS_CUSTOM_ELEMENT(i))
+ element_info[i].collect_count_initial = 0;
+
+ /* add collect count values for all elements from pre-defined list */
+ for (i = 0; collect_count_list[i].element != EL_UNDEFINED; i++)
+ element_info[collect_count_list[i].element].collect_count_initial =
+ collect_count_list[i].count;
+
+ /* ---------- initialize access direction -------------------------------- */
+
+ /* initialize access direction values to default (access from every side) */
+ for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+ if (!IS_CUSTOM_ELEMENT(i))
+ element_info[i].access_direction = MV_ALL_DIRECTIONS;
+
+ /* set access direction value for certain elements from pre-defined list */
+ for (i = 0; access_direction_list[i].element != EL_UNDEFINED; i++)
+ element_info[access_direction_list[i].element].access_direction =
+ access_direction_list[i].direction;
+
+ /* ---------- initialize explosion content ------------------------------- */
+ for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+ {
+ if (IS_CUSTOM_ELEMENT(i))
+ continue;
+
+ for (y = 0; y < 3; y++) for (x = 0; x < 3; x++)
+ {
+ /* (content for EL_YAMYAM set at run-time with game.yamyam_content_nr) */
+
+ element_info[i].content.e[x][y] =
+ (i == EL_PLAYER_1 ? EL_EMERALD_YELLOW :
+ i == EL_PLAYER_2 ? EL_EMERALD_RED :
+ i == EL_PLAYER_3 ? EL_EMERALD :
+ i == EL_PLAYER_4 ? EL_EMERALD_PURPLE :
+ i == EL_MOLE ? EL_EMERALD_RED :
+ i == EL_PENGUIN ? EL_EMERALD_PURPLE :
+ i == EL_BUG ? (x == 1 && y == 1 ? EL_DIAMOND : EL_EMERALD) :
+ i == EL_BD_BUTTERFLY ? EL_BD_DIAMOND :
+ i == EL_SP_ELECTRON ? EL_SP_INFOTRON :
+ i == EL_AMOEBA_TO_DIAMOND ? level.amoeba_content :
+ i == EL_WALL_EMERALD ? EL_EMERALD :
+ i == EL_WALL_DIAMOND ? EL_DIAMOND :
+ i == EL_WALL_BD_DIAMOND ? EL_BD_DIAMOND :
+ i == EL_WALL_EMERALD_YELLOW ? EL_EMERALD_YELLOW :
+ i == EL_WALL_EMERALD_RED ? EL_EMERALD_RED :
+ i == EL_WALL_EMERALD_PURPLE ? EL_EMERALD_PURPLE :
+ i == EL_WALL_PEARL ? EL_PEARL :
+ i == EL_WALL_CRYSTAL ? EL_CRYSTAL :
+ EL_EMPTY);
+ }
+ }
+
+ /* ---------- initialize recursion detection ------------------------------ */
+ recursion_loop_depth = 0;
+ recursion_loop_detected = FALSE;
+ recursion_loop_element = EL_UNDEFINED;
+
+ /* ---------- initialize graphics engine ---------------------------------- */
+ game.scroll_delay_value =
+ (game.forced_scroll_delay_value != -1 ? game.forced_scroll_delay_value :
+ setup.scroll_delay ? setup.scroll_delay_value : 0);
+ game.scroll_delay_value =
+ MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
+
+ /* ---------- initialize game engine snapshots ---------------------------- */
+ for (i = 0; i < MAX_PLAYERS; i++)
+ game.snapshot.last_action[i] = 0;
+ game.snapshot.changed_action = FALSE;
+ game.snapshot.collected_item = FALSE;
+ game.snapshot.mode =
+ (strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_STEP) ?
+ SNAPSHOT_MODE_EVERY_STEP :
+ strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_MOVE) ?
+ SNAPSHOT_MODE_EVERY_MOVE :
+ strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_COLLECT) ?
+ SNAPSHOT_MODE_EVERY_COLLECT : SNAPSHOT_MODE_OFF);
+ game.snapshot.save_snapshot = FALSE;
+
+ /* ---------- initialize level time for Supaplex engine ------------------- */
+ /* Supaplex levels with time limit currently unsupported -- should be added */
+ if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
+ level.time = 0;
+}
+
+int get_num_special_action(int element, int action_first, int action_last)
+{
+ int num_special_action = 0;
+ int i, j;
+
+ for (i = action_first; i <= action_last; i++)
+ {
+ boolean found = FALSE;
+
+ for (j = 0; j < NUM_DIRECTIONS; j++)
+ if (el_act_dir2img(element, i, j) !=
+ el_act_dir2img(element, ACTION_DEFAULT, j))
+ found = TRUE;
+
+ if (found)
+ num_special_action++;
+ else
+ break;
+ }
+
+ return num_special_action;
+}
+
+
+/*
+ =============================================================================
+ InitGame()
+ -----------------------------------------------------------------------------
+ initialize and start new game
+ =============================================================================
+*/
+
+#if DEBUG_INIT_PLAYER
+static void DebugPrintPlayerStatus(char *message)
+{
+ int i;
+
+ if (!options.debug)
+ return;
+
+ printf("%s:\n", message);
+
+ for (i = 0; i < MAX_PLAYERS; i++)
+ {
+ struct PlayerInfo *player = &stored_player[i];
+
+ printf("- player %d: present == %d, connected == %d [%d/%d], active == %d",
+ i + 1,
+ player->present,
+ player->connected,
+ player->connected_locally,
+ player->connected_network,
+ player->active);
+
+ if (local_player == player)
+ printf(" (local player)");
+
+ printf("\n");
+ }
+}
+#endif
+
+void InitGame()
+{
+ int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
+ int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
+ int fade_mask = REDRAW_FIELD;
+
+ 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 initial_move_dir = MV_DOWN;
+ int i, j, x, y;
+
+ // required here to update video display before fading (FIX THIS)
+ DrawMaskedBorder(REDRAW_DOOR_2);
+
+ if (!game.restart_level)
+ CloseDoor(DOOR_CLOSE_1);
+
+ SetGameStatus(GAME_MODE_PLAYING);
+
+ if (level_editor_test_game)
+ FadeSkipNextFadeIn();
+ else
+ FadeSetEnterScreen();
+
+ if (CheckIfGlobalBorderOrPlayfieldViewportHasChanged())
+ fade_mask = REDRAW_ALL;
+
+ FadeLevelSoundsAndMusic();
+
+ ExpireSoundLoops(TRUE);
+
+ if (!level_editor_test_game)
+ FadeOut(fade_mask);
+
+ /* needed if different viewport properties defined for playing */
+ ChangeViewportPropertiesIfNeeded();
+
+ ClearField();
+
+ DrawCompleteVideoDisplay();
+
+ OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
+
+ InitGameEngine();
+ InitGameControlValues();
+
+ /* don't play tapes over network */
+ network_playing = (network.enabled && !tape.playing);
+
+ for (i = 0; i < MAX_PLAYERS; i++)
+ {
+ struct PlayerInfo *player = &stored_player[i];
+
+ player->index_nr = i;
+ player->index_bit = (1 << i);
+ player->element_nr = EL_PLAYER_1 + i;
+
+ player->present = FALSE;
+ player->active = FALSE;
+ player->mapped = FALSE;
+
+ player->killed = FALSE;
+ player->reanimated = FALSE;
+
+ player->action = 0;
+ player->effective_action = 0;
+ player->programmed_action = 0;
+
+ player->mouse_action.lx = 0;
+ player->mouse_action.ly = 0;
+ player->mouse_action.button = 0;
+ player->mouse_action.button_hint = 0;
+
+ player->effective_mouse_action.lx = 0;
+ player->effective_mouse_action.ly = 0;
+ player->effective_mouse_action.button = 0;
+ player->effective_mouse_action.button_hint = 0;
+
+ player->score = 0;
+ player->score_final = 0;
+
+ player->health = MAX_HEALTH;
+ player->health_final = MAX_HEALTH;
+
+ 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 < MAX_NUM_KEYS; j++)
+ player->key[j] = FALSE;
+
+ player->num_white_keys = 0;
+
+ player->dynabomb_count = 0;
+ player->dynabomb_size = 1;
+ player->dynabombs_left = 0;
+ player->dynabomb_xl = FALSE;
+
+ player->MovDir = initial_move_dir;
+ player->MovPos = 0;
+ player->GfxPos = 0;
+ player->GfxDir = initial_move_dir;
+ player->GfxAction = ACTION_DEFAULT;
+ player->Frame = 0;
+ player->StepFrame = 0;
+
+ player->initial_element = player->element_nr;
+ player->artwork_element =
+ (level.use_artwork_element[i] ? level.artwork_element[i] :
+ player->element_nr);
+ player->use_murphy = FALSE;
+
+ player->block_last_field = FALSE; /* initialized in InitPlayerField() */
+ player->block_delay_adjustment = 0; /* initialized in InitPlayerField() */
+
+ player->gravity = level.initial_player_gravity[i];
+
+ player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
+
+ player->actual_frame_counter = 0;
+
+ player->step_counter = 0;
+
+ player->last_move_dir = initial_move_dir;
+
+ player->is_active = FALSE;
+
+ player->is_waiting = FALSE;
+ player->is_moving = FALSE;
+ player->is_auto_moving = FALSE;
+ player->is_digging = FALSE;
+ player->is_snapping = FALSE;
+ player->is_collecting = FALSE;
+ player->is_pushing = FALSE;
+ player->is_switching = FALSE;
+ player->is_dropping = FALSE;
+ player->is_dropping_pressed = FALSE;
+
+ player->is_bored = FALSE;
+ player->is_sleeping = FALSE;
+
+ player->was_waiting = TRUE;
+ player->was_moving = FALSE;
+ player->was_snapping = FALSE;
+ player->was_dropping = FALSE;
+
+ player->force_dropping = FALSE;
+
+ player->frame_counter_bored = -1;
+ player->frame_counter_sleeping = -1;
+
+ player->anim_delay_counter = 0;
+ player->post_delay_counter = 0;
+
+ player->dir_waiting = initial_move_dir;
+ player->action_waiting = ACTION_DEFAULT;
+ player->last_action_waiting = ACTION_DEFAULT;
+ player->special_action_bored = ACTION_DEFAULT;
+ player->special_action_sleeping = ACTION_DEFAULT;
+
+ player->switch_x = -1;
+ player->switch_y = -1;
+
+ player->drop_x = -1;
+ player->drop_y = -1;
+
+ player->show_envelope = 0;
+
+ SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
+
+ player->push_delay = -1; /* initialized when pushing starts */
+ player->push_delay_value = game.initial_push_delay_value;
+
+ player->drop_delay = 0;
+ player->drop_pressed_delay = 0;
+
+ player->last_jx = -1;
+ player->last_jy = -1;
+ player->jx = -1;
+ player->jy = -1;
+
+ player->shield_normal_time_left = 0;
+ player->shield_deadly_time_left = 0;
+
+ player->inventory_infinite_element = EL_UNDEFINED;
+ player->inventory_size = 0;
+
+ if (level.use_initial_inventory[i])
+ {
+ for (j = 0; j < level.initial_inventory_size[i]; j++)
+ {
+ int element = level.initial_inventory_content[i][j];
+ int collect_count = element_info[element].collect_count_initial;
+ int k;
+
+ if (!IS_CUSTOM_ELEMENT(element))
+ collect_count = 1;
+
+ if (collect_count == 0)
+ player->inventory_infinite_element = element;
+ else
+ for (k = 0; k < collect_count; k++)
+ if (player->inventory_size < MAX_INVENTORY_SIZE)
+ player->inventory_element[player->inventory_size++] = element;
+ }
+ }
+
+ DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
+ SnapField(player, 0, 0);
+
+ player->LevelSolved = FALSE;
+ player->GameOver = FALSE;
+
+ player->LevelSolved_GameWon = FALSE;
+ player->LevelSolved_GameEnd = FALSE;
+ player->LevelSolved_PanelOff = FALSE;
+ player->LevelSolved_SaveTape = FALSE;
+ player->LevelSolved_SaveScore = FALSE;
+
+ player->LevelSolved_CountingTime = 0;
+ player->LevelSolved_CountingScore = 0;
+ player->LevelSolved_CountingHealth = 0;
+
+ map_player_action[i] = i;
+ }
+
+ network_player_action_received = FALSE;
+
+ /* initial null action */
+ if (network_playing)
+ SendToServer_MovePlayer(MV_NONE);
+
+ ZX = ZY = -1;
+ ExitX = ExitY = -1;
+
+ FrameCounter = 0;
+ TimeFrames = 0;
+ TimePlayed = 0;
+ TimeLeft = level.time;
+ TapeTime = 0;
+
+ ScreenMovDir = MV_NONE;
+ ScreenMovPos = 0;
+ ScreenGfxPos = 0;
+
+ ScrollStepSize = 0; /* will be correctly initialized by ScrollScreen() */
+
+ AllPlayersGone = FALSE;
+
+ game.no_time_limit = (level.time == 0);
+
+ game.yamyam_content_nr = 0;
+ game.robot_wheel_active = FALSE;
+ 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.wind_direction = level.wind_direction_initial;
+
+ game.lenses_time_left = 0;
+ game.magnify_time_left = 0;
+
+ game.ball_state = level.ball_state_initial;
+ game.ball_content_nr = 0;
+
+ game.envelope_active = FALSE;
+
+ for (i = 0; i < NUM_BELTS; i++)
+ {
+ game.belt_dir[i] = MV_NONE;
+ game.belt_dir_nr[i] = 3; /* not moving, next moving left */
+ }
+
+ for (i = 0; i < MAX_NUM_AMOEBA; i++)
+ AmoebaCnt[i] = AmoebaCnt2[i] = 0;
+
+#if DEBUG_INIT_PLAYER
+ DebugPrintPlayerStatus("Player status at level initialization");
+#endif
+
+ SCAN_PLAYFIELD(x, y)
+ {
+ Feld[x][y] = level.field[x][y];
+ MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
+ ChangeDelay[x][y] = 0;
+ ChangePage[x][y] = -1;
+ CustomValue[x][y] = 0; /* initialized in InitField() */
+ Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
+ AmoebaNr[x][y] = 0;
+ WasJustMoving[x][y] = 0;
+ WasJustFalling[x][y] = 0;
+ CheckCollision[x][y] = 0;
+ CheckImpact[x][y] = 0;
+ Stop[x][y] = FALSE;
+ Pushed[x][y] = FALSE;
+
+ ChangeCount[x][y] = 0;
+ ChangeEvent[x][y] = -1;
+
+ ExplodePhase[x][y] = 0;
+ ExplodeDelay[x][y] = 0;
+ ExplodeField[x][y] = EX_TYPE_NONE;
+
+ RunnerVisit[x][y] = 0;
+ PlayerVisit[x][y] = 0;
+
+ GfxFrame[x][y] = 0;
+ GfxRandom[x][y] = INIT_GFX_RANDOM();
+ GfxElement[x][y] = EL_UNDEFINED;
+ GfxAction[x][y] = ACTION_DEFAULT;
+ GfxDir[x][y] = MV_NONE;
+ GfxRedraw[x][y] = GFX_REDRAW_NONE;
+ }
+
+ SCAN_PLAYFIELD(x, y)
+ {
+ 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);
+
+ ResetGfxAnimation(x, y);
+ }
+
+ InitBeltMovement();
+
+ for (i = 0; i < MAX_PLAYERS; i++)
+ {
+ struct PlayerInfo *player = &stored_player[i];
+
+ /* set number of special actions for bored and sleeping animation */
+ player->num_special_action_bored =
+ get_num_special_action(player->artwork_element,
+ ACTION_BORING_1, ACTION_BORING_LAST);
+ player->num_special_action_sleeping =
+ get_num_special_action(player->artwork_element,
+ ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
+ }
+
+ game.emulation = (emulate_bd ? EMU_BOULDERDASH :
+ emulate_sb ? EMU_SOKOBAN :
+ emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
+
+ /* initialize type of slippery elements */
+ for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+ {
+ if (!IS_CUSTOM_ELEMENT(i))
+ {
+ /* default: elements slip down either to the left or right randomly */
+ element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
+
+ /* SP style elements prefer to slip down on the left side */
+ if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
+ element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
+
+ /* BD style elements prefer to slip down on the left side */
+ if (game.emulation == EMU_BOULDERDASH)
+ element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
+ }
+ }
+
+ /* initialize explosion and ignition delay */
+ for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+ {
+ if (!IS_CUSTOM_ELEMENT(i))
+ {
+ int num_phase = 8;
+ int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
+ game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
+ game.emulation == EMU_SUPAPLEX ? 3 : 2);
+ int last_phase = (num_phase + 1) * delay;
+ int half_phase = (num_phase / 2) * delay;
+
+ element_info[i].explosion_delay = last_phase - 1;
+ element_info[i].ignition_delay = half_phase;
+
+ if (i == EL_BLACK_ORB)
+ element_info[i].ignition_delay = 1;
+ }
+ }
+
+ /* correct non-moving belts to start moving left */
+ for (i = 0; i < NUM_BELTS; i++)
+ if (game.belt_dir[i] == MV_NONE)
+ game.belt_dir_nr[i] = 3; /* not moving, next moving left */
+
+#if USE_NEW_PLAYER_ASSIGNMENTS
+ for (i = 0; i < MAX_PLAYERS; i++)
+ {
+ stored_player[i].connected = FALSE;
+
+ /* in network game mode, the local player might not be the first player */
+ if (stored_player[i].connected_locally)
+ local_player = &stored_player[i];
+ }
+
+ if (!network.enabled)
+ local_player->connected = TRUE;
+
+ if (tape.playing)
+ {
+ for (i = 0; i < MAX_PLAYERS; i++)
+ stored_player[i].connected = tape.player_participates[i];
+ }
+ else if (network.enabled)
+ {
+ /* add team mode players connected over the network (needed for correct
+ assignment of player figures from level to locally playing players) */
+
+ for (i = 0; i < MAX_PLAYERS; i++)
+ if (stored_player[i].connected_network)
+ stored_player[i].connected = TRUE;
+ }
+ else if (game.team_mode)
+ {
+ /* try to guess locally connected team mode players (needed for correct
+ assignment of player figures from level to locally playing players) */
+
+ for (i = 0; i < MAX_PLAYERS; i++)
+ if (setup.input[i].use_joystick ||
+ setup.input[i].key.left != KSYM_UNDEFINED)
+ stored_player[i].connected = TRUE;
+ }
+
+#if DEBUG_INIT_PLAYER
+ DebugPrintPlayerStatus("Player status after level initialization");
+#endif
+
+#if DEBUG_INIT_PLAYER
+ if (options.debug)
+ printf("Reassigning players ...\n");
+#endif
+
+ /* 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)
+ {
+ struct PlayerInfo *field_player = NULL;
+
+#if DEBUG_INIT_PLAYER
+ if (options.debug)
+ printf("- looking for field player for player %d ...\n", i + 1);
+#endif
+
+ /* assign first free player found that is present in the playfield */
+
+ /* first try: look for unmapped playfield player that is not connected */
+ for (j = 0; j < MAX_PLAYERS; j++)
+ if (field_player == NULL &&
+ stored_player[j].present &&
+ !stored_player[j].mapped &&
+ !stored_player[j].connected)
+ field_player = &stored_player[j];
+
+ /* second try: look for *any* unmapped playfield player */
+ for (j = 0; j < MAX_PLAYERS; j++)
+ if (field_player == NULL &&
+ stored_player[j].present &&
+ !stored_player[j].mapped)
+ field_player = &stored_player[j];
+
+ if (field_player != NULL)
+ {
+ int jx = field_player->jx, jy = field_player->jy;
+
+#if DEBUG_INIT_PLAYER
+ if (options.debug)
+ printf("- found player %d\n", field_player->index_nr + 1);
+#endif
+
+ player->present = FALSE;
+ player->active = FALSE;
+
+ field_player->present = TRUE;
+ field_player->active = TRUE;
+
+ /*
+ player->initial_element = field_player->initial_element;
+ player->artwork_element = field_player->artwork_element;
+
+ player->block_last_field = field_player->block_last_field;
+ player->block_delay_adjustment = field_player->block_delay_adjustment;
+ */
+
+ StorePlayer[jx][jy] = field_player->element_nr;
+
+ field_player->jx = field_player->last_jx = jx;
+ field_player->jy = field_player->last_jy = jy;
+
+ if (local_player == player)
+ local_player = field_player;
+
+ map_player_action[field_player->index_nr] = i;
+
+ field_player->mapped = TRUE;
+
+#if DEBUG_INIT_PLAYER
+ if (options.debug)
+ printf("- map_player_action[%d] == %d\n",
+ field_player->index_nr + 1, i + 1);
+#endif
+ }
+ }
+
+ if (player->connected && player->present)
+ player->mapped = TRUE;
+ }
+
+#if DEBUG_INIT_PLAYER
+ DebugPrintPlayerStatus("Player status after player assignment (first stage)");
+#endif
+
+#else
+
+ /* 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 *field_player = &stored_player[j];
+ int jx = field_player->jx, jy = field_player->jy;
+
+ /* assign first free player found that is present in the playfield */
+ if (field_player->present && !field_player->connected)
+ {
+ player->present = TRUE;
+ player->active = TRUE;
+
+ field_player->present = FALSE;
+ field_player->active = FALSE;
+
+ player->initial_element = field_player->initial_element;
+ player->artwork_element = field_player->artwork_element;
+
+ player->block_last_field = field_player->block_last_field;
+ player->block_delay_adjustment = field_player->block_delay_adjustment;
+
+ StorePlayer[jx][jy] = player->element_nr;
+
+ player->jx = player->last_jx = jx;
+ player->jy = player->last_jy = jy;
+
+ break;
+ }
+ }
+ }
+ }
+#endif
+
+#if 0
+ printf("::: local_player->present == %d\n", local_player->present);
+#endif
+
+ /* set focus to local player for network games, else to all players */
+ game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
+ game.centered_player_nr_next = game.centered_player_nr;
+ game.set_centered_player = FALSE;
+
+ if (network_playing && tape.recording)
+ {
+ /* store client dependent player focus when recording network games */
+ tape.centered_player_nr_next = game.centered_player_nr_next;
+ tape.set_centered_player = TRUE;
+ }
+
+ if (tape.playing)
+ {
+ /* when playing a tape, eliminate all players who do not participate */
+
+#if USE_NEW_PLAYER_ASSIGNMENTS
+
+ if (!game.team_mode)
+ {
+ for (i = 0; i < MAX_PLAYERS; i++)
+ {
+ if (stored_player[i].active &&
+ !tape.player_participates[map_player_action[i]])
+ {
+ struct PlayerInfo *player = &stored_player[i];
+ int jx = player->jx, jy = player->jy;
+
+#if DEBUG_INIT_PLAYER
+ if (options.debug)
+ printf("Removing player %d at (%d, %d)\n", i + 1, jx, jy);
+#endif
+
+ player->active = FALSE;
+ StorePlayer[jx][jy] = 0;
+ Feld[jx][jy] = EL_EMPTY;
+ }
+ }
+ }
+
+#else
+
+ 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;
+ }
+ }
+#endif
+ }
+ else if (!network.enabled && !game.team_mode) /* && !tape.playing */
+ {
+ /* when in single player mode, eliminate all but the local player */
+
+ for (i = 0; i < MAX_PLAYERS; i++)
+ {
+ struct PlayerInfo *player = &stored_player[i];
+
+ if (player->active && player != local_player)
+ {
+ int jx = player->jx, jy = player->jy;
+
+ player->active = FALSE;
+ player->present = 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)
+ {
+#if USE_NEW_PLAYER_ASSIGNMENTS
+ for (i = 0; i < MAX_PLAYERS; i++)
+ if (stored_player[i].connected)
+ tape.player_participates[i] = TRUE;
+#else
+ for (i = 0; i < MAX_PLAYERS; i++)
+ if (stored_player[i].active)
+ tape.player_participates[i] = TRUE;
+#endif
+ }
+
+#if DEBUG_INIT_PLAYER
+ DebugPrintPlayerStatus("Player status after player assignment (final stage)");
+#endif
+
+ 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 (full_lev_fieldx <= SCR_FIELDX)
+ SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
+ if (full_lev_fieldy <= SCR_FIELDY)
+ SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
+
+ if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
+ SBX_Left--;
+ if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
+ SBY_Upper--;
+
+ /* 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;
+ int player_nr = local_player->index_nr;
+
+ SCAN_PLAYFIELD(x, y)
+ {
+ int element = Feld[x][y];
+ int content;
+ int xx, yy;
+ boolean is_player;
+
+ if (level.use_start_element[player_nr] &&
+ level.start_element[player_nr] == element &&
+ found_rating < 4)
+ {
+ start_x = x;
+ start_y = y;
+
+ found_rating = 4;
+ found_element = element;
+ }
+
+ if (!IS_CUSTOM_ELEMENT(element))
+ continue;
+
+ if (CAN_CHANGE(element))
+ {
+ for (i = 0; i < element_info[element].num_change_pages; i++)
+ {
+ /* check for player created from custom element as single target */
+ content = element_info[element].change_page[i].target_element;
+ is_player = ELEM_IS_PLAYER(content);
+
+ if (is_player && (found_rating < 3 ||
+ (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++)
+ {
+ /* check for player created from custom element as explosion content */
+ content = element_info[element].content.e[xx][yy];
+ is_player = ELEM_IS_PLAYER(content);
+
+ if (is_player && (found_rating < 2 ||
+ (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++)
+ {
+ /* check for player created from custom element as extended target */
+ content =
+ element_info[element].change_page[i].target_content.e[xx][yy];
+
+ is_player = ELEM_IS_PLAYER(content);
+
+ if (is_player && (found_rating < 1 ||
+ (found_rating == 1 && element < found_element)))
+ {
+ start_x = x + xx - 1;
+ start_y = y + yy - 1;
+
+ found_rating = 1;
+ found_element = element;
+ }
+ }
+ }
+ }
+
+ scroll_x = SCROLL_POSITION_X(start_x);
+ scroll_y = SCROLL_POSITION_Y(start_y);
+ }
+ else
+ {
+ scroll_x = SCROLL_POSITION_X(local_player->jx);
+ scroll_y = SCROLL_POSITION_Y(local_player->jy);
+ }
+
+ /* !!! FIX THIS (START) !!! */
+ if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+ {
+ InitGameEngine_EM();
+ }
+ else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
+ {
+ InitGameEngine_SP();
+ }
+ else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
+ {
+ InitGameEngine_MM();
+ }
+ else
+ {
+ DrawLevel(REDRAW_FIELD);
+ DrawAllPlayers();
+
+ /* after drawing the level, correct some elements */
+ if (game.timegate_time_left == 0)
+ CloseAllOpenTimegates();
+ }
+
+ /* blit playfield from scroll buffer to normal back buffer for fading in */
+ BlitScreenToBitmap(backbuffer);
+ /* !!! FIX THIS (END) !!! */
+
+ DrawMaskedBorder(fade_mask);
+
+ FadeIn(fade_mask);
+
+#if 1
+ // full screen redraw is required at this point in the following cases:
+ // - special editor door undrawn when game was started from level editor
+ // - drawing area (playfield) was changed and has to be removed completely
+ redraw_mask = REDRAW_ALL;
+ BackToFront();
+#endif
+
+ if (!game.restart_level)
+ {
+ /* copy default game door content to main double buffer */
+
+ /* !!! CHECK AGAIN !!! */
+ SetPanelBackground();
+ // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
+ DrawBackground(DX, DY, DXSIZE, DYSIZE);
+ }
+
+ SetPanelBackground();
+ SetDrawBackgroundMask(REDRAW_DOOR_1);
+
+ UpdateAndDisplayGameControlValues();
+
+ if (!game.restart_level)
+ {
+ UnmapGameButtons();
+ UnmapTapeButtons();
+
+ FreeGameButtons();
+ CreateGameButtons();
+
+ MapGameButtons();
+ MapTapeButtons();
+
+ /* copy actual game door content to door double buffer for OpenDoor() */
+ BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
+
+ OpenDoor(DOOR_OPEN_ALL);
+
+ KeyboardAutoRepeatOffUnlessAutoplay();
+
+#if DEBUG_INIT_PLAYER
+ DebugPrintPlayerStatus("Player status (final)");
+#endif
+ }
+
+ UnmapAllGadgets();
+
+ MapGameButtons();
+ MapTapeButtons();
+
+ if (!game.restart_level && !tape.playing)
+ {
+ LevelStats_incPlayed(level_nr);
+
+ SaveLevelSetup_SeriesInfo();
+ }
+
+ game.restart_level = FALSE;
+ game.restart_game_message = NULL;
+
+ if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
+ InitGameActions_MM();
+
+ SaveEngineSnapshotToListInitial();
+
+ if (!game.restart_level)
+ {
+ PlaySound(SND_GAME_STARTING);
+
+ if (setup.sound_music)
+ PlayLevelMusic();
+ }
+}
+
+void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
+ int actual_player_x, int actual_player_y)
+{
+ /* this is used for non-R'n'D game engines to update certain engine values */
+
+ if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+ {
+ actual_player_x = correctLevelPosX_EM(actual_player_x);
+ actual_player_y = correctLevelPosY_EM(actual_player_y);
+ }
+
+ /* needed to determine if sounds are played within the visible screen area */
+ scroll_x = actual_scroll_x;
+ scroll_y = actual_scroll_y;
+
+ /* needed to get player position for "follow finger" playing input method */
+ local_player->jx = actual_player_x;
+ local_player->jy = actual_player_y;
+}
+
+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_YAMYAM_LEFT:
+ case EL_YAMYAM_RIGHT:
+ case EL_YAMYAM_UP:
+ case EL_YAMYAM_DOWN:
+ Feld[x][y] = EL_YAMYAM;
+ MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
+ 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))
+ {
+ struct ElementInfo *ei = &element_info[element];
+ int move_direction_initial = ei->move_direction_initial;
+ int move_pattern = ei->move_pattern;
+
+ if (move_direction_initial == MV_START_PREVIOUS)
+ {
+ if (MovDir[x][y] != MV_NONE)
+ return;
+
+ move_direction_initial = MV_START_AUTOMATIC;
+ }
+
+ if (move_direction_initial == MV_START_RANDOM)
+ MovDir[x][y] = 1 << RND(4);
+ else if (move_direction_initial & MV_ANY_DIRECTION)
+ MovDir[x][y] = move_direction_initial;
+ else if (move_pattern == MV_ALL_DIRECTIONS ||
+ move_pattern == MV_TURNING_LEFT ||
+ move_pattern == MV_TURNING_RIGHT ||
+ move_pattern == MV_TURNING_LEFT_RIGHT ||
+ move_pattern == MV_TURNING_RIGHT_LEFT ||
+ move_pattern == MV_TURNING_RANDOM)
+ MovDir[x][y] = 1 << RND(4);
+ else if (move_pattern == MV_HORIZONTAL)
+ MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
+ else if (move_pattern == MV_VERTICAL)
+ MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
+ else if (move_pattern & MV_ANY_DIRECTION)
+ MovDir[x][y] = element_info[element].move_pattern;
+ else if (move_pattern == MV_ALONG_LEFT_SIDE ||
+ move_pattern == MV_ALONG_RIGHT_SIDE)
+ {
+ /* use random direction as default start direction */
+ if (game.engine_version >= VERSION_IDENT(3,1,0,0))
+ MovDir[x][y] = 1 << RND(4);
+
+ for (i = 0; i < NUM_DIRECTIONS; i++)
+ {
+ int x1 = x + xy[i][0];
+ int y1 = y + xy[i][1];
+
+ if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
+ {
+ if (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 < NUM_DIRECTIONS; 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;
+ }
+
+ GfxDir[x][y] = MovDir[x][y];
+}
+
+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]++;
+}
+
+static void PlayerWins(struct PlayerInfo *player)
+{
+ player->LevelSolved = TRUE;
+ player->GameOver = TRUE;
+
+ player->score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
+ level.native_em_level->lev->score :
+ level.game_engine_type == GAME_ENGINE_TYPE_MM ?
+ game_mm.score :
+ player->score);
+ player->health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
+ MM_HEALTH(game_mm.laser_overload_value) :
+ player->health);
+
+ player->LevelSolved_CountingTime = (game.no_time_limit ? TimePlayed :
+ TimeLeft);
+ player->LevelSolved_CountingScore = player->score_final;
+ player->LevelSolved_CountingHealth = player->health_final;
+}
+
+void GameWon()
+{
+ static int time_count_steps;
+ static int time, time_final;
+ static int score, score_final;
+ static int health, health_final;
+ static int game_over_delay_1 = 0;
+ static int game_over_delay_2 = 0;
+ static int game_over_delay_3 = 0;
+ int game_over_delay_value_1 = 50;
+ int game_over_delay_value_2 = 25;
+ int game_over_delay_value_3 = 50;
+
+ if (!local_player->LevelSolved_GameWon)
+ {
+ int i;
+
+ /* do not start end game actions before the player stops moving (to exit) */
+ if (local_player->MovPos)
+ return;
+
+ local_player->LevelSolved_GameWon = TRUE;
+ local_player->LevelSolved_SaveTape = tape.recording;
+ local_player->LevelSolved_SaveScore = !tape.playing;
+
+ if (!tape.playing)
+ {
+ LevelStats_incSolved(level_nr);
+
+ SaveLevelSetup_SeriesInfo();
+ }
+
+ if (tape.auto_play) /* tape might already be stopped here */
+ tape.auto_play_level_solved = TRUE;
+
+ TapeStop();
+
+ game_over_delay_1 = 0;
+ game_over_delay_2 = 0;
+ game_over_delay_3 = game_over_delay_value_3;
+
+ time = time_final = (game.no_time_limit ? TimePlayed : TimeLeft);
+ score = score_final = local_player->score_final;
+ health = health_final = local_player->health_final;
+
+ if (level.score[SC_TIME_BONUS] > 0)
+ {
+ if (TimeLeft > 0)
+ {
+ time_final = 0;
+ score_final += TimeLeft * level.score[SC_TIME_BONUS];
+ }
+ else if (game.no_time_limit && TimePlayed < 999)
+ {
+ time_final = 999;
+ score_final += (999 - TimePlayed) * level.score[SC_TIME_BONUS];
+ }
+
+ time_count_steps = MAX(1, ABS(time_final - time) / 100);
+
+ game_over_delay_1 = game_over_delay_value_1;
+
+ if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
+ {
+ health_final = 0;
+ score_final += health * level.score[SC_TIME_BONUS];
+
+ game_over_delay_2 = game_over_delay_value_2;
+ }
+
+ local_player->score_final = score_final;
+ local_player->health_final = health_final;
+ }
+
+ if (level_editor_test_game)
+ {
+ time = time_final;
+ score = score_final;
+
+ local_player->LevelSolved_CountingTime = time;
+ local_player->LevelSolved_CountingScore = score;
+
+ game_panel_controls[GAME_PANEL_TIME].value = time;
+ game_panel_controls[GAME_PANEL_SCORE].value = score;
+
+ DisplayGameControlValues();
+ }
+
+ if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
+ {
+ if (ExitX >= 0 && ExitY >= 0) /* local player has left the level */
+ {
+ /* close exit door after last player */
+ if ((AllPlayersGone &&
+ (Feld[ExitX][ExitY] == EL_EXIT_OPEN ||
+ Feld[ExitX][ExitY] == EL_SP_EXIT_OPEN ||
+ Feld[ExitX][ExitY] == EL_STEEL_EXIT_OPEN)) ||
+ Feld[ExitX][ExitY] == EL_EM_EXIT_OPEN ||
+ Feld[ExitX][ExitY] == EL_EM_STEEL_EXIT_OPEN)
+ {
+ int element = Feld[ExitX][ExitY];
+
+ Feld[ExitX][ExitY] =
+ (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
+ element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
+ element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
+ element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
+ EL_EM_STEEL_EXIT_CLOSING);
+
+ PlayLevelSoundElementAction(ExitX, ExitY, element, ACTION_CLOSING);
+ }
+
+ /* player disappears */
+ DrawLevelField(ExitX, ExitY);
+ }
+
+ for (i = 0; i < MAX_PLAYERS; i++)
+ {
+ struct PlayerInfo *player = &stored_player[i];
+
+ if (player->present)
+ {
+ RemovePlayer(player);
+
+ /* player disappears */
+ DrawLevelField(player->jx, player->jy);
+ }
+ }
+ }
+
+ PlaySound(SND_GAME_WINNING);
+ }
+
+ if (game_over_delay_1 > 0)
+ {
+ game_over_delay_1--;
+
+ return;
+ }
+
+ if (time != time_final)
+ {
+ int time_to_go = ABS(time_final - time);
+ int time_count_dir = (time < time_final ? +1 : -1);
+
+ if (time_to_go < time_count_steps)
+ time_count_steps = 1;
+
+ time += time_count_steps * time_count_dir;
+ score += time_count_steps * level.score[SC_TIME_BONUS];
+
+ local_player->LevelSolved_CountingTime = time;
+ local_player->LevelSolved_CountingScore = score;
+
+ game_panel_controls[GAME_PANEL_TIME].value = time;
+ game_panel_controls[GAME_PANEL_SCORE].value = score;
+
+ DisplayGameControlValues();
+
+ if (time == time_final)
+ StopSound(SND_GAME_LEVELTIME_BONUS);
+ else if (setup.sound_loops)
+ PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
+ else
+ PlaySound(SND_GAME_LEVELTIME_BONUS);
+
+ return;
+ }
+
+ if (game_over_delay_2 > 0)
+ {
+ game_over_delay_2--;
+
+ return;
+ }
+
+ if (health != health_final)
+ {
+ int health_count_dir = (health < health_final ? +1 : -1);
+
+ health += health_count_dir;
+ score += level.score[SC_TIME_BONUS];
+
+ local_player->LevelSolved_CountingHealth = health;
+ local_player->LevelSolved_CountingScore = score;
+
+ game_panel_controls[GAME_PANEL_HEALTH].value = health;
+ game_panel_controls[GAME_PANEL_SCORE].value = score;
+
+ DisplayGameControlValues();
+
+ if (health == health_final)
+ StopSound(SND_GAME_LEVELTIME_BONUS);
+ else if (setup.sound_loops)
+ PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
+ else
+ PlaySound(SND_GAME_LEVELTIME_BONUS);
+
+ return;
+ }
+
+ local_player->LevelSolved_PanelOff = TRUE;
+
+ if (game_over_delay_3 > 0)
+ {
+ game_over_delay_3--;
+
+ return;
+ }
+
+ GameEnd();
+}
+
+void GameEnd()
+{
+ int hi_pos;
+ int last_level_nr = level_nr;
+
+ local_player->LevelSolved_GameEnd = TRUE;
+
+ if (local_player->LevelSolved_SaveTape)
+ {
+ /* make sure that request dialog to save tape does not open door again */
+ if (!global.use_envelope_request)
+ CloseDoor(DOOR_CLOSE_1);
+
+ SaveTapeChecked_LevelSolved(tape.level_nr); /* ask to save tape */
+ }
+
+ /* if no tape is to be saved, close both doors simultaneously */
+ CloseDoor(DOOR_CLOSE_ALL);
+
+ if (level_editor_test_game)
+ {
+ SetGameStatus(GAME_MODE_MAIN);
+
+ DrawMainMenu();
+
+ return;
+ }
+
+ if (!local_player->LevelSolved_SaveScore)
+ {
+ SetGameStatus(GAME_MODE_MAIN);
+
+ DrawMainMenu();
+
+ return;
+ }
+
+ if (level_nr == leveldir_current->handicap_level)
+ {
+ leveldir_current->handicap_level++;
+
+ SaveLevelSetup_SeriesInfo();
+ }
+
+ if (setup.increment_levels &&
+ level_nr < leveldir_current->last_level)
+ {
+ level_nr++; /* advance to next level */
+ TapeErase(); /* start with empty tape */
+
+ if (setup.auto_play_next_level)
+ {
+ LoadLevel(level_nr);
+
+ SaveLevelSetup_SeriesInfo();
+ }
+ }
+
+ hi_pos = NewHiScore(last_level_nr);
+
+ if (hi_pos >= 0)
+ {
+ SetGameStatus(GAME_MODE_SCORES);
+
+ DrawHallOfFame(last_level_nr, hi_pos);
+ }
+ else if (!setup.auto_play_next_level || !setup.increment_levels)
+ {
+ SetGameStatus(GAME_MODE_MAIN);
+
+ DrawMainMenu();
+ }
+ else
+ {
+ StartGameActions(network.enabled, setup.autorecord, level.random_seed);
+ }
+}
+
+int NewHiScore(int level_nr)
+{
+ int k, l;
+ int position = -1;
+ boolean one_score_entry_per_name = !program.many_scores_per_name;
+
+ LoadScore(level_nr);
+
+ if (strEqual(setup.player_name, EMPTY_PLAYER_NAME) ||
+ local_player->score_final < highscore[MAX_SCORE_ENTRIES - 1].Score)
+ return -1;
+
+ for (k = 0; k < MAX_SCORE_ENTRIES; k++)
+ {
+ if (local_player->score_final > highscore[k].Score)
+ {
+ /* player has made it to the hall of fame */
+
+ if (k < MAX_SCORE_ENTRIES - 1)
+ {
+ int m = MAX_SCORE_ENTRIES - 1;
+
+ if (one_score_entry_per_name)
+ {
+ for (l = k; l < MAX_SCORE_ENTRIES; l++)
+ if (strEqual(setup.player_name, highscore[l].Name))
+ m = l;
+
+ if (m == k) /* player's new highscore overwrites his old one */
+ goto put_into_list;
+ }
+
+ for (l = m; l > k; l--)
+ {
+ strcpy(highscore[l].Name, highscore[l - 1].Name);
+ highscore[l].Score = highscore[l - 1].Score;
+ }
+ }
+
+ put_into_list:
+
+ 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_final;
+ position = k;
+
+ break;
+ }
+ else if (one_score_entry_per_name &&
+ !strncmp(setup.player_name, highscore[k].Name,
+ MAX_PLAYER_NAME_LEN))
+ break; /* player already there with a higher score */
+ }
+
+ if (position >= 0)
+ SaveScore(level_nr);
+
+ return position;
+}
+
+inline static int getElementMoveStepsizeExt(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 horiz_move = (dx != 0);
+ int sign = (horiz_move ? dx : dy);
+ int step = sign * element_info[element].move_stepsize;
+
+ /* special values for move stepsize for spring and things on conveyor belt */
+ if (horiz_move)
+ {
+ if (CAN_FALL(element) &&
+ y < lev_fieldy - 1 && IS_BELT_ACTIVE(Feld[x][y + 1]))
+ step = sign * MOVE_STEPSIZE_NORMAL / 2;
+ else if (element == EL_SPRING)
+ step = sign * MOVE_STEPSIZE_NORMAL * 2;
+ }
+
+ return step;
+}
+
+inline static int getElementMoveStepsize(int x, int y)
+{
+ return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
+}
+
+void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
+{
+ if (player->GfxAction != action || player->GfxDir != dir)
+ {
+ player->GfxAction = action;
+ player->GfxDir = dir;
+ player->Frame = 0;
+ player->StepFrame = 0;
+ }
+}
+
+static void ResetGfxFrame(int x, int y)
+{
+ // profiling showed that "autotest" spends 10~20% of its time in this function
+ if (DrawingDeactivatedField())
+ return;
+
+ int element = Feld[x][y];
+ int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
+
+ if (graphic_info[graphic].anim_global_sync)
+ GfxFrame[x][y] = FrameCounter;
+ else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
+ GfxFrame[x][y] = CustomValue[x][y];
+ else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
+ GfxFrame[x][y] = element_info[element].collect_score;
+ else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
+ GfxFrame[x][y] = ChangeDelay[x][y];
+}
+
+static void ResetGfxAnimation(int x, int y)
+{
+ GfxAction[x][y] = ACTION_DEFAULT;
+ GfxDir[x][y] = MovDir[x][y];
+ GfxFrame[x][y] = 0;
+
+ ResetGfxFrame(x, y);
+}
+
+static void ResetRandomAnimationValue(int x, int y)
+{
+ GfxRandom[x][y] = INIT_GFX_RANDOM();
+}
+
+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;
+ boolean is_moving_before, is_moving_after;
+
+ /* check if element was/is moving or being moved before/after mode change */
+ is_moving_before = (WasJustMoving[x][y] != 0);
+ is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
+
+ /* reset animation only for moving elements which change direction of moving
+ or which just started or stopped moving
+ (else CEs with property "can move" / "not moving" are reset each frame) */
+ if (is_moving_before != is_moving_after ||
+ direction != MovDir[x][y])
+ ResetGfxAnimation(x, y);
+
+ MovDir[x][y] = direction;
+ GfxDir[x][y] = direction;
+
+ GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
+ direction == MV_DOWN && CAN_FALL(element) ?
+ ACTION_FALLING : ACTION_MOVING);
+
+ /* this is needed for CEs with property "can move" / "not moving" */
+
+ if (is_moving_after)
+ {
+ if (Feld[newx][newy] == EL_EMPTY)
+ Feld[newx][newy] = EL_BLOCKED;
+
+ MovDir[newx][newy] = MovDir[x][y];
+
+ CustomValue[newx][newy] = CustomValue[x][y];
+
+ GfxFrame[newx][newy] = GfxFrame[x][y];
+ GfxRandom[newx][newy] = GfxRandom[x][y];
+ GfxAction[newx][newy] = GfxAction[x][y];
+ GfxDir[newx][newy] = GfxDir[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;
+
+ CustomValue[x][y] = 0;
+
+ AmoebaNr[x][y] = 0;
+ ChangeDelay[x][y] = 0;
+ ChangePage[x][y] = -1;
+ Pushed[x][y] = FALSE;
+
+ GfxElement[x][y] = EL_UNDEFINED;
+ GfxAction[x][y] = ACTION_DEFAULT;
+ GfxDir[x][y] = MV_NONE;
+}
+
+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)
+ {
+ /* element is moving, but target field is not free (blocked), but
+ already occupied by something different (example: acid pool);
+ in this case, only remove the moving field, but not the target */
+
+ RemoveField(oldx, oldy);
+
+ Store[oldx][oldy] = Store2[oldx][oldy] = 0;
+
+ TEST_DrawLevelField(oldx, oldy);
+
+ 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_QUICKSAND_FAST_EMPTYING ||
+ Feld[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
+ Feld[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
+ Feld[oldx][oldy] == EL_DC_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;
+
+ TEST_DrawLevelField(oldx, oldy);
+ TEST_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 (Back[x][y] || Store[x][y])
+ DrawGraphicThruMask(sx, sy, graphic, frame);
+ else
+ DrawGraphic(sx, sy, graphic, frame);
+}
+
+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);
+ PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
+
+ return;
+ }
+ }
+
+ StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
+
+ Bang(x, y);
+}
+
+static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
+{
+ boolean num_checked_players = 0;
+ int i;
+
+ for (i = 0; i < MAX_PLAYERS; i++)
+ {
+ if (stored_player[i].active)
+ {
+ int sx = stored_player[i].jx;
+ int sy = stored_player[i].jy;
+
+ if (num_checked_players == 0)
+ {
+ *sx1 = *sx2 = sx;
+ *sy1 = *sy2 = sy;
+ }
+ else
+ {
+ *sx1 = MIN(*sx1, sx);
+ *sy1 = MIN(*sy1, sy);
+ *sx2 = MAX(*sx2, sx);
+ *sy2 = MAX(*sy2, sy);
+ }
+
+ num_checked_players++;
+ }
+ }
+}
+
+static boolean checkIfAllPlayersFitToScreen_RND()
+{
+ int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
+
+ setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
+
+ return (sx2 - sx1 < SCR_FIELDX &&
+ sy2 - sy1 < SCR_FIELDY);
+}
+
+static void setScreenCenteredToAllPlayers(int *sx, int *sy)
+{
+ int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
+
+ setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
+
+ *sx = (sx1 + sx2) / 2;
+ *sy = (sy1 + sy2) / 2;
+}
+
+void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
+ boolean center_screen, boolean quick_relocation)
+{
+ unsigned int frame_delay_value_old = GetVideoFrameDelay();
+ boolean ffwd_delay = (tape.playing && tape.fast_forward);
+ boolean no_delay = (tape.warp_forward);
+ int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
+ int wait_delay_value = (no_delay ? 0 : frame_delay_value);
+ int new_scroll_x, new_scroll_y;
+
+ if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
+ {
+ /* case 1: quick relocation inside visible screen (without scrolling) */
+
+ RedrawPlayfield();
+
+ return;
+ }
+
+ if (!level.shifted_relocation || center_screen)
+ {
+ /* relocation _with_ centering of screen */
+
+ new_scroll_x = SCROLL_POSITION_X(x);
+ new_scroll_y = SCROLL_POSITION_Y(y);
+ }
+ else
+ {
+ /* relocation _without_ centering of screen */
+
+ int center_scroll_x = SCROLL_POSITION_X(old_x);
+ int center_scroll_y = SCROLL_POSITION_Y(old_y);
+ int offset_x = x + (scroll_x - center_scroll_x);
+ int offset_y = y + (scroll_y - center_scroll_y);
+
+ /* for new screen position, apply previous offset to center position */
+ new_scroll_x = SCROLL_POSITION_X(offset_x);
+ new_scroll_y = SCROLL_POSITION_Y(offset_y);
+ }
+
+ if (quick_relocation)
+ {
+ /* case 2: quick relocation (redraw without visible scrolling) */
+
+ scroll_x = new_scroll_x;
+ scroll_y = new_scroll_y;
+
+ RedrawPlayfield();
+
+ return;
+ }
+
+ /* case 3: visible relocation (with scrolling to new position) */
+
+ ScrollScreen(NULL, SCROLL_GO_ON); /* scroll last frame to full tile */
+
+ SetVideoFrameDelay(wait_delay_value);
+
+ while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
+ {
+ int dx = 0, dy = 0;
+ int fx = FX, fy = FY;
+
+ dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
+ dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
+
+ if (dx == 0 && dy == 0) /* no scrolling needed at all */
+ break;
+
+ scroll_x -= dx;
+ scroll_y -= dy;
+
+ fx += dx * TILEX / 2;
+ fy += dy * TILEY / 2;
+
+ ScrollLevel(dx, dy);
+ DrawAllPlayers();
+
+ /* scroll in two steps of half tile size to make things smoother */
+ BlitBitmap(drawto_field, window, fx, fy, SXSIZE, SYSIZE, SX, SY);
+
+ /* scroll second step to align at full tile size */
+ BlitScreenToBitmap(window);
+ }
+
+ DrawAllPlayers();
+ BackToFront();
+
+ SetVideoFrameDelay(frame_delay_value_old);
+}
+
+void RelocatePlayer(int jx, int jy, int el_player_raw)
+{
+ int el_player = GET_PLAYER_ELEMENT(el_player_raw);
+ int player_nr = GET_PLAYER_NR(el_player);
+ struct PlayerInfo *player = &stored_player[player_nr];
+ boolean ffwd_delay = (tape.playing && tape.fast_forward);
+ boolean no_delay = (tape.warp_forward);
+ int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
+ int wait_delay_value = (no_delay ? 0 : frame_delay_value);
+ int old_jx = player->jx;
+ int old_jy = player->jy;
+ int old_element = Feld[old_jx][old_jy];
+ int element = Feld[jx][jy];
+ boolean player_relocated = (old_jx != jx || old_jy != jy);
+
+ int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
+ int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
+ int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
+ int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
+ int leave_side_horiz = move_dir_horiz;
+ int leave_side_vert = move_dir_vert;
+ int enter_side = enter_side_horiz | enter_side_vert;
+ int leave_side = leave_side_horiz | leave_side_vert;
+
+ if (player->GameOver) /* do not reanimate dead player */
+ return;
+
+ if (!player_relocated) /* no need to relocate the player */
+ return;
+
+ if (IS_PLAYER(jx, jy)) /* player already placed at new position */
+ {
+ RemoveField(jx, jy); /* temporarily remove newly placed player */
+ DrawLevelField(jx, jy);
+ }
+
+ if (player->present)
+ {
+ while (player->MovPos)
+ {
+ ScrollPlayer(player, SCROLL_GO_ON);
+ ScrollScreen(NULL, SCROLL_GO_ON);
+
+ AdvanceFrameAndPlayerCounters(player->index_nr);
+
+ DrawPlayer(player);
+
+ BackToFront_WithFrameDelay(wait_delay_value);
+ }
+
+ DrawPlayer(player); /* needed here only to cleanup last field */
+ DrawLevelField(player->jx, player->jy); /* remove player graphic */
+
+ player->is_moving = FALSE;
+ }
+
+ if (IS_CUSTOM_ELEMENT(old_element))
+ CheckElementChangeByPlayer(old_jx, old_jy, old_element,
+ CE_LEFT_BY_PLAYER,
+ player->index_bit, leave_side);
+
+ CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
+ CE_PLAYER_LEAVES_X,
+ player->index_bit, leave_side);
+
+ Feld[jx][jy] = el_player;
+ InitPlayerField(jx, jy, el_player, TRUE);
+
+ /* "InitPlayerField()" above sets Feld[jx][jy] to EL_EMPTY, but it may be
+ possible that the relocation target field did not contain a player element,
+ but a walkable element, to which the new player was relocated -- in this
+ case, restore that (already initialized!) element on the player field */
+ if (!ELEM_IS_PLAYER(element)) /* player may be set on walkable element */
+ {
+ Feld[jx][jy] = element; /* restore previously existing element */
+ }
+
+ /* only visually relocate centered player */
+ DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir,
+ FALSE, level.instant_relocation);
+
+ TestIfPlayerTouchesBadThing(jx, jy);
+ TestIfPlayerTouchesCustomElement(jx, jy);
+
+ if (IS_CUSTOM_ELEMENT(element))
+ CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
+ player->index_bit, enter_side);
+
+ CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
+ player->index_bit, enter_side);
+
+ if (player->is_switching)
+ {
+ /* ensure that relocation while still switching an element does not cause
+ a new element to be treated as also switched directly after relocation
+ (this is important for teleporter switches that teleport the player to
+ a place where another teleporter switch is in the same direction, which
+ would then incorrectly be treated as immediately switched before the
+ direction key that caused the switch was released) */
+
+ player->switch_x += jx - old_jx;
+ player->switch_y += jy - old_jy;
+ }
+}
+
+void Explode(int ex, int ey, int phase, int mode)
+{
+ int x, y;
+ int last_phase;
+ int border_element;
+
+ /* !!! eliminate this variable !!! */
+ int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
+
+ if (game.explosions_delayed)
+ {
+ ExplodeField[ex][ey] = mode;
+ return;
+ }
+
+ if (phase == EX_PHASE_START) /* initialize 'Store[][]' field */
+ {
+ int center_element = Feld[ex][ey];
+ int artwork_element, explosion_element; /* set these values later */
+
+ /* 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;
+ }
+
+ /* now "center_element" is finally determined -- set related values now */
+ artwork_element = center_element; /* for custom player artwork */
+ explosion_element = center_element; /* for custom player artwork */
+
+ if (IS_PLAYER(ex, ey))
+ {
+ int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
+
+ artwork_element = stored_player[player_nr].artwork_element;
+
+ if (level.use_explosion_element[player_nr])
+ {
+ explosion_element = level.explosion_element[player_nr];
+ artwork_element = explosion_element;
+ }
+ }
+
+ if (mode == EX_TYPE_NORMAL ||
+ mode == EX_TYPE_CENTER ||
+ mode == EX_TYPE_CROSS)
+ PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
+
+ last_phase = element_info[explosion_element].explosion_delay + 1;
+
+ 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_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
+ (mode == EX_TYPE_CROSS && (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);
+ }
+
+ /* indestructible elements can only explode in center (but not flames) */
+ if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
+ mode == EX_TYPE_BORDER)) ||
+ element == EL_FLAMES)
+ continue;
+
+ /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
+ behaviour, for example when touching a yamyam that explodes to rocks
+ with active deadly shield, a rock is created under the player !!! */
+ /* (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8) */
+#if 0
+ if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
+ (game.engine_version < VERSION_IDENT(3,1,0,0) ||
+ (x == ex && y == ey && mode != EX_TYPE_BORDER)))
+#else
+ if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
+#endif
+ {
+ if (IS_ACTIVE_BOMB(element))
+ {
+ /* re-activate things under the bomb like gate or penguin */
+ Feld[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
+ Back[x][y] = 0;
+ }
+
+ continue;
+ }
+
+ /* save walkable background elements while explosion on same tile */
+ if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
+ (x != ex || y != ey || mode == EX_TYPE_BORDER))
+ Back[x][y] = element;
+
+ /* ignite explodable elements reached by other explosion */
+ if (element == EL_EXPLOSION)
+ element = Store2[x][y];
+
+ 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);
+
+ if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
+ {
+ int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
+
+ Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
+
+ if (PLAYERINFO(ex, ey)->use_murphy)
+ Store[x][y] = EL_EMPTY;
+ }
+
+ /* !!! check this case -- currently needed for rnd_rado_negundo_v,
+ !!! levels 015 018 019 020 021 022 023 026 027 028 !!! */
+ else if (ELEM_IS_PLAYER(center_element))
+ Store[x][y] = EL_EMPTY;
+ else if (center_element == EL_YAMYAM)
+ Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
+ else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
+ Store[x][y] = element_info[center_element].content.e[xx][yy];
+#if 1
+ /* needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
+ (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
+ otherwise) -- FIX THIS !!! */
+ else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
+ Store[x][y] = element_info[element].content.e[1][1];
+#else
+ else if (!CAN_EXPLODE(element))
+ Store[x][y] = element_info[element].content.e[1][1];
+#endif
+ else
+ Store[x][y] = EL_EMPTY;
+
+ if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
+ center_element == EL_AMOEBA_TO_DIAMOND)
+ Store2[x][y] = element;
+
+ Feld[x][y] = EL_EXPLOSION;
+ GfxElement[x][y] = artwork_element;
+
+ ExplodePhase[x][y] = 1;
+ ExplodeDelay[x][y] = last_phase;
+
+ Stop[x][y] = TRUE;
+ }
+
+ if (center_element == EL_YAMYAM)
+ game.yamyam_content_nr =
+ (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
+
+ return;
+ }
+
+ if (Stop[ex][ey])
+ return;
+
+ x = ex;
+ y = ey;
+
+ if (phase == 1)
+ GfxFrame[x][y] = 0; /* restart explosion animation */
+
+ last_phase = ExplodeDelay[x][y];
+
+ ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
+
+ /* this can happen if the player leaves an explosion just in time */
+ if (GfxElement[x][y] == EL_UNDEFINED)
+ GfxElement[x][y] = EL_EMPTY;
+
+ border_element = Store2[x][y];
+ if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
+ border_element = StorePlayer[x][y];
+
+ if (phase == element_info[border_element].ignition_delay ||
+ phase == last_phase)
+ {
+ boolean border_explosion = FALSE;
+
+ if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
+ !PLAYER_EXPLOSION_PROTECTED(x, y))
+ {
+ KillPlayerUnlessExplosionProtected(x, y);
+ border_explosion = TRUE;
+ }
+ else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
+ {
+ Feld[x][y] = Store2[x][y];
+ Store2[x][y] = 0;
+ Bang(x, y);
+ border_explosion = TRUE;
+ }
+ else if (border_element == EL_AMOEBA_TO_DIAMOND)
+ {
+ AmoebeUmwandeln(x, y);
+ Store2[x][y] = 0;
+ border_explosion = TRUE;
+ }
+
+ /* if an element just explodes due to another explosion (chain-reaction),
+ do not immediately end the new explosion when it was the last frame of
+ the explosion (as it would be done in the following "if"-statement!) */
+ if (border_explosion && phase == last_phase)
+ return;
+ }
+
+ if (phase == last_phase)
+ {
+ int element;
+
+ element = Feld[x][y] = Store[x][y];
+ Store[x][y] = Store2[x][y] = 0;
+ GfxElement[x][y] = EL_UNDEFINED;
+
+ /* player can escape from explosions and might therefore be still alive */
+ if (element >= EL_PLAYER_IS_EXPLODING_1 &&
+ element <= EL_PLAYER_IS_EXPLODING_4)
+ {
+ int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
+ int explosion_element = EL_PLAYER_1 + player_nr;
+ int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
+ int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
+
+ if (level.use_explosion_element[player_nr])
+ explosion_element = level.explosion_element[player_nr];
+
+ Feld[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
+ element_info[explosion_element].content.e[xx][yy]);
+ }
+
+ /* restore probably existing indestructible background element */
+ if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
+ element = Feld[x][y] = Back[x][y];
+ Back[x][y] = 0;
+
+ MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
+ GfxDir[x][y] = MV_NONE;
+ ChangeDelay[x][y] = 0;
+ ChangePage[x][y] = -1;
+
+ CustomValue[x][y] = 0;
+
+ InitField_WithBug2(x, y, FALSE);
+
+ TEST_DrawLevelField(x, y);
+
+ TestIfElementTouchesCustomElement(x, y);
+
+ if (GFX_CRUMBLED(element))
+ TEST_DrawLevelFieldCrumbledNeighbours(x, y);
+
+ if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
+ StorePlayer[x][y] = 0;
+
+ if (ELEM_IS_PLAYER(element))
+ RelocatePlayer(x, y, element);
+ }
+ else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
+ {
+ int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
+ int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
+
+ if (phase == delay)
+ TEST_DrawLevelFieldCrumbled(x, y);
+
+ if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
+ {
+ DrawLevelElement(x, y, Back[x][y]);
+ DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
+ }
+ else if (IS_WALKABLE_UNDER(Back[x][y]))
+ {
+ DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
+ DrawLevelElementThruMask(x, y, Back[x][y]);
+ }
+ else if (!IS_WALKABLE_INSIDE(Back[x][y]))
+ DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
+ }
+}
+
+void DynaExplode(int ex, int ey)
+{
+ int i, j;
+ int dynabomb_element = Feld[ex][ey];
+ int dynabomb_size = 1;
+ boolean dynabomb_xl = FALSE;
+ struct PlayerInfo *player;
+ static int xy[4][2] =
+ {
+ { 0, -1 },
+ { -1, 0 },
+ { +1, 0 },
+ { 0, +1 }
+ };
+
+ if (IS_ACTIVE_BOMB(dynabomb_element))
+ {
+ player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
+ dynabomb_size = player->dynabomb_size;
+ dynabomb_xl = player->dynabomb_xl;
+ player->dynabombs_left++;
+ }
+
+ Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
+
+ for (i = 0; i < NUM_DIRECTIONS; i++)
+ {
+ for (j = 1; j <= dynabomb_size; j++)
+ {
+ int x = ex + j * xy[i][0];
+ int y = ey + j * xy[i][1];
+ int element;
+
+ if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Feld[x][y]))
+ break;
+
+ element = Feld[x][y];
+
+ /* do not restart explosions of fields with active bombs */
+ if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
+ continue;
+
+ Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
+
+ if (element != EL_EMPTY && element != EL_EXPLOSION &&
+ !IS_DIGGABLE(element) && !dynabomb_xl)
+ break;
+ }
+ }
+}
+
+void Bang(int x, int y)
+{
+ int element = MovingOrBlocked2Element(x, y);
+ int explosion_type = EX_TYPE_NORMAL;
+
+ if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
+ {
+ struct PlayerInfo *player = PLAYERINFO(x, y);
+
+ element = Feld[x][y] = player->initial_element;
+
+ if (level.use_explosion_element[player->index_nr])
+ {
+ int explosion_element = level.explosion_element[player->index_nr];
+
+ if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
+ explosion_type = EX_TYPE_CROSS;
+ else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
+ explosion_type = EX_TYPE_CENTER;
+ }
+ }
+
+ switch (element)
+ {
+ case EL_BUG:
+ case EL_SPACESHIP:
+ case EL_BD_BUTTERFLY:
+ case EL_BD_FIREFLY:
+ case EL_YAMYAM:
+ case EL_DARK_YAMYAM:
+ case EL_ROBOT:
+ case EL_PACMAN:
+ case EL_MOLE:
+ RaiseScoreElement(element);
+ break;
+
+ case EL_DYNABOMB_PLAYER_1_ACTIVE:
+ case EL_DYNABOMB_PLAYER_2_ACTIVE:
+ case EL_DYNABOMB_PLAYER_3_ACTIVE:
+ case EL_DYNABOMB_PLAYER_4_ACTIVE:
+ case EL_DYNABOMB_INCREASE_NUMBER:
+ case EL_DYNABOMB_INCREASE_SIZE:
+ case EL_DYNABOMB_INCREASE_POWER:
+ explosion_type = EX_TYPE_DYNA;
+ break;
+
+ case EL_DC_LANDMINE:
+ explosion_type = EX_TYPE_CENTER;
+ break;
+
+ case EL_PENGUIN:
+ case EL_LAMP:
+ case EL_LAMP_ACTIVE:
+ case EL_AMOEBA_TO_DIAMOND:
+ if (!IS_PLAYER(x, y)) /* penguin and player may be at same field */
+ explosion_type = EX_TYPE_CENTER;
+ break;
+
+ default:
+ if (element_info[element].explosion_type == EXPLODES_CROSS)
+ explosion_type = EX_TYPE_CROSS;
+ else if (element_info[element].explosion_type == EXPLODES_1X1)
+ explosion_type = EX_TYPE_CENTER;
+ break;
+ }
+
+ if (explosion_type == EX_TYPE_DYNA)
+ DynaExplode(x, y);
+ else
+ Explode(x, y, EX_PHASE_START, explosion_type);
+
+ CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
+}
+
+void SplashAcid(int x, int y)
+{
+ if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
+ (!IN_LEV_FIELD(x - 1, y - 2) ||
+ !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
+ Feld[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
+
+ if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
+ (!IN_LEV_FIELD(x + 1, y - 2) ||
+ !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
+ Feld[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
+
+ PlayLevelSound(x, y, SND_ACID_SPLASHING);
+}
+
+static void InitBeltMovement()
+{
+ static int belt_base_element[4] =
+ {
+ EL_CONVEYOR_BELT_1_LEFT,
+ EL_CONVEYOR_BELT_2_LEFT,
+ EL_CONVEYOR_BELT_3_LEFT,
+ EL_CONVEYOR_BELT_4_LEFT
+ };
+ static int belt_base_active_element[4] =
+ {
+ EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
+ EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
+ EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
+ EL_CONVEYOR_BELT_4_LEFT_ACTIVE
+ };
+
+ int x, y, i, j;
+
+ /* set frame order for belt animation graphic according to belt direction */
+ for (i = 0; i < NUM_BELTS; i++)
+ {
+ int belt_nr = i;
+
+ for (j = 0; j < NUM_BELT_PARTS; j++)
+ {
+ int element = belt_base_active_element[belt_nr] + j;
+ int graphic_1 = el2img(element);
+ int graphic_2 = el2panelimg(element);
+
+ if (game.belt_dir[i] == MV_LEFT)
+ {
+ graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
+ graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
+ }
+ else
+ {
+ graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
+ graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
+ }
+ }
+ }
+
+ SCAN_PLAYFIELD(x, y)
+ {
+ int element = Feld[x][y];
+
+ for (i = 0; i < NUM_BELTS; i++)
+ {
+ if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
+ {
+ int e_belt_nr = getBeltNrFromBeltElement(element);
+ int belt_nr = i;
+
+ if (e_belt_nr == belt_nr)
+ {
+ int belt_part = Feld[x][y] - belt_base_element[belt_nr];
+
+ Feld[x][y] = belt_base_active_element[belt_nr] + belt_part;
+ }
+ }
+ }
+ }
+}
+
+static void ToggleBeltSwitch(int x, int y)
+{
+ static int belt_base_element[4] =
+ {
+ EL_CONVEYOR_BELT_1_LEFT,
+ EL_CONVEYOR_BELT_2_LEFT,
+ EL_CONVEYOR_BELT_3_LEFT,
+ EL_CONVEYOR_BELT_4_LEFT
+ };
+ static int belt_base_active_element[4] =
+ {
+ EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
+ EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
+ EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
+ EL_CONVEYOR_BELT_4_LEFT_ACTIVE
+ };
+ static int belt_base_switch_element[4] =
+ {
+ EL_CONVEYOR_BELT_1_SWITCH_LEFT,
+ EL_CONVEYOR_BELT_2_SWITCH_LEFT,
+ EL_CONVEYOR_BELT_3_SWITCH_LEFT,
+ EL_CONVEYOR_BELT_4_SWITCH_LEFT
+ };
+ static int belt_move_dir[4] =
+ {
+ MV_LEFT,
+ MV_NONE,
+ MV_RIGHT,
+ MV_NONE,
+ };
+
+ int element = Feld[x][y];
+ int belt_nr = getBeltNrFromBeltSwitchElement(element);
+ int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
+ int belt_dir = belt_move_dir[belt_dir_nr];
+ int xx, yy, i;
+
+ if (!IS_BELT_SWITCH(element))
+ return;
+
+ game.belt_dir_nr[belt_nr] = belt_dir_nr;
+ game.belt_dir[belt_nr] = belt_dir;
+
+ if (belt_dir_nr == 3)
+ belt_dir_nr = 1;
+
+ /* set frame order for belt animation graphic according to belt direction */
+ for (i = 0; i < NUM_BELT_PARTS; i++)
+ {
+ int element = belt_base_active_element[belt_nr] + i;
+ int graphic_1 = el2img(element);
+ int graphic_2 = el2panelimg(element);
+
+ if (belt_dir == MV_LEFT)
+ {
+ graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
+ graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
+ }
+ else
+ {
+ graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
+ graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
+ }
+ }
+
+ SCAN_PLAYFIELD(xx, yy)
+ {
+ int element = Feld[xx][yy];
+
+ if (IS_BELT_SWITCH(element))
+ {
+ int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
+
+ if (e_belt_nr == belt_nr)
+ {
+ Feld[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
+ TEST_DrawLevelField(xx, yy);
+ }
+ }
+ else if (IS_BELT(element) && belt_dir != MV_NONE)
+ {
+ int e_belt_nr = getBeltNrFromBeltElement(element);
+
+ if (e_belt_nr == belt_nr)
+ {
+ int belt_part = Feld[xx][yy] - belt_base_element[belt_nr];
+
+ Feld[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
+ TEST_DrawLevelField(xx, yy);
+ }
+ }
+ else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
+ {
+ int e_belt_nr = getBeltNrFromBeltActiveElement(element);
+
+ if (e_belt_nr == belt_nr)
+ {
+ int belt_part = Feld[xx][yy] - belt_base_active_element[belt_nr];
+
+ Feld[xx][yy] = belt_base_element[belt_nr] + belt_part;
+ TEST_DrawLevelField(xx, yy);
+ }
+ }
+ }
+}
+
+static void ToggleSwitchgateSwitch(int x, int y)
+{
+ int xx, yy;
+
+ game.switchgate_pos = !game.switchgate_pos;
+
+ SCAN_PLAYFIELD(xx, yy)
+ {
+ int element = Feld[xx][yy];
+
+ if (element == EL_SWITCHGATE_SWITCH_UP)
+ {
+ Feld[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
+ TEST_DrawLevelField(xx, yy);
+ }
+ else if (element == EL_SWITCHGATE_SWITCH_DOWN)
+ {
+ Feld[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
+ TEST_DrawLevelField(xx, yy);
+ }
+ else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
+ {
+ Feld[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
+ TEST_DrawLevelField(xx, yy);
+ }
+ else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
+ {
+ Feld[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
+ TEST_DrawLevelField(xx, yy);
+ }
+ else if (element == EL_SWITCHGATE_OPEN ||
+ element == EL_SWITCHGATE_OPENING)
+ {
+ Feld[xx][yy] = EL_SWITCHGATE_CLOSING;
+
+ PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
+ }
+ else if (element == EL_SWITCHGATE_CLOSED ||
+ element == EL_SWITCHGATE_CLOSING)
+ {
+ Feld[xx][yy] = EL_SWITCHGATE_OPENING;
+
+ PlayLevelSoundAction(xx, yy, ACTION_OPENING);
+ }
+ }
+}
+
+static int getInvisibleActiveFromInvisibleElement(int element)
+{
+ return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
+ element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
+ element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
+ element);
+}
+
+static int getInvisibleFromInvisibleActiveElement(int element)
+{
+ return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
+ element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
+ element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
+ element);
+}
+
+static void RedrawAllLightSwitchesAndInvisibleElements()
+{
+ int x, y;
+
+ SCAN_PLAYFIELD(x, y)
+ {
+ int element = Feld[x][y];
+
+ if (element == EL_LIGHT_SWITCH &&
+ game.light_time_left > 0)
+ {
+ Feld[x][y] = EL_LIGHT_SWITCH_ACTIVE;
+ TEST_DrawLevelField(x, y);
+ }
+ else if (element == EL_LIGHT_SWITCH_ACTIVE &&
+ game.light_time_left == 0)
+ {
+ Feld[x][y] = EL_LIGHT_SWITCH;
+ TEST_DrawLevelField(x, y);
+ }
+ else if (element == EL_EMC_DRIPPER &&
+ game.light_time_left > 0)
+ {
+ Feld[x][y] = EL_EMC_DRIPPER_ACTIVE;
+ TEST_DrawLevelField(x, y);
+ }
+ else if (element == EL_EMC_DRIPPER_ACTIVE &&
+ game.light_time_left == 0)
+ {
+ Feld[x][y] = EL_EMC_DRIPPER;
+ TEST_DrawLevelField(x, y);
+ }
+ else if (element == EL_INVISIBLE_STEELWALL ||
+ element == EL_INVISIBLE_WALL ||
+ element == EL_INVISIBLE_SAND)
+ {
+ if (game.light_time_left > 0)
+ Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
+
+ TEST_DrawLevelField(x, y);
+
+ /* uncrumble neighbour fields, if needed */
+ if (element == EL_INVISIBLE_SAND)
+ TEST_DrawLevelFieldCrumbledNeighbours(x, y);
+ }
+ else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
+ element == EL_INVISIBLE_WALL_ACTIVE ||
+ element == EL_INVISIBLE_SAND_ACTIVE)
+ {
+ if (game.light_time_left == 0)
+ Feld[x][y] = getInvisibleFromInvisibleActiveElement(element);
+
+ TEST_DrawLevelField(x, y);
+
+ /* re-crumble neighbour fields, if needed */
+ if (element == EL_INVISIBLE_SAND)
+ TEST_DrawLevelFieldCrumbledNeighbours(x, y);
+ }
+ }
+}
+
+static void RedrawAllInvisibleElementsForLenses()
+{
+ int x, y;
+
+ SCAN_PLAYFIELD(x, y)
+ {
+ int element = Feld[x][y];
+
+ if (element == EL_EMC_DRIPPER &&
+ game.lenses_time_left > 0)
+ {
+ Feld[x][y] = EL_EMC_DRIPPER_ACTIVE;
+ TEST_DrawLevelField(x, y);
+ }
+ else if (element == EL_EMC_DRIPPER_ACTIVE &&
+ game.lenses_time_left == 0)
+ {
+ Feld[x][y] = EL_EMC_DRIPPER;
+ TEST_DrawLevelField(x, y);
+ }
+ else if (element == EL_INVISIBLE_STEELWALL ||
+ element == EL_INVISIBLE_WALL ||
+ element == EL_INVISIBLE_SAND)
+ {
+ if (game.lenses_time_left > 0)
+ Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
+
+ TEST_DrawLevelField(x, y);
+
+ /* uncrumble neighbour fields, if needed */
+ if (element == EL_INVISIBLE_SAND)
+ TEST_DrawLevelFieldCrumbledNeighbours(x, y);
+ }
+ else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
+ element == EL_INVISIBLE_WALL_ACTIVE ||
+ element == EL_INVISIBLE_SAND_ACTIVE)
+ {
+ if (game.lenses_time_left == 0)
+ Feld[x][y] = getInvisibleFromInvisibleActiveElement(element);
+
+ TEST_DrawLevelField(x, y);
+
+ /* re-crumble neighbour fields, if needed */
+ if (element == EL_INVISIBLE_SAND)
+ TEST_DrawLevelFieldCrumbledNeighbours(x, y);
+ }
+ }
+}
+
+static void RedrawAllInvisibleElementsForMagnifier()
+{
+ int x, y;
+
+ SCAN_PLAYFIELD(x, y)
+ {
+ int element = Feld[x][y];
+
+ if (element == EL_EMC_FAKE_GRASS &&
+ game.magnify_time_left > 0)
+ {
+ Feld[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
+ TEST_DrawLevelField(x, y);
+ }
+ else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
+ game.magnify_time_left == 0)
+ {
+ Feld[x][y] = EL_EMC_FAKE_GRASS;
+ TEST_DrawLevelField(x, y);
+ }
+ else if (IS_GATE_GRAY(element) &&
+ game.magnify_time_left > 0)
+ {
+ Feld[x][y] = (IS_RND_GATE_GRAY(element) ?
+ element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
+ IS_EM_GATE_GRAY(element) ?
+ element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
+ IS_EMC_GATE_GRAY(element) ?
+ element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
+ IS_DC_GATE_GRAY(element) ?
+ EL_DC_GATE_WHITE_GRAY_ACTIVE :
+ element);
+ TEST_DrawLevelField(x, y);
+ }
+ else if (IS_GATE_GRAY_ACTIVE(element) &&
+ game.magnify_time_left == 0)
+ {
+ Feld[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
+ element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
+ IS_EM_GATE_GRAY_ACTIVE(element) ?
+ element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
+ IS_EMC_GATE_GRAY_ACTIVE(element) ?
+ element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
+ IS_DC_GATE_GRAY_ACTIVE(element) ?
+ EL_DC_GATE_WHITE_GRAY :
+ element);
+ TEST_DrawLevelField(x, y);
+ }
+ }
+}
+
+static void ToggleLightSwitch(int x, int y)
+{
+ int element = Feld[x][y];
+
+ game.light_time_left =
+ (element == EL_LIGHT_SWITCH ?
+ level.time_light * FRAMES_PER_SECOND : 0);
+
+ RedrawAllLightSwitchesAndInvisibleElements();
+}
+
+static void ActivateTimegateSwitch(int x, int y)
+{
+ int xx, yy;
+
+ game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
+
+ SCAN_PLAYFIELD(xx, yy)
+ {
+ int element = Feld[xx][yy];
+
+ if (element == EL_TIMEGATE_CLOSED ||
+ element == EL_TIMEGATE_CLOSING)
+ {
+ Feld[xx][yy] = EL_TIMEGATE_OPENING;
+ PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
+ }
+
+ /*
+ else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
+ {
+ Feld[xx][yy] = EL_TIMEGATE_SWITCH;
+ TEST_DrawLevelField(xx, yy);
+ }
+ */
+
+ }
+
+ Feld[x][y] = (Feld[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
+ EL_DC_TIMEGATE_SWITCH_ACTIVE);
+}
+
+void Impact(int x, int y)
+{
+ boolean last_line = (y == lev_fieldy - 1);
+ boolean object_hit = FALSE;
+ boolean impact = (last_line || object_hit);
+ int element = Feld[x][y];
+ int smashed = EL_STEELWALL;
+
+ if (!last_line) /* check if element below was hit */
+ {
+ if (Feld[x][y + 1] == EL_PLAYER_IS_LEAVING)
+ return;
+
+ object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
+ MovDir[x][y + 1] != MV_DOWN ||
+ MovPos[x][y + 1] <= TILEY / 2));
+
+ /* do not smash moving elements that left the smashed field in time */
+ if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
+ ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
+ object_hit = FALSE;
+
+#if USE_QUICKSAND_IMPACT_BUGFIX
+ if (Feld[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
+ {
+ RemoveMovingField(x, y + 1);
+ Feld[x][y + 1] = EL_QUICKSAND_EMPTY;
+ Feld[x][y + 2] = EL_ROCK;
+ TEST_DrawLevelField(x, y + 2);
+
+ object_hit = TRUE;
+ }
+
+ if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
+ {
+ RemoveMovingField(x, y + 1);
+ Feld[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
+ Feld[x][y + 2] = EL_ROCK;
+ TEST_DrawLevelField(x, y + 2);
+
+ object_hit = TRUE;
+ }
+#endif
+
+ if (object_hit)
+ smashed = MovingOrBlocked2Element(x, y + 1);
+
+ impact = (last_line || object_hit);
+ }
+
+ if (!last_line && smashed == EL_ACID) /* element falls into acid */
+ {
+ SplashAcid(x, y + 1);
+ return;
+ }
+
+ /* !!! not sufficient for all cases -- see EL_PEARL below !!! */
+ /* only reset graphic animation if graphic really changes after impact */
+ if (impact &&
+ el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
+ {
+ ResetGfxAnimation(x, y);
+ TEST_DrawLevelField(x, y);
+ }
+
+ if (impact && CAN_EXPLODE_IMPACT(element))
+ {
+ Bang(x, y);
+ return;
+ }
+ else if (impact && element == EL_PEARL &&
+ smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
+ {
+ ResetGfxAnimation(x, y);
+
+ Feld[x][y] = EL_PEARL_BREAKING;
+ PlayLevelSound(x, y, SND_PEARL_BREAKING);
+ return;
+ }
+ else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
+ {
+ PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
+
+ return;
+ }
+
+ if (impact && element == EL_AMOEBA_DROP)
+ {
+ if (object_hit && IS_PLAYER(x, y + 1))
+ KillPlayerUnlessEnemyProtected(x, y + 1);
+ else if (object_hit && smashed == EL_PENGUIN)
+ Bang(x, y + 1);
+ else
+ {
+ Feld[x][y] = EL_AMOEBA_GROWING;
+ Store[x][y] = EL_AMOEBA_WET;
+
+ ResetRandomAnimationValue(x, y);
+ }
+ return;
+ }
+
+ if (object_hit) /* check which object was hit */
+ {
+ if ((CAN_PASS_MAGIC_WALL(element) &&
+ (smashed == EL_MAGIC_WALL ||
+ smashed == EL_BD_MAGIC_WALL)) ||
+ (CAN_PASS_DC_MAGIC_WALL(element) &&
+ smashed == EL_DC_MAGIC_WALL))
+ {
+ int xx, yy;
+ int activated_magic_wall =
+ (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
+ smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
+ EL_DC_MAGIC_WALL_ACTIVE);
+
+ /* activate magic wall / mill */
+ SCAN_PLAYFIELD(xx, yy)
+ {
+ if (Feld[xx][yy] == smashed)
+ Feld[xx][yy] = activated_magic_wall;
+ }
+
+ game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
+ game.magic_wall_active = TRUE;
+
+ PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
+ SND_MAGIC_WALL_ACTIVATING :
+ smashed == EL_BD_MAGIC_WALL ?
+ SND_BD_MAGIC_WALL_ACTIVATING :
+ SND_DC_MAGIC_WALL_ACTIVATING));
+ }
+
+ if (IS_PLAYER(x, y + 1))
+ {
+ if (CAN_SMASH_PLAYER(element))
+ {
+ KillPlayerUnlessEnemyProtected(x, y + 1);
+ return;
+ }
+ }
+ else if (smashed == EL_PENGUIN)
+ {
+ if (CAN_SMASH_PLAYER(element))
+ {
+ Bang(x, y + 1);
+ return;
+ }
+ }
+ else if (element == EL_BD_DIAMOND)
+ {
+ if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
+ {
+ Bang(x, y + 1);
+ return;
+ }
+ }
+ else if (((element == EL_SP_INFOTRON ||
+ element == EL_SP_ZONK) &&
+ (smashed == EL_SP_SNIKSNAK ||
+ smashed == EL_SP_ELECTRON ||
+ smashed == EL_SP_DISK_ORANGE)) ||
+ (element == EL_SP_INFOTRON &&
+ smashed == EL_SP_DISK_YELLOW))
+ {
+ Bang(x, y + 1);
+ return;
+ }
+ else if (CAN_SMASH_EVERYTHING(element))
+ {
+ if (IS_CLASSIC_ENEMY(smashed) ||
+ CAN_EXPLODE_SMASHED(smashed))
+ {
+ Bang(x, y + 1);
+ return;
+ }
+ else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
+ {
+ if (smashed == EL_LAMP ||
+ smashed == EL_LAMP_ACTIVE)
+ {
+ Bang(x, y + 1);
+ return;
+ }
+ else if (smashed == EL_NUT)
+ {
+ Feld[x][y + 1] = EL_NUT_BREAKING;
+ PlayLevelSound(x, y, SND_NUT_BREAKING);
+ RaiseScoreElement(EL_NUT);
+ return;
+ }
+ else if (smashed == EL_PEARL)
+ {
+ ResetGfxAnimation(x, y);
+
+ Feld[x][y + 1] = EL_PEARL_BREAKING;
+ PlayLevelSound(x, y, SND_PEARL_BREAKING);
+ return;
+ }
+ else if (smashed == EL_DIAMOND)
+ {
+ Feld[x][y + 1] = EL_DIAMOND_BREAKING;
+ PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
+ return;
+ }
+ else if (IS_BELT_SWITCH(smashed))
+ {
+ ToggleBeltSwitch(x, y + 1);
+ }
+ else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
+ smashed == EL_SWITCHGATE_SWITCH_DOWN ||
+ smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
+ smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
+ {
+ ToggleSwitchgateSwitch(x, y + 1);
+ }
+ else if (smashed == EL_LIGHT_SWITCH ||
+ smashed == EL_LIGHT_SWITCH_ACTIVE)
+ {
+ ToggleLightSwitch(x, y + 1);
+ }
+ else
+ {
+ CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
+
+ CheckElementChangeBySide(x, y + 1, smashed, element,
+ CE_SWITCHED, CH_SIDE_TOP);
+ CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
+ CH_SIDE_TOP);
+ }
+ }
+ else
+ {
+ CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
+ }
+ }
+ }
+
+ /* play sound of magic wall / mill */
+ if (!last_line &&
+ (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
+ Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
+ Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
+ {
+ if (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
+ PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
+ else if (Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
+ PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
+ else if (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
+ PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
+
+ return;
+ }
+
+ /* play sound of object that hits the ground */
+ if (last_line || object_hit)
+ PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
+}
+
+inline static void TurnRoundExt(int x, int y)
+{
+ 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 }
+ };
+ static struct
+ {
+ int left, right, back;
+ } turn[] =
+ {
+ { 0, 0, 0 },
+ { MV_DOWN, MV_UP, MV_RIGHT },
+ { MV_UP, MV_DOWN, MV_LEFT },
+ { 0, 0, 0 },
+ { MV_LEFT, MV_RIGHT, MV_DOWN },
+ { 0, 0, 0 },
+ { 0, 0, 0 },
+ { 0, 0, 0 },
+ { MV_RIGHT, MV_LEFT, MV_UP }
+ };
+
+ int element = Feld[x][y];
+ int move_pattern = element_info[element].move_pattern;
+
+ int old_move_dir = MovDir[x][y];
+ int left_dir = turn[old_move_dir].left;
+ int right_dir = turn[old_move_dir].right;
+ int back_dir = turn[old_move_dir].back;
+
+ int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
+ int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
+ int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
+ int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
+
+ int left_x = x + left_dx, left_y = y + left_dy;
+ int right_x = x + right_dx, right_y = y + right_dy;
+ int move_x = x + move_dx, move_y = y + move_dy;
+
+ int xx, yy;
+
+ if (element == EL_BUG || element == EL_BD_BUTTERFLY)
+ {
+ TestIfBadThingTouchesOtherBadThing(x, y);
+
+ if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
+ MovDir[x][y] = right_dir;
+ else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
+ MovDir[x][y] = left_dir;
+
+ if (element == EL_BUG && MovDir[x][y] != old_move_dir)
+ MovDelay[x][y] = 9;
+ else if (element == EL_BD_BUTTERFLY) /* && MovDir[x][y] == left_dir) */
+ MovDelay[x][y] = 1;
+ }
+ else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
+ {
+ TestIfBadThingTouchesOtherBadThing(x, y);
+
+ if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
+ MovDir[x][y] = left_dir;
+ else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
+ MovDir[x][y] = right_dir;
+
+ if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
+ MovDelay[x][y] = 9;
+ else if (element == EL_BD_FIREFLY) /* && MovDir[x][y] == right_dir) */
+ MovDelay[x][y] = 1;
+ }
+ else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
+ {
+ TestIfBadThingTouchesOtherBadThing(x, y);
+
+ if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
+ MovDir[x][y] = left_dir;
+ else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
+ MovDir[x][y] = right_dir;
+
+ if (MovDir[x][y] != old_move_dir)
+ MovDelay[x][y] = 9;
+ }
+ else if (element == EL_YAMYAM)
+ {
+ boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
+ boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
+
+ if (can_turn_left && can_turn_right)
+ MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
+ else if (can_turn_left)
+ MovDir[x][y] = (RND(2) ? left_dir : back_dir);
+ else if (can_turn_right)
+ MovDir[x][y] = (RND(2) ? right_dir : back_dir);
+ else
+ MovDir[x][y] = back_dir;
+
+ MovDelay[x][y] = 16 + 16 * RND(3);
+ }
+ else if (element == EL_DARK_YAMYAM)
+ {
+ boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
+ left_x, left_y);
+ boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
+ right_x, right_y);
+
+ if (can_turn_left && can_turn_right)
+ MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
+ else if (can_turn_left)
+ MovDir[x][y] = (RND(2) ? left_dir : back_dir);
+ else if (can_turn_right)
+ MovDir[x][y] = (RND(2) ? right_dir : back_dir);
+ else
+ MovDir[x][y] = back_dir;
+
+ MovDelay[x][y] = 16 + 16 * RND(3);
+ }
+ else if (element == EL_PACMAN)
+ {
+ boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
+ boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
+
+ if (can_turn_left && can_turn_right)
+ MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
+ else if (can_turn_left)
+ MovDir[x][y] = (RND(2) ? left_dir : back_dir);
+ else if (can_turn_right)
+ MovDir[x][y] = (RND(2) ? right_dir : back_dir);
+ else
+ MovDir[x][y] = back_dir;
+
+ MovDelay[x][y] = 6 + RND(40);
+ }
+ else if (element == EL_PIG)
+ {
+ boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
+ boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
+ boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
+ boolean should_turn_left, should_turn_right, should_move_on;
+ int rnd_value = 24;
+ int rnd = RND(rnd_value);
+
+ should_turn_left = (can_turn_left &&
+ (!can_move_on ||
+ IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
+ y + back_dy + left_dy)));
+ should_turn_right = (can_turn_right &&
+ (!can_move_on ||
+ IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
+ y + back_dy + right_dy)));
+ should_move_on = (can_move_on &&
+ (!can_turn_left ||
+ !can_turn_right ||
+ IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
+ y + move_dy + left_dy) ||
+ IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
+ y + move_dy + right_dy)));
+
+ if (should_turn_left || should_turn_right || should_move_on)
+ {
+ if (should_turn_left && should_turn_right && should_move_on)
+ MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
+ rnd < 2 * rnd_value / 3 ? right_dir :
+ old_move_dir);
+ else if (should_turn_left && should_turn_right)
+ MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
+ else if (should_turn_left && should_move_on)
+ MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
+ else if (should_turn_right && should_move_on)
+ MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
+ else if (should_turn_left)
+ MovDir[x][y] = left_dir;
+ else if (should_turn_right)
+ MovDir[x][y] = right_dir;
+ else if (should_move_on)
+ MovDir[x][y] = old_move_dir;
+ }
+ else if (can_move_on && rnd > rnd_value / 8)
+ MovDir[x][y] = old_move_dir;
+ else if (can_turn_left && can_turn_right)
+ MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
+ else if (can_turn_left && rnd > rnd_value / 8)
+ MovDir[x][y] = left_dir;
+ else if (can_turn_right && rnd > rnd_value/8)
+ MovDir[x][y] = right_dir;
+ else
+ MovDir[x][y] = back_dir;
+
+ xx = x + move_xy[MovDir[x][y]].dx;
+ yy = y + move_xy[MovDir[x][y]].dy;
+
+ if (!IN_LEV_FIELD(xx, yy) ||
+ (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Feld[xx][yy])))
+ MovDir[x][y] = old_move_dir;
+
+ MovDelay[x][y] = 0;
+ }
+ else if (element == EL_DRAGON)
+ {
+ boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
+ boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
+ boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
+ int rnd_value = 24;
+ int rnd = RND(rnd_value);
+
+ if (can_move_on && rnd > rnd_value / 8)
+ MovDir[x][y] = old_move_dir;
+ else if (can_turn_left && can_turn_right)
+ MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
+ else if (can_turn_left && rnd > rnd_value / 8)
+ MovDir[x][y] = left_dir;
+ else if (can_turn_right && rnd > rnd_value / 8)
+ MovDir[x][y] = right_dir;
+ else
+ MovDir[x][y] = back_dir;
+
+ xx = x + move_xy[MovDir[x][y]].dx;
+ yy = y + move_xy[MovDir[x][y]].dy;
+
+ if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
+ MovDir[x][y] = old_move_dir;
+
+ MovDelay[x][y] = 0;
+ }
+ else if (element == EL_MOLE)
+ {
+ boolean can_move_on =
+ (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
+ IS_AMOEBOID(Feld[move_x][move_y]) ||
+ Feld[move_x][move_y] == EL_AMOEBA_SHRINKING));
+ if (!can_move_on)
+ {
+ boolean can_turn_left =
+ (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
+ IS_AMOEBOID(Feld[left_x][left_y])));
+
+ boolean can_turn_right =
+ (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
+ IS_AMOEBOID(Feld[right_x][right_y])));
+
+ if (can_turn_left && can_turn_right)
+ MovDir[x][y] = (RND(2) ? left_dir : right_dir);
+ else if (can_turn_left)
+ MovDir[x][y] = left_dir;
+ else
+ MovDir[x][y] = right_dir;
+ }
+
+ if (MovDir[x][y] != old_move_dir)
+ MovDelay[x][y] = 9;
+ }
+ else if (element == EL_BALLOON)
+ {
+ MovDir[x][y] = game.wind_direction;
+ MovDelay[x][y] = 0;
+ }
+ else if (element == EL_SPRING)
+ {
+ if (MovDir[x][y] & MV_HORIZONTAL)
+ {
+ if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
+ !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
+ {
+ Feld[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
+ ResetGfxAnimation(move_x, move_y);
+ TEST_DrawLevelField(move_x, move_y);
+
+ MovDir[x][y] = back_dir;
+ }
+ else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
+ SPRING_CAN_ENTER_FIELD(element, x, y + 1))
+ MovDir[x][y] = MV_NONE;
+ }
+
+ MovDelay[x][y] = 0;
+ }
+ else if (element == EL_ROBOT ||
+ element == EL_SATELLITE ||
+ element == EL_PENGUIN ||
+ element == EL_EMC_ANDROID)
+ {
+ int attr_x = -1, attr_y = -1;
+
+ if (AllPlayersGone)
+ {
+ attr_x = ExitX;
+ attr_y = ExitY;
+ }
+ else
+ {
+ int i;
+
+ for (i = 0; i < MAX_PLAYERS; i++)
+ {
+ struct PlayerInfo *player = &stored_player[i];
+ int jx = player->jx, jy = player->jy;
+
+ if (!player->active)
+ continue;
+
+ if (attr_x == -1 ||
+ ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
+ {
+ attr_x = jx;
+ attr_y = jy;
+ }
+ }
+ }
+
+ if (element == EL_ROBOT && ZX >= 0 && ZY >= 0 &&
+ (Feld[ZX][ZY] == EL_ROBOT_WHEEL_ACTIVE ||
+ game.engine_version < VERSION_IDENT(3,1,0,0)))
+ {
+ attr_x = ZX;
+ attr_y = ZY;
+ }
+
+ if (element == EL_PENGUIN)
+ {
+ int i;
+ static int xy[4][2] =
+ {
+ { 0, -1 },
+ { -1, 0 },
+ { +1, 0 },
+ { 0, +1 }
+ };
+
+ for (i = 0; i < NUM_DIRECTIONS; i++)
+ {
+ int ex = x + xy[i][0];
+ int ey = y + xy[i][1];
+
+ if (IN_LEV_FIELD(ex, ey) && (Feld[ex][ey] == EL_EXIT_OPEN ||
+ Feld[ex][ey] == EL_EM_EXIT_OPEN ||
+ Feld[ex][ey] == EL_STEEL_EXIT_OPEN ||
+ Feld[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
+ {
+ attr_x = ex;
+ attr_y = ey;
+ break;
+ }
+ }
+ }
+
+ MovDir[x][y] = MV_NONE;
+ if (attr_x < x)
+ MovDir[x][y] |= (AllPlayersGone ? MV_RIGHT : MV_LEFT);
+ else if (attr_x > x)
+ MovDir[x][y] |= (AllPlayersGone ? MV_LEFT : MV_RIGHT);
+ if (attr_y < y)
+ MovDir[x][y] |= (AllPlayersGone ? MV_DOWN : MV_UP);
+ else if (attr_y > y)
+ MovDir[x][y] |= (AllPlayersGone ? MV_UP : MV_DOWN);
+
+ if (element == EL_ROBOT)
+ {
+ int newx, newy;
+
+ if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
+ MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
+ Moving2Blocked(x, y, &newx, &newy);
+
+ if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
+ MovDelay[x][y] = 8 + 8 * !RND(3);
+ else
+ MovDelay[x][y] = 16;
+ }
+ else if (element == EL_PENGUIN)
+ {
+ int newx, newy;
+
+ MovDelay[x][y] = 1;
+
+ if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
+ {
+ boolean first_horiz = RND(2);
+ int new_move_dir = MovDir[x][y];
+
+ MovDir[x][y] =
+ new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
+ Moving2Blocked(x, y, &newx, &newy);
+
+ if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
+ return;
+
+ MovDir[x][y] =
+ new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
+ Moving2Blocked(x, y, &newx, &newy);
+
+ if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
+ return;
+
+ MovDir[x][y] = old_move_dir;
+ return;
+ }
+ }
+ else if (element == EL_SATELLITE)
+ {
+ int newx, newy;