+ /* make sure that stepsize value is always a power of 2 */
+ move_stepsize = (1 << log_2(move_stepsize));
+
+ return TILEX / move_stepsize;
+}
+
+static void SetPlayerMoveSpeed(struct PlayerInfo *player, int move_stepsize,
+ boolean init_game)
+{
+ int player_nr = player->index_nr;
+ int move_delay = get_move_delay_from_stepsize(move_stepsize);
+ boolean cannot_move = (move_stepsize == STEPSIZE_NOT_MOVING ? TRUE : FALSE);
+
+ /* do no immediately change move delay -- the player might just be moving */
+ player->move_delay_value_next = move_delay;
+
+ /* information if player can move must be set separately */
+ player->cannot_move = cannot_move;
+
+ if (init_game)
+ {
+ player->move_delay = game.initial_move_delay[player_nr];
+ player->move_delay_value = game.initial_move_delay_value[player_nr];
+
+ player->move_delay_value_next = -1;
+
+ player->move_delay_reset_counter = 0;
+ }
+}
+
+void GetPlayerConfig()
+{
+ GameFrameDelay = setup.game_frame_delay;
+
+ if (!audio.sound_available)
+ setup.sound_simple = FALSE;
+
+ if (!audio.loops_available)
+ setup.sound_loops = FALSE;
+
+ if (!audio.music_available)
+ setup.sound_music = FALSE;
+
+ if (!video.fullscreen_available)
+ setup.fullscreen = FALSE;
+
+ setup.sound = (setup.sound_simple || setup.sound_loops || setup.sound_music);
+
+ SetAudioMode(setup.sound);
+}
+
+int GetElementFromGroupElement(int element)
+{
+ if (IS_GROUP_ELEMENT(element))
+ {
+ struct ElementGroupInfo *group = element_info[element].group;
+ int last_anim_random_frame = gfx.anim_random_frame;
+ int element_pos;
+
+ if (group->choice_mode == ANIM_RANDOM)
+ gfx.anim_random_frame = RND(group->num_elements_resolved);
+
+ element_pos = getAnimationFrame(group->num_elements_resolved, 1,
+ group->choice_mode, 0,
+ group->choice_pos);
+
+ if (group->choice_mode == ANIM_RANDOM)
+ gfx.anim_random_frame = last_anim_random_frame;
+
+ group->choice_pos++;
+
+ element = group->element_resolved[element_pos];
+ }
+
+ return element;
+}
+
+static void InitPlayerField(int x, int y, int element, boolean init_game)
+{
+ if (element == EL_SP_MURPHY)
+ {
+ if (init_game)
+ {
+ if (stored_player[0].present)
+ {
+ Feld[x][y] = EL_SP_MURPHY_CLONE;
+
+ return;
+ }
+ else
+ {
+ stored_player[0].initial_element = element;
+ stored_player[0].use_murphy = TRUE;
+
+ if (!level.use_artwork_element[0])
+ stored_player[0].artwork_element = EL_SP_MURPHY;
+ }
+
+ Feld[x][y] = EL_PLAYER_1;
+ }
+ }
+
+ if (init_game)
+ {
+ struct PlayerInfo *player = &stored_player[Feld[x][y] - EL_PLAYER_1];
+ int jx = player->jx, jy = player->jy;
+
+ player->present = TRUE;
+
+ player->block_last_field = (element == EL_SP_MURPHY ?
+ level.sp_block_last_field :
+ level.block_last_field);
+
+ /* ---------- initialize player's last field block delay --------------- */
+
+ /* always start with reliable default value (no adjustment needed) */
+ player->block_delay_adjustment = 0;
+
+ /* special case 1: in Supaplex, Murphy blocks last field one more frame */
+ if (player->block_last_field && element == EL_SP_MURPHY)
+ player->block_delay_adjustment = 1;
+
+ /* special case 2: in game engines before 3.1.1, blocking was different */
+ if (game.use_block_last_field_bug)
+ player->block_delay_adjustment = (player->block_last_field ? -1 : 1);
+
+ if (!options.network || player->connected)
+ {
+ player->active = TRUE;
+
+ /* remove potentially duplicate players */
+ if (StorePlayer[jx][jy] == Feld[x][y])
+ StorePlayer[jx][jy] = 0;
+
+ StorePlayer[x][y] = Feld[x][y];
+
+#if DEBUG_INIT_PLAYER
+ if (options.debug)
+ {
+ printf("- player element %d activated", player->element_nr);
+ printf(" (local player is %d and currently %s)\n",
+ local_player->element_nr,
+ local_player->active ? "active" : "not active");
+ }
+ }
+#endif
+
+ Feld[x][y] = EL_EMPTY;
+
+ player->jx = player->last_jx = x;
+ player->jy = player->last_jy = y;
+ }
+
+ if (!init_game)
+ {
+ int player_nr = GET_PLAYER_NR(element);
+ struct PlayerInfo *player = &stored_player[player_nr];
+
+ if (player->active && player->killed)
+ player->reanimated = TRUE; /* if player was just killed, reanimate him */
+ }
+}
+
+static void InitField(int x, int y, boolean init_game)
+{
+ int element = Feld[x][y];
+
+ switch (element)
+ {
+ case EL_SP_MURPHY:
+ case EL_PLAYER_1:
+ case EL_PLAYER_2:
+ case EL_PLAYER_3:
+ case EL_PLAYER_4:
+ InitPlayerField(x, y, element, init_game);
+ break;
+
+ case EL_SOKOBAN_FIELD_PLAYER:
+ element = Feld[x][y] = EL_PLAYER_1;
+ InitField(x, y, init_game);
+
+ element = Feld[x][y] = EL_SOKOBAN_FIELD_EMPTY;
+ InitField(x, y, init_game);
+ break;
+
+ case EL_SOKOBAN_FIELD_EMPTY:
+ local_player->sokobanfields_still_needed++;
+ break;
+
+ case EL_STONEBLOCK:
+ if (x < lev_fieldx-1 && Feld[x+1][y] == EL_ACID)
+ Feld[x][y] = EL_ACID_POOL_TOPLEFT;
+ else if (x > 0 && Feld[x-1][y] == EL_ACID)
+ Feld[x][y] = EL_ACID_POOL_TOPRIGHT;
+ else if (y > 0 && Feld[x][y-1] == EL_ACID_POOL_TOPLEFT)
+ Feld[x][y] = EL_ACID_POOL_BOTTOMLEFT;
+ else if (y > 0 && Feld[x][y-1] == EL_ACID)
+ Feld[x][y] = EL_ACID_POOL_BOTTOM;
+ else if (y > 0 && Feld[x][y-1] == EL_ACID_POOL_TOPRIGHT)
+ Feld[x][y] = EL_ACID_POOL_BOTTOMRIGHT;
+ break;
+
+ case EL_BUG:
+ case EL_BUG_RIGHT:
+ case EL_BUG_UP:
+ case EL_BUG_LEFT:
+ case EL_BUG_DOWN:
+ case EL_SPACESHIP:
+ case EL_SPACESHIP_RIGHT:
+ case EL_SPACESHIP_UP:
+ case EL_SPACESHIP_LEFT:
+ case EL_SPACESHIP_DOWN:
+ case EL_BD_BUTTERFLY:
+ case EL_BD_BUTTERFLY_RIGHT:
+ case EL_BD_BUTTERFLY_UP:
+ case EL_BD_BUTTERFLY_LEFT:
+ case EL_BD_BUTTERFLY_DOWN:
+ case EL_BD_FIREFLY:
+ case EL_BD_FIREFLY_RIGHT:
+ case EL_BD_FIREFLY_UP:
+ case EL_BD_FIREFLY_LEFT:
+ case EL_BD_FIREFLY_DOWN:
+ case EL_PACMAN_RIGHT:
+ case EL_PACMAN_UP:
+ case EL_PACMAN_LEFT:
+ case EL_PACMAN_DOWN:
+ case EL_YAMYAM:
+ case EL_YAMYAM_LEFT:
+ case EL_YAMYAM_RIGHT:
+ case EL_YAMYAM_UP:
+ case EL_YAMYAM_DOWN:
+ case EL_DARK_YAMYAM:
+ case EL_ROBOT:
+ case EL_PACMAN:
+ case EL_SP_SNIKSNAK:
+ case EL_SP_ELECTRON:
+ case EL_MOLE:
+ case EL_MOLE_LEFT:
+ case EL_MOLE_RIGHT:
+ case EL_MOLE_UP:
+ case EL_MOLE_DOWN:
+ InitMovDir(x, y);
+ break;
+
+ case EL_AMOEBA_FULL:
+ case EL_BD_AMOEBA:
+ InitAmoebaNr(x, y);
+ break;
+
+ case EL_AMOEBA_DROP:
+ if (y == lev_fieldy - 1)
+ {
+ Feld[x][y] = EL_AMOEBA_GROWING;
+ Store[x][y] = EL_AMOEBA_WET;
+ }
+ break;
+
+ case EL_DYNAMITE_ACTIVE:
+ case EL_SP_DISK_RED_ACTIVE:
+ case EL_DYNABOMB_PLAYER_1_ACTIVE:
+ case EL_DYNABOMB_PLAYER_2_ACTIVE:
+ case EL_DYNABOMB_PLAYER_3_ACTIVE:
+ case EL_DYNABOMB_PLAYER_4_ACTIVE:
+ MovDelay[x][y] = 96;
+ break;
+
+ case EL_EM_DYNAMITE_ACTIVE:
+ MovDelay[x][y] = 32;
+ break;
+
+ case EL_LAMP:
+ local_player->lights_still_needed++;
+ break;
+
+ case EL_PENGUIN:
+ local_player->friends_still_needed++;
+ break;
+
+ case EL_PIG:
+ case EL_DRAGON:
+ GfxDir[x][y] = MovDir[x][y] = 1 << RND(4);
+ break;
+
+ case EL_CONVEYOR_BELT_1_SWITCH_LEFT:
+ case EL_CONVEYOR_BELT_1_SWITCH_MIDDLE:
+ case EL_CONVEYOR_BELT_1_SWITCH_RIGHT:
+ case EL_CONVEYOR_BELT_2_SWITCH_LEFT:
+ case EL_CONVEYOR_BELT_2_SWITCH_MIDDLE:
+ case EL_CONVEYOR_BELT_2_SWITCH_RIGHT:
+ case EL_CONVEYOR_BELT_3_SWITCH_LEFT:
+ case EL_CONVEYOR_BELT_3_SWITCH_MIDDLE:
+ case EL_CONVEYOR_BELT_3_SWITCH_RIGHT:
+ case EL_CONVEYOR_BELT_4_SWITCH_LEFT:
+ case EL_CONVEYOR_BELT_4_SWITCH_MIDDLE:
+ case EL_CONVEYOR_BELT_4_SWITCH_RIGHT:
+ if (init_game)
+ {
+ int belt_nr = getBeltNrFromBeltSwitchElement(Feld[x][y]);
+ int belt_dir = getBeltDirFromBeltSwitchElement(Feld[x][y]);
+ int belt_dir_nr = getBeltDirNrFromBeltSwitchElement(Feld[x][y]);
+
+ if (game.belt_dir_nr[belt_nr] == 3) /* initial value */
+ {
+ game.belt_dir[belt_nr] = belt_dir;
+ game.belt_dir_nr[belt_nr] = belt_dir_nr;
+ }
+ else /* more than one switch -- set it like the first switch */
+ {
+ Feld[x][y] = Feld[x][y] - belt_dir_nr + game.belt_dir_nr[belt_nr];
+ }
+ }
+ break;
+
+ case EL_LIGHT_SWITCH_ACTIVE:
+ if (init_game)
+ game.light_time_left = level.time_light * FRAMES_PER_SECOND;
+ break;
+
+ case EL_INVISIBLE_STEELWALL:
+ case EL_INVISIBLE_WALL:
+ case EL_INVISIBLE_SAND:
+ if (game.light_time_left > 0 ||
+ game.lenses_time_left > 0)
+ Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
+ break;
+
+ case EL_EMC_MAGIC_BALL:
+ if (game.ball_state)
+ Feld[x][y] = EL_EMC_MAGIC_BALL_ACTIVE;
+ break;
+
+ case EL_EMC_MAGIC_BALL_SWITCH:
+ if (game.ball_state)
+ Feld[x][y] = EL_EMC_MAGIC_BALL_SWITCH_ACTIVE;
+ break;
+
+ case EL_TRIGGER_PLAYER:
+ case EL_TRIGGER_ELEMENT:
+ case EL_TRIGGER_CE_VALUE:
+ case EL_TRIGGER_CE_SCORE:
+ case EL_SELF:
+ case EL_ANY_ELEMENT:
+ case EL_CURRENT_CE_VALUE:
+ case EL_CURRENT_CE_SCORE:
+ case EL_PREV_CE_1:
+ case EL_PREV_CE_2:
+ case EL_PREV_CE_3:
+ case EL_PREV_CE_4:
+ case EL_PREV_CE_5:
+ case EL_PREV_CE_6:
+ case EL_PREV_CE_7:
+ case EL_PREV_CE_8:
+ case EL_NEXT_CE_1:
+ case EL_NEXT_CE_2:
+ case EL_NEXT_CE_3:
+ case EL_NEXT_CE_4:
+ case EL_NEXT_CE_5:
+ case EL_NEXT_CE_6:
+ case EL_NEXT_CE_7:
+ case EL_NEXT_CE_8:
+ /* reference elements should not be used on the playfield */
+ Feld[x][y] = EL_EMPTY;
+ break;
+
+ default:
+ if (IS_CUSTOM_ELEMENT(element))
+ {
+ if (CAN_MOVE(element))
+ InitMovDir(x, y);
+
+ if (!element_info[element].use_last_ce_value || init_game)
+ CustomValue[x][y] = GET_NEW_CE_VALUE(Feld[x][y]);
+ }
+ else if (IS_GROUP_ELEMENT(element))
+ {
+ Feld[x][y] = GetElementFromGroupElement(element);
+
+ InitField(x, y, init_game);
+ }
+
+ break;
+ }
+
+ if (!init_game)
+ CheckTriggeredElementChange(x, y, element, CE_CREATION_OF_X);
+}
+
+inline static void InitField_WithBug1(int x, int y, boolean init_game)
+{
+ InitField(x, y, init_game);
+
+ /* not needed to call InitMovDir() -- already done by InitField()! */
+ if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
+ CAN_MOVE(Feld[x][y]))
+ InitMovDir(x, y);
+}
+
+inline static void InitField_WithBug2(int x, int y, boolean init_game)
+{
+ int old_element = Feld[x][y];
+
+ InitField(x, y, init_game);
+
+ /* not needed to call InitMovDir() -- already done by InitField()! */
+ if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
+ CAN_MOVE(old_element) &&
+ (old_element < EL_MOLE_LEFT || old_element > EL_MOLE_DOWN))
+ InitMovDir(x, y);
+
+ /* this case is in fact a combination of not less than three bugs:
+ first, it calls InitMovDir() for elements that can move, although this is
+ already done by InitField(); then, it checks the element that was at this
+ field _before_ the call to InitField() (which can change it); lastly, it
+ was not called for "mole with direction" elements, which were treated as
+ "cannot move" due to (fixed) wrong element initialization in "src/init.c"
+ */
+}
+
+static int get_key_element_from_nr(int key_nr)
+{
+ int key_base_element = (key_nr >= STD_NUM_KEYS ? EL_EMC_KEY_5 - STD_NUM_KEYS :
+ level.game_engine_type == GAME_ENGINE_TYPE_EM ?
+ EL_EM_KEY_1 : EL_KEY_1);
+
+ return key_base_element + key_nr;
+}
+
+static int get_next_dropped_element(struct PlayerInfo *player)
+{
+ return (player->inventory_size > 0 ?
+ player->inventory_element[player->inventory_size - 1] :
+ player->inventory_infinite_element != EL_UNDEFINED ?
+ player->inventory_infinite_element :
+ player->dynabombs_left > 0 ?
+ EL_DYNABOMB_PLAYER_1_ACTIVE + player->index_nr :
+ EL_UNDEFINED);
+}
+
+static int get_inventory_element_from_pos(struct PlayerInfo *player, int pos)
+{
+ /* pos >= 0: get element from bottom of the stack;
+ pos < 0: get element from top of the stack */
+
+ if (pos < 0)
+ {
+ int min_inventory_size = -pos;
+ int inventory_pos = player->inventory_size - min_inventory_size;
+ int min_dynabombs_left = min_inventory_size - player->inventory_size;
+
+ return (player->inventory_size >= min_inventory_size ?
+ player->inventory_element[inventory_pos] :
+ player->inventory_infinite_element != EL_UNDEFINED ?
+ player->inventory_infinite_element :
+ player->dynabombs_left >= min_dynabombs_left ?
+ EL_DYNABOMB_PLAYER_1 + player->index_nr :
+ EL_UNDEFINED);
+ }
+ else
+ {
+ int min_dynabombs_left = pos + 1;
+ int min_inventory_size = pos + 1 - player->dynabombs_left;
+ int inventory_pos = pos - player->dynabombs_left;
+
+ return (player->inventory_infinite_element != EL_UNDEFINED ?
+ player->inventory_infinite_element :
+ player->dynabombs_left >= min_dynabombs_left ?
+ EL_DYNABOMB_PLAYER_1 + player->index_nr :
+ player->inventory_size >= min_inventory_size ?
+ player->inventory_element[inventory_pos] :
+ EL_UNDEFINED);
+ }
+}
+
+static int compareGamePanelOrderInfo(const void *object1, const void *object2)
+{
+ const struct GamePanelOrderInfo *gpo1 = (struct GamePanelOrderInfo *)object1;
+ const struct GamePanelOrderInfo *gpo2 = (struct GamePanelOrderInfo *)object2;
+ int compare_result;
+
+ if (gpo1->sort_priority != gpo2->sort_priority)
+ compare_result = gpo1->sort_priority - gpo2->sort_priority;
+ else
+ compare_result = gpo1->nr - gpo2->nr;
+
+ return compare_result;
+}
+
+int getPlayerInventorySize(int player_nr)
+{
+ if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+ return level.native_em_level->ply[player_nr]->dynamite;
+ else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
+ return level.native_sp_level->game_sp->red_disk_count;
+ else
+ return stored_player[player_nr].inventory_size;
+}
+
+void InitGameControlValues()
+{
+ int i;
+
+ for (i = 0; game_panel_controls[i].nr != -1; i++)
+ {
+ struct GamePanelControlInfo *gpc = &game_panel_controls[i];
+ struct GamePanelOrderInfo *gpo = &game_panel_order[i];
+ struct TextPosInfo *pos = gpc->pos;
+ int nr = gpc->nr;
+ int type = gpc->type;
+
+ if (nr != i)
+ {
+ Error(ERR_INFO, "'game_panel_controls' structure corrupted at %d", i);
+ Error(ERR_EXIT, "this should not happen -- please debug");
+ }
+
+ /* force update of game controls after initialization */
+ gpc->value = gpc->last_value = -1;
+ gpc->frame = gpc->last_frame = -1;
+ gpc->gfx_frame = -1;
+
+ /* determine panel value width for later calculation of alignment */
+ if (type == TYPE_INTEGER || type == TYPE_STRING)
+ {
+ pos->width = pos->size * getFontWidth(pos->font);
+ pos->height = getFontHeight(pos->font);
+ }
+ else if (type == TYPE_ELEMENT)
+ {
+ pos->width = pos->size;
+ pos->height = pos->size;
+ }
+
+ /* fill structure for game panel draw order */
+ gpo->nr = gpc->nr;
+ gpo->sort_priority = pos->sort_priority;
+ }
+
+ /* sort game panel controls according to sort_priority and control number */
+ qsort(game_panel_order, NUM_GAME_PANEL_CONTROLS,
+ sizeof(struct GamePanelOrderInfo), compareGamePanelOrderInfo);
+}
+
+void UpdatePlayfieldElementCount()
+{
+ boolean use_element_count = FALSE;
+ int i, j, x, y;
+
+ /* first check if it is needed at all to calculate playfield element count */
+ for (i = GAME_PANEL_ELEMENT_COUNT_1; i <= GAME_PANEL_ELEMENT_COUNT_8; i++)
+ if (!PANEL_DEACTIVATED(game_panel_controls[i].pos))
+ use_element_count = TRUE;
+
+ if (!use_element_count)
+ return;
+
+ for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+ element_info[i].element_count = 0;
+
+ SCAN_PLAYFIELD(x, y)
+ {
+ element_info[Feld[x][y]].element_count++;
+ }
+
+ for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
+ for (j = 0; j < MAX_NUM_ELEMENTS; j++)
+ if (IS_IN_GROUP(j, i))
+ element_info[EL_GROUP_START + i].element_count +=
+ element_info[j].element_count;
+}
+
+void UpdateGameControlValues()
+{
+ int i, k;
+ int time = (local_player->LevelSolved ?
+ local_player->LevelSolved_CountingTime :
+ level.game_engine_type == GAME_ENGINE_TYPE_EM ?
+ level.native_em_level->lev->time :
+ level.game_engine_type == GAME_ENGINE_TYPE_SP ?
+ level.native_sp_level->game_sp->time_played :
+ level.game_engine_type == GAME_ENGINE_TYPE_MM ?
+ game_mm.energy_left :
+ game.no_time_limit ? TimePlayed : TimeLeft);
+ int score = (local_player->LevelSolved ?
+ local_player->LevelSolved_CountingScore :
+ level.game_engine_type == GAME_ENGINE_TYPE_EM ?
+ level.native_em_level->lev->score :
+ level.game_engine_type == GAME_ENGINE_TYPE_SP ?
+ level.native_sp_level->game_sp->score :
+ level.game_engine_type == GAME_ENGINE_TYPE_MM ?
+ game_mm.score :
+ local_player->score);
+ int gems = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
+ level.native_em_level->lev->required :
+ level.game_engine_type == GAME_ENGINE_TYPE_SP ?
+ level.native_sp_level->game_sp->infotrons_still_needed :
+ level.game_engine_type == GAME_ENGINE_TYPE_MM ?
+ game_mm.kettles_still_needed :
+ local_player->gems_still_needed);
+ int exit_closed = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
+ level.native_em_level->lev->required > 0 :
+ level.game_engine_type == GAME_ENGINE_TYPE_SP ?
+ level.native_sp_level->game_sp->infotrons_still_needed > 0 :
+ level.game_engine_type == GAME_ENGINE_TYPE_MM ?
+ game_mm.kettles_still_needed > 0 ||
+ game_mm.lights_still_needed > 0 :
+ local_player->gems_still_needed > 0 ||
+ local_player->sokobanfields_still_needed > 0 ||
+ local_player->lights_still_needed > 0);
+ int health = (local_player->LevelSolved ?
+ local_player->LevelSolved_CountingHealth :
+ level.game_engine_type == GAME_ENGINE_TYPE_MM ?
+ MM_HEALTH(game_mm.laser_overload_value) :
+ local_player->health);
+
+ UpdatePlayfieldElementCount();
+
+ /* update game panel control values */
+
+ game_panel_controls[GAME_PANEL_LEVEL_NUMBER].value = level_nr;
+ game_panel_controls[GAME_PANEL_GEMS].value = gems;
+
+ game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value = 0;
+ for (i = 0; i < MAX_NUM_KEYS; i++)
+ game_panel_controls[GAME_PANEL_KEY_1 + i].value = EL_EMPTY;
+ game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_EMPTY;
+ game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value = 0;
+
+ if (game.centered_player_nr == -1)
+ {
+ for (i = 0; i < MAX_PLAYERS; i++)
+ {
+ /* only one player in Supaplex game engine */
+ if (level.game_engine_type == GAME_ENGINE_TYPE_SP && i > 0)
+ break;
+
+ for (k = 0; k < MAX_NUM_KEYS; k++)
+ {
+ if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+ {
+ if (level.native_em_level->ply[i]->keys & (1 << k))
+ game_panel_controls[GAME_PANEL_KEY_1 + k].value =
+ get_key_element_from_nr(k);
+ }
+ else if (stored_player[i].key[k])
+ game_panel_controls[GAME_PANEL_KEY_1 + k].value =
+ get_key_element_from_nr(k);
+ }
+
+ game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
+ getPlayerInventorySize(i);
+
+ if (stored_player[i].num_white_keys > 0)
+ game_panel_controls[GAME_PANEL_KEY_WHITE].value =
+ EL_DC_KEY_WHITE;
+
+ game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
+ stored_player[i].num_white_keys;
+ }
+ }
+ else
+ {
+ int player_nr = game.centered_player_nr;
+
+ for (k = 0; k < MAX_NUM_KEYS; k++)
+ {
+ if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+ {
+ if (level.native_em_level->ply[player_nr]->keys & (1 << k))
+ game_panel_controls[GAME_PANEL_KEY_1 + k].value =
+ get_key_element_from_nr(k);
+ }
+ else if (stored_player[player_nr].key[k])
+ game_panel_controls[GAME_PANEL_KEY_1 + k].value =
+ get_key_element_from_nr(k);
+ }
+
+ game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
+ getPlayerInventorySize(player_nr);
+
+ if (stored_player[player_nr].num_white_keys > 0)
+ game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_DC_KEY_WHITE;
+
+ game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
+ stored_player[player_nr].num_white_keys;
+ }
+
+ for (i = 0; i < NUM_PANEL_INVENTORY; i++)
+ {
+ game_panel_controls[GAME_PANEL_INVENTORY_FIRST_1 + i].value =
+ get_inventory_element_from_pos(local_player, i);
+ game_panel_controls[GAME_PANEL_INVENTORY_LAST_1 + i].value =
+ get_inventory_element_from_pos(local_player, -i - 1);
+ }
+
+ game_panel_controls[GAME_PANEL_SCORE].value = score;
+ game_panel_controls[GAME_PANEL_HIGHSCORE].value = highscore[0].Score;
+
+ game_panel_controls[GAME_PANEL_TIME].value = time;
+
+ game_panel_controls[GAME_PANEL_TIME_HH].value = time / 3600;
+ game_panel_controls[GAME_PANEL_TIME_MM].value = (time / 60) % 60;
+ game_panel_controls[GAME_PANEL_TIME_SS].value = time % 60;
+
+ if (game.no_time_limit)
+ game_panel_controls[GAME_PANEL_TIME_ANIM].value = 100;
+ else
+ game_panel_controls[GAME_PANEL_TIME_ANIM].value = time * 100 / level.time;
+
+ game_panel_controls[GAME_PANEL_HEALTH].value = health;
+ game_panel_controls[GAME_PANEL_HEALTH_ANIM].value = health;
+
+ game_panel_controls[GAME_PANEL_FRAME].value = FrameCounter;
+
+ game_panel_controls[GAME_PANEL_SHIELD_NORMAL].value =
+ (local_player->shield_normal_time_left > 0 ? EL_SHIELD_NORMAL_ACTIVE :
+ EL_EMPTY);
+ game_panel_controls[GAME_PANEL_SHIELD_NORMAL_TIME].value =
+ local_player->shield_normal_time_left;
+ game_panel_controls[GAME_PANEL_SHIELD_DEADLY].value =
+ (local_player->shield_deadly_time_left > 0 ? EL_SHIELD_DEADLY_ACTIVE :
+ EL_EMPTY);
+ game_panel_controls[GAME_PANEL_SHIELD_DEADLY_TIME].value =
+ local_player->shield_deadly_time_left;
+
+ game_panel_controls[GAME_PANEL_EXIT].value =
+ (exit_closed ? EL_EXIT_CLOSED : EL_EXIT_OPEN);
+
+ game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL].value =
+ (game.ball_state ? EL_EMC_MAGIC_BALL_ACTIVE : EL_EMC_MAGIC_BALL);
+ game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL_SWITCH].value =
+ (game.ball_state ? EL_EMC_MAGIC_BALL_SWITCH_ACTIVE :
+ EL_EMC_MAGIC_BALL_SWITCH);
+
+ game_panel_controls[GAME_PANEL_LIGHT_SWITCH].value =
+ (game.light_time_left > 0 ? EL_LIGHT_SWITCH_ACTIVE : EL_LIGHT_SWITCH);
+ game_panel_controls[GAME_PANEL_LIGHT_SWITCH_TIME].value =
+ game.light_time_left;
+
+ game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH].value =
+ (game.timegate_time_left > 0 ? EL_TIMEGATE_OPEN : EL_TIMEGATE_CLOSED);
+ game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH_TIME].value =
+ game.timegate_time_left;
+
+ game_panel_controls[GAME_PANEL_SWITCHGATE_SWITCH].value =
+ EL_SWITCHGATE_SWITCH_UP + game.switchgate_pos;
+
+ game_panel_controls[GAME_PANEL_EMC_LENSES].value =
+ (game.lenses_time_left > 0 ? EL_EMC_LENSES : EL_EMPTY);
+ game_panel_controls[GAME_PANEL_EMC_LENSES_TIME].value =
+ game.lenses_time_left;
+
+ game_panel_controls[GAME_PANEL_EMC_MAGNIFIER].value =
+ (game.magnify_time_left > 0 ? EL_EMC_MAGNIFIER : EL_EMPTY);
+ game_panel_controls[GAME_PANEL_EMC_MAGNIFIER_TIME].value =
+ game.magnify_time_left;
+
+ game_panel_controls[GAME_PANEL_BALLOON_SWITCH].value =
+ (game.wind_direction == MV_LEFT ? EL_BALLOON_SWITCH_LEFT :
+ game.wind_direction == MV_RIGHT ? EL_BALLOON_SWITCH_RIGHT :
+ game.wind_direction == MV_UP ? EL_BALLOON_SWITCH_UP :
+ game.wind_direction == MV_DOWN ? EL_BALLOON_SWITCH_DOWN :
+ EL_BALLOON_SWITCH_NONE);
+
+ game_panel_controls[GAME_PANEL_DYNABOMB_NUMBER].value =
+ local_player->dynabomb_count;
+ game_panel_controls[GAME_PANEL_DYNABOMB_SIZE].value =
+ local_player->dynabomb_size;
+ game_panel_controls[GAME_PANEL_DYNABOMB_POWER].value =
+ (local_player->dynabomb_xl ? EL_DYNABOMB_INCREASE_POWER : EL_EMPTY);
+
+ game_panel_controls[GAME_PANEL_PENGUINS].value =
+ local_player->friends_still_needed;
+
+ game_panel_controls[GAME_PANEL_SOKOBAN_OBJECTS].value =
+ local_player->sokobanfields_still_needed;
+ game_panel_controls[GAME_PANEL_SOKOBAN_FIELDS].value =
+ local_player->sokobanfields_still_needed;
+
+ game_panel_controls[GAME_PANEL_ROBOT_WHEEL].value =
+ (game.robot_wheel_active ? EL_ROBOT_WHEEL_ACTIVE : EL_ROBOT_WHEEL);
+
+ for (i = 0; i < NUM_BELTS; i++)
+ {
+ game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1 + i].value =
+ (game.belt_dir[i] != MV_NONE ? EL_CONVEYOR_BELT_1_MIDDLE_ACTIVE :
+ EL_CONVEYOR_BELT_1_MIDDLE) + i;
+ game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1_SWITCH + i].value =
+ getBeltSwitchElementFromBeltNrAndBeltDir(i, game.belt_dir[i]);
+ }
+
+ game_panel_controls[GAME_PANEL_MAGIC_WALL].value =
+ (game.magic_wall_active ? EL_MAGIC_WALL_ACTIVE : EL_MAGIC_WALL);
+ game_panel_controls[GAME_PANEL_MAGIC_WALL_TIME].value =
+ game.magic_wall_time_left;
+
+ game_panel_controls[GAME_PANEL_GRAVITY_STATE].value =
+ local_player->gravity;
+
+ for (i = 0; i < NUM_PANEL_GRAPHICS; i++)
+ game_panel_controls[GAME_PANEL_GRAPHIC_1 + i].value = EL_GRAPHIC_1 + i;
+
+ for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
+ game_panel_controls[GAME_PANEL_ELEMENT_1 + i].value =
+ (IS_DRAWABLE_ELEMENT(game.panel.element[i].id) ?
+ game.panel.element[i].id : EL_UNDEFINED);
+
+ for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
+ game_panel_controls[GAME_PANEL_ELEMENT_COUNT_1 + i].value =
+ (IS_VALID_ELEMENT(game.panel.element_count[i].id) ?
+ element_info[game.panel.element_count[i].id].element_count : 0);
+
+ for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
+ game_panel_controls[GAME_PANEL_CE_SCORE_1 + i].value =
+ (IS_CUSTOM_ELEMENT(game.panel.ce_score[i].id) ?
+ element_info[game.panel.ce_score[i].id].collect_score : 0);
+
+ for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
+ game_panel_controls[GAME_PANEL_CE_SCORE_1_ELEMENT + i].value =
+ (IS_CUSTOM_ELEMENT(game.panel.ce_score_element[i].id) ?
+ element_info[game.panel.ce_score_element[i].id].collect_score :
+ EL_UNDEFINED);
+
+ game_panel_controls[GAME_PANEL_PLAYER_NAME].value = 0;
+ game_panel_controls[GAME_PANEL_LEVEL_NAME].value = 0;
+ game_panel_controls[GAME_PANEL_LEVEL_AUTHOR].value = 0;
+
+ /* update game panel control frames */
+
+ for (i = 0; game_panel_controls[i].nr != -1; i++)
+ {
+ struct GamePanelControlInfo *gpc = &game_panel_controls[i];
+
+ if (gpc->type == TYPE_ELEMENT)
+ {
+ if (gpc->value != EL_UNDEFINED && gpc->value != EL_EMPTY)
+ {
+ int last_anim_random_frame = gfx.anim_random_frame;
+ int element = gpc->value;
+ int graphic = el2panelimg(element);
+
+ if (gpc->value != gpc->last_value)
+ {
+ gpc->gfx_frame = 0;
+ gpc->gfx_random = INIT_GFX_RANDOM();
+ }
+ else
+ {
+ gpc->gfx_frame++;
+
+ if (ANIM_MODE(graphic) == ANIM_RANDOM &&
+ IS_NEXT_FRAME(gpc->gfx_frame, graphic))
+ gpc->gfx_random = INIT_GFX_RANDOM();
+ }
+
+ if (ANIM_MODE(graphic) == ANIM_RANDOM)
+ gfx.anim_random_frame = gpc->gfx_random;
+
+ if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
+ gpc->gfx_frame = element_info[element].collect_score;
+
+ gpc->frame = getGraphicAnimationFrame(el2panelimg(gpc->value),
+ gpc->gfx_frame);
+
+ if (ANIM_MODE(graphic) == ANIM_RANDOM)
+ gfx.anim_random_frame = last_anim_random_frame;
+ }
+ }
+ else if (gpc->type == TYPE_GRAPHIC)
+ {
+ if (gpc->graphic != IMG_UNDEFINED)
+ {
+ int last_anim_random_frame = gfx.anim_random_frame;
+ int graphic = gpc->graphic;
+
+ if (gpc->value != gpc->last_value)
+ {
+ gpc->gfx_frame = 0;
+ gpc->gfx_random = INIT_GFX_RANDOM();
+ }
+ else
+ {
+ gpc->gfx_frame++;
+
+ if (ANIM_MODE(graphic) == ANIM_RANDOM &&
+ IS_NEXT_FRAME(gpc->gfx_frame, graphic))
+ gpc->gfx_random = INIT_GFX_RANDOM();
+ }
+
+ if (ANIM_MODE(graphic) == ANIM_RANDOM)
+ gfx.anim_random_frame = gpc->gfx_random;
+
+ gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
+
+ if (ANIM_MODE(graphic) == ANIM_RANDOM)
+ gfx.anim_random_frame = last_anim_random_frame;
+ }
+ }
+ }
+}
+
+void DisplayGameControlValues()
+{
+ boolean redraw_panel = FALSE;
+ int i;
+
+ for (i = 0; game_panel_controls[i].nr != -1; i++)
+ {
+ struct GamePanelControlInfo *gpc = &game_panel_controls[i];
+
+ if (PANEL_DEACTIVATED(gpc->pos))
+ continue;
+
+ if (gpc->value == gpc->last_value &&
+ gpc->frame == gpc->last_frame)
+ continue;
+
+ redraw_panel = TRUE;
+ }
+
+ if (!redraw_panel)
+ return;
+
+ /* copy default game door content to main double buffer */
+
+ /* !!! CHECK AGAIN !!! */
+ SetPanelBackground();
+ // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
+ DrawBackground(DX, DY, DXSIZE, DYSIZE);
+
+ /* redraw game control buttons */
+ RedrawGameButtons();
+
+ SetGameStatus(GAME_MODE_PSEUDO_PANEL);
+
+ for (i = 0; i < NUM_GAME_PANEL_CONTROLS; i++)
+ {
+ int nr = game_panel_order[i].nr;
+ struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
+ struct TextPosInfo *pos = gpc->pos;
+ int type = gpc->type;
+ int value = gpc->value;
+ int frame = gpc->frame;
+ int size = pos->size;
+ int font = pos->font;
+ boolean draw_masked = pos->draw_masked;
+ int mask_mode = (draw_masked ? BLIT_MASKED : BLIT_OPAQUE);
+
+ if (PANEL_DEACTIVATED(pos))
+ continue;
+
+ gpc->last_value = value;
+ gpc->last_frame = frame;
+
+ if (type == TYPE_INTEGER)
+ {
+ if (nr == GAME_PANEL_LEVEL_NUMBER ||
+ nr == GAME_PANEL_TIME)
+ {
+ boolean use_dynamic_size = (size == -1 ? TRUE : FALSE);
+
+ if (use_dynamic_size) /* use dynamic number of digits */
+ {
+ int value_change = (nr == GAME_PANEL_LEVEL_NUMBER ? 100 : 1000);
+ int size1 = (nr == GAME_PANEL_LEVEL_NUMBER ? 2 : 3);
+ int size2 = size1 + 1;
+ int font1 = pos->font;
+ int font2 = pos->font_alt;
+
+ size = (value < value_change ? size1 : size2);
+ font = (value < value_change ? font1 : font2);
+ }
+ }
+
+ /* correct text size if "digits" is zero or less */
+ if (size <= 0)
+ size = strlen(int2str(value, size));
+
+ /* dynamically correct text alignment */
+ pos->width = size * getFontWidth(font);
+
+ DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
+ int2str(value, size), font, mask_mode);
+ }
+ else if (type == TYPE_ELEMENT)
+ {
+ int element, graphic;
+ Bitmap *src_bitmap;
+ int src_x, src_y;
+ int width, height;
+ int dst_x = PANEL_XPOS(pos);
+ int dst_y = PANEL_YPOS(pos);
+
+ if (value != EL_UNDEFINED && value != EL_EMPTY)
+ {
+ element = value;
+ graphic = el2panelimg(value);
+
+ // printf("::: %d, '%s' [%d]\n", element, EL_NAME(element), size);
+
+ if (element >= EL_GRAPHIC_1 && element <= EL_GRAPHIC_8 && size == 0)
+ size = TILESIZE;
+
+ getSizedGraphicSource(graphic, frame, size, &src_bitmap,
+ &src_x, &src_y);
+
+ width = graphic_info[graphic].width * size / TILESIZE;
+ height = graphic_info[graphic].height * size / TILESIZE;
+
+ if (draw_masked)
+ BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
+ dst_x, dst_y);
+ else
+ BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
+ dst_x, dst_y);
+ }
+ }
+ else if (type == TYPE_GRAPHIC)
+ {
+ int graphic = gpc->graphic;
+ int graphic_active = gpc->graphic_active;
+ Bitmap *src_bitmap;
+ int src_x, src_y;
+ int width, height;
+ int dst_x = PANEL_XPOS(pos);
+ int dst_y = PANEL_YPOS(pos);
+ boolean skip = (pos->class == get_hash_from_key("mm_engine_only") &&
+ level.game_engine_type != GAME_ENGINE_TYPE_MM);
+
+ if (graphic != IMG_UNDEFINED && !skip)
+ {
+ if (pos->style == STYLE_REVERSE)
+ value = 100 - value;
+
+ getGraphicSource(graphic_active, frame, &src_bitmap, &src_x, &src_y);
+
+ if (pos->direction & MV_HORIZONTAL)
+ {
+ width = graphic_info[graphic_active].width * value / 100;
+ height = graphic_info[graphic_active].height;
+
+ if (pos->direction == MV_LEFT)
+ {
+ src_x += graphic_info[graphic_active].width - width;
+ dst_x += graphic_info[graphic_active].width - width;
+ }
+ }
+ else
+ {
+ width = graphic_info[graphic_active].width;
+ height = graphic_info[graphic_active].height * value / 100;
+
+ if (pos->direction == MV_UP)
+ {
+ src_y += graphic_info[graphic_active].height - height;
+ dst_y += graphic_info[graphic_active].height - height;
+ }
+ }
+
+ if (draw_masked)
+ BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
+ dst_x, dst_y);
+ else
+ BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
+ dst_x, dst_y);
+
+ getGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
+
+ if (pos->direction & MV_HORIZONTAL)
+ {
+ if (pos->direction == MV_RIGHT)
+ {
+ src_x += width;
+ dst_x += width;
+ }
+ else
+ {
+ dst_x = PANEL_XPOS(pos);
+ }
+
+ width = graphic_info[graphic].width - width;
+ }
+ else
+ {
+ if (pos->direction == MV_DOWN)
+ {
+ src_y += height;
+ dst_y += height;
+ }
+ else
+ {
+ dst_y = PANEL_YPOS(pos);
+ }
+
+ height = graphic_info[graphic].height - height;
+ }
+
+ if (draw_masked)
+ BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
+ dst_x, dst_y);
+ else
+ BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
+ dst_x, dst_y);
+ }
+ }
+ else if (type == TYPE_STRING)
+ {
+ boolean active = (value != 0);
+ char *state_normal = "off";
+ char *state_active = "on";
+ char *state = (active ? state_active : state_normal);
+ char *s = (nr == GAME_PANEL_GRAVITY_STATE ? state :
+ nr == GAME_PANEL_PLAYER_NAME ? setup.player_name :
+ nr == GAME_PANEL_LEVEL_NAME ? level.name :
+ nr == GAME_PANEL_LEVEL_AUTHOR ? level.author : NULL);
+
+ if (nr == GAME_PANEL_GRAVITY_STATE)
+ {
+ int font1 = pos->font; /* (used for normal state) */
+ int font2 = pos->font_alt; /* (used for active state) */
+
+ font = (active ? font2 : font1);
+ }
+
+ if (s != NULL)
+ {
+ char *s_cut;
+
+ if (size <= 0)
+ {
+ /* don't truncate output if "chars" is zero or less */
+ size = strlen(s);
+
+ /* dynamically correct text alignment */
+ pos->width = size * getFontWidth(font);
+ }
+
+ s_cut = getStringCopyN(s, size);
+
+ DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
+ s_cut, font, mask_mode);
+
+ free(s_cut);
+ }
+ }
+
+ redraw_mask |= REDRAW_DOOR_1;
+ }
+
+ SetGameStatus(GAME_MODE_PLAYING);
+}
+
+void UpdateAndDisplayGameControlValues()
+{
+ if (tape.deactivate_display)
+ return;
+
+ UpdateGameControlValues();
+ DisplayGameControlValues();
+}
+
+void UpdateGameDoorValues()
+{
+ UpdateGameControlValues();
+}
+
+void DrawGameDoorValues()
+{
+ DisplayGameControlValues();
+}
+
+
+/*
+ =============================================================================
+ InitGameEngine()
+ -----------------------------------------------------------------------------
+ initialize game engine due to level / tape version number
+ =============================================================================
+*/
+
+static void InitGameEngine()
+{
+ int i, j, k, l, x, y;
+
+ /* set game engine from tape file when re-playing, else from level file */
+ game.engine_version = (tape.playing ? tape.engine_version :
+ level.game_version);
+
+ /* set single or multi-player game mode (needed for re-playing tapes) */
+ game.team_mode = setup.team_mode;
+
+ if (tape.playing)
+ {
+ int num_players = 0;
+
+ for (i = 0; i < MAX_PLAYERS; i++)
+ if (tape.player_participates[i])
+ num_players++;
+
+ /* multi-player tapes contain input data for more than one player */
+ game.team_mode = (num_players > 1);
+ }
+
+ /* ---------------------------------------------------------------------- */
+ /* set flags for bugs and changes according to active game engine version */
+ /* ---------------------------------------------------------------------- */
+
+ /*
+ Summary of bugfix/change:
+ Fixed handling for custom elements that change when pushed by the player.
+
+ Fixed/changed in version:
+ 3.1.0
+
+ Description:
+ Before 3.1.0, custom elements that "change when pushing" changed directly
+ after the player started pushing them (until then handled in "DigField()").
+ Since 3.1.0, these custom elements are not changed until the "pushing"
+ move of the element is finished (now handled in "ContinueMoving()").
+
+ Affected levels/tapes:
+ The first condition is generally needed for all levels/tapes before version
+ 3.1.0, which might use the old behaviour before it was changed; known tapes
+ that are affected are some tapes from the level set "Walpurgis Gardens" by
+ Jamie Cullen.
+ The second condition is an exception from the above case and is needed for
+ the special case of tapes recorded with game (not engine!) version 3.1.0 or
+ above (including some development versions of 3.1.0), but before it was
+ known that this change would break tapes like the above and was fixed in
+ 3.1.1, so that the changed behaviour was active although the engine version
+ while recording maybe was before 3.1.0. There is at least one tape that is
+ affected by this exception, which is the tape for the one-level set "Bug
+ Machine" by Juergen Bonhagen.
+ */
+
+ game.use_change_when_pushing_bug =
+ (game.engine_version < VERSION_IDENT(3,1,0,0) &&
+ !(tape.playing &&
+ tape.game_version >= VERSION_IDENT(3,1,0,0) &&
+ tape.game_version < VERSION_IDENT(3,1,1,0)));
+
+ /*
+ Summary of bugfix/change:
+ Fixed handling for blocking the field the player leaves when moving.
+
+ Fixed/changed in version:
+ 3.1.1
+
+ Description:
+ Before 3.1.1, when "block last field when moving" was enabled, the field
+ the player is leaving when moving was blocked for the time of the move,
+ and was directly unblocked afterwards. This resulted in the last field
+ being blocked for exactly one less than the number of frames of one player
+ move. Additionally, even when blocking was disabled, the last field was
+ blocked for exactly one frame.
+ Since 3.1.1, due to changes in player movement handling, the last field
+ is not blocked at all when blocking is disabled. When blocking is enabled,
+ the last field is blocked for exactly the number of frames of one player
+ move. Additionally, if the player is Murphy, the hero of Supaplex, the
+ last field is blocked for exactly one more than the number of frames of
+ one player move.
+
+ Affected levels/tapes:
+ (!!! yet to be determined -- probably many !!!)
+ */
+
+ game.use_block_last_field_bug =
+ (game.engine_version < VERSION_IDENT(3,1,1,0));
+
+ game_em.use_single_button =
+ (game.engine_version > VERSION_IDENT(4,0,0,2));
+
+ game_em.use_snap_key_bug =
+ (game.engine_version < VERSION_IDENT(4,0,1,0));
+
+ /* ---------------------------------------------------------------------- */
+
+ /* set maximal allowed number of custom element changes per game frame */
+ game.max_num_changes_per_frame = 1;
+
+ /* default scan direction: scan playfield from top/left to bottom/right */
+ InitPlayfieldScanMode(CA_ARG_SCAN_MODE_NORMAL);
+
+ /* dynamically adjust element properties according to game engine version */
+ InitElementPropertiesEngine(game.engine_version);
+
+#if 0
+ printf("level %d: level version == %06d\n", level_nr, level.game_version);
+ printf(" tape version == %06d [%s] [file: %06d]\n",
+ tape.engine_version, (tape.playing ? "PLAYING" : "RECORDING"),
+ tape.file_version);
+ printf(" => game.engine_version == %06d\n", game.engine_version);
+#endif
+
+ /* ---------- initialize player's initial move delay --------------------- */
+
+ /* dynamically adjust player properties according to level information */
+ for (i = 0; i < MAX_PLAYERS; i++)
+ game.initial_move_delay_value[i] =
+ get_move_delay_from_stepsize(level.initial_player_stepsize[i]);
+
+ /* dynamically adjust player properties according to game engine version */
+ for (i = 0; i < MAX_PLAYERS; i++)
+ game.initial_move_delay[i] =
+ (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
+ game.initial_move_delay_value[i] : 0);
+
+ /* ---------- initialize player's initial push delay --------------------- */
+
+ /* dynamically adjust player properties according to game engine version */
+ game.initial_push_delay_value =
+ (game.engine_version < VERSION_IDENT(3,0,7,1) ? 5 : -1);
+
+ /* ---------- initialize changing elements ------------------------------- */
+
+ /* initialize changing elements information */
+ for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+ {
+ struct ElementInfo *ei = &element_info[i];
+
+ /* this pointer might have been changed in the level editor */
+ ei->change = &ei->change_page[0];
+
+ if (!IS_CUSTOM_ELEMENT(i))
+ {
+ ei->change->target_element = EL_EMPTY_SPACE;
+ ei->change->delay_fixed = 0;
+ ei->change->delay_random = 0;
+ ei->change->delay_frames = 1;
+ }
+
+ for (j = 0; j < NUM_CHANGE_EVENTS; j++)
+ {
+ ei->has_change_event[j] = FALSE;
+
+ ei->event_page_nr[j] = 0;
+ ei->event_page[j] = &ei->change_page[0];
+ }
+ }
+
+ /* add changing elements from pre-defined list */
+ for (i = 0; change_delay_list[i].element != EL_UNDEFINED; i++)
+ {
+ struct ChangingElementInfo *ch_delay = &change_delay_list[i];
+ struct ElementInfo *ei = &element_info[ch_delay->element];
+
+ ei->change->target_element = ch_delay->target_element;
+ ei->change->delay_fixed = ch_delay->change_delay;
+
+ ei->change->pre_change_function = ch_delay->pre_change_function;
+ ei->change->change_function = ch_delay->change_function;
+ ei->change->post_change_function = ch_delay->post_change_function;
+
+ ei->change->can_change = TRUE;
+ ei->change->can_change_or_has_action = TRUE;
+
+ ei->has_change_event[CE_DELAY] = TRUE;
+
+ SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE, TRUE);
+ SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE);
+ }
+
+ /* ---------- initialize internal run-time variables --------------------- */
+
+ for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
+ {
+ struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
+
+ for (j = 0; j < ei->num_change_pages; j++)
+ {
+ ei->change_page[j].can_change_or_has_action =
+ (ei->change_page[j].can_change |
+ ei->change_page[j].has_action);
+ }
+ }
+
+ /* add change events from custom element configuration */
+ for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
+ {
+ struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
+
+ for (j = 0; j < ei->num_change_pages; j++)
+ {
+ if (!ei->change_page[j].can_change_or_has_action)
+ continue;
+
+ for (k = 0; k < NUM_CHANGE_EVENTS; k++)
+ {
+ /* only add event page for the first page found with this event */
+ if (ei->change_page[j].has_event[k] && !(ei->has_change_event[k]))
+ {
+ ei->has_change_event[k] = TRUE;
+
+ ei->event_page_nr[k] = j;
+ ei->event_page[k] = &ei->change_page[j];
+ }
+ }
+ }
+ }
+
+ /* ---------- initialize reference elements in change conditions --------- */
+
+ for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
+ {
+ int element = EL_CUSTOM_START + i;
+ struct ElementInfo *ei = &element_info[element];
+
+ for (j = 0; j < ei->num_change_pages; j++)
+ {
+ int trigger_element = ei->change_page[j].initial_trigger_element;
+
+ if (trigger_element >= EL_PREV_CE_8 &&
+ trigger_element <= EL_NEXT_CE_8)
+ trigger_element = RESOLVED_REFERENCE_ELEMENT(element, trigger_element);
+
+ ei->change_page[j].trigger_element = trigger_element;
+ }
+ }
+
+ /* ---------- initialize run-time trigger player and element ------------- */
+
+ for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
+ {
+ struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
+
+ for (j = 0; j < ei->num_change_pages; j++)
+ {
+ ei->change_page[j].actual_trigger_element = EL_EMPTY;
+ ei->change_page[j].actual_trigger_player = EL_EMPTY;
+ ei->change_page[j].actual_trigger_player_bits = CH_PLAYER_NONE;
+ ei->change_page[j].actual_trigger_side = CH_SIDE_NONE;
+ ei->change_page[j].actual_trigger_ce_value = 0;
+ ei->change_page[j].actual_trigger_ce_score = 0;
+ }
+ }
+
+ /* ---------- initialize trigger events ---------------------------------- */
+
+ /* initialize trigger events information */
+ for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+ for (j = 0; j < NUM_CHANGE_EVENTS; j++)
+ trigger_events[i][j] = FALSE;
+
+ /* add trigger events from element change event properties */
+ for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+ {
+ struct ElementInfo *ei = &element_info[i];
+
+ for (j = 0; j < ei->num_change_pages; j++)
+ {
+ if (!ei->change_page[j].can_change_or_has_action)
+ continue;
+
+ if (ei->change_page[j].has_event[CE_BY_OTHER_ACTION])
+ {
+ int trigger_element = ei->change_page[j].trigger_element;
+
+ for (k = 0; k < NUM_CHANGE_EVENTS; k++)
+ {
+ if (ei->change_page[j].has_event[k])
+ {
+ if (IS_GROUP_ELEMENT(trigger_element))
+ {
+ struct ElementGroupInfo *group =
+ element_info[trigger_element].group;
+
+ for (l = 0; l < group->num_elements_resolved; l++)
+ trigger_events[group->element_resolved[l]][k] = TRUE;
+ }
+ else if (trigger_element == EL_ANY_ELEMENT)
+ for (l = 0; l < MAX_NUM_ELEMENTS; l++)
+ trigger_events[l][k] = TRUE;
+ else
+ trigger_events[trigger_element][k] = TRUE;
+ }
+ }
+ }
+ }
+ }
+
+ /* ---------- initialize push delay -------------------------------------- */
+
+ /* initialize push delay values to default */
+ for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+ {
+ if (!IS_CUSTOM_ELEMENT(i))
+ {
+ /* set default push delay values (corrected since version 3.0.7-1) */
+ if (game.engine_version < VERSION_IDENT(3,0,7,1))
+ {
+ element_info[i].push_delay_fixed = 2;
+ element_info[i].push_delay_random = 8;
+ }
+ else
+ {
+ element_info[i].push_delay_fixed = 8;
+ element_info[i].push_delay_random = 8;
+ }
+ }
+ }
+
+ /* set push delay value for certain elements from pre-defined list */
+ for (i = 0; push_delay_list[i].element != EL_UNDEFINED; i++)
+ {
+ int e = push_delay_list[i].element;
+
+ element_info[e].push_delay_fixed = push_delay_list[i].push_delay_fixed;
+ element_info[e].push_delay_random = push_delay_list[i].push_delay_random;
+ }
+
+ /* set push delay value for Supaplex elements for newer engine versions */
+ if (game.engine_version >= VERSION_IDENT(3,1,0,0))
+ {
+ for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+ {
+ if (IS_SP_ELEMENT(i))
+ {
+ /* set SP push delay to just enough to push under a falling zonk */
+ int delay = (game.engine_version >= VERSION_IDENT(3,1,1,0) ? 8 : 6);
+
+ element_info[i].push_delay_fixed = delay;
+ element_info[i].push_delay_random = 0;
+ }
+ }
+ }
+
+ /* ---------- initialize move stepsize ----------------------------------- */
+
+ /* initialize move stepsize values to default */
+ for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+ if (!IS_CUSTOM_ELEMENT(i))
+ element_info[i].move_stepsize = MOVE_STEPSIZE_NORMAL;
+
+ /* set move stepsize value for certain elements from pre-defined list */
+ for (i = 0; move_stepsize_list[i].element != EL_UNDEFINED; i++)
+ {
+ int e = move_stepsize_list[i].element;
+
+ element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
+ }
+
+ /* ---------- initialize collect score ----------------------------------- */
+
+ /* initialize collect score values for custom elements from initial value */
+ for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+ if (IS_CUSTOM_ELEMENT(i))
+ element_info[i].collect_score = element_info[i].collect_score_initial;
+
+ /* ---------- initialize collect count ----------------------------------- */
+
+ /* initialize collect count values for non-custom elements */
+ for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+ if (!IS_CUSTOM_ELEMENT(i))
+ element_info[i].collect_count_initial = 0;
+
+ /* add collect count values for all elements from pre-defined list */
+ for (i = 0; collect_count_list[i].element != EL_UNDEFINED; i++)
+ element_info[collect_count_list[i].element].collect_count_initial =
+ collect_count_list[i].count;
+
+ /* ---------- initialize access direction -------------------------------- */
+
+ /* initialize access direction values to default (access from every side) */
+ for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+ if (!IS_CUSTOM_ELEMENT(i))
+ element_info[i].access_direction = MV_ALL_DIRECTIONS;
+
+ /* set access direction value for certain elements from pre-defined list */
+ for (i = 0; access_direction_list[i].element != EL_UNDEFINED; i++)
+ element_info[access_direction_list[i].element].access_direction =
+ access_direction_list[i].direction;
+
+ /* ---------- initialize explosion content ------------------------------- */
+ for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+ {
+ if (IS_CUSTOM_ELEMENT(i))
+ continue;
+
+ for (y = 0; y < 3; y++) for (x = 0; x < 3; x++)
+ {
+ /* (content for EL_YAMYAM set at run-time with game.yamyam_content_nr) */
+
+ element_info[i].content.e[x][y] =
+ (i == EL_PLAYER_1 ? EL_EMERALD_YELLOW :
+ i == EL_PLAYER_2 ? EL_EMERALD_RED :
+ i == EL_PLAYER_3 ? EL_EMERALD :
+ i == EL_PLAYER_4 ? EL_EMERALD_PURPLE :
+ i == EL_MOLE ? EL_EMERALD_RED :
+ i == EL_PENGUIN ? EL_EMERALD_PURPLE :
+ i == EL_BUG ? (x == 1 && y == 1 ? EL_DIAMOND : EL_EMERALD) :
+ i == EL_BD_BUTTERFLY ? EL_BD_DIAMOND :
+ i == EL_SP_ELECTRON ? EL_SP_INFOTRON :
+ i == EL_AMOEBA_TO_DIAMOND ? level.amoeba_content :
+ i == EL_WALL_EMERALD ? EL_EMERALD :
+ i == EL_WALL_DIAMOND ? EL_DIAMOND :
+ i == EL_WALL_BD_DIAMOND ? EL_BD_DIAMOND :
+ i == EL_WALL_EMERALD_YELLOW ? EL_EMERALD_YELLOW :
+ i == EL_WALL_EMERALD_RED ? EL_EMERALD_RED :
+ i == EL_WALL_EMERALD_PURPLE ? EL_EMERALD_PURPLE :
+ i == EL_WALL_PEARL ? EL_PEARL :
+ i == EL_WALL_CRYSTAL ? EL_CRYSTAL :
+ EL_EMPTY);
+ }
+ }
+
+ /* ---------- initialize recursion detection ------------------------------ */
+ recursion_loop_depth = 0;
+ recursion_loop_detected = FALSE;
+ recursion_loop_element = EL_UNDEFINED;
+
+ /* ---------- initialize graphics engine ---------------------------------- */
+ game.scroll_delay_value =
+ (game.forced_scroll_delay_value != -1 ? game.forced_scroll_delay_value :
+ setup.scroll_delay ? setup.scroll_delay_value : 0);
+ game.scroll_delay_value =
+ MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
+
+ /* ---------- initialize game engine snapshots ---------------------------- */
+ for (i = 0; i < MAX_PLAYERS; i++)
+ game.snapshot.last_action[i] = 0;
+ game.snapshot.changed_action = FALSE;
+ game.snapshot.collected_item = FALSE;
+ game.snapshot.mode =
+ (strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_STEP) ?
+ SNAPSHOT_MODE_EVERY_STEP :
+ strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_MOVE) ?
+ SNAPSHOT_MODE_EVERY_MOVE :
+ strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_COLLECT) ?
+ SNAPSHOT_MODE_EVERY_COLLECT : SNAPSHOT_MODE_OFF);
+ game.snapshot.save_snapshot = FALSE;
+
+ /* ---------- initialize level time for Supaplex engine ------------------- */
+ /* Supaplex levels with time limit currently unsupported -- should be added */
+ if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
+ level.time = 0;
+}
+
+int get_num_special_action(int element, int action_first, int action_last)
+{
+ int num_special_action = 0;
+ int i, j;
+
+ for (i = action_first; i <= action_last; i++)
+ {
+ boolean found = FALSE;
+
+ for (j = 0; j < NUM_DIRECTIONS; j++)
+ if (el_act_dir2img(element, i, j) !=
+ el_act_dir2img(element, ACTION_DEFAULT, j))
+ found = TRUE;
+
+ if (found)
+ num_special_action++;
+ else
+ break;
+ }
+
+ return num_special_action;
+}
+
+
+/*
+ =============================================================================
+ InitGame()
+ -----------------------------------------------------------------------------
+ initialize and start new game
+ =============================================================================
+*/
+
+void InitGame()
+{
+ int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
+ int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
+ int fade_mask = REDRAW_FIELD;