+ { EL_SOKOBAN_OBJECT, 2, 0 },
+ { EL_SOKOBAN_FIELD_FULL, 2, 0 },
+ { EL_SATELLITE, 2, 0 },
+ { EL_SP_DISK_YELLOW, 2, 0 },
+
+ { EL_UNDEFINED, 0, 0 },
+};
+
+struct
+{
+ int element;
+ int move_stepsize;
+}
+move_stepsize_list[] =
+{
+ { EL_AMOEBA_DROP, 2 },
+ { EL_AMOEBA_DROPPING, 2 },
+ { EL_QUICKSAND_FILLING, 1 },
+ { EL_QUICKSAND_EMPTYING, 1 },
+ { EL_QUICKSAND_FAST_FILLING, 2 },
+ { EL_QUICKSAND_FAST_EMPTYING, 2 },
+ { EL_MAGIC_WALL_FILLING, 2 },
+ { EL_MAGIC_WALL_EMPTYING, 2 },
+ { EL_BD_MAGIC_WALL_FILLING, 2 },
+ { EL_BD_MAGIC_WALL_EMPTYING, 2 },
+ { 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(Tile[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(void)
+{
+ 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(void)
+{
+ 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(void)
+{
+ 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 IncrementSokobanFieldsNeeded(void)
+{
+ if (level.sb_fields_needed)
+ game.sokoban_fields_still_needed++;
+}
+
+static void IncrementSokobanObjectsNeeded(void)
+{
+ if (level.sb_objects_needed)
+ game.sokoban_objects_still_needed++;
+}
+
+static void DecrementSokobanFieldsNeeded(void)
+{
+ if (game.sokoban_fields_still_needed > 0)
+ game.sokoban_fields_still_needed--;
+}
+
+static void DecrementSokobanObjectsNeeded(void)
+{
+ if (game.sokoban_objects_still_needed > 0)
+ game.sokoban_objects_still_needed--;
+}
+
+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)
+ {
+ Tile[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;
+ }
+
+ Tile[x][y] = EL_PLAYER_1;
+ }
+ }
+
+ if (init_game)
+ {
+ struct PlayerInfo *player = &stored_player[Tile[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] == Tile[x][y])
+ StorePlayer[jx][jy] = 0;
+
+ StorePlayer[x][y] = Tile[x][y];
+
+#if DEBUG_INIT_PLAYER
+ Debug("game:init:player", "- player element %d activated",
+ player->element_nr);
+ Debug("game:init:player", " (local player is %d and currently %s)",
+ local_player->element_nr,
+ local_player->active ? "active" : "not active");
+ }
+#endif
+
+ Tile[x][y] = EL_EMPTY;
+
+ player->jx = player->last_jx = x;
+ player->jy = player->last_jy = y;
+ }
+
+ // always check if player was just killed and should be reanimated
+ {
+ 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 = Tile[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 = Tile[x][y] = EL_PLAYER_1;
+ InitField(x, y, init_game);
+
+ element = Tile[x][y] = EL_SOKOBAN_FIELD_EMPTY;
+ InitField(x, y, init_game);
+ break;
+
+ case EL_SOKOBAN_FIELD_EMPTY:
+ IncrementSokobanFieldsNeeded();
+ break;
+
+ case EL_SOKOBAN_OBJECT:
+ IncrementSokobanObjectsNeeded();
+ break;
+
+ case EL_STONEBLOCK:
+ if (x < lev_fieldx-1 && Tile[x+1][y] == EL_ACID)
+ Tile[x][y] = EL_ACID_POOL_TOPLEFT;
+ else if (x > 0 && Tile[x-1][y] == EL_ACID)
+ Tile[x][y] = EL_ACID_POOL_TOPRIGHT;
+ else if (y > 0 && Tile[x][y-1] == EL_ACID_POOL_TOPLEFT)
+ Tile[x][y] = EL_ACID_POOL_BOTTOMLEFT;
+ else if (y > 0 && Tile[x][y-1] == EL_ACID)
+ Tile[x][y] = EL_ACID_POOL_BOTTOM;
+ else if (y > 0 && Tile[x][y-1] == EL_ACID_POOL_TOPRIGHT)
+ Tile[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:
+ case EL_SPRING_LEFT:
+ case EL_SPRING_RIGHT:
+ 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)
+ {
+ Tile[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:
+ game.lights_still_needed++;
+ break;
+
+ case EL_PENGUIN:
+ game.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(Tile[x][y]);
+ int belt_dir = getBeltDirFromBeltSwitchElement(Tile[x][y]);
+ int belt_dir_nr = getBeltDirNrFromBeltSwitchElement(Tile[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
+ {
+ Tile[x][y] = Tile[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)
+ Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
+ break;
+
+ case EL_EMC_MAGIC_BALL:
+ if (game.ball_active)
+ Tile[x][y] = EL_EMC_MAGIC_BALL_ACTIVE;
+ break;
+
+ case EL_EMC_MAGIC_BALL_SWITCH:
+ if (game.ball_active)
+ Tile[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
+ Tile[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(Tile[x][y]);
+ }
+ else if (IS_GROUP_ELEMENT(element))
+ {
+ Tile[x][y] = GetElementFromGroupElement(element);
+
+ InitField(x, y, init_game);
+ }
+
+ break;
+ }
+
+ if (!init_game)
+ CheckTriggeredElementChange(x, y, element, CE_CREATION_OF_X);
+}
+
+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(Tile[x][y]))
+ InitMovDir(x, y);
+}
+
+static void InitField_WithBug2(int x, int y, boolean init_game)
+{
+ int old_element = Tile[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 game_em.ply[player_nr]->dynamite;
+ else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
+ return game_sp.red_disk_count;
+ else
+ return stored_player[player_nr].inventory_size;
+}
+
+static void InitGameControlValues(void)
+{
+ 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("'game_panel_controls' structure corrupted at %d", i);
+
+ Fail("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);
+}
+
+static void UpdatePlayfieldElementCount(void)
+{
+ 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[Tile[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;
+}
+
+static void UpdateGameControlValues(void)
+{
+ int i, k;
+ int time = (game.LevelSolved ?
+ game.LevelSolved_CountingTime :
+ level.game_engine_type == GAME_ENGINE_TYPE_EM ?
+ game_em.lev->time :
+ level.game_engine_type == GAME_ENGINE_TYPE_SP ?
+ game_sp.time_played :
+ level.game_engine_type == GAME_ENGINE_TYPE_MM ?
+ game_mm.energy_left :
+ game.no_time_limit ? TimePlayed : TimeLeft);
+ int score = (game.LevelSolved ?
+ game.LevelSolved_CountingScore :
+ level.game_engine_type == GAME_ENGINE_TYPE_EM ?
+ game_em.lev->score :
+ level.game_engine_type == GAME_ENGINE_TYPE_SP ?
+ game_sp.score :
+ level.game_engine_type == GAME_ENGINE_TYPE_MM ?
+ game_mm.score :
+ game.score);
+ int gems = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
+ game_em.lev->gems_needed :
+ level.game_engine_type == GAME_ENGINE_TYPE_SP ?
+ game_sp.infotrons_still_needed :
+ level.game_engine_type == GAME_ENGINE_TYPE_MM ?
+ game_mm.kettles_still_needed :
+ game.gems_still_needed);
+ int exit_closed = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
+ game_em.lev->gems_needed > 0 :
+ level.game_engine_type == GAME_ENGINE_TYPE_SP ?
+ 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 :
+ game.gems_still_needed > 0 ||
+ game.sokoban_fields_still_needed > 0 ||
+ game.sokoban_objects_still_needed > 0 ||
+ game.lights_still_needed > 0);
+ int health = (game.LevelSolved ?
+ game.LevelSolved_CountingHealth :
+ level.game_engine_type == GAME_ENGINE_TYPE_MM ?
+ MM_HEALTH(game_mm.laser_overload_value) :
+ game.health);
+ int sync_random_frame = INIT_GFX_RANDOM(); // random, but synchronized
+
+ UpdatePlayfieldElementCount();
+
+ // update game panel control values
+
+ // used instead of "level_nr" (for network games)
+ game_panel_controls[GAME_PANEL_LEVEL_NUMBER].value = levelset.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 (game_em.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 (game_em.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;
+ }
+
+ // re-arrange keys on game panel, if needed or if defined by style settings
+ for (i = 0; i < MAX_NUM_KEYS + 1; i++) // all normal keys + white key
+ {
+ int nr = GAME_PANEL_KEY_1 + i;
+ struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
+ struct TextPosInfo *pos = gpc->pos;
+
+ // skip check if key is not in the player's inventory
+ if (gpc->value == EL_EMPTY)
+ continue;
+
+ // check if keys should be arranged on panel from left to right
+ if (pos->style == STYLE_LEFTMOST_POSITION)
+ {
+ // check previous key positions (left from current key)
+ for (k = 0; k < i; k++)
+ {
+ int nr_new = GAME_PANEL_KEY_1 + k;
+
+ if (game_panel_controls[nr_new].value == EL_EMPTY)
+ {
+ game_panel_controls[nr_new].value = gpc->value;
+ gpc->value = EL_EMPTY;
+
+ break;
+ }
+ }
+ }
+
+ // check if "undefined" keys can be placed at some other position
+ if (pos->x == -1 && pos->y == -1)
+ {
+ int nr_new = GAME_PANEL_KEY_1 + i % STD_NUM_KEYS;
+
+ // 1st try: display key at the same position as normal or EM keys
+ if (game_panel_controls[nr_new].value == EL_EMPTY)
+ {
+ game_panel_controls[nr_new].value = gpc->value;
+ }
+ else
+ {
+ // 2nd try: display key at the next free position in the key panel
+ for (k = 0; k < STD_NUM_KEYS; k++)
+ {
+ nr_new = GAME_PANEL_KEY_1 + k;
+
+ if (game_panel_controls[nr_new].value == EL_EMPTY)
+ {
+ game_panel_controls[nr_new].value = gpc->value;
+
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ 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 = scores.entry[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_active ? EL_EMC_MAGIC_BALL_ACTIVE : EL_EMC_MAGIC_BALL);
+ game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL_SWITCH].value =
+ (game.ball_active ? 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 =
+ game.friends_still_needed;
+
+ game_panel_controls[GAME_PANEL_SOKOBAN_OBJECTS].value =
+ game.sokoban_objects_still_needed;
+ game_panel_controls[GAME_PANEL_SOKOBAN_FIELDS].value =
+ game.sokoban_fields_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);
+ int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
+ sync_random_frame : INIT_GFX_RANDOM());
+
+ 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(graphic, 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;
+ int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
+ sync_random_frame : INIT_GFX_RANDOM());
+
+ 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;
+ }
+ }
+ }
+}
+
+static void DisplayGameControlValues(void)
+{
+ 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;
+
+ if (pos->class == get_hash_from_key("extra_panel_items") &&
+ !setup.prefer_extra_panel_items)
+ 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);
+
+#if 0
+ Debug("game:DisplayGameControlValues", "%d, '%s' [%d]",
+ element, EL_NAME(element), size);
+#endif
+
+ 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(void)
+{
+ if (tape.deactivate_display)
+ return;
+
+ UpdateGameControlValues();
+ DisplayGameControlValues();
+}
+
+void UpdateGameDoorValues(void)
+{
+ UpdateGameControlValues();
+}
+
+void DrawGameDoorValues(void)
+{
+ DisplayGameControlValues();
+}
+
+
+// ============================================================================
+// InitGameEngine()
+// ----------------------------------------------------------------------------
+// initialize game engine due to level / tape version number
+// ============================================================================
+
+static void InitGameEngine(void)
+{
+ 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);
+ }
+
+#if 0
+ Debug("game:init:level", "level %d: level.game_version == %06d", level_nr,
+ level.game_version);
+ Debug("game:init:level", " tape.file_version == %06d",
+ tape.file_version);
+ Debug("game:init:level", " tape.game_version == %06d",
+ tape.game_version);
+ Debug("game:init:level", " tape.engine_version == %06d",
+ tape.engine_version);
+ Debug("game:init:level", " => game.engine_version == %06d [tape mode: %s]",
+ game.engine_version, (tape.playing ? "PLAYING" : "RECORDING"));
+#endif
+
+ // --------------------------------------------------------------------------
+ // set flags for bugs and changes according to active game engine version
+ // --------------------------------------------------------------------------
+
+ /*
+ Summary of bugfix:
+ Fixed property "can fall" for run-time element "EL_AMOEBA_DROPPING"
+
+ Bug was introduced in version:
+ 2.0.1
+
+ Bug was fixed in version:
+ 4.2.0.0
+
+ Description:
+ In version 2.0.1, a new run-time element "EL_AMOEBA_DROPPING" was added,
+ but the property "can fall" was missing, which caused some levels to be
+ unsolvable. This was fixed in version 4.2.0.0.
+
+ Affected levels/tapes:
+ An example for a tape that was fixed by this bugfix is tape 029 from the
+ level set "rnd_sam_bateman".
+ The wrong behaviour will still be used for all levels or tapes that were
+ created/recorded with it. An example for this is tape 023 from the level
+ set "rnd_gerhard_haeusler", which was recorded with a buggy game engine.
+ */
+
+ boolean use_amoeba_dropping_cannot_fall_bug =
+ ((game.engine_version >= VERSION_IDENT(2,0,1,0) &&
+ game.engine_version < VERSION_IDENT(4,2,0,0)) ||
+ (tape.playing &&
+ tape.game_version >= VERSION_IDENT(2,0,1,0) &&
+ tape.game_version < VERSION_IDENT(4,2,0,0)));
+
+ /*
+ Summary of bugfix/change:
+ Fixed move speed of elements entering or leaving magic wall.
+
+ Fixed/changed in version:
+ 2.0.1
+
+ Description:
+ Before 2.0.1, move speed of elements entering or leaving magic wall was
+ twice as fast as it is now.
+ Since 2.0.1, this is set to a lower value by using move_stepsize_list[].
+
+ Affected levels/tapes:
+ The first condition is generally needed for all levels/tapes before version
+ 2.0.1, which might use the old behaviour before it was changed; known tapes
+ that are affected: Tape 014 from the level set "rnd_conor_mancone".
+ 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 2.0.1 or
+ above, but before it was known that this change would break tapes like the
+ above and was fixed in 4.2.0.0, so that the changed behaviour was active
+ although the engine version while recording maybe was before 2.0.1. There
+ are a lot of tapes that are affected by this exception, like tape 006 from
+ the level set "rnd_conor_mancone".
+ */
+
+ boolean use_old_move_stepsize_for_magic_wall =
+ (game.engine_version < VERSION_IDENT(2,0,1,0) &&
+ !(tape.playing &&
+ tape.game_version >= VERSION_IDENT(2,0,1,0) &&
+ tape.game_version < VERSION_IDENT(4,2,0,0)));
+
+ /*
+ 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));
+
+ /* various special flags and settings for native Emerald Mine game engine */
+
+ 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));
+
+ game_em.use_random_bug =
+ (tape.property_bits & TAPE_PROPERTY_EM_RANDOM_BUG);
+
+ boolean use_old_em_engine = (game.engine_version < VERSION_IDENT(4,2,0,0));
+
+ game_em.use_old_explosions = use_old_em_engine;
+ game_em.use_old_android = use_old_em_engine;
+ game_em.use_old_push_elements = use_old_em_engine;
+ game_em.use_old_push_into_acid = use_old_em_engine;
+
+ game_em.use_wrap_around = !use_old_em_engine;
+
+ // --------------------------------------------------------------------------
+
+ // 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);
+
+ // ---------- initialize special element properties -------------------------
+
+ // "EL_AMOEBA_DROPPING" missed property "can fall" in older game versions
+ if (use_amoeba_dropping_cannot_fall_bug)
+ SET_PROPERTY(EL_AMOEBA_DROPPING, EP_CAN_FALL, FALSE);
+
+ // ---------- 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;
+
+ // set move stepsize value for certain elements for older engine versions
+ if (use_old_move_stepsize_for_magic_wall)
+ {
+ if (e == EL_MAGIC_WALL_FILLING ||
+ e == EL_MAGIC_WALL_EMPTYING ||
+ e == EL_BD_MAGIC_WALL_FILLING ||
+ e == EL_BD_MAGIC_WALL_EMPTYING)
+ element_info[e].move_stepsize *= 2;
+ }
+ }
+
+ // ---------- 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 :
+ level.game_engine_type == GAME_ENGINE_TYPE_EM &&
+ !setup.forced_scroll_delay ? 0 :
+ 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;
+
+ // ---------- initialize flags for handling game actions --------------------
+
+ // set flags for game actions to default values
+ game.use_key_actions = TRUE;
+ game.use_mouse_actions = FALSE;
+
+ // when using Mirror Magic game engine, handle mouse events only
+ if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
+ {
+ game.use_key_actions = FALSE;
+ game.use_mouse_actions = TRUE;
+ }
+
+ // check for custom elements with mouse click events
+ if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
+ {
+ for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
+ {
+ int element = EL_CUSTOM_START + i;
+
+ if (HAS_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
+ HAS_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE) ||
+ HAS_CHANGE_EVENT(element, CE_MOUSE_CLICKED_ON_X) ||
+ HAS_CHANGE_EVENT(element, CE_MOUSE_PRESSED_ON_X))
+ game.use_mouse_actions = TRUE;
+ }
+ }
+}
+
+static 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;
+
+ Debug("game:init:player", "%s:", message);
+
+ for (i = 0; i < MAX_PLAYERS; i++)
+ {
+ struct PlayerInfo *player = &stored_player[i];
+
+ Debug("game:init:player",
+ "- player %d: present == %d, connected == %d [%d/%d], active == %d%s",
+ i + 1,
+ player->present,
+ player->connected,
+ player->connected_locally,
+ player->connected_network,
+ player->active,
+ (local_player == player ? " (local player)" : ""));
+ }
+}
+#endif
+
+void InitGame(void)
+{
+ 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_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)
+ FadeSkipNextFadeOut();
+ else
+ FadeSetEnterScreen();
+
+ if (CheckFadeAll())
+ fade_mask = REDRAW_ALL;
+
+ FadeLevelSoundsAndMusic();
+
+ ExpireSoundLoops(TRUE);
+
+ FadeOut(fade_mask);
+
+ if (level_editor_test_game)
+ FadeSkipNextFadeIn();
+
+ // needed if different viewport properties defined for playing
+ ChangeViewportPropertiesIfNeeded();
+
+ ClearField();
+
+ DrawCompleteVideoDisplay();
+
+ OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
+
+ InitGameEngine();
+ InitGameControlValues();
+
+ if (tape.recording)
+ {
+ // initialize tape actions from game when recording tape
+ tape.use_key_actions = game.use_key_actions;
+ tape.use_mouse_actions = game.use_mouse_actions;
+
+ // initialize visible playfield size when recording tape (for team mode)
+ tape.scr_fieldx = SCR_FIELDX;
+ tape.scr_fieldy = SCR_FIELDY;
+ }
+
+ // 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->buried = FALSE;
+
+ player->action = 0;
+ player->effective_action = 0;
+ player->programmed_action = 0;
+ player->snap_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;
+
+ 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->last_removed_element = EL_UNDEFINED;
+
+ 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);
+
+ map_player_action[i] = i;
+ }
+
+ network_player_action_received = FALSE;
+
+ // initial null action
+ if (network_playing)
+ SendToServer_MovePlayer(MV_NONE);
+
+ 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()
+
+ game.robot_wheel_x = -1;
+ game.robot_wheel_y = -1;
+
+ game.exit_x = -1;
+ game.exit_y = -1;
+
+ game.all_players_gone = FALSE;
+
+ game.LevelSolved = FALSE;
+ game.GameOver = FALSE;
+
+ game.GamePlayed = !tape.playing;
+
+ game.LevelSolved_GameWon = FALSE;
+ game.LevelSolved_GameEnd = FALSE;
+ game.LevelSolved_SaveTape = FALSE;
+ game.LevelSolved_SaveScore = FALSE;
+
+ game.LevelSolved_CountingTime = 0;
+ game.LevelSolved_CountingScore = 0;
+ game.LevelSolved_CountingHealth = 0;
+
+ game.panel.active = TRUE;
+
+ 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.time_final = 0;
+ game.score_time_final = 0;
+
+ game.score = 0;
+ game.score_final = 0;
+
+ game.health = MAX_HEALTH;
+ game.health_final = MAX_HEALTH;
+
+ game.gems_still_needed = level.gems_needed;
+ game.sokoban_fields_still_needed = 0;
+ game.sokoban_objects_still_needed = 0;
+ game.lights_still_needed = 0;
+ game.players_still_needed = 0;
+ game.friends_still_needed = 0;
+
+ game.lenses_time_left = 0;
+ game.magnify_time_left = 0;
+
+ game.ball_active = level.ball_active_initial;
+ game.ball_content_nr = 0;
+
+ game.explosions_delayed = TRUE;
+
+ 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)
+ {
+ Tile[x][y] = Last[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(Tile[x][y]))
+ emulate_bd = FALSE;
+ if (emulate_sp && !IS_SP_ELEMENT(Tile[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_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
+ // use preferred player also in local single-player mode
+ if (!network.enabled && !game.team_mode)
+ {
+ int new_index_nr = setup.network_player_nr;
+
+ if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
+ {
+ for (i = 0; i < MAX_PLAYERS; i++)
+ stored_player[i].connected_locally = FALSE;
+
+ stored_player[new_index_nr].connected_locally = TRUE;
+ }
+ }
+
+ 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
+ Debug("game:init:player", "Reassigning players ...");
+#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
+ Debug("game:init:player",
+ "- looking for field player for player %d ...", 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
+ Debug("game:init:player", "- found player %d",
+ 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
+ Debug("game:init:player", "- map_player_action[%d] == %d",
+ 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
+ Debug("game:init:player", "local_player->present == %d",
+ 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;
+ game.set_centered_player_wrap = 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
+ Debug("game:init:player", "Removing player %d at (%d, %d)",
+ i + 1, jx, jy);
+#endif
+
+ player->active = FALSE;
+ StorePlayer[jx][jy] = 0;
+ Tile[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;
+ Tile[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;
+ Tile[jx][jy] = EL_EMPTY;
+ }
+ }
+ }
+
+ for (i = 0; i < MAX_PLAYERS; i++)
+ if (stored_player[i].active)
+ game.players_still_needed++;
+
+ if (level.solved_by_one_player)
+ game.players_still_needed = 1;
+
+ // 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 = Tile[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 = IS_PLAYER_ELEMENT(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 = IS_PLAYER_ELEMENT(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 = IS_PLAYER_ELEMENT(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;
+
+ game.request_active = FALSE;
+ game.request_active_or_moving = FALSE;
+
+ 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
+
+ // 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 = Tile[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:
+ Tile[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:
+ Tile[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:
+ Tile[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:
+ Tile[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:
+ Tile[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:
+ Tile[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:
+ Tile[x][y] = EL_MOLE;
+ MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
+ break;
+
+ case EL_SPRING_LEFT:
+ case EL_SPRING_RIGHT:
+ Tile[x][y] = EL_SPRING;
+ MovDir[x][y] = direction[2][element - EL_SPRING_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 = AmoebaNeighbourNr(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 LevelSolved_SetFinalGameValues(void)
+{
+ game.time_final = (game.no_time_limit ? TimePlayed : TimeLeft);
+ game.score_time_final = (level.use_step_counter ? TimePlayed :
+ TimePlayed * FRAMES_PER_SECOND + TimeFrames);
+
+ game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
+ game_em.lev->score :
+ level.game_engine_type == GAME_ENGINE_TYPE_MM ?
+ game_mm.score :
+ game.score);
+
+ game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
+ MM_HEALTH(game_mm.laser_overload_value) :
+ game.health);
+
+ game.LevelSolved_CountingTime = game.time_final;
+ game.LevelSolved_CountingScore = game.score_final;
+ game.LevelSolved_CountingHealth = game.health_final;
+}
+
+static void LevelSolved_DisplayFinalGameValues(int time, int score, int health)
+{
+ game.LevelSolved_CountingTime = time;
+ game.LevelSolved_CountingScore = score;
+ game.LevelSolved_CountingHealth = health;
+
+ game_panel_controls[GAME_PANEL_TIME].value = time;
+ game_panel_controls[GAME_PANEL_SCORE].value = score;
+ game_panel_controls[GAME_PANEL_HEALTH].value = health;
+
+ DisplayGameControlValues();
+}
+
+static void LevelSolved(void)
+{
+ if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
+ game.players_still_needed > 0)
+ return;
+
+ game.LevelSolved = TRUE;
+ game.GameOver = TRUE;
+
+ // needed here to display correct panel values while player walks into exit
+ LevelSolved_SetFinalGameValues();
+}
+
+void GameWon(void)
+{
+ static int time_count_steps;
+ static int time, time_final;
+ static float score, score_final; // needed for time score < 10 for 10 seconds
+ 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 time_score_base = MIN(MAX(1, level.time_score_base), 10);
+ float time_score = (float)level.score[SC_TIME_BONUS] / time_score_base;
+
+ if (!game.LevelSolved_GameWon)
+ {
+ int i;
+
+ // do not start end game actions before the player stops moving (to exit)
+ if (local_player->active && local_player->MovPos)
+ return;
+
+ // calculate final game values after player finished walking into exit
+ LevelSolved_SetFinalGameValues();
+
+ game.LevelSolved_GameWon = TRUE;
+ game.LevelSolved_SaveTape = tape.recording;
+ game.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 = FRAMES_PER_SECOND; // delay before counting time
+ game_over_delay_2 = FRAMES_PER_SECOND / 2; // delay before counting health
+ game_over_delay_3 = FRAMES_PER_SECOND; // delay before ending the game
+
+ time = time_final = game.time_final;
+ score = score_final = game.score_final;
+ health = health_final = game.health_final;
+
+ // update game panel values before (delayed) counting of score (if any)
+ LevelSolved_DisplayFinalGameValues(time, score, health);
+
+ // if level has time score defined, calculate new final game values
+ if (time_score > 0)
+ {
+ int time_final_max = 999;
+ int time_frames_final_max = time_final_max * FRAMES_PER_SECOND;
+ int time_frames = 0;
+ int time_frames_left = TimeLeft * FRAMES_PER_SECOND - TimeFrames;
+ int time_frames_played = TimePlayed * FRAMES_PER_SECOND + TimeFrames;
+
+ if (TimeLeft > 0)
+ {
+ time_final = 0;
+ time_frames = time_frames_left;
+ }
+ else if (game.no_time_limit && TimePlayed < time_final_max)
+ {
+ time_final = time_final_max;
+ time_frames = time_frames_final_max - time_frames_played;
+ }
+
+ score_final += time_score * time_frames / FRAMES_PER_SECOND + 0.5;
+
+ time_count_steps = MAX(1, ABS(time_final - time) / 100);
+
+ if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
+ {
+ health_final = 0;
+ score_final += health * time_score;
+ }
+
+ game.score_final = score_final;
+ game.health_final = health_final;
+ }
+
+ // if not counting score after game, immediately update game panel values
+ if (level_editor_test_game || !setup.count_score_after_game)
+ {
+ time = time_final;
+ score = score_final;
+
+ LevelSolved_DisplayFinalGameValues(time, score, health);
+ }
+
+ if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
+ {
+ // check if last player has left the level
+ if (game.exit_x >= 0 &&
+ game.exit_y >= 0)
+ {
+ int x = game.exit_x;
+ int y = game.exit_y;
+ int element = Tile[x][y];
+
+ // close exit door after last player
+ if ((game.all_players_gone &&
+ (element == EL_EXIT_OPEN ||
+ element == EL_SP_EXIT_OPEN ||
+ element == EL_STEEL_EXIT_OPEN)) ||
+ element == EL_EM_EXIT_OPEN ||
+ element == EL_EM_STEEL_EXIT_OPEN)
+ {
+
+ Tile[x][y] =
+ (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(x, y, element, ACTION_CLOSING);
+ }
+
+ // player disappears
+ DrawLevelField(x, y);
+ }
+
+ 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 (setup.count_score_after_game)
+ {
+ if (time != time_final)
+ {
+ if (game_over_delay_1 > 0)
+ {
+ game_over_delay_1--;
+
+ return;
+ }
+
+ 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 * time_score;
+
+ // set final score to correct rounding differences after counting score
+ if (time == time_final)
+ score = score_final;
+
+ LevelSolved_DisplayFinalGameValues(time, score, health);
+
+ 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 (health != health_final)
+ {
+ if (game_over_delay_2 > 0)
+ {
+ game_over_delay_2--;
+
+ return;
+ }
+
+ int health_count_dir = (health < health_final ? +1 : -1);
+
+ health += health_count_dir;
+ score += time_score;
+
+ LevelSolved_DisplayFinalGameValues(time, score, health);
+
+ 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;
+ }
+ }
+
+ game.panel.active = FALSE;
+
+ if (game_over_delay_3 > 0)
+ {
+ game_over_delay_3--;
+
+ return;
+ }
+
+ GameEnd();
+}
+
+void GameEnd(void)
+{
+ // used instead of "level_nr" (needed for network games)
+ int last_level_nr = levelset.level_nr;
+
+ game.LevelSolved_GameEnd = TRUE;
+
+ if (game.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
+
+ // set unique basename for score tape (also saved in high score table)
+ strcpy(tape.score_tape_basename, getScoreTapeBasename(setup.player_name));
+ }
+
+ // 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 (!game.LevelSolved_SaveScore)
+ {
+ SetGameStatus(GAME_MODE_MAIN);
+
+ DrawMainMenu();
+
+ return;
+ }
+
+ if (level_nr == leveldir_current->handicap_level)
+ {
+ leveldir_current->handicap_level++;
+
+ SaveLevelSetup_SeriesInfo();
+ }
+
+ // save score and score tape before potentially erasing tape below
+ NewHighScore(last_level_nr);
+
+ if (setup.increment_levels &&
+ level_nr < leveldir_current->last_level &&
+ !network_playing)
+ {
+ level_nr++; // advance to next level
+ TapeErase(); // start with empty tape
+
+ if (setup.auto_play_next_level)
+ {
+ LoadLevel(level_nr);
+
+ SaveLevelSetup_SeriesInfo();
+ }
+ }
+
+ if (scores.last_added >= 0 && setup.show_scores_after_game)
+ {
+ SetGameStatus(GAME_MODE_SCORES);
+
+ DrawHallOfFame(last_level_nr);
+ }
+ else if (setup.auto_play_next_level && setup.increment_levels &&
+ last_level_nr < leveldir_current->last_level &&
+ !network_playing)
+ {
+ StartGameActions(network.enabled, setup.autorecord, level.random_seed);
+ }
+ else
+ {
+ SetGameStatus(GAME_MODE_MAIN);
+
+ DrawMainMenu();
+ }
+}
+
+static int addScoreEntry(struct ScoreInfo *list, struct ScoreEntry *new_entry,
+ boolean one_score_entry_per_name)
+{
+ int i;
+
+ if (strEqual(new_entry->name, EMPTY_PLAYER_NAME))
+ return -1;
+
+ for (i = 0; i < MAX_SCORE_ENTRIES; i++)
+ {
+ struct ScoreEntry *entry = &list->entry[i];
+ boolean score_is_better = (new_entry->score > entry->score);
+ boolean score_is_equal = (new_entry->score == entry->score);
+ boolean time_is_better = (new_entry->time < entry->time);
+ boolean time_is_equal = (new_entry->time == entry->time);
+ boolean better_by_score = (score_is_better ||
+ (score_is_equal && time_is_better));
+ boolean better_by_time = (time_is_better ||
+ (time_is_equal && score_is_better));
+ boolean is_better = (level.rate_time_over_score ? better_by_time :
+ better_by_score);
+ boolean entry_is_empty = (entry->score == 0 &&
+ entry->time == 0);
+
+ // prevent adding server score entries if also existing in local score file
+ // (special case: historic score entries have an empty tape basename entry)
+ if (strEqual(new_entry->tape_basename, entry->tape_basename) &&
+ !strEqual(new_entry->tape_basename, UNDEFINED_FILENAME))
+ return -1;
+
+ if (is_better || entry_is_empty)
+ {
+ // player has made it to the hall of fame
+
+ if (i < MAX_SCORE_ENTRIES - 1)
+ {
+ int m = MAX_SCORE_ENTRIES - 1;
+ int l;
+
+ if (one_score_entry_per_name)
+ {
+ for (l = i; l < MAX_SCORE_ENTRIES; l++)
+ if (strEqual(list->entry[l].name, new_entry->name))
+ m = l;
+
+ if (m == i) // player's new highscore overwrites his old one
+ goto put_into_list;
+ }
+
+ for (l = m; l > i; l--)
+ list->entry[l] = list->entry[l - 1];
+ }
+
+ put_into_list:
+
+ *entry = *new_entry;
+
+ return i;
+ }
+ else if (one_score_entry_per_name &&
+ strEqual(entry->name, new_entry->name))
+ {
+ // player already in high score list with better score or time
+
+ return -1;
+ }
+ }
+
+ return -1;
+}
+
+void NewHighScore(int level_nr)
+{
+ struct ScoreEntry new_entry = {{ 0 }}; // (prevent warning from GCC bug 53119)
+ boolean one_per_name = FALSE;
+
+ strncpy(new_entry.tape_basename, tape.score_tape_basename, MAX_FILENAME_LEN);
+ strncpy(new_entry.name, setup.player_name, MAX_PLAYER_NAME_LEN);
+
+ new_entry.score = game.score_final;
+ new_entry.time = game.score_time_final;
+
+ LoadScore(level_nr);
+
+ scores.last_added = addScoreEntry(&scores, &new_entry, one_per_name);
+
+ if (scores.last_added >= 0)
+ {
+ SaveScore(level_nr);
+
+ // store last added local score entry (before merging server scores)
+ scores.last_added_local = scores.last_added;
+ }
+
+ if (game.LevelSolved_SaveTape)
+ {
+ SaveScoreTape(level_nr);
+ SaveServerScore(level_nr);
+ }
+}
+
+void MergeServerScore(void)
+{
+ struct ScoreEntry last_added_entry;
+ boolean one_per_name = FALSE;
+ int i;
+
+ if (scores.last_added >= 0)
+ last_added_entry = scores.entry[scores.last_added];
+
+ for (i = 0; i < server_scores.num_entries; i++)
+ {
+ int pos = addScoreEntry(&scores, &server_scores.entry[i], one_per_name);
+
+ if (pos >= 0 && pos <= scores.last_added)
+ scores.last_added++;
+ }
+
+ if (scores.last_added >= MAX_SCORE_ENTRIES)
+ {
+ scores.last_added = MAX_SCORE_ENTRIES - 1;
+ scores.force_last_added = TRUE;
+
+ scores.entry[scores.last_added] = last_added_entry;
+ }
+}
+
+static int getElementMoveStepsizeExt(int x, int y, int direction)
+{
+ int element = Tile[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(Tile[x][y + 1]))
+ step = sign * MOVE_STEPSIZE_NORMAL / 2;
+ else if (element == EL_SPRING)
+ step = sign * MOVE_STEPSIZE_NORMAL * 2;
+ }
+
+ return step;
+}
+
+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 = Tile[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();
+}
+
+static void InitMovingField(int x, int y, int direction)
+{
+ int element = Tile[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 (Tile[newx][newy] == EL_EMPTY)
+ Tile[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;
+}
+
+static int MovingOrBlocked2Element(int x, int y)
+{
+ int element = Tile[x][y];
+
+ if (element == EL_BLOCKED)
+ {
+ int oldx, oldy;
+
+ Blocked2Moving(x, y, &oldx, &oldy);
+ return Tile[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 = Tile[x][y];
+
+ if (IS_MOVING(x, y))
+ {
+ if (element == EL_BLOCKED)
+ {
+ int oldx, oldy;
+
+ Blocked2Moving(x, y, &oldx, &oldy);
+ return Tile[oldx][oldy];
+ }
+ else
+ return EL_BLOCKED;
+ }
+ else
+ return element;
+}
+
+static void RemoveField(int x, int y)
+{
+ Tile[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;
+}
+
+static void RemoveMovingField(int x, int y)
+{
+ int oldx = x, oldy = y, newx = x, newy = y;
+ int element = Tile[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 (Tile[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 &&
+ (Tile[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
+ Tile[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
+ Tile[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
+ Tile[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
+ Tile[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
+ Tile[oldx][oldy] == EL_AMOEBA_DROPPING))
+ next_element = get_next_element(Tile[oldx][oldy]);
+
+ RemoveField(oldx, oldy);
+ RemoveField(newx, newy);
+
+ Store[oldx][oldy] = Store2[oldx][oldy] = 0;
+
+ if (next_element != EL_UNDEFINED)
+ Tile[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(Tile[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])
+ DrawLevelElement(x, y, Back[x][y]);
+ else if (Store[x][y])
+ DrawLevelElement(x, y, Store[x][y]);
+ else if (game.use_masked_elements)
+ DrawLevelElement(x, y, EL_EMPTY);
+
+ frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
+
+ if (Back[x][y] || Store[x][y] || game.use_masked_elements)
+ DrawGraphicThruMask(sx, sy, graphic, frame);
+ else
+ DrawGraphic(sx, sy, graphic, frame);
+}
+
+static 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(void)
+{
+ 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;
+}
+
+static 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 = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
+ int 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;
+
+ // set values for horizontal/vertical screen scrolling (half tile size)
+ int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
+ int dir_y = (dy != 0 ? MV_VERTICAL : 0);
+ int pos_x = dx * TILEX / 2;
+ int pos_y = dy * TILEY / 2;
+ int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
+ int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
+
+ ScrollLevel(dx, dy);
+ DrawAllPlayers();
+
+ // scroll in two steps of half tile size to make things smoother
+ BlitScreenToBitmapExt_RND(window, fx, fy);
+
+ // scroll second step to align at full tile size
+ BlitScreenToBitmap(window);
+ }
+
+ DrawAllPlayers();
+ BackToFront();
+
+ SetVideoFrameDelay(frame_delay_value_old);
+}
+
+static 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 = Tile[old_jx][old_jy];
+ int element = Tile[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->buried) // 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);
+
+ Tile[jx][jy] = el_player;
+ InitPlayerField(jx, jy, el_player, TRUE);
+
+ /* "InitPlayerField()" above sets Tile[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 (!IS_PLAYER_ELEMENT(element)) // player may be set on walkable element
+ {
+ Tile[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);