+ GAME_PANEL_CONVEYOR_BELT_1,
+ &game.panel.conveyor_belt[0],
+ TYPE_ELEMENT,
+ },
+ {
+ GAME_PANEL_CONVEYOR_BELT_2,
+ &game.panel.conveyor_belt[1],
+ TYPE_ELEMENT,
+ },
+ {
+ GAME_PANEL_CONVEYOR_BELT_3,
+ &game.panel.conveyor_belt[2],
+ TYPE_ELEMENT,
+ },
+ {
+ GAME_PANEL_CONVEYOR_BELT_4,
+ &game.panel.conveyor_belt[3],
+ TYPE_ELEMENT,
+ },
+ {
+ GAME_PANEL_CONVEYOR_BELT_1_SWITCH,
+ &game.panel.conveyor_belt_switch[0],
+ TYPE_ELEMENT,
+ },
+ {
+ GAME_PANEL_CONVEYOR_BELT_2_SWITCH,
+ &game.panel.conveyor_belt_switch[1],
+ TYPE_ELEMENT,
+ },
+ {
+ GAME_PANEL_CONVEYOR_BELT_3_SWITCH,
+ &game.panel.conveyor_belt_switch[2],
+ TYPE_ELEMENT,
+ },
+ {
+ GAME_PANEL_CONVEYOR_BELT_4_SWITCH,
+ &game.panel.conveyor_belt_switch[3],
+ TYPE_ELEMENT,
+ },
+ {
+ GAME_PANEL_MAGIC_WALL,
+ &game.panel.magic_wall,
+ TYPE_ELEMENT,
+ },
+ {
+ GAME_PANEL_MAGIC_WALL_TIME,
+ &game.panel.magic_wall_time,
+ TYPE_INTEGER,
+ },
+ {
+ GAME_PANEL_GRAVITY_STATE,
+ &game.panel.gravity_state,
+ TYPE_STRING,
+ },
+ {
+ GAME_PANEL_GRAPHIC_1,
+ &game.panel.graphic[0],
+ TYPE_ELEMENT,
+ },
+ {
+ GAME_PANEL_GRAPHIC_2,
+ &game.panel.graphic[1],
+ TYPE_ELEMENT,
+ },
+ {
+ GAME_PANEL_GRAPHIC_3,
+ &game.panel.graphic[2],
+ TYPE_ELEMENT,
+ },
+ {
+ GAME_PANEL_GRAPHIC_4,
+ &game.panel.graphic[3],
+ TYPE_ELEMENT,
+ },
+ {
+ GAME_PANEL_GRAPHIC_5,
+ &game.panel.graphic[4],
+ TYPE_ELEMENT,
+ },
+ {
+ GAME_PANEL_GRAPHIC_6,
+ &game.panel.graphic[5],
+ TYPE_ELEMENT,
+ },
+ {
+ GAME_PANEL_GRAPHIC_7,
+ &game.panel.graphic[6],
+ TYPE_ELEMENT,
+ },
+ {
+ GAME_PANEL_GRAPHIC_8,
+ &game.panel.graphic[7],
+ TYPE_ELEMENT,
+ },
+ {
+ GAME_PANEL_ELEMENT_1,
+ &game.panel.element[0],
+ TYPE_ELEMENT,
+ },
+ {
+ GAME_PANEL_ELEMENT_2,
+ &game.panel.element[1],
+ TYPE_ELEMENT,
+ },
+ {
+ GAME_PANEL_ELEMENT_3,
+ &game.panel.element[2],
+ TYPE_ELEMENT,
+ },
+ {
+ GAME_PANEL_ELEMENT_4,
+ &game.panel.element[3],
+ TYPE_ELEMENT,
+ },
+ {
+ GAME_PANEL_ELEMENT_5,
+ &game.panel.element[4],
+ TYPE_ELEMENT,
+ },
+ {
+ GAME_PANEL_ELEMENT_6,
+ &game.panel.element[5],
+ TYPE_ELEMENT,
+ },
+ {
+ GAME_PANEL_ELEMENT_7,
+ &game.panel.element[6],
+ TYPE_ELEMENT,
+ },
+ {
+ GAME_PANEL_ELEMENT_8,
+ &game.panel.element[7],
+ TYPE_ELEMENT,
+ },
+ {
+ GAME_PANEL_ELEMENT_COUNT_1,
+ &game.panel.element_count[0],
+ TYPE_INTEGER,
+ },
+ {
+ GAME_PANEL_ELEMENT_COUNT_2,
+ &game.panel.element_count[1],
+ TYPE_INTEGER,
+ },
+ {
+ GAME_PANEL_ELEMENT_COUNT_3,
+ &game.panel.element_count[2],
+ TYPE_INTEGER,
+ },
+ {
+ GAME_PANEL_ELEMENT_COUNT_4,
+ &game.panel.element_count[3],
+ TYPE_INTEGER,
+ },
+ {
+ GAME_PANEL_ELEMENT_COUNT_5,
+ &game.panel.element_count[4],
+ TYPE_INTEGER,
+ },
+ {
+ GAME_PANEL_ELEMENT_COUNT_6,
+ &game.panel.element_count[5],
+ TYPE_INTEGER,
+ },
+ {
+ GAME_PANEL_ELEMENT_COUNT_7,
+ &game.panel.element_count[6],
+ TYPE_INTEGER,
+ },
+ {
+ GAME_PANEL_ELEMENT_COUNT_8,
+ &game.panel.element_count[7],
+ TYPE_INTEGER,
+ },
+ {
+ GAME_PANEL_CE_SCORE_1,
+ &game.panel.ce_score[0],
+ TYPE_INTEGER,
+ },
+ {
+ GAME_PANEL_CE_SCORE_2,
+ &game.panel.ce_score[1],
+ TYPE_INTEGER,
+ },
+ {
+ GAME_PANEL_CE_SCORE_3,
+ &game.panel.ce_score[2],
+ TYPE_INTEGER,
+ },
+ {
+ GAME_PANEL_CE_SCORE_4,
+ &game.panel.ce_score[3],
+ TYPE_INTEGER,
+ },
+ {
+ GAME_PANEL_CE_SCORE_5,
+ &game.panel.ce_score[4],
+ TYPE_INTEGER,
+ },
+ {
+ GAME_PANEL_CE_SCORE_6,
+ &game.panel.ce_score[5],
+ TYPE_INTEGER,
+ },
+ {
+ GAME_PANEL_CE_SCORE_7,
+ &game.panel.ce_score[6],
+ TYPE_INTEGER,
+ },
+ {
+ GAME_PANEL_CE_SCORE_8,
+ &game.panel.ce_score[7],
+ TYPE_INTEGER,
+ },
+ {
+ GAME_PANEL_CE_SCORE_1_ELEMENT,
+ &game.panel.ce_score_element[0],
+ TYPE_ELEMENT,
+ },
+ {
+ GAME_PANEL_CE_SCORE_2_ELEMENT,
+ &game.panel.ce_score_element[1],
+ TYPE_ELEMENT,
+ },
+ {
+ GAME_PANEL_CE_SCORE_3_ELEMENT,
+ &game.panel.ce_score_element[2],
+ TYPE_ELEMENT,
+ },
+ {
+ GAME_PANEL_CE_SCORE_4_ELEMENT,
+ &game.panel.ce_score_element[3],
+ TYPE_ELEMENT,
+ },
+ {
+ GAME_PANEL_CE_SCORE_5_ELEMENT,
+ &game.panel.ce_score_element[4],
+ TYPE_ELEMENT,
+ },
+ {
+ GAME_PANEL_CE_SCORE_6_ELEMENT,
+ &game.panel.ce_score_element[5],
+ TYPE_ELEMENT,
+ },
+ {
+ GAME_PANEL_CE_SCORE_7_ELEMENT,
+ &game.panel.ce_score_element[6],
+ TYPE_ELEMENT,
+ },
+ {
+ GAME_PANEL_CE_SCORE_8_ELEMENT,
+ &game.panel.ce_score_element[7],
+ TYPE_ELEMENT,
+ },
+ {
+ GAME_PANEL_PLAYER_NAME,
+ &game.panel.player_name,
+ TYPE_STRING,
+ },
+ {
+ GAME_PANEL_LEVEL_NAME,
+ &game.panel.level_name,
+ TYPE_STRING,
+ },
+ {
+ GAME_PANEL_LEVEL_AUTHOR,
+ &game.panel.level_author,
+ TYPE_STRING,
+ },
+
+ {
+ -1,
+ NULL,
+ -1,
+ }
+};
+
+// values for delayed check of falling and moving elements and for collision
+#define CHECK_DELAY_MOVING 3
+#define CHECK_DELAY_FALLING CHECK_DELAY_MOVING
+#define CHECK_DELAY_COLLISION 2
+#define CHECK_DELAY_IMPACT CHECK_DELAY_COLLISION
+
+// values for initial player move delay (initial delay counter value)
+#define INITIAL_MOVE_DELAY_OFF -1
+#define INITIAL_MOVE_DELAY_ON 0
+
+// values for player movement speed (which is in fact a delay value)
+#define MOVE_DELAY_MIN_SPEED 32
+#define MOVE_DELAY_NORMAL_SPEED 8
+#define MOVE_DELAY_HIGH_SPEED 4
+#define MOVE_DELAY_MAX_SPEED 1
+
+#define DOUBLE_MOVE_DELAY(x) (x = (x < MOVE_DELAY_MIN_SPEED ? x * 2 : x))
+#define HALVE_MOVE_DELAY(x) (x = (x > MOVE_DELAY_MAX_SPEED ? x / 2 : x))
+
+#define DOUBLE_PLAYER_SPEED(p) (HALVE_MOVE_DELAY( (p)->move_delay_value))
+#define HALVE_PLAYER_SPEED(p) (DOUBLE_MOVE_DELAY((p)->move_delay_value))
+
+// values for scroll positions
+#define SCROLL_POSITION_X(x) ((x) < SBX_Left + MIDPOSX ? SBX_Left : \
+ (x) > SBX_Right + MIDPOSX ? SBX_Right :\
+ (x) - MIDPOSX)
+#define SCROLL_POSITION_Y(y) ((y) < SBY_Upper + MIDPOSY ? SBY_Upper :\
+ (y) > SBY_Lower + MIDPOSY ? SBY_Lower :\
+ (y) - MIDPOSY)
+
+// values for other actions
+#define MOVE_STEPSIZE_NORMAL (TILEX / MOVE_DELAY_NORMAL_SPEED)
+#define MOVE_STEPSIZE_MIN (1)
+#define MOVE_STEPSIZE_MAX (TILEX)
+
+#define GET_DX_FROM_DIR(d) ((d) == MV_LEFT ? -1 : (d) == MV_RIGHT ? 1 : 0)
+#define GET_DY_FROM_DIR(d) ((d) == MV_UP ? -1 : (d) == MV_DOWN ? 1 : 0)
+
+#define INIT_GFX_RANDOM() (GetSimpleRandom(1000000))
+
+#define GET_NEW_PUSH_DELAY(e) ( (element_info[e].push_delay_fixed) + \
+ RND(element_info[e].push_delay_random))
+#define GET_NEW_DROP_DELAY(e) ( (element_info[e].drop_delay_fixed) + \
+ RND(element_info[e].drop_delay_random))
+#define GET_NEW_MOVE_DELAY(e) ( (element_info[e].move_delay_fixed) + \
+ RND(element_info[e].move_delay_random))
+#define GET_MAX_MOVE_DELAY(e) ( (element_info[e].move_delay_fixed) + \
+ (element_info[e].move_delay_random))
+#define GET_NEW_STEP_DELAY(e) ( (element_info[e].step_delay_fixed) + \
+ RND(element_info[e].step_delay_random))
+#define GET_MAX_STEP_DELAY(e) ( (element_info[e].step_delay_fixed) + \
+ (element_info[e].step_delay_random))
+#define GET_NEW_CE_VALUE(e) ( (element_info[e].ce_value_fixed_initial) +\
+ RND(element_info[e].ce_value_random_initial))
+#define GET_CE_SCORE(e) ( (element_info[e].collect_score))
+#define GET_CHANGE_DELAY(c) ( ((c)->delay_fixed * (c)->delay_frames) + \
+ RND((c)->delay_random * (c)->delay_frames))
+#define GET_CE_DELAY_VALUE(c) ( ((c)->delay_fixed) + \
+ RND((c)->delay_random))
+
+
+#define GET_VALID_RUNTIME_ELEMENT(e) \
+ ((e) >= NUM_RUNTIME_ELEMENTS ? EL_UNKNOWN : (e))
+
+#define RESOLVED_REFERENCE_ELEMENT(be, e) \
+ ((be) + (e) - EL_SELF < EL_CUSTOM_START ? EL_CUSTOM_START : \
+ (be) + (e) - EL_SELF > EL_CUSTOM_END ? EL_CUSTOM_END : \
+ (be) + (e) - EL_SELF)
+
+#define GET_PLAYER_FROM_BITS(p) \
+ (EL_PLAYER_1 + ((p) != PLAYER_BITS_ANY ? log_2(p) : 0))
+
+#define GET_TARGET_ELEMENT(be, e, ch, cv, cs) \
+ ((e) == EL_TRIGGER_PLAYER ? (ch)->actual_trigger_player : \
+ (e) == EL_TRIGGER_ELEMENT ? (ch)->actual_trigger_element : \
+ (e) == EL_TRIGGER_CE_VALUE ? (ch)->actual_trigger_ce_value : \
+ (e) == EL_TRIGGER_CE_SCORE ? (ch)->actual_trigger_ce_score : \
+ (e) == EL_CURRENT_CE_VALUE ? (cv) : \
+ (e) == EL_CURRENT_CE_SCORE ? (cs) : \
+ (e) >= EL_PREV_CE_8 && (e) <= EL_NEXT_CE_8 ? \
+ RESOLVED_REFERENCE_ELEMENT(be, e) : \
+ (e))
+
+#define CAN_GROW_INTO(e) \
+ ((e) == EL_SAND || (IS_DIGGABLE(e) && level.grow_into_diggable))
+
+#define ELEMENT_CAN_ENTER_FIELD_BASE_X(x, y, condition) \
+ (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
+ (condition)))
+
+#define ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, condition) \
+ (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
+ (CAN_MOVE_INTO_ACID(e) && \
+ Tile[x][y] == EL_ACID) || \
+ (condition)))
+
+#define ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, condition) \
+ (IN_LEV_FIELD(x, y) && (IS_FREE_OR_PLAYER(x, y) || \
+ (CAN_MOVE_INTO_ACID(e) && \
+ Tile[x][y] == EL_ACID) || \
+ (condition)))
+
+#define ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, condition) \
+ (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
+ (condition) || \
+ (CAN_MOVE_INTO_ACID(e) && \
+ Tile[x][y] == EL_ACID) || \
+ (DONT_COLLIDE_WITH(e) && \
+ IS_PLAYER(x, y) && \
+ !PLAYER_ENEMY_PROTECTED(x, y))))
+
+#define ELEMENT_CAN_ENTER_FIELD(e, x, y) \
+ ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, 0)
+
+#define SATELLITE_CAN_ENTER_FIELD(x, y) \
+ ELEMENT_CAN_ENTER_FIELD_BASE_2(EL_SATELLITE, x, y, 0)
+
+#define ANDROID_CAN_ENTER_FIELD(e, x, y) \
+ ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, Tile[x][y] == EL_EMC_PLANT)
+
+#define ANDROID_CAN_CLONE_FIELD(x, y) \
+ (IN_LEV_FIELD(x, y) && (CAN_BE_CLONED_BY_ANDROID(Tile[x][y]) || \
+ CAN_BE_CLONED_BY_ANDROID(EL_TRIGGER_ELEMENT)))
+
+#define ENEMY_CAN_ENTER_FIELD(e, x, y) \
+ ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
+
+#define YAMYAM_CAN_ENTER_FIELD(e, x, y) \
+ ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, Tile[x][y] == EL_DIAMOND)
+
+#define DARK_YAMYAM_CAN_ENTER_FIELD(e, x, y) \
+ ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x,y, IS_FOOD_DARK_YAMYAM(Tile[x][y]))
+
+#define PACMAN_CAN_ENTER_FIELD(e, x, y) \
+ ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, IS_AMOEBOID(Tile[x][y]))
+
+#define PIG_CAN_ENTER_FIELD(e, x, y) \
+ ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, IS_FOOD_PIG(Tile[x][y]))
+
+#define PENGUIN_CAN_ENTER_FIELD(e, x, y) \
+ ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (Tile[x][y] == EL_EXIT_OPEN || \
+ Tile[x][y] == EL_EM_EXIT_OPEN || \
+ Tile[x][y] == EL_STEEL_EXIT_OPEN || \
+ Tile[x][y] == EL_EM_STEEL_EXIT_OPEN || \
+ IS_FOOD_PENGUIN(Tile[x][y])))
+#define DRAGON_CAN_ENTER_FIELD(e, x, y) \
+ ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
+
+#define MOLE_CAN_ENTER_FIELD(e, x, y, condition) \
+ ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (condition))
+
+#define SPRING_CAN_ENTER_FIELD(e, x, y) \
+ ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
+
+#define SPRING_CAN_BUMP_FROM_FIELD(x, y) \
+ (IN_LEV_FIELD(x, y) && (Tile[x][y] == EL_EMC_SPRING_BUMPER || \
+ Tile[x][y] == EL_EMC_SPRING_BUMPER_ACTIVE))
+
+#define MOVE_ENTER_EL(e) (element_info[e].move_enter_element)
+
+#define CE_ENTER_FIELD_COND(e, x, y) \
+ (!IS_PLAYER(x, y) && \
+ IS_EQUAL_OR_IN_GROUP(Tile[x][y], MOVE_ENTER_EL(e)))
+
+#define CUSTOM_ELEMENT_CAN_ENTER_FIELD(e, x, y) \
+ ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, CE_ENTER_FIELD_COND(e, x, y))
+
+#define IN_LEV_FIELD_AND_IS_FREE(x, y) (IN_LEV_FIELD(x, y) && IS_FREE(x, y))
+#define IN_LEV_FIELD_AND_NOT_FREE(x, y) (IN_LEV_FIELD(x, y) && !IS_FREE(x, y))
+
+#define ACCESS_FROM(e, d) (element_info[e].access_direction &(d))
+#define IS_WALKABLE_FROM(e, d) (IS_WALKABLE(e) && ACCESS_FROM(e, d))
+#define IS_PASSABLE_FROM(e, d) (IS_PASSABLE(e) && ACCESS_FROM(e, d))
+#define IS_ACCESSIBLE_FROM(e, d) (IS_ACCESSIBLE(e) && ACCESS_FROM(e, d))
+
+#define MM_HEALTH(x) (MIN(MAX(0, MAX_HEALTH - (x)), MAX_HEALTH))
+
+// game button identifiers
+#define GAME_CTRL_ID_STOP 0
+#define GAME_CTRL_ID_PAUSE 1
+#define GAME_CTRL_ID_PLAY 2
+#define GAME_CTRL_ID_UNDO 3
+#define GAME_CTRL_ID_REDO 4
+#define GAME_CTRL_ID_SAVE 5
+#define GAME_CTRL_ID_PAUSE2 6
+#define GAME_CTRL_ID_LOAD 7
+#define GAME_CTRL_ID_PANEL_STOP 8
+#define GAME_CTRL_ID_PANEL_PAUSE 9
+#define GAME_CTRL_ID_PANEL_PLAY 10
+#define GAME_CTRL_ID_TOUCH_STOP 11
+#define GAME_CTRL_ID_TOUCH_PAUSE 12
+#define SOUND_CTRL_ID_MUSIC 13
+#define SOUND_CTRL_ID_LOOPS 14
+#define SOUND_CTRL_ID_SIMPLE 15
+#define SOUND_CTRL_ID_PANEL_MUSIC 16
+#define SOUND_CTRL_ID_PANEL_LOOPS 17
+#define SOUND_CTRL_ID_PANEL_SIMPLE 18
+
+#define NUM_GAME_BUTTONS 19
+
+
+// forward declaration for internal use
+
+static void CreateField(int, int, int);
+
+static void ResetGfxAnimation(int, int);
+
+static void SetPlayerWaiting(struct PlayerInfo *, boolean);
+static void AdvanceFrameAndPlayerCounters(int);
+
+static boolean MovePlayerOneStep(struct PlayerInfo *, int, int, int, int);
+static boolean MovePlayer(struct PlayerInfo *, int, int);
+static void ScrollPlayer(struct PlayerInfo *, int);
+static void ScrollScreen(struct PlayerInfo *, int);
+
+static int DigField(struct PlayerInfo *, int, int, int, int, int, int, int);
+static boolean DigFieldByCE(int, int, int);
+static boolean SnapField(struct PlayerInfo *, int, int);
+static boolean DropElement(struct PlayerInfo *);
+
+static void InitBeltMovement(void);
+static void CloseAllOpenTimegates(void);
+static void CheckGravityMovement(struct PlayerInfo *);
+static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *);
+static void KillPlayerUnlessEnemyProtected(int, int);
+static void KillPlayerUnlessExplosionProtected(int, int);
+
+static void TestIfPlayerTouchesCustomElement(int, int);
+static void TestIfElementTouchesCustomElement(int, int);
+static void TestIfElementHitsCustomElement(int, int, int);
+
+static void HandleElementChange(int, int, int);
+static void ExecuteCustomElementAction(int, int, int, int);
+static boolean ChangeElement(int, int, int, int);
+
+static boolean CheckTriggeredElementChangeExt(int, int, int, int, int,int,int);
+#define CheckTriggeredElementChange(x, y, e, ev) \
+ CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY,CH_SIDE_ANY, -1)
+#define CheckTriggeredElementChangeByPlayer(x, y, e, ev, p, s) \
+ CheckTriggeredElementChangeExt(x, y, e, ev, p, s, -1)
+#define CheckTriggeredElementChangeBySide(x, y, e, ev, s) \
+ CheckTriggeredElementChangeExt(x, y, e, ev, CH_PLAYER_ANY, s, -1)
+#define CheckTriggeredElementChangeByPage(x, y, e, ev, p) \
+ CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY, CH_SIDE_ANY, p)
+#define CheckTriggeredElementChangeByMouse(x, y, e, ev, s) \
+ CheckTriggeredElementChangeExt(x, y, e, ev, CH_PLAYER_ANY, s, -1)
+
+static boolean CheckElementChangeExt(int, int, int, int, int, int, int);
+#define CheckElementChange(x, y, e, te, ev) \
+ CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, CH_SIDE_ANY)
+#define CheckElementChangeByPlayer(x, y, e, ev, p, s) \
+ CheckElementChangeExt(x, y, e, EL_EMPTY, ev, p, s)
+#define CheckElementChangeBySide(x, y, e, te, ev, s) \
+ CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, s)
+#define CheckElementChangeByMouse(x, y, e, ev, s) \
+ CheckElementChangeExt(x, y, e, EL_UNDEFINED, ev, CH_PLAYER_ANY, s)
+
+static void PlayLevelSound(int, int, int);
+static void PlayLevelSoundNearest(int, int, int);
+static void PlayLevelSoundAction(int, int, int);
+static void PlayLevelSoundElementAction(int, int, int, int);
+static void PlayLevelSoundElementActionIfLoop(int, int, int, int);
+static void PlayLevelSoundActionIfLoop(int, int, int);
+static void StopLevelSoundActionIfLoop(int, int, int);
+static void PlayLevelMusic(void);
+static void FadeLevelSoundsAndMusic(void);
+
+static void HandleGameButtons(struct GadgetInfo *);
+
+int AmoebaNeighbourNr(int, int);
+void AmoebaToDiamond(int, int);
+void ContinueMoving(int, int);
+void Bang(int, int);
+void InitMovDir(int, int);
+void InitAmoebaNr(int, int);
+void NewHighScore(int);
+
+void TestIfGoodThingHitsBadThing(int, int, int);
+void TestIfBadThingHitsGoodThing(int, int, int);
+void TestIfPlayerTouchesBadThing(int, int);
+void TestIfPlayerRunsIntoBadThing(int, int, int);
+void TestIfBadThingTouchesPlayer(int, int);
+void TestIfBadThingRunsIntoPlayer(int, int, int);
+void TestIfFriendTouchesBadThing(int, int);
+void TestIfBadThingTouchesFriend(int, int);
+void TestIfBadThingTouchesOtherBadThing(int, int);
+void TestIfGoodThingGetsHitByBadThing(int, int, int);
+
+void KillPlayer(struct PlayerInfo *);
+void BuryPlayer(struct PlayerInfo *);
+void RemovePlayer(struct PlayerInfo *);
+void ExitPlayer(struct PlayerInfo *);
+
+static int getInvisibleActiveFromInvisibleElement(int);
+static int getInvisibleFromInvisibleActiveElement(int);
+
+static void TestFieldAfterSnapping(int, int, int, int, int);
+
+static struct GadgetInfo *game_gadget[NUM_GAME_BUTTONS];
+
+// for detection of endless loops, caused by custom element programming
+// (using maximal playfield width x 10 is just a rough approximation)
+#define MAX_ELEMENT_CHANGE_RECURSION_DEPTH (MAX_PLAYFIELD_WIDTH * 10)
+
+#define RECURSION_LOOP_DETECTION_START(e, rc) \
+{ \
+ if (recursion_loop_detected) \
+ return (rc); \
+ \
+ if (recursion_loop_depth > MAX_ELEMENT_CHANGE_RECURSION_DEPTH) \
+ { \
+ recursion_loop_detected = TRUE; \
+ recursion_loop_element = (e); \
+ } \
+ \
+ recursion_loop_depth++; \
+}
+
+#define RECURSION_LOOP_DETECTION_END() \
+{ \
+ recursion_loop_depth--; \
+}
+
+static int recursion_loop_depth;
+static boolean recursion_loop_detected;
+static boolean recursion_loop_element;
+
+static int map_player_action[MAX_PLAYERS];
+
+
+// ----------------------------------------------------------------------------
+// definition of elements that automatically change to other elements after
+// a specified time, eventually calling a function when changing
+// ----------------------------------------------------------------------------
+
+// forward declaration for changer functions
+static void InitBuggyBase(int, int);
+static void WarnBuggyBase(int, int);
+
+static void InitTrap(int, int);
+static void ActivateTrap(int, int);
+static void ChangeActiveTrap(int, int);
+
+static void InitRobotWheel(int, int);
+static void RunRobotWheel(int, int);
+static void StopRobotWheel(int, int);
+
+static void InitTimegateWheel(int, int);
+static void RunTimegateWheel(int, int);
+
+static void InitMagicBallDelay(int, int);
+static void ActivateMagicBall(int, int);
+
+struct ChangingElementInfo
+{
+ int element;
+ int target_element;
+ int change_delay;
+ void (*pre_change_function)(int x, int y);
+ void (*change_function)(int x, int y);
+ void (*post_change_function)(int x, int y);
+};
+
+static struct ChangingElementInfo change_delay_list[] =
+{
+ {
+ EL_NUT_BREAKING,
+ EL_EMERALD,
+ 6,
+ NULL,
+ NULL,
+ NULL
+ },
+ {
+ EL_PEARL_BREAKING,
+ EL_EMPTY,
+ 8,
+ NULL,
+ NULL,
+ NULL
+ },
+ {
+ EL_EXIT_OPENING,
+ EL_EXIT_OPEN,
+ 29,
+ NULL,
+ NULL,
+ NULL
+ },
+ {
+ EL_EXIT_CLOSING,
+ EL_EXIT_CLOSED,
+ 29,
+ NULL,
+ NULL,
+ NULL
+ },
+ {
+ EL_STEEL_EXIT_OPENING,
+ EL_STEEL_EXIT_OPEN,
+ 29,
+ NULL,
+ NULL,
+ NULL
+ },
+ {
+ EL_STEEL_EXIT_CLOSING,
+ EL_STEEL_EXIT_CLOSED,
+ 29,
+ NULL,
+ NULL,
+ NULL
+ },
+ {
+ EL_EM_EXIT_OPENING,
+ EL_EM_EXIT_OPEN,
+ 29,
+ NULL,
+ NULL,
+ NULL
+ },
+ {
+ EL_EM_EXIT_CLOSING,
+ EL_EMPTY,
+ 29,
+ NULL,
+ NULL,
+ NULL
+ },
+ {
+ EL_EM_STEEL_EXIT_OPENING,
+ EL_EM_STEEL_EXIT_OPEN,
+ 29,
+ NULL,
+ NULL,
+ NULL
+ },
+ {
+ EL_EM_STEEL_EXIT_CLOSING,
+ EL_STEELWALL,
+ 29,
+ NULL,
+ NULL,
+ NULL
+ },
+ {
+ EL_SP_EXIT_OPENING,
+ EL_SP_EXIT_OPEN,
+ 29,
+ NULL,
+ NULL,
+ NULL
+ },
+ {
+ EL_SP_EXIT_CLOSING,
+ EL_SP_EXIT_CLOSED,
+ 29,
+ NULL,
+ NULL,
+ NULL
+ },
+ {
+ EL_SWITCHGATE_OPENING,
+ EL_SWITCHGATE_OPEN,
+ 29,
+ NULL,
+ NULL,
+ NULL
+ },
+ {
+ EL_SWITCHGATE_CLOSING,
+ EL_SWITCHGATE_CLOSED,
+ 29,
+ NULL,
+ NULL,
+ NULL
+ },
+ {
+ EL_TIMEGATE_OPENING,
+ EL_TIMEGATE_OPEN,
+ 29,
+ NULL,
+ NULL,
+ NULL
+ },
+ {
+ EL_TIMEGATE_CLOSING,
+ EL_TIMEGATE_CLOSED,
+ 29,
+ NULL,
+ NULL,
+ NULL
+ },
+
+ {
+ EL_ACID_SPLASH_LEFT,
+ EL_EMPTY,
+ 8,
+ NULL,
+ NULL,
+ NULL
+ },
+ {
+ EL_ACID_SPLASH_RIGHT,
+ EL_EMPTY,
+ 8,
+ NULL,
+ NULL,
+ NULL
+ },
+ {
+ EL_SP_BUGGY_BASE,
+ EL_SP_BUGGY_BASE_ACTIVATING,
+ 0,
+ InitBuggyBase,
+ NULL,
+ NULL
+ },
+ {
+ EL_SP_BUGGY_BASE_ACTIVATING,
+ EL_SP_BUGGY_BASE_ACTIVE,
+ 0,
+ InitBuggyBase,
+ NULL,
+ NULL
+ },
+ {
+ EL_SP_BUGGY_BASE_ACTIVE,
+ EL_SP_BUGGY_BASE,
+ 0,
+ InitBuggyBase,
+ WarnBuggyBase,
+ NULL
+ },
+ {
+ EL_TRAP,
+ EL_TRAP_ACTIVE,
+ 0,
+ InitTrap,
+ NULL,
+ ActivateTrap
+ },
+ {
+ EL_TRAP_ACTIVE,
+ EL_TRAP,
+ 31,
+ NULL,
+ ChangeActiveTrap,
+ NULL
+ },
+ {
+ EL_ROBOT_WHEEL_ACTIVE,
+ EL_ROBOT_WHEEL,
+ 0,
+ InitRobotWheel,
+ RunRobotWheel,
+ StopRobotWheel
+ },
+ {
+ EL_TIMEGATE_SWITCH_ACTIVE,
+ EL_TIMEGATE_SWITCH,
+ 0,
+ InitTimegateWheel,
+ RunTimegateWheel,
+ NULL
+ },
+ {
+ EL_DC_TIMEGATE_SWITCH_ACTIVE,
+ EL_DC_TIMEGATE_SWITCH,
+ 0,
+ InitTimegateWheel,
+ RunTimegateWheel,
+ NULL
+ },
+ {
+ EL_EMC_MAGIC_BALL_ACTIVE,
+ EL_EMC_MAGIC_BALL_ACTIVE,
+ 0,
+ InitMagicBallDelay,
+ NULL,
+ ActivateMagicBall
+ },
+ {
+ EL_EMC_SPRING_BUMPER_ACTIVE,
+ EL_EMC_SPRING_BUMPER,
+ 8,
+ NULL,
+ NULL,
+ NULL
+ },
+ {
+ EL_DIAGONAL_SHRINKING,
+ EL_UNDEFINED,
+ 0,
+ NULL,
+ NULL,
+ NULL
+ },
+ {
+ EL_DIAGONAL_GROWING,
+ EL_UNDEFINED,
+ 0,
+ NULL,
+ NULL,
+ NULL,
+ },
+
+ {
+ EL_UNDEFINED,
+ EL_UNDEFINED,
+ -1,
+ NULL,
+ NULL,
+ NULL
+ }
+};
+
+struct
+{
+ int element;
+ int push_delay_fixed, push_delay_random;
+}
+push_delay_list[] =
+{
+ { EL_SPRING, 0, 0 },
+ { EL_BALLOON, 0, 0 },
+
+ { 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++)