rnd-20051123-1-src
[rocksndiamonds.git] / src / game.c
index ce4948b85851c59eca8b4a04355079b6597b133c..d8039785faa95e2179dfd8d154821fcca63bb23c 100644 (file)
 /* EXPERIMENTAL STUFF */
 #define USE_NEW_AMOEBA_CODE    FALSE
 
+/* EXPERIMENTAL STUFF */
+#define USE_NEW_STUFF                  (TRUE                           * 1)
+
+#define USE_NEW_MOVE_STYLE             (TRUE   * USE_NEW_STUFF         * 1)
+#define USE_NEW_MOVE_DELAY             (TRUE   * USE_NEW_STUFF         * 1)
+#define USE_NEW_PUSH_DELAY             (TRUE   * USE_NEW_STUFF         * 1)
+#define USE_NEW_BLOCK_STYLE            (TRUE   * USE_NEW_STUFF         * 1)
+#define USE_NEW_SP_SLIPPERY            (TRUE   * USE_NEW_STUFF         * 1)
+#define USE_NEW_RANDOMIZE              (TRUE   * USE_NEW_STUFF         * 1)
+
+#define USE_CAN_MOVE_NOT_MOVING                (TRUE   * USE_NEW_STUFF         * 1)
+#define USE_PREVIOUS_MOVE_DIR          (TRUE   * USE_NEW_STUFF         * 1)
+
+#define USE_PUSH_BUGFIX                        (TRUE   * USE_NEW_STUFF         * 1)
+#if 0
+#define USE_BLOCK_DELAY_BUGFIX         (TRUE   * USE_NEW_STUFF         * 1)
+#endif
+#define USE_GRAVITY_BUGFIX_NEW         (TRUE   * USE_NEW_STUFF         * 1)
+#define USE_GRAVITY_BUGFIX_OLD         (TRUE   * USE_NEW_STUFF         * 0)
+
+#define USE_PENGUIN_COLLECT_BUGFIX     (TRUE   * USE_NEW_STUFF         * 1)
+
+#define USE_IMPACT_BUGFIX              (TRUE   * USE_NEW_STUFF         * 1)
+
+#define USE_HITTING_SOMETHING_BUGFIX   (TRUE   * USE_NEW_STUFF         * 1)
+#define USE_HIT_BY_SOMETHING_BUGFIX    (TRUE   * USE_NEW_STUFF         * 1)
+
+#define USE_DROP_BUGFIX                        (TRUE   * USE_NEW_STUFF         * 1)
+
+#define USE_CHANGE_TO_TRIGGERED                (TRUE   * USE_NEW_STUFF         * 1)
+
+#define USE_BACK_WALKABLE_BUGFIX       (TRUE   * USE_NEW_STUFF         * 1)
+
+
 /* for DigField() */
 #define DF_NO_PUSH             0
 #define DF_DIG                 1
                                 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_CHANGE_DELAY(c)    (   ((c)->delay_fixed  * (c)->delay_frames) + \
+                                RND((c)->delay_random * (c)->delay_frames))
 
 #define GET_TARGET_ELEMENT(e, ch)                                      \
        ((e) == EL_TRIGGER_ELEMENT ? (ch)->actual_trigger_element :     \
         (e) == EL_TRIGGER_PLAYER  ? (ch)->actual_trigger_player : (e))
 
+#define GET_VALID_PLAYER_ELEMENT(e)                                    \
+       ((e) >= EL_PLAYER_1 && (e) <= EL_PLAYER_4 ? (e) : EL_PLAYER_1)
+
 #define CAN_GROW_INTO(e)                                               \
-       (e == EL_SAND || (IS_DIGGABLE(e) && level.grow_into_diggable))
+       ((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) ||                \
 
 /* forward declaration for internal use */
 
+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);
@@ -609,37 +650,44 @@ struct
 }
 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_UNDEFINED,              MV_NO_MOVING                         }
+  { 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_NO_MOVING                         }
 };
 
-static unsigned long trigger_events[MAX_NUM_ELEMENTS];
+static boolean trigger_events[MAX_NUM_ELEMENTS][NUM_CHANGE_EVENTS];
 
-#define IS_AUTO_CHANGING(e)    (element_info[e].change_events & \
-                                CH_EVENT_BIT(CE_DELAY))
+#define IS_AUTO_CHANGING(e)    (element_info[e].has_change_event[CE_DELAY])
 #define IS_JUST_CHANGING(x, y) (ChangeDelay[x][y] != 0)
 #define IS_CHANGING(x, y)      (IS_AUTO_CHANGING(Feld[x][y]) || \
                                 IS_JUST_CHANGING(x, y))
@@ -750,6 +798,65 @@ static void InitPlayerField(int x, int y, int element, boolean init_game)
                                level.sp_block_last_field :
                                level.block_last_field);
 
+#if USE_NEW_BLOCK_STYLE
+#if 1
+
+    /* ---------- 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 0
+    /* blocking the last field when moving was corrected in version 3.1.1 */
+    if (game.use_block_last_field_bug)
+    {
+      /* even "not blocking" was blocking the last field for one frame */
+      level.block_delay    = (level.block_last_field    ? 7 : 1);
+      level.sp_block_delay = (level.sp_block_last_field ? 7 : 1);
+
+      level.block_last_field = TRUE;
+      level.sp_block_last_field = TRUE;
+    }
+#endif
+
+#if 0  /* !!! THIS IS NOT A LEVEL SETTING => REMOVED !!! */
+    level.block_delay = 8;             /* when blocking, block 8 frames */
+    level.sp_block_delay = 9;          /* SP indeed blocks 9 frames, not 8 */
+#endif
+
+#if 0
+    printf("::: %d, %d\n", level.block_delay, level.sp_block_delay);
+#endif
+
+#else
+
+#if 1
+    player->block_delay = (player->block_last_field ?
+                          (element == EL_SP_MURPHY ?
+                           level.sp_block_delay :
+                           level.block_delay) : 0);
+#else
+    player->block_delay = (element == EL_SP_MURPHY ?
+                          (player->block_last_field ? 7 : 1) :
+                          (player->block_last_field ? 7 : 1));
+#endif
+
+#endif
+
+#if 0
+    printf("::: block_last_field == %d, block_delay = %d\n",
+          player->block_last_field, player->block_delay);
+#endif
+#endif
+
     if (!options.network || player->connected)
     {
       player->active = TRUE;
@@ -1019,14 +1126,21 @@ inline void DrawGameValue_Dynamite(int value)
   DrawText(DX_DYNAMITE, DY_DYNAMITE, int2str(value, 3), FONT_TEXT_2);
 }
 
-inline void DrawGameValue_Keys(struct PlayerInfo *player)
+inline void DrawGameValue_Keys(int key[MAX_NUM_KEYS])
 {
   int i;
 
-  for (i = 0; i < MAX_KEYS; i++)
-    if (player->key[i])
+  /* currently only 4 of 8 possible keys are displayed */
+  for (i = 0; i < STD_NUM_KEYS; i++)
+  {
+    if (key[i])
       DrawMiniGraphicExt(drawto, DX_KEYS + i * MINI_TILEX, DY_KEYS,
                         el2edimg(EL_KEY_1 + i));
+    else
+      BlitBitmap(graphic_info[IMG_GLOBAL_DOOR].bitmap, drawto,
+                DOOR_GFX_PAGEX5 + XX_KEYS + i * MINI_TILEX, YY_KEYS,
+                MINI_TILEX, MINI_TILEY, DX_KEYS + i * MINI_TILEX, DY_KEYS);
+  }
 }
 
 inline void DrawGameValue_Score(int value)
@@ -1066,19 +1180,45 @@ inline void DrawGameValue_Level(int value)
   }
 }
 
-void DrawGameDoorValues()
+void DrawAllGameValues(int emeralds, int dynamite, int score, int time,
+                      int key_bits)
 {
+  int key[MAX_NUM_KEYS];
   int i;
 
+  for (i = 0; i < MAX_NUM_KEYS; i++)
+    key[i] = key_bits & (1 << i);
+
   DrawGameValue_Level(level_nr);
 
-  for (i = 0; i < MAX_PLAYERS; i++)
-    DrawGameValue_Keys(&stored_player[i]);
+  DrawGameValue_Emeralds(emeralds);
+  DrawGameValue_Dynamite(dynamite);
+  DrawGameValue_Score(score);
+  DrawGameValue_Time(time);
+
+  DrawGameValue_Keys(key);
+}
+
+void DrawGameDoorValues()
+{
+  int i;
+
+  if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+  {
+    DrawGameDoorValues_EM();
+
+    return;
+  }
+
+  DrawGameValue_Level(level_nr);
 
   DrawGameValue_Emeralds(local_player->gems_still_needed);
   DrawGameValue_Dynamite(local_player->inventory_size);
   DrawGameValue_Score(local_player->score);
   DrawGameValue_Time(TimeLeft);
+
+  for (i = 0; i < MAX_PLAYERS; i++)
+    DrawGameValue_Keys(stored_player[i].key);
 }
 
 static void resolve_group_element(int group_element, int recursion_depth)
@@ -1147,12 +1287,80 @@ static void resolve_group_element(int group_element, int recursion_depth)
 
 static void InitGameEngine()
 {
-  int i, j, k;
+  int i, j, k, l;
 
   /* 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 flags for bugs and changes according to active game engine version */
+  /* ---------------------------------------------------------------------- */
+
+  /*
+    Summary of bugfix/change:
+    Fixed handling for custom elements that change when pushed by the player.
+
+    Fixed/changed in version:
+    3.1.0
+
+    Description:
+    Before 3.1.0, custom elements that "change when pushing" changed directly
+    after the player started pushing them (until then handled in "DigField()").
+    Since 3.1.0, these custom elements are not changed until the "pushing"
+    move of the element is finished (now handled in "ContinueMoving()").
+
+    Affected levels/tapes:
+    The first condition is generally needed for all levels/tapes before version
+    3.1.0, which might use the old behaviour before it was changed; known tapes
+    that are affected are some tapes from the level set "Walpurgis Gardens" by
+    Jamie Cullen.
+    The second condition is an exception from the above case and is needed for
+    the special case of tapes recorded with game (not engine!) version 3.1.0 or
+    above (including some development versions of 3.1.0), but before it was
+    known that this change would break tapes like the above and was fixed in
+    3.1.1, so that the changed behaviour was active although the engine version
+    while recording maybe was before 3.1.0. There is at least one tape that is
+    affected by this exception, which is the tape for the one-level set "Bug
+    Machine" by Juergen Bonhagen.
+  */
+
+  game.use_change_when_pushing_bug =
+    (game.engine_version < VERSION_IDENT(3,1,0,0) &&
+     !(tape.playing &&
+       tape.game_version >= VERSION_IDENT(3,1,0,0) &&
+       tape.game_version <  VERSION_IDENT(3,1,1,0)));
+
+  /*
+    Summary of bugfix/change:
+    Fixed handling for blocking the field the player leaves when moving.
+
+    Fixed/changed in version:
+    3.1.1
+
+    Description:
+    Before 3.1.1, when "block last field when moving" was enabled, the field
+    the player is leaving when moving was blocked for the time of the move,
+    and was directly unblocked afterwards. This resulted in the last field
+    being blocked for exactly one less than the number of frames of one player
+    move. Additionally, even when blocking was disabled, the last field was
+    blocked for exactly one frame.
+    Since 3.1.1, due to changes in player movement handling, the last field
+    is not blocked at all when blocking is disabled. When blocking is enabled,
+    the last field is blocked for exactly the number of frames of one player
+    move. Additionally, if the player is Murphy, the hero of Supaplex, the
+    last field is blocked for exactly one more than the number of frames of
+    one player move.
+
+    Affected levels/tapes:
+    (!!! yet to be determined -- probably many !!!)
+  */
+
+  game.use_block_last_field_bug =
+    (game.engine_version < VERSION_IDENT(3,1,1,0));
+
+  /* ---------------------------------------------------------------------- */
+
   /* dynamically adjust element properties according to game engine version */
   InitElementPropertiesEngine(game.engine_version);
 
@@ -1175,6 +1383,15 @@ static void InitGameEngine()
 
   /* ---------- initialize player's initial move delay --------------------- */
 
+#if USE_NEW_MOVE_DELAY
+  /* dynamically adjust player properties according to level information */
+  game.initial_move_delay_value =
+    (level.double_speed ? MOVE_DELAY_HIGH_SPEED : MOVE_DELAY_NORMAL_SPEED);
+
+  /* dynamically adjust player properties according to game engine version */
+  game.initial_move_delay = (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
+                            game.initial_move_delay_value : 0);
+#else
   /* dynamically adjust player properties according to game engine version */
   game.initial_move_delay =
     (game.engine_version <= VERSION_IDENT(2,0,1,0) ? INITIAL_MOVE_DELAY_ON :
@@ -1183,6 +1400,7 @@ static void InitGameEngine()
   /* dynamically adjust player properties according to level information */
   game.initial_move_delay_value =
     (level.double_speed ? MOVE_DELAY_HIGH_SPEED : MOVE_DELAY_NORMAL_SPEED);
+#endif
 
   /* ---------- initialize player's initial push delay --------------------- */
 
@@ -1208,9 +1426,10 @@ static void InitGameEngine()
       ei->change->delay_frames = 1;
     }
 
-    ei->change_events = CE_BITMASK_DEFAULT;
     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];
     }
@@ -1229,7 +1448,7 @@ static void InitGameEngine()
     ei->change->change_function      = ch_delay->change_function;
     ei->change->post_change_function = ch_delay->post_change_function;
 
-    ei->change_events |= CH_EVENT_BIT(CE_DELAY);
+    ei->has_change_event[CE_DELAY] = TRUE;
 
 #if 1
     SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE, TRUE);
@@ -1250,10 +1469,10 @@ static void InitGameEngine()
       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].events & CH_EVENT_BIT(k) &&
-           !(ei->change_events & CH_EVENT_BIT(k)))
+       if (ei->change_page[j].has_event[k] && !(ei->has_change_event[k]))
        {
-         ei->change_events |= CH_EVENT_BIT(k);
+         ei->has_change_event[k] = TRUE;
+
          ei->event_page_nr[k] = j;
          ei->event_page[k] = &ei->change_page[j];
        }
@@ -1270,7 +1489,7 @@ static void InitGameEngine()
 
     /* only add custom elements that change after fixed/random frame delay */
     if (CAN_CHANGE(element) && HAS_CHANGE_EVENT(element, CE_DELAY))
-      element_info[element].change_events |= CH_EVENT_BIT(CE_DELAY);
+      element_info[element].has_change_event[CE_DELAY] = TRUE;
   }
 #endif
 
@@ -1291,7 +1510,8 @@ static void InitGameEngine()
 
   /* initialize trigger events information */
   for (i = 0; i < MAX_NUM_ELEMENTS; i++)
-    trigger_events[i] = EP_BITMASK_DEFAULT;
+    for (j = 0; j < NUM_CHANGE_EVENTS; j++)
+      trigger_events[i][j] = FALSE;
 
 #if 1
   /* add trigger events from element change event properties */
@@ -1304,20 +1524,26 @@ static void InitGameEngine()
       if (!ei->change_page[j].can_change)
        continue;
 
-      if (ei->change_page[j].events & CH_EVENT_BIT(CE_BY_OTHER_ACTION))
+      if (ei->change_page[j].has_event[CE_BY_OTHER_ACTION])
       {
        int trigger_element = ei->change_page[j].trigger_element;
 
-       if (IS_GROUP_ELEMENT(trigger_element))
+       for (k = 0; k < NUM_CHANGE_EVENTS; k++)
        {
-         struct ElementGroupInfo *group = element_info[trigger_element].group;
+         if (ei->change_page[j].has_event[k])
+         {
+           if (IS_GROUP_ELEMENT(trigger_element))
+           {
+             struct ElementGroupInfo *group =
+               element_info[trigger_element].group;
 
-         for (k = 0; k < group->num_elements_resolved; k++)
-           trigger_events[group->element_resolved[k]]
-             |= ei->change_page[j].events;
+             for (l = 0; l < group->num_elements_resolved; l++)
+               trigger_events[group->element_resolved[l]][k] = TRUE;
+           }
+           else
+             trigger_events[trigger_element][k] = TRUE;
+         }
        }
-       else
-         trigger_events[trigger_element] |= ei->change_page[j].events;
       }
     }
   }
@@ -1325,8 +1551,9 @@ static void InitGameEngine()
   /* add trigger events from element change event properties */
   for (i = 0; i < MAX_NUM_ELEMENTS; i++)
     if (HAS_CHANGE_EVENT(i, CE_BY_OTHER_ACTION))
-      trigger_events[element_info[i].change->trigger_element] |=
-       element_info[i].change->events;
+      for (j = 0; j < NUM_CHANGE_EVENTS; j++)
+       if (element_info[i].change->has_event[j])
+         trigger_events[element_info[i].change->trigger_element][j] = TRUE;
 #endif
 
   /* ---------- initialize push delay -------------------------------------- */
@@ -1357,8 +1584,16 @@ static void InitGameEngine()
     {
       if (IS_SP_ELEMENT(i))
       {
+#if USE_NEW_MOVE_STYLE
+       /* 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;
+#else
        element_info[i].push_delay_fixed  = 6;  /* just enough to escape ... */
        element_info[i].push_delay_random = 0;  /* ... from falling zonk     */
+#endif
       }
     }
   }
@@ -1465,7 +1700,7 @@ void InitGame()
     player->lights_still_needed = 0;
     player->friends_still_needed = 0;
 
-    for (j = 0; j < MAX_KEYS; j++)
+    for (j = 0; j < MAX_NUM_KEYS; j++)
       player->key[j] = FALSE;
 
     player->dynabomb_count = 0;
@@ -1483,7 +1718,9 @@ void InitGame()
 
     player->use_murphy_graphic = FALSE;
 
-    player->block_last_field = FALSE;
+    player->block_last_field = FALSE;  /* initialized in InitPlayerField() */
+    player->block_delay_adjustment = 0;        /* initialized in InitPlayerField() */
+
     player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
 
     player->actual_frame_counter = 0;
@@ -1552,6 +1789,11 @@ void InitGame()
     player->switch_x = -1;
     player->switch_y = -1;
 
+#if USE_DROP_BUGFIX
+    player->drop_x = -1;
+    player->drop_y = -1;
+#endif
+
     player->show_envelope = 0;
 
     player->move_delay       = game.initial_move_delay;
@@ -1559,8 +1801,13 @@ void InitGame()
 
     player->move_delay_reset_counter = 0;
 
-    player->push_delay = 0;
+#if USE_NEW_PUSH_DELAY
+    player->push_delay       = -1;     /* initialized when pushing starts */
+    player->push_delay_value = game.initial_push_delay_value;
+#else
+    player->push_delay       = 0;
     player->push_delay_value = game.initial_push_delay_value;
+#endif
 
     player->drop_delay = 0;
 
@@ -1582,13 +1829,14 @@ void InitGame()
 
   network_player_action_received = FALSE;
 
-#if defined(PLATFORM_UNIX)
+#if defined(NETWORK_AVALIABLE)
   /* initial null action */
   if (network_playing)
     SendToServer_MovePlayer(MV_NO_MOVING);
 #endif
 
   ZX = ZY = -1;
+  ExitX = ExitY = -1;
 
   FrameCounter = 0;
   TimeFrames = 0;
@@ -1641,8 +1889,8 @@ void InitGame()
       Stop[x][y] = FALSE;
       Pushed[x][y] = FALSE;
 
-      Changed[x][y] = CE_BITMASK_DEFAULT;
-      ChangeEvent[x][y] = CE_BITMASK_DEFAULT;
+      Changed[x][y] = FALSE;
+      ChangeEvent[x][y] = -1;
 
       ExplodePhase[x][y] = 0;
       ExplodeDelay[x][y] = 0;
@@ -1743,6 +1991,11 @@ void InitGame()
          player->element_nr = some_player->element_nr;
 #endif
 
+#if USE_NEW_BLOCK_STYLE
+         player->block_last_field       = some_player->block_last_field;
+         player->block_delay_adjustment = some_player->block_delay_adjustment;
+#endif
+
          StorePlayer[jx][jy] = player->element_nr;
          player->jx = player->last_jx = jx;
          player->jy = player->last_jy = jy;
@@ -1943,60 +2196,87 @@ void InitGame()
 #endif
   }
 
-  CloseDoor(DOOR_CLOSE_1);
+  if (!game.restart_level)
+    CloseDoor(DOOR_CLOSE_1);
 
-  DrawLevel();
-  DrawAllPlayers();
+  /* !!! FIX THIS (START) !!! */
+  if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+  {
+    InitGameEngine_EM();
+  }
+  else
+  {
+    DrawLevel();
+    DrawAllPlayers();
 
-  /* after drawing the level, correct some elements */
-  if (game.timegate_time_left == 0)
-    CloseAllOpenTimegates();
+    /* after drawing the level, correct some elements */
+    if (game.timegate_time_left == 0)
+      CloseAllOpenTimegates();
 
-  if (setup.soft_scrolling)
-    BlitBitmap(fieldbuffer, backbuffer, FX, FY, SXSIZE, SYSIZE, SX, SY);
+    if (setup.soft_scrolling)
+      BlitBitmap(fieldbuffer, backbuffer, FX, FY, SXSIZE, SYSIZE, SX, SY);
 
-  redraw_mask |= REDRAW_FROM_BACKBUFFER;
-  FadeToFront();
+    redraw_mask |= REDRAW_FROM_BACKBUFFER;
+    FadeToFront();
+  }
+  /* !!! FIX THIS (END) !!! */
 
-  /* copy default game door content to main double buffer */
-  BlitBitmap(graphic_info[IMG_GLOBAL_DOOR].bitmap, drawto,
-            DOOR_GFX_PAGEX5, DOOR_GFX_PAGEY1, DXSIZE, DYSIZE, DX, DY);
+  if (!game.restart_level)
+  {
+    /* copy default game door content to main double buffer */
+    BlitBitmap(graphic_info[IMG_GLOBAL_DOOR].bitmap, drawto,
+              DOOR_GFX_PAGEX5, DOOR_GFX_PAGEY1, DXSIZE, DYSIZE, DX, DY);
+  }
 
   DrawGameDoorValues();
 
-  UnmapGameButtons();
-  UnmapTapeButtons();
-  game_gadget[SOUND_CTRL_ID_MUSIC]->checked = setup.sound_music;
-  game_gadget[SOUND_CTRL_ID_LOOPS]->checked = setup.sound_loops;
-  game_gadget[SOUND_CTRL_ID_SIMPLE]->checked = setup.sound_simple;
-  MapGameButtons();
-  MapTapeButtons();
+  if (!game.restart_level)
+  {
+    UnmapGameButtons();
+    UnmapTapeButtons();
+    game_gadget[SOUND_CTRL_ID_MUSIC]->checked = setup.sound_music;
+    game_gadget[SOUND_CTRL_ID_LOOPS]->checked = setup.sound_loops;
+    game_gadget[SOUND_CTRL_ID_SIMPLE]->checked = setup.sound_simple;
+    MapGameButtons();
+    MapTapeButtons();
 
-  /* copy actual game door content to door double buffer for OpenDoor() */
-  BlitBitmap(drawto, bitmap_db_door,
-            DX, DY, DXSIZE, DYSIZE, DOOR_GFX_PAGEX1, DOOR_GFX_PAGEY1);
+    /* copy actual game door content to door double buffer for OpenDoor() */
+    BlitBitmap(drawto, bitmap_db_door,
+              DX, DY, DXSIZE, DYSIZE, DOOR_GFX_PAGEX1, DOOR_GFX_PAGEY1);
 
-  OpenDoor(DOOR_OPEN_ALL);
+    OpenDoor(DOOR_OPEN_ALL);
 
-  PlaySoundStereo(SND_GAME_STARTING, SOUND_MIDDLE);
+    PlaySoundStereo(SND_GAME_STARTING, SOUND_MIDDLE);
 
-  if (setup.sound_music)
-    PlayLevelMusic();
+    if (setup.sound_music)
+      PlayLevelMusic();
 
-  KeyboardAutoRepeatOffUnlessAutoplay();
+    KeyboardAutoRepeatOffUnlessAutoplay();
 
-  if (options.debug)
-  {
-    for (i = 0; i < MAX_PLAYERS; i++)
-      printf("Player %d %sactive.\n",
-            i + 1, (stored_player[i].active ? "" : "not "));
+    if (options.debug)
+    {
+      for (i = 0; i < MAX_PLAYERS; i++)
+       printf("Player %d %sactive.\n",
+              i + 1, (stored_player[i].active ? "" : "not "));
+    }
   }
 
+  game.restart_level = FALSE;
+
 #if 0
   printf("::: starting game [%d]\n", FrameCounter);
 #endif
 }
 
+void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y)
+{
+  /* this is used for non-R'n'D game engines to update certain engine values */
+
+  /* needed to determine if sounds are played within the visible screen area */
+  scroll_x = actual_scroll_x;
+  scroll_y = actual_scroll_y;
+}
+
 void InitMovDir(int x, int y)
 {
   int i, element = Feld[x][y];
@@ -2109,7 +2389,7 @@ void InitMovDir(int x, int y)
        {
 #if 1
          /* use random direction as default start direction */
-         if (game.engine_version >= VERSION_IDENT(3,1,0,2))
+         if (game.engine_version >= VERSION_IDENT(3,1,0,0))
            MovDir[x][y] = 1 << RND(4);
 #endif
 
@@ -2267,8 +2547,9 @@ void GameWon()
   }
 
   /* close exit door after last player */
-  if ((Feld[ExitX][ExitY] == EL_EXIT_OPEN ||
-       Feld[ExitX][ExitY] == EL_SP_EXIT_OPEN) && AllPlayersGone)
+  if (AllPlayersGone && ExitX >= 0 && ExitY >= 0 &&
+      (Feld[ExitX][ExitY] == EL_EXIT_OPEN ||
+       Feld[ExitX][ExitY] == EL_SP_EXIT_OPEN))
   {
     int element = Feld[ExitX][ExitY];
 
@@ -2279,7 +2560,9 @@ void GameWon()
   }
 
   /* Hero disappears */
-  DrawLevelField(ExitX, ExitY);
+  if (ExitX >= 0 && ExitY >= 0)
+    DrawLevelField(ExitX, ExitY);
+
   BackToFront();
 
   if (tape.playing)
@@ -2388,6 +2671,37 @@ int NewHiScore()
   return position;
 }
 
+inline static int getElementMoveStepsize(int x, int y)
+{
+  int element = Feld[x][y];
+  int direction = MovDir[x][y];
+  int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
+  int dy = (direction == MV_UP   ? -1 : direction == MV_DOWN  ? +1 : 0);
+  int horiz_move = (dx != 0);
+  int sign = (horiz_move ? dx : dy);
+  int step = sign * element_info[element].move_stepsize;
+
+  /* special values for move stepsize for spring and things on conveyor belt */
+  if (horiz_move)
+  {
+#if 0
+    if (element == EL_SPRING)
+      step = sign * MOVE_STEPSIZE_NORMAL * 2;
+    else if (CAN_FALL(element) && !CAN_MOVE(element) &&
+            y < lev_fieldy - 1 && IS_BELT_ACTIVE(Feld[x][y + 1]))
+      step = sign * MOVE_STEPSIZE_NORMAL / 2;
+#else
+    if (CAN_FALL(element) &&
+       y < lev_fieldy - 1 && IS_BELT_ACTIVE(Feld[x][y + 1]))
+      step = sign * MOVE_STEPSIZE_NORMAL / 2;
+    else if (element == EL_SPRING)
+      step = sign * MOVE_STEPSIZE_NORMAL * 2;
+#endif
+  }
+
+  return step;
+}
+
 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
 {
   if (player->GfxAction != action || player->GfxDir != dir)
@@ -2427,6 +2741,27 @@ void InitMovingField(int x, int y, int direction)
   if (!WasJustMoving[x][y] || direction != MovDir[x][y])
     ResetGfxAnimation(x, y);
 
+#if USE_CAN_MOVE_NOT_MOVING
+
+  MovDir[x][y] = direction;
+  GfxDir[x][y] = direction;
+  GfxAction[x][y] = (direction == MV_DOWN && CAN_FALL(element) ?
+                    ACTION_FALLING : ACTION_MOVING);
+
+  if (getElementMoveStepsize(x, y) != 0)       /* moving or being moved */
+  {
+    if (Feld[newx][newy] == EL_EMPTY)
+      Feld[newx][newy] = EL_BLOCKED;
+
+    MovDir[newx][newy] = MovDir[x][y];
+    GfxFrame[newx][newy] = GfxFrame[x][y];
+    GfxRandom[newx][newy] = GfxRandom[x][y];
+    GfxAction[newx][newy] = GfxAction[x][y];
+    GfxDir[newx][newy] = GfxDir[x][y];
+  }
+
+#else
+
   MovDir[newx][newy] = MovDir[x][y] = direction;
   GfxDir[x][y] = direction;
 
@@ -2442,6 +2777,7 @@ void InitMovingField(int x, int y, int direction)
   GfxRandom[newx][newy] = GfxRandom[x][y];
   GfxAction[newx][newy] = GfxAction[x][y];
   GfxDir[newx][newy] = GfxDir[x][y];
+#endif
 }
 
 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
@@ -2818,7 +3154,11 @@ void DrawRelocatePlayer(struct PlayerInfo *player)
 
 void RelocatePlayer(int jx, int jy, int el_player_raw)
 {
+#if 1
+  int el_player = GET_VALID_PLAYER_ELEMENT(el_player_raw);
+#else
   int el_player = (el_player_raw == EL_SP_MURPHY ? EL_PLAYER_1 :el_player_raw);
+#endif
   struct PlayerInfo *player = &stored_player[el_player - EL_PLAYER_1];
   boolean ffwd_delay = (tape.playing && tape.fast_forward);
   boolean no_delay = (tape.warp_forward);
@@ -2830,6 +3170,14 @@ void RelocatePlayer(int jx, int jy, int el_player_raw)
   int element = Feld[jx][jy];
   boolean player_relocated = (old_jx != jx || old_jy != jy);
 
+  int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
+  int move_dir_vert  = (jy < old_jy ? MV_UP   : jy > old_jy ? MV_DOWN  : 0);
+#if 1
+  int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
+  int enter_side_vert  = MV_DIR_OPPOSITE(move_dir_vert);
+  int leave_side_horiz = move_dir_horiz;
+  int leave_side_vert  = move_dir_vert;
+#else
   static int trigger_sides[4][2] =
   {
     /* enter side               leave side */
@@ -2838,13 +3186,12 @@ void RelocatePlayer(int jx, int jy, int el_player_raw)
     { CH_SIDE_BOTTOM,          CH_SIDE_TOP     },      /* moving up    */
     { CH_SIDE_TOP,             CH_SIDE_BOTTOM  }       /* moving down  */
   };
-  int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
-  int move_dir_vert  = (jy < old_jy ? MV_UP   : jy > old_jy ? MV_DOWN  : 0);
   int enter_side_horiz = trigger_sides[MV_DIR_BIT(move_dir_horiz)][0];
   int enter_side_vert  = trigger_sides[MV_DIR_BIT(move_dir_vert)][0];
-  int enter_side = enter_side_horiz | enter_side_vert;
   int leave_side_horiz = trigger_sides[MV_DIR_BIT(move_dir_horiz)][1];
   int leave_side_vert  = trigger_sides[MV_DIR_BIT(move_dir_vert)][1];
+#endif
+  int enter_side = enter_side_horiz | enter_side_vert;
   int leave_side = leave_side_horiz | leave_side_vert;
 
   if (player->GameOver)                /* do not reanimate dead player */
@@ -2865,7 +3212,12 @@ void RelocatePlayer(int jx, int jy, int el_player_raw)
     {
       ScrollPlayer(player, SCROLL_GO_ON);
       ScrollScreen(NULL, SCROLL_GO_ON);
+
+#if USE_NEW_MOVE_DELAY
+      AdvanceFrameAndPlayerCounters(player->index_nr);
+#else
       FrameCounter++;
+#endif
 
       DrawPlayer(player);
 
@@ -2886,7 +3238,7 @@ void RelocatePlayer(int jx, int jy, int el_player_raw)
                               player->index_bit, leave_side);
 
   CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
-                                     CE_OTHER_GETS_LEFT,
+                                     CE_PLAYER_LEAVES_X,
                                      player->index_bit, leave_side);
 #endif
 
@@ -2909,10 +3261,28 @@ void RelocatePlayer(int jx, int jy, int el_player_raw)
   TestIfPlayerTouchesCustomElement(jx, jy);
 #endif
 
-#if 1
+#if 0
+  printf("::: %d,%d: %d\n", jx, jy-1, Changed[jx][jy-1]);
+#endif
+
+#if 0
+#if 0
+  /* needed to allow change of walkable custom element by entering player */
+  if (!(Changed[jx][jy] & CH_EVENT_BIT(CE_ENTERED_BY_PLAYER)))
+    Changed[jx][jy] = 0;       /* allow another change (but prevent loop) */
+#else
   /* needed to allow change of walkable custom element by entering player */
   Changed[jx][jy] = 0;         /* allow another change */
 #endif
+#endif
+
+#if 0
+  printf("::: player entering %d, %d from %s ...\n", jx, jy,
+        enter_side == MV_LEFT  ? "left" :
+        enter_side == MV_RIGHT ? "right" :
+        enter_side == MV_UP    ? "top" :
+        enter_side == MV_DOWN  ? "bottom" : "oops! no idea!");
+#endif
 
 #if 1
   if (IS_CUSTOM_ELEMENT(element))
@@ -2920,7 +3290,7 @@ void RelocatePlayer(int jx, int jy, int el_player_raw)
                               player->index_bit, enter_side);
 
   CheckTriggeredElementChangeByPlayer(jx, jy, element,
-                                     CE_OTHER_GETS_ENTERED,
+                                     CE_PLAYER_ENTERS_X,
                                      player->index_bit, enter_side);
 #endif
 }
@@ -2967,8 +3337,15 @@ void Explode(int ex, int ey, int phase, int mode)
       return;
 #endif
 
+#if 1
+    if (mode == EX_TYPE_NORMAL ||
+       mode == EX_TYPE_CENTER ||
+       mode == EX_TYPE_CROSS)
+      PlayLevelSoundAction(ex, ey, ACTION_EXPLODING);
+#else
     if (mode == EX_TYPE_NORMAL || mode == EX_TYPE_CENTER)
       PlayLevelSoundAction(ex, ey, ACTION_EXPLODING);
+#endif
 
     /* remove things displayed in background while burning dynamite */
     if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
@@ -3037,10 +3414,17 @@ void Explode(int ex, int ey, int phase, int mode)
        continue;
 #else
       /* indestructible elements can only explode in center (but not flames) */
+#if 1
+      if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
+                                          mode == EX_TYPE_BORDER)) ||
+         element == EL_FLAMES)
+       continue;
+#else
       if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey)) ||
          element == EL_FLAMES)
        continue;
 #endif
+#endif
 
 #else
       if ((IS_INDESTRUCTIBLE(element) &&
@@ -3050,13 +3434,34 @@ void Explode(int ex, int ey, int phase, int mode)
        continue;
 #endif
 
+      /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
+        behaviour, for example when touching a yamyam that explodes to rocks
+        with active deadly shield, a rock is created under the player !!! */
+      /* (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8) */
+#if 0
+      if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
+         (game.engine_version < VERSION_IDENT(3,1,0,0) ||
+          (x == ex && y == ey && mode != EX_TYPE_BORDER)))
+#else
       if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
+#endif
       {
        if (IS_ACTIVE_BOMB(element))
        {
          /* re-activate things under the bomb like gate or penguin */
+#if 1
+         Feld[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
+         Back[x][y] = 0;
+#else
          Feld[x][y] = (Store[x][y] ? Store[x][y] : EL_EMPTY);
          Store[x][y] = 0;
+#endif
+
+#if 0
+       printf("::: %d,%d: %d %s [%d, %d]\n", x, y, Feld[x][y],
+              element_info[Feld[x][y]].token_name,
+              Store[x][y], Store2[x][y]);
+#endif
        }
 
        continue;
@@ -3068,9 +3473,15 @@ void Explode(int ex, int ey, int phase, int mode)
        Back[x][y] = element;
 #else
 #if 1
+#if 1
+      if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
+         (x != ex || y != ey || mode == EX_TYPE_BORDER))
+       Back[x][y] = element;
+#else
       if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
          (x != ex || y != ey))
        Back[x][y] = element;
+#endif
 #else
       if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element))
        Back[x][y] = element;
@@ -3159,10 +3570,15 @@ void Explode(int ex, int ey, int phase, int mode)
       else
        Store[x][y] = EL_EMPTY;
 
-      if (x != ex || y != ey ||
-         center_element == EL_AMOEBA_TO_DIAMOND || mode == EX_TYPE_BORDER)
+      if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
+         center_element == EL_AMOEBA_TO_DIAMOND)
        Store2[x][y] = element;
 
+#if 0
+      printf("::: %d,%d: %d %s\n", x, y, Store2[x][y],
+            element_info[Store2[x][y]].token_name);
+#endif
+
 #if 0
       if (AmoebaNr[x][y] &&
          (element == EL_AMOEBA_FULL ||
@@ -3209,6 +3625,11 @@ void Explode(int ex, int ey, int phase, int mode)
       game.yamyam_content_nr =
        (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
 
+#if 0
+  printf("::: %d,%d: %d %s [%d]\n", ex + 1, ey, Feld[ex + 1][ey],
+        element_info[Feld[ex + 1][ey]].token_name, Store2[ex + 1][ey]);
+#endif
+
     return;
   }
 
@@ -3254,8 +3675,18 @@ void Explode(int ex, int ey, int phase, int mode)
 #if 1
 
   border_element = Store2[x][y];
+#if 1
+  if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
+    border_element = StorePlayer[x][y];
+#else
   if (IS_PLAYER(x, y))
     border_element = StorePlayer[x][y];
+#endif
+
+#if 0
+  printf("::: %d,%d: %d %s [%d]\n", x, y, border_element,
+        element_info[border_element].token_name, Store2[x][y]);
+#endif
 
 #if 0
   printf("::: phase == %d\n", phase);
@@ -3267,7 +3698,12 @@ void Explode(int ex, int ey, int phase, int mode)
     boolean border_explosion = FALSE;
 
 #if 1
+#if 1
+    if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
+       !PLAYER_EXPLOSION_PROTECTED(x, y))
+#else
     if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present)
+#endif
 #else
     if (IS_PLAYER(x, y))
 #endif
@@ -3282,6 +3718,11 @@ void Explode(int ex, int ey, int phase, int mode)
     }
     else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
     {
+#if 0
+      printf("::: %d,%d: %d %s\n", x, y, border_element,
+            element_info[border_element].token_name);
+#endif
+
       Feld[x][y] = Store2[x][y];
       Store2[x][y] = 0;
       Bang(x, y);
@@ -3620,7 +4061,7 @@ void Bang(int x, int y)
       break;
   }
 
-  CheckTriggeredElementChange(x, y, element, CE_OTHER_IS_EXPLODING);
+  CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
 }
 
 void SplashAcid(int x, int y)
@@ -3914,6 +4355,10 @@ static void RedrawAllLightSwitchesAndInvisibleElements()
          Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
 
        DrawLevelField(x, y);
+
+       /* uncrumble neighbour fields, if needed */
+       if (element == EL_INVISIBLE_SAND)
+         DrawLevelFieldCrumbledSandNeighbours(x, y);
       }
       else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
               element == EL_INVISIBLE_WALL_ACTIVE ||
@@ -3923,6 +4368,10 @@ static void RedrawAllLightSwitchesAndInvisibleElements()
          Feld[x][y] = getInvisibleFromInvisibleActiveElement(element);
 
        DrawLevelField(x, y);
+
+       /* re-crumble neighbour fields, if needed */
+       if (element == EL_INVISIBLE_SAND)
+         DrawLevelFieldCrumbledSandNeighbours(x, y);
       }
     }
   }
@@ -3972,46 +4421,19 @@ static void ActivateTimegateSwitch(int x, int y)
   Feld[x][y] = EL_TIMEGATE_SWITCH_ACTIVE;
 }
 
-inline static int getElementMoveStepsize(int x, int y)
+void Impact(int x, int y)
 {
+  boolean last_line = (y == lev_fieldy - 1);
+  boolean object_hit = FALSE;
+  boolean impact = (last_line || object_hit);
   int element = Feld[x][y];
-  int direction = MovDir[x][y];
-  int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
-  int dy = (direction == MV_UP   ? -1 : direction == MV_DOWN  ? +1 : 0);
-  int horiz_move = (dx != 0);
-  int sign = (horiz_move ? dx : dy);
-  int step = sign * element_info[element].move_stepsize;
+  int smashed = EL_STEELWALL;
 
-  /* special values for move stepsize for spring and things on conveyor belt */
-  if (horiz_move)
-  {
 #if 0
-    if (element == EL_SPRING)
-      step = sign * MOVE_STEPSIZE_NORMAL * 2;
-    else if (CAN_FALL(element) && !CAN_MOVE(element) &&
-            y < lev_fieldy - 1 && IS_BELT_ACTIVE(Feld[x][y + 1]))
-      step = sign * MOVE_STEPSIZE_NORMAL / 2;
-#else
-    if (CAN_FALL(element) &&
-       y < lev_fieldy - 1 && IS_BELT_ACTIVE(Feld[x][y + 1]))
-      step = sign * MOVE_STEPSIZE_NORMAL / 2;
-    else if (element == EL_SPRING)
-      step = sign * MOVE_STEPSIZE_NORMAL * 2;
+  printf("IMPACT!\n");
 #endif
-  }
-
-  return step;
-}
-
-void Impact(int x, int y)
-{
-  boolean lastline = (y == lev_fieldy-1);
-  boolean object_hit = FALSE;
-  boolean impact = (lastline || object_hit);
-  int element = Feld[x][y];
-  int smashed = EL_STEELWALL;
 
-  if (!lastline)       /* check if element below was hit */
+  if (!last_line)      /* check if element below was hit */
   {
     if (Feld[x][y + 1] == EL_PLAYER_IS_LEAVING)
       return;
@@ -4032,10 +4454,10 @@ void Impact(int x, int y)
     if (object_hit)
       smashed = MovingOrBlocked2Element(x, y + 1);
 
-    impact = (lastline || object_hit);
+    impact = (last_line || object_hit);
   }
 
-  if (!lastline && smashed == EL_ACID) /* element falls into acid */
+  if (!last_line && smashed == EL_ACID)        /* element falls into acid */
   {
     SplashAcid(x, y + 1);
     return;
@@ -4217,10 +4639,10 @@ void Impact(int x, int y)
          CheckElementChangeBySide(x, y + 1, smashed, element,
                                   CE_SWITCHED, CH_SIDE_TOP);
          CheckTriggeredElementChangeBySide(x, y + 1, smashed,
-                                           CE_OTHER_IS_SWITCHING,CH_SIDE_TOP);
+                                           CE_SWITCH_OF_X, CH_SIDE_TOP);
 #else
          CheckTriggeredElementChangeBySide(x, y + 1, smashed,
-                                           CE_OTHER_IS_SWITCHING,CH_SIDE_TOP);
+                                           CE_SWITCH_OF_X, CH_SIDE_TOP);
          CheckElementChangeBySide(x, y + 1, smashed, element,
                                   CE_SWITCHED, CH_SIDE_TOP);
 #endif
@@ -4234,7 +4656,7 @@ void Impact(int x, int y)
   }
 
   /* play sound of magic wall / mill */
-  if (!lastline &&
+  if (!last_line &&
       (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
        Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE))
   {
@@ -4247,7 +4669,7 @@ void Impact(int x, int y)
   }
 
   /* play sound of object that hits the ground */
-  if (lastline || object_hit)
+  if (last_line || object_hit)
     PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
 }
 
@@ -4470,8 +4892,15 @@ inline static void TurnRoundExt(int x, int y)
     xx = x + move_xy[MovDir[x][y]].x;
     yy = y + move_xy[MovDir[x][y]].y;
 
+#if 1
+    /* !!! this bugfix breaks at least BD2K3, level 010 !!! [re-recorded] */
+    if (!IN_LEV_FIELD(xx, yy) ||
+        (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Feld[xx][yy])))
+      MovDir[x][y] = old_move_dir;
+#else
     if (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Feld[xx][yy]))
       MovDir[x][y] = old_move_dir;
+#endif
 
     MovDelay[x][y] = 0;
   }
@@ -4734,6 +5163,11 @@ inline static void TurnRoundExt(int x, int y)
     boolean can_turn_right =
       CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
 
+#if USE_CAN_MOVE_NOT_MOVING
+    if (element_info[element].move_stepsize == 0)      /* not moving */
+      return;
+#endif
+
     if (move_pattern == MV_TURNING_LEFT)
       MovDir[x][y] = left_dir;
     else if (move_pattern == MV_TURNING_RIGHT)
@@ -4844,6 +5278,16 @@ inline static void TurnRoundExt(int x, int y)
       boolean first_horiz = RND(2);
       int new_move_dir = MovDir[x][y];
 
+#if USE_CAN_MOVE_NOT_MOVING
+      if (element_info[element].move_stepsize == 0)    /* not moving */
+      {
+       first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
+       MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
+
+       return;
+      }
+#endif
+
       MovDir[x][y] =
        new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
       Moving2Blocked(x, y, &newx, &newy);
@@ -4937,7 +5381,13 @@ inline static void TurnRoundExt(int x, int y)
 
     MovDir[x][y] = new_move_dir;
     if (old_move_dir != new_move_dir)
+    {
+#if 1
+      MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
+#else
       MovDelay[x][y] = 9;
+#endif
+    }
   }
 }
 
@@ -5156,6 +5606,15 @@ void StartMoving(int x, int y)
     else if ((game.engine_version >= VERSION_IDENT(3,1,0,0) &&
              CheckCollision[x][y] && !IS_FREE(x, y + 1)) ||
 
+#if USE_IMPACT_BUGFIX
+            (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
+             CAN_FALL(element) && WasJustFalling[x][y] &&
+             (Feld[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
+
+            (game.engine_version < VERSION_IDENT(2,2,0,7) &&
+             CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
+             (Feld[x][y + 1] == EL_BLOCKED)))
+#else
             (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
              CAN_SMASH(element) && WasJustFalling[x][y] &&
              (Feld[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
@@ -5163,6 +5622,7 @@ void StartMoving(int x, int y)
             (game.engine_version < VERSION_IDENT(2,2,0,7) &&
              CAN_SMASH(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
              (Feld[x][y + 1] == EL_BLOCKED)))
+#endif
 
 #else
 #if 1
@@ -5193,6 +5653,11 @@ void StartMoving(int x, int y)
 
       CheckCollision[x][y] = 0;
 
+#if 0
+      if (IS_PLAYER(x, y + 1))
+       printf("::: we ARE now killing the player [%d]\n", FrameCounter);
+#endif
+
       Impact(x, y);
     }
     else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
@@ -5260,13 +5725,39 @@ void StartMoving(int x, int y)
        can_fall_both = (can_fall_left && can_fall_right);
       }
 
+#if USE_NEW_SP_SLIPPERY
+      /* !!! better use the same properties as for custom elements here !!! */
+      else if (game.engine_version >= VERSION_IDENT(3,1,1,0) &&
+              can_fall_both && IS_SP_ELEMENT(Feld[x][y + 1]))
+      {
+       can_fall_right = FALSE;         /* slip down on left side */
+       can_fall_both = FALSE;
+      }
+#endif
+
+#if 1
+      if (can_fall_both)
+      {
+       if (game.emulation == EMU_BOULDERDASH ||
+           element == EL_BD_ROCK || element == EL_BD_DIAMOND)
+         can_fall_right = FALSE;       /* slip down on left side */
+       else
+         can_fall_left = !(can_fall_right = RND(2));
+
+       can_fall_both = FALSE;
+      }
+#endif
+
       if (can_fall_any)
       {
+#if 0
        if (can_fall_both &&
            (game.emulation != EMU_BOULDERDASH &&
             element != EL_BD_ROCK && element != EL_BD_DIAMOND))
          can_fall_left = !(can_fall_right = RND(2));
+#endif
 
+       /* if not determined otherwise, prefer left side for slipping down */
        InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
        started_moving = TRUE;
       }
@@ -5344,7 +5835,7 @@ void StartMoving(int x, int y)
 
 #if 1
     if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
-       CheckCollision[x][y] && IN_LEV_FIELD_AND_NOT_FREE(newx, newy))
+       CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
 #else
     if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
        WasJustMoving[x][y] && IN_LEV_FIELD(newx, newy) &&
@@ -5357,8 +5848,8 @@ void StartMoving(int x, int y)
             WasJustMoving[x][y],
             HAS_ANY_CHANGE_EVENT(element, CE_HITTING_SOMETHING),
             HAS_ANY_CHANGE_EVENT(element, CE_HIT_BY_SOMETHING),
-            HAS_ANY_CHANGE_EVENT(element, CE_OTHER_IS_HITTING),
-            HAS_ANY_CHANGE_EVENT(element, CE_OTHER_GETS_HIT));
+            HAS_ANY_CHANGE_EVENT(element, CE_HITTING_X),
+            HAS_ANY_CHANGE_EVENT(element, CE_HIT_BY_X));
 #endif
 
 #if 1
@@ -5749,7 +6240,16 @@ void StartMoving(int x, int y)
 #if 1
       Store[newx][newy] = EL_EMPTY;
       if (IS_EQUAL_OR_IN_GROUP(new_element, MOVE_ENTER_EL(element)))
+      {
+#if USE_CHANGE_TO_TRIGGERED
+       int move_leave_element = element_info[element].move_leave_element;
+
+       Store[newx][newy] = (move_leave_element == EL_TRIGGER_ELEMENT ?
+                            new_element : move_leave_element);
+#else
        Store[newx][newy] = element_info[element].move_leave_element;
+#endif
+      }
 #else
       Store[newx][newy] = EL_EMPTY;
       if (IS_EQUAL_OR_IN_GROUP(new_element, MOVE_ENTER_EL(element)) ||
@@ -5980,6 +6480,10 @@ void StartMoving(int x, int y)
     ContinueMoving(x, y);
 }
 
+void dummy()
+{
+}
+
 void ContinueMoving(int x, int y)
 {
   int element = Feld[x][y];
@@ -5998,6 +6502,7 @@ void ContinueMoving(int x, int y)
 #else
   boolean pushed_by_player = Pushed[x][y];
 #endif
+  boolean last_line = (newy == lev_fieldy - 1);
 
   MovPos[x][y] += getElementMoveStepsize(x, y);
 
@@ -6113,7 +6618,9 @@ void ContinueMoving(int x, int y)
 #endif
 
   Store[x][y] = EL_EMPTY;
-  MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
+  MovPos[x][y] = 0;
+  MovDir[x][y] = 0;
+  MovDelay[x][y] = 0;
   MovDelay[newx][newy] = 0;
 
   if (CAN_CHANGE(element))
@@ -6127,8 +6634,8 @@ void ContinueMoving(int x, int y)
 
   ChangeDelay[x][y] = 0;
   ChangePage[x][y] = -1;
-  Changed[x][y] = CE_BITMASK_DEFAULT;
-  ChangeEvent[x][y] = CE_BITMASK_DEFAULT;
+  Changed[x][y] = FALSE;
+  ChangeEvent[x][y] = -1;
 
   /* copy animation control values to new field */
   GfxFrame[newx][newy]  = GfxFrame[x][y];
@@ -6138,7 +6645,10 @@ void ContinueMoving(int x, int y)
 
   Pushed[x][y] = Pushed[newx][newy] = FALSE;
 
+#if 0
+  /* do this after checking for left-behind element */
   ResetGfxAnimation(x, y);     /* reset animation values for old field */
+#endif
 
 #if 1
   /* some elements can leave other elements behind after moving */
@@ -6154,7 +6664,19 @@ void ContinueMoving(int x, int y)
   {
     int move_leave_element = ei->move_leave_element;
 
+#if USE_CHANGE_TO_TRIGGERED
+    if (ei->move_leave_type == LEAVE_TYPE_LIMITED &&
+        ei->move_leave_element == EL_TRIGGER_ELEMENT)
+      move_leave_element = stored;
+#endif
+
     Feld[x][y] = move_leave_element;
+
+#if USE_PREVIOUS_MOVE_DIR
+    if (element_info[Feld[x][y]].move_direction_initial == MV_START_PREVIOUS)
+      MovDir[x][y] = direction;
+#endif
+
     InitField(x, y, FALSE);
 
     if (GFX_CRUMBLED(Feld[x][y]))
@@ -6183,6 +6705,11 @@ void ContinueMoving(int x, int y)
   ei->can_leave_element = FALSE;
 #endif
 
+#if 1
+  /* do this after checking for left-behind element */
+  ResetGfxAnimation(x, y);     /* reset animation values for old field */
+#endif
+
 #if 0
   /* 2.1.1 (does not work correctly for spring) */
   if (!CAN_MOVE(element))
@@ -6268,13 +6795,41 @@ void ContinueMoving(int x, int y)
   else if (element == EL_PENGUIN)
     TestIfFriendTouchesBadThing(newx, newy);
 
+#if USE_NEW_MOVE_STYLE
+#if 0
+  if (CAN_FALL(element) && direction == MV_DOWN &&
+      !last_line && IS_PLAYER(x, newy + 1))
+    printf("::: we would now kill the player [%d]\n", FrameCounter);
+#endif
+
+  /* give the player one last chance (one more frame) to move away */
   if (CAN_FALL(element) && direction == MV_DOWN &&
-      (newy == lev_fieldy - 1 || !IS_FREE(x, newy + 1)))
+      (last_line || (!IS_FREE(x, newy + 1) &&
+                    (!IS_PLAYER(x, newy + 1) ||
+                     game.engine_version < VERSION_IDENT(3,1,1,0)))))
     Impact(x, newy);
+#else
+  if (CAN_FALL(element) && direction == MV_DOWN &&
+      (last_line || !IS_FREE(x, newy + 1)))
+    Impact(x, newy);
+#endif
 
 #if 1
+
+#if USE_PUSH_BUGFIX
+#if 1
+  if (pushed_by_player && !game.use_change_when_pushing_bug)
+#else
+  if (pushed_by_player && game.engine_version >= VERSION_IDENT(3,1,0,0))
+#endif
+#else
   if (pushed_by_player)
+#endif
+
   {
+#if 1
+    int dig_side = MV_DIR_OPPOSITE(direction);
+#else
     static int trigger_sides[4] =
     {
       CH_SIDE_RIGHT,   /* moving left  */
@@ -6283,11 +6838,12 @@ void ContinueMoving(int x, int y)
       CH_SIDE_TOP,     /* moving down  */
     };
     int dig_side = trigger_sides[MV_DIR_BIT(direction)];
+#endif
     struct PlayerInfo *player = PLAYERINFO(x, y);
 
     CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
                               player->index_bit, dig_side);
-    CheckTriggeredElementChangeByPlayer(newx,newy,element,CE_OTHER_GETS_PUSHED,
+    CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
                                        player->index_bit, dig_side);
   }
 #endif
@@ -6334,7 +6890,7 @@ void ContinueMoving(int x, int y)
                                 CE_HIT_BY_SOMETHING, opposite_direction);
 
        if (IS_CUSTOM_ELEMENT(hitting_element) &&
-           HAS_ANY_CHANGE_EVENT(hitting_element, CE_OTHER_IS_HITTING))
+           HAS_ANY_CHANGE_EVENT(hitting_element, CE_HITTING_X))
        {
          for (i = 0; i < element_info[hitting_element].num_change_pages; i++)
          {
@@ -6342,19 +6898,19 @@ void ContinueMoving(int x, int y)
              &element_info[hitting_element].change_page[i];
 
            if (change->can_change &&
-               change->events & CH_EVENT_BIT(CE_OTHER_IS_HITTING) &&
+               change->has_event[CE_HITTING_X] &&
                change->trigger_side & touched_side &&
                change->trigger_element == touched_element)
            {
              CheckElementChangeByPage(newx, newy, hitting_element,
-                                      touched_element, CE_OTHER_IS_HITTING,i);
+                                      touched_element, CE_HITTING_X, i);
              break;
            }
          }
        }
 
        if (IS_CUSTOM_ELEMENT(touched_element) &&
-           HAS_ANY_CHANGE_EVENT(touched_element, CE_OTHER_GETS_HIT))
+           HAS_ANY_CHANGE_EVENT(touched_element, CE_HIT_BY_X))
        {
          for (i = 0; i < element_info[touched_element].num_change_pages; i++)
          {
@@ -6362,12 +6918,12 @@ void ContinueMoving(int x, int y)
              &element_info[touched_element].change_page[i];
 
            if (change->can_change &&
-               change->events & CH_EVENT_BIT(CE_OTHER_GETS_HIT) &&
+               change->has_event[CE_HIT_BY_X] &&
                change->trigger_side & hitting_side &&
                change->trigger_element == hitting_element)
            {
              CheckElementChangeByPage(nextx, nexty, touched_element,
-                                      hitting_element, CE_OTHER_GETS_HIT, i);
+                                      hitting_element, CE_HIT_BY_X,i);
              break;
            }
          }
@@ -7320,159 +7876,492 @@ static void ChangeActiveTrap(int x, int y)
     DrawLevelFieldCrumbledSand(x, y);
 }
 
-static void ChangeElementNowExt(int x, int y, int target_element)
+static int getSpecialActionElement(int element, int number, int base_element)
 {
-  int previous_move_direction = MovDir[x][y];
-#if 1
-  boolean add_player = (ELEM_IS_PLAYER(target_element) &&
-                       IS_WALKABLE(Feld[x][y]));
-#else
-  boolean add_player = (ELEM_IS_PLAYER(target_element) &&
-                       IS_WALKABLE(Feld[x][y]) &&
-                       !IS_MOVING(x, y));
-#endif
-
-  /* check if element under player changes from accessible to unaccessible
-     (needed for special case of dropping element which then changes) */
-  if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y) &&
-      IS_ACCESSIBLE(Feld[x][y]) && !IS_ACCESSIBLE(target_element))
-  {
-    Bang(x, y);
-    return;
-  }
-
-#if 1
-  if (!add_player)
-#endif
-  {
-#if 1
-    if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
-      RemoveMovingField(x, y);
-    else
-      RemoveField(x, y);
-
-    Feld[x][y] = target_element;
-#else
-    RemoveField(x, y);
-    Feld[x][y] = target_element;
-#endif
+  return (element != EL_EMPTY ? element :
+         number != -1 ? base_element + number - 1 :
+         EL_EMPTY);
+}
 
-    ResetGfxAnimation(x, y);
-    ResetRandomAnimationValue(x, y);
+static int getModifiedActionNumber(int value_old, int value_min, int value_max,
+                                  int operator, int operand)
+{
+  int value_new = (operator == CA_MODE_ADD      ? value_old + operand :
+                  operator == CA_MODE_SUBTRACT ? value_old - operand :
+                  operator == CA_MODE_MULTIPLY ? value_old * operand :
+                  operator == CA_MODE_DIVIDE   ? value_old / MAX(1, operand) :
+                  operator == CA_MODE_SET      ? operand :
+                  value_old);
 
-    if (element_info[Feld[x][y]].move_direction_initial == MV_START_PREVIOUS)
-      MovDir[x][y] = previous_move_direction;
+  return (value_new < value_min ? value_min :
+         value_new > value_max ? value_max :
+         value_new);
+}
 
-#if 1
-    InitField_WithBug1(x, y, FALSE);
-#else
-    InitField(x, y, FALSE);
-    if (CAN_MOVE(Feld[x][y]))
-      InitMovDir(x, y);
-#endif
+static void ExecuteCustomElementAction(int element, int page)
+{
+  struct ElementInfo *ei = &element_info[element];
+  struct ElementChangeInfo *change = &ei->change_page[page];
+  int action_type = change->action_type;
+  int action_mode = change->action_mode;
+  int action_arg = change->action_arg;
+  int i;
 
-    DrawLevelField(x, y);
+  /* ---------- determine action paramater values ---------- */
+
+  int action_arg_element =
+    (action_arg == CA_ARG_PLAYER_TRIGGER  ? change->actual_trigger_player :
+     action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
+     action_arg == CA_ARG_ELEMENT_TARGET  ? change->target_element :
+     EL_EMPTY);
+
+  int action_arg_number =
+    (action_arg <= CA_ARG_MAX ? action_arg :
+     action_arg == CA_ARG_NUMBER_MIN ? CA_ARG_MIN :
+     action_arg == CA_ARG_NUMBER_MAX ? CA_ARG_MAX :
+     action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
+     action_arg == CA_ARG_NUMBER_CE_COUNT ? ei->collect_count :
+     action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CHANGE_DELAY(change) :
+     -1);
+
+  /* (for explicit player choice, set invalid value to "no player") */
+  int action_arg_player_bits =
+    (action_arg == CA_ARG_PLAYER_ANY ? action_arg - CA_ARG_PLAYER :
+     action_arg >= CA_ARG_PLAYER_1 &&
+     action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
+     action_arg >= CA_ARG_1 &&
+     action_arg <= CA_ARG_PLAYER_4 ? (1 << (action_arg - 1)) :
+     action_arg_element >= EL_PLAYER_1 &&
+     action_arg_element <= EL_PLAYER_4 ?
+     (1 << (action_arg_element - EL_PLAYER_1)) :
+     0);
+
+  /* (for implicit player choice, set invalid value to "all players") */
+  int trigger_player_bits =
+    (change->actual_trigger_player >= EL_PLAYER_1 &&
+     change->actual_trigger_player <= EL_PLAYER_4 ?
+     (1 << (change->actual_trigger_player - EL_PLAYER_1)) :
+     PLAYER_BITS_ANY);
+
+  /* ---------- execute action  ---------- */
+
+  switch(action_type)
+  {
+    case CA_NO_ACTION:
+    {
+      return;
+    }
 
-    if (GFX_CRUMBLED(Feld[x][y]))
-      DrawLevelFieldCrumbledSandNeighbours(x, y);
-  }
+    case CA_EXIT_PLAYER:
+    {
+      for (i = 0; i < MAX_PLAYERS; i++)
+       if (action_arg_player_bits & (1 << i))
+         stored_player[i].LevelSolved = stored_player[i].GameOver = TRUE;
 
-  Changed[x][y] |= ChangeEvent[x][y];  /* ignore same changes in this frame */
+      break;
+    }
 
-#if 0
-  TestIfBadThingTouchesHero(x, y);
-  TestIfPlayerTouchesCustomElement(x, y);
-  TestIfElementTouchesCustomElement(x, y);
-#endif
+    case CA_KILL_PLAYER:
+    {
+      for (i = 0; i < MAX_PLAYERS; i++)
+       if (action_arg_player_bits & (1 << i))
+         KillHero(&stored_player[i]);
 
-  if (ELEM_IS_PLAYER(target_element))
-    RelocatePlayer(x, y, target_element);
+      break;
+    }
 
-#if 1
-  TestIfBadThingTouchesHero(x, y);
-  TestIfPlayerTouchesCustomElement(x, y);
-  TestIfElementTouchesCustomElement(x, y);
-#endif
-}
+    case CA_RESTART_LEVEL:
+    {
+      game.restart_level = TRUE;
 
-static boolean ChangeElementNow(int x, int y, int element, int page)
-{
-  struct ElementChangeInfo *change = &element_info[element].change_page[page];
-  int target_element;
-  int old_element = Feld[x][y];
+      break;
+    }
 
-  /* always use default change event to prevent running into a loop */
-  if (ChangeEvent[x][y] == CE_BITMASK_DEFAULT)
-    ChangeEvent[x][y] = CH_EVENT_BIT(CE_DELAY);
+    case CA_SHOW_ENVELOPE:
+    {
+      int element = getSpecialActionElement(action_arg_element,
+                                           action_arg_number, EL_ENVELOPE_1);
 
-  if (ChangeEvent[x][y] == CH_EVENT_BIT(CE_DELAY))
-  {
-    /* reset actual trigger element and player */
-    change->actual_trigger_element = EL_EMPTY;
-    change->actual_trigger_player = EL_PLAYER_1;
-  }
+      if (IS_ENVELOPE(element))
+       local_player->show_envelope = element;
 
-  /* do not change already changed elements with same change event */
-#if 0
-  if (Changed[x][y] & ChangeEvent[x][y])
-    return FALSE;
-#else
-  if (Changed[x][y])
-    return FALSE;
-#endif
+      break;
+    }
 
-  Changed[x][y] |= ChangeEvent[x][y];  /* ignore same changes in this frame */
+    case CA_ADD_KEY:
+    {
+      int element = getSpecialActionElement(action_arg_element,
+                                           action_arg_number, EL_KEY_1);
 
-#if 0
-  /* !!! indirect change before direct change !!! */
-  CheckTriggeredElementChangeByPage(x,y,Feld[x][y], CE_OTHER_IS_CHANGING,page);
-#endif
+      if (IS_KEY(element))
+      {
+       for (i = 0; i < MAX_PLAYERS; i++)
+       {
+         if (trigger_player_bits & (1 << i))
+         {
+           stored_player[i].key[KEY_NR(element)] = TRUE;
 
-  if (change->explode)
-  {
-    Bang(x, y);
+           DrawGameValue_Keys(stored_player[i].key);
 
-    return TRUE;
-  }
+           redraw_mask |= REDRAW_DOOR_1;
+         }
+       }
+      }
 
-  if (change->use_target_content)
-  {
-    boolean complete_replace = TRUE;
-    boolean can_replace[3][3];
-    int xx, yy;
+      break;
+    }
 
-    for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
+    case CA_DEL_KEY:
     {
-      boolean is_empty;
-      boolean is_walkable;
-      boolean is_diggable;
-      boolean is_collectible;
-      boolean is_removable;
-      boolean is_destructible;
-      int ex = x + xx - 1;
-      int ey = y + yy - 1;
-      int content_element = change->target_content[xx][yy];
-      int e;
-
-      can_replace[xx][yy] = TRUE;
-
-      if (ex == x && ey == y)  /* do not check changing element itself */
-       continue;
+      int element = getSpecialActionElement(action_arg_element,
+                                           action_arg_number, EL_KEY_1);
 
-      if (content_element == EL_EMPTY_SPACE)
+      if (IS_KEY(element))
       {
-       can_replace[xx][yy] = FALSE;    /* do not replace border with space */
+       for (i = 0; i < MAX_PLAYERS; i++)
+       {
+         if (trigger_player_bits & (1 << i))
+         {
+           stored_player[i].key[KEY_NR(element)] = FALSE;
 
-       continue;
+           DrawGameValue_Keys(stored_player[i].key);
+
+           redraw_mask |= REDRAW_DOOR_1;
+         }
+       }
       }
 
-      if (!IN_LEV_FIELD(ex, ey))
-      {
+      break;
+    }
+
+    case CA_SET_PLAYER_SPEED:
+    {
+      for (i = 0; i < MAX_PLAYERS; i++)
+      {
+       if (trigger_player_bits & (1 << i))
+       {
+         if (action_arg == CA_ARG_NUMBER_RESET)
+           stored_player[i].move_delay_value = game.initial_move_delay_value;
+         else if (action_arg == CA_ARG_NUMBER_NORMAL)
+           stored_player[i].move_delay_value = MOVE_DELAY_NORMAL_SPEED;
+         else if (action_arg == CA_ARG_NUMBER_MIN)
+           stored_player[i].move_delay_value = 16;
+         else if (action_arg == CA_ARG_NUMBER_MAX)
+           stored_player[i].move_delay_value = MOVE_DELAY_HIGH_SPEED;
+         else
+         {
+#if 0
+           if (action_mode == CA_MODE_ADD)
+           {
+             action_mode = CA_MODE_DIVIDE;
+             action_arg_number = (1 << action_arg_number);
+           }
+           else if (action_mode == CA_MODE_SUBTRACT)
+           {
+             action_mode = CA_MODE_MULTIPLY;
+             action_arg_number = (1 << action_arg_number);
+           }
+
+           int mode = (action_mode == CA_MODE_MULTIPLY ? CA_MODE_DIVIDE :
+                       action_mode == CA_MODE_DIVIDE   ? CA_MODE_MULTIPLY :
+                       action_mode);
+
+           stored_player[i].move_delay_value =
+             getModifiedActionNumber(stored_player[i].move_delay_value,
+                                     1, 16,
+                                     action_mode, action_arg_number);
+#endif
+         }
+       }
+      }
+
+      break;
+    }
+
+    case CA_SET_GEMS:
+    {
+      local_player->gems_still_needed =
+       getModifiedActionNumber(local_player->gems_still_needed, 0, 999,
+                               action_mode, action_arg_number);
+
+      DrawGameValue_Emeralds(local_player->gems_still_needed);
+
+      break;
+    }
+
+    case CA_SET_TIME:
+    {
+      if (level.time > 0)      /* only modify limited time value */
+      {
+       TimeLeft = getModifiedActionNumber(TimeLeft, 0, 9999,
+                                          action_mode, action_arg_number);
+
+       DrawGameValue_Time(TimeLeft);
+      }
+
+      break;
+    }
+
+    case CA_SET_SCORE:
+    {
+      local_player->score =
+       getModifiedActionNumber(local_player->score, 0, 99999,
+                               action_mode, action_arg_number);
+
+      DrawGameValue_Score(local_player->score);
+
+      break;
+    }
+
+    case CA_SET_CE_SCORE:
+    {
+      printf("::: CA_SET_CE_SCORE -- not yet implemented\n");
+
+      break;
+    }
+
+    case CA_SET_CE_COUNT:
+    {
+      printf("::: CA_SET_CE_COUNT -- not yet implemented\n");
+
+      break;
+    }
+
+    case CA_SET_DYNABOMB_NUMBER:
+    {
+      printf("::: CA_SET_DYNABOMB_NUMBER -- not yet implemented\n");
+
+      break;
+    }
+
+    case CA_SET_DYNABOMB_SIZE:
+    {
+      printf("::: CA_SET_DYNABOMB_SIZE -- not yet implemented\n");
+
+      break;
+    }
+
+    case CA_SET_DYNABOMB_POWER:
+    {
+      printf("::: CA_SET_DYNABOMB_POWER -- not yet implemented\n");
+
+      break;
+    }
+
+    case CA_TOGGLE_PLAYER_GRAVITY:
+    {
+      game.gravity = !game.gravity;
+
+      break;
+    }
+
+    case CA_ENABLE_PLAYER_GRAVITY:
+    {
+      game.gravity = TRUE;
+
+      break;
+    }
+
+    case CA_DISABLE_PLAYER_GRAVITY:
+    {
+      game.gravity = FALSE;
+
+      break;
+    }
+
+    default:
+      break;
+  }
+}
+
+static void ChangeElementNowExt(struct ElementChangeInfo *change,
+                               int x, int y, int target_element)
+{
+  int previous_move_direction = MovDir[x][y];
+#if 1
+  boolean add_player = (ELEM_IS_PLAYER(target_element) &&
+                       IS_WALKABLE(Feld[x][y]));
+#else
+  boolean add_player = (ELEM_IS_PLAYER(target_element) &&
+                       IS_WALKABLE(Feld[x][y]) &&
+                       !IS_MOVING(x, y));
+#endif
+
+  /* check if element under player changes from accessible to unaccessible
+     (needed for special case of dropping element which then changes) */
+  if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y) &&
+      IS_ACCESSIBLE(Feld[x][y]) && !IS_ACCESSIBLE(target_element))
+  {
+#if 0
+    printf("::: BOOOM! [%d, '%s']\n", target_element,
+          element_info[target_element].token_name);
+#endif
+
+    Bang(x, y);
+    return;
+  }
+
+#if 1
+  if (!add_player)
+#endif
+  {
+#if 1
+    if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
+      RemoveMovingField(x, y);
+    else
+      RemoveField(x, y);
+
+    Feld[x][y] = target_element;
+#else
+    RemoveField(x, y);
+    Feld[x][y] = target_element;
+#endif
+
+    ResetGfxAnimation(x, y);
+    ResetRandomAnimationValue(x, y);
+
+    if (element_info[Feld[x][y]].move_direction_initial == MV_START_PREVIOUS)
+      MovDir[x][y] = previous_move_direction;
+
+#if 1
+    InitField_WithBug1(x, y, FALSE);
+#else
+    InitField(x, y, FALSE);
+    if (CAN_MOVE(Feld[x][y]))
+      InitMovDir(x, y);
+#endif
+
+    DrawLevelField(x, y);
+
+    if (GFX_CRUMBLED(Feld[x][y]))
+      DrawLevelFieldCrumbledSandNeighbours(x, y);
+  }
+
+#if 0
+  Changed[x][y] |= ChangeEvent[x][y];  /* ignore same changes in this frame */
+#endif
+
+#if 0
+  TestIfBadThingTouchesHero(x, y);
+  TestIfPlayerTouchesCustomElement(x, y);
+  TestIfElementTouchesCustomElement(x, y);
+#endif
+
+  /* "Changed[][]" not set yet to allow "entered by player" change one time */
+  if (ELEM_IS_PLAYER(target_element))
+    RelocatePlayer(x, y, target_element);
+
+#if 1
+  Changed[x][y] = TRUE;                /* ignore all further changes in this frame */
+#else
+  Changed[x][y] |= ChangeEvent[x][y];  /* ignore same changes in this frame */
+#endif
+
+#if 1
+  TestIfBadThingTouchesHero(x, y);
+  TestIfPlayerTouchesCustomElement(x, y);
+  TestIfElementTouchesCustomElement(x, y);
+#endif
+
+#if 0
+  if (change->use_action)
+    ExecuteCustomElementAction(...);
+#endif
+}
+
+static boolean ChangeElementNow(int x, int y, int element, int page)
+{
+  struct ElementChangeInfo *change = &element_info[element].change_page[page];
+  int target_element;
+  int old_element = Feld[x][y];
+
+  /* always use default change event to prevent running into a loop */
+  if (ChangeEvent[x][y] == -1)
+    ChangeEvent[x][y] = CE_DELAY;
+
+  if (ChangeEvent[x][y] == CE_DELAY)
+  {
+    /* reset actual trigger element, trigger player and action element */
+    change->actual_trigger_element = EL_EMPTY;
+    change->actual_trigger_player = EL_PLAYER_1;
+  }
+
+#if 1
+  /* do not change any elements that have already changed in this frame */
+  if (Changed[x][y])
+    return FALSE;
+#else
+  /* do not change already changed elements with same change event */
+  if (Changed[x][y] & ChangeEvent[x][y])
+    return FALSE;
+#endif
+
+#if 1
+  Changed[x][y] = TRUE;                /* ignore all further changes in this frame */
+#else
+  Changed[x][y] |= ChangeEvent[x][y];  /* ignore same changes in this frame */
+#endif
+
+#if 0
+  /* !!! indirect change before direct change !!! */
+  CheckTriggeredElementChangeByPage(x, y, Feld[x][y], CE_CHANGE_OF_X, page);
+#endif
+
+  if (change->explode)
+  {
+    Bang(x, y);
+
+    return TRUE;
+  }
+
+  if (change->use_target_content)
+  {
+    boolean complete_replace = TRUE;
+    boolean can_replace[3][3];
+    int xx, yy;
+
+    for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
+    {
+      boolean is_empty;
+      boolean is_walkable;
+      boolean is_diggable;
+      boolean is_collectible;
+      boolean is_removable;
+      boolean is_destructible;
+      int ex = x + xx - 1;
+      int ey = y + yy - 1;
+      int content_element = change->target_content[xx][yy];
+      int e;
+
+      can_replace[xx][yy] = TRUE;
+
+      if (ex == x && ey == y)  /* do not check changing element itself */
+       continue;
+
+      if (content_element == EL_EMPTY_SPACE)
+      {
+       can_replace[xx][yy] = FALSE;    /* do not replace border with space */
+
+       continue;
+      }
+
+      if (!IN_LEV_FIELD(ex, ey))
+      {
+       can_replace[xx][yy] = FALSE;
+       complete_replace = FALSE;
+
+       continue;
+      }
+
+#if 0
+      if (Changed[ex][ey])     /* do not change already changed elements */
+      {
        can_replace[xx][yy] = FALSE;
        complete_replace = FALSE;
 
        continue;
       }
+#endif
 
       e = Feld[ex][ey];
 
@@ -7487,9 +8376,17 @@ static boolean ChangeElementNow(int x, int y, int element, int page)
                  (IS_WALKABLE(e) && ELEM_IS_PLAYER(content_element) &&
                   !IS_MOVING(ex, ey) && !IS_BLOCKED(ex, ey)));
 #else
+
+#if 0
       is_empty = (IS_FREE(ex, ey) ||
                  (IS_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
+#else
+      is_empty = (IS_FREE(ex, ey) ||
+                 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
 #endif
+
+#endif
+
       is_walkable     = (is_empty || IS_WALKABLE(e));
       is_diggable     = (is_empty || IS_DIGGABLE(e));
       is_collectible  = (is_empty || IS_COLLECTIBLE(e));
@@ -7497,12 +8394,13 @@ static boolean ChangeElementNow(int x, int y, int element, int page)
       is_removable    = (is_diggable || is_collectible);
 
       can_replace[xx][yy] =
-       ((change->replace_when == CP_WHEN_EMPTY        && is_empty) ||
-        (change->replace_when == CP_WHEN_WALKABLE     && is_walkable) ||
-        (change->replace_when == CP_WHEN_DIGGABLE     && is_diggable) ||
-        (change->replace_when == CP_WHEN_COLLECTIBLE  && is_collectible) ||
-        (change->replace_when == CP_WHEN_REMOVABLE    && is_removable) ||
-        (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible));
+       (((change->replace_when == CP_WHEN_EMPTY        && is_empty) ||
+         (change->replace_when == CP_WHEN_WALKABLE     && is_walkable) ||
+         (change->replace_when == CP_WHEN_DIGGABLE     && is_diggable) ||
+         (change->replace_when == CP_WHEN_COLLECTIBLE  && is_collectible) ||
+         (change->replace_when == CP_WHEN_REMOVABLE    && is_removable) ||
+         (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
+        !(IS_PLAYER(ex, ey) && ELEM_IS_PLAYER(content_element)));
 
       if (!can_replace[xx][yy])
        complete_replace = FALSE;
@@ -7550,7 +8448,7 @@ static boolean ChangeElementNow(int x, int y, int element, int page)
          content_element = change->target_content[xx][yy];
          target_element = GET_TARGET_ELEMENT(content_element, change);
 
-         ChangeElementNowExt(ex, ey, target_element);
+         ChangeElementNowExt(change, ex, ey, target_element);
 
          something_has_changed = TRUE;
 
@@ -7568,14 +8466,14 @@ static boolean ChangeElementNow(int x, int y, int element, int page)
   {
     target_element = GET_TARGET_ELEMENT(change->target_element, change);
 
-    ChangeElementNowExt(x, y, target_element);
+    ChangeElementNowExt(change, x, y, target_element);
 
     PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
   }
 
 #if 1
   /* this uses direct change before indirect change */
-  CheckTriggeredElementChangeByPage(x,y,old_element,CE_OTHER_IS_CHANGING,page);
+  CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
 #endif
 
   return TRUE;
@@ -7611,8 +8509,12 @@ static void ChangeElement(int x, int y, int page)
 
   if (ChangeDelay[x][y] == 0)          /* initialize element change */
   {
+#if 1
+    ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
+#else
     ChangeDelay[x][y] = (    change->delay_fixed  * change->delay_frames +
                         RND(change->delay_random * change->delay_frames)) + 1;
+#endif
 
     ResetGfxAnimation(x, y);
     ResetRandomAnimationValue(x, y);
@@ -7655,6 +8557,11 @@ static void ChangeElement(int x, int y, int page)
       return;
     }
 
+#if 1
+    if (change->use_action)
+      ExecuteCustomElementAction(element, page);
+#endif
+
     if (ChangeElementNow(x, y, element, page))
     {
       if (change->post_change_function)
@@ -7673,7 +8580,7 @@ static boolean CheckTriggeredElementChangeExt(int lx, int ly,
   int i, j, x, y;
   int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
 
-  if (!(trigger_events[trigger_element] & CH_EVENT_BIT(trigger_event)))
+  if (!(trigger_events[trigger_element][trigger_event]))
     return FALSE;
 
   for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
@@ -7691,14 +8598,14 @@ static boolean CheckTriggeredElementChangeExt(int lx, int ly,
       struct ElementChangeInfo *change = &element_info[element].change_page[j];
 
       if (change->can_change &&
-         change->events & CH_EVENT_BIT(trigger_event) &&
+         change->has_event[trigger_event] &&
          change->trigger_side & trigger_side &&
          change->trigger_player & trigger_player &&
          change->trigger_page & trigger_page_bits &&
          IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
       {
 #if 0
-       if (!(change->events & CH_EVENT_BIT(trigger_event)))
+       if (!(change->has_event[trigger_event]))
          printf("::: !!! %d triggers %d: using wrong page %d [event %d]\n",
                 trigger_element-EL_CUSTOM_START+1, i+1, j, trigger_event);
 #endif
@@ -7726,7 +8633,7 @@ static boolean CheckTriggeredElementChangeExt(int lx, int ly,
       if (Feld[x][y] == element)
       {
        ChangeDelay[x][y] = 1;
-       ChangeEvent[x][y] = CH_EVENT_BIT(trigger_event);
+       ChangeEvent[x][y] = trigger_event;
        ChangeElement(x, y, page);
       }
     }
@@ -7777,7 +8684,7 @@ static boolean CheckElementChangeExt(int x, int y,
       struct ElementChangeInfo *change = &element_info[element].change_page[i];
 
       if (change->can_change &&
-         change->events & CH_EVENT_BIT(trigger_event) &&
+         change->has_event[trigger_event] &&
          change->trigger_side & trigger_side &&
          change->trigger_player & trigger_player)
       {
@@ -7815,7 +8722,7 @@ static boolean CheckElementChangeExt(int x, int y,
 #endif
 
   ChangeDelay[x][y] = 1;
-  ChangeEvent[x][y] = CH_EVENT_BIT(trigger_event);
+  ChangeEvent[x][y] = trigger_event;
   ChangeElement(x, y, trigger_page);
 
   return TRUE;
@@ -8165,10 +9072,49 @@ static void PlayerActions(struct PlayerInfo *player, byte player_action)
 }
 #endif
 
+void AdvanceFrameAndPlayerCounters(int player_nr)
+{
+  int i;
+
+  /* advance frame counters (global frame counter and time frame counter) */
+  FrameCounter++;
+  TimeFrames++;
+
+  /* advance player counters (counters for move delay, move animation etc.) */
+  for (i = 0; i < MAX_PLAYERS; i++)
+  {
+    boolean advance_player_counters = (player_nr == -1 || player_nr == i);
+    int move_frames =
+      MOVE_DELAY_NORMAL_SPEED /  stored_player[i].move_delay_value;
+
+    if (!advance_player_counters)      /* not all players may be affected */
+      continue;
+
+    stored_player[i].Frame += move_frames;
+
+    if (stored_player[i].MovPos != 0)
+      stored_player[i].StepFrame += move_frames;
+
+#if USE_NEW_MOVE_DELAY
+    if (stored_player[i].move_delay > 0)
+      stored_player[i].move_delay--;
+#endif
+
+#if USE_NEW_PUSH_DELAY
+    /* due to bugs in previous versions, counter must count up, not down */
+    if (stored_player[i].push_delay != -1)
+      stored_player[i].push_delay++;
+#endif
+
+    if (stored_player[i].drop_delay > 0)
+      stored_player[i].drop_delay--;
+  }
+}
+
 void GameActions()
 {
-  static unsigned long action_delay = 0;
-  unsigned long action_delay_value;
+  static unsigned long game_frame_delay = 0;
+  unsigned long game_frame_delay_value;
   int magic_wall_x = 0, magic_wall_y = 0;
   int i, x, y, element, graphic;
   byte *recorded_player_action;
@@ -8180,15 +9126,15 @@ void GameActions()
   if (game_status != GAME_MODE_PLAYING)
     return;
 
-  action_delay_value =
+  game_frame_delay_value =
     (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
 
   if (tape.playing && tape.warp_forward && !tape.pausing)
-    action_delay_value = 0;
+    game_frame_delay_value = 0;
 
   /* ---------- main game synchronization point ---------- */
 
-  WaitUntilDelayReached(&action_delay, action_delay_value);
+  WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
 
   if (network_playing && !network_player_action_received)
   {
@@ -8198,7 +9144,7 @@ void GameActions()
 #endif
     */
 
-#if defined(PLATFORM_UNIX)
+#if defined(NETWORK_AVALIABLE)
     /* last chance to get network player actions without main loop delay */
     HandleNetworking();
 #endif
@@ -8227,6 +9173,7 @@ void GameActions()
   recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
 
 #if 1
+  /* !!! CHECK THIS (tape.pausing is always FALSE here!) !!! */
   if (recorded_player_action == NULL && tape.pausing)
     return;
 #endif
@@ -8249,7 +9196,7 @@ void GameActions()
       stored_player[i].effective_action = stored_player[i].action;
   }
 
-#if defined(PLATFORM_UNIX)
+#if defined(NETWORK_AVALIABLE)
   if (network_playing)
     SendToServer_MovePlayer(summarized_player_action);
 #endif
@@ -8359,7 +9306,7 @@ void GameActions()
 #endif
 
 #if 1
-  /* for downwards compatibility, the following code emulates a fixed bug that
+  /* for backwards compatibility, the following code emulates a fixed bug that
      occured when pushing elements (causing elements that just made their last
      pushing step to already (if possible) make their first falling step in the
      same game frame, which is bad); this code is also needed to use the famous
@@ -8403,8 +9350,18 @@ void GameActions()
 
   for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
   {
-    Changed[x][y] = CE_BITMASK_DEFAULT;
-    ChangeEvent[x][y] = CE_BITMASK_DEFAULT;
+    Changed[x][y] = FALSE;
+    ChangeEvent[x][y] = -1;
+
+#if USE_NEW_BLOCK_STYLE
+    /* this must be handled before main playfield loop */
+    if (Feld[x][y] == EL_PLAYER_IS_LEAVING)
+    {
+      MovDelay[x][y]--;
+      if (MovDelay[x][y] <= 0)
+       RemoveField(x, y);
+    }
+#endif
 
 #if DEBUG
     if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
@@ -8856,7 +9813,9 @@ void GameActions()
           stored_player[0].StepFrame);
 #endif
 
-#if 1
+#if USE_NEW_MOVE_DELAY
+  AdvanceFrameAndPlayerCounters(-1);   /* advance counters for all players */
+#else
   FrameCounter++;
   TimeFrames++;
 
@@ -8870,6 +9829,11 @@ void GameActions()
     if (stored_player[i].MovPos != 0)
       stored_player[i].StepFrame += move_frames;
 
+#if USE_NEW_MOVE_DELAY
+    if (stored_player[i].move_delay > 0)
+      stored_player[i].move_delay--;
+#endif
+
     if (stored_player[i].drop_delay > 0)
       stored_player[i].drop_delay--;
   }
@@ -8883,6 +9847,12 @@ void GameActions()
     local_player->show_envelope = 0;
   }
 #endif
+
+#if USE_NEW_RANDOMIZE
+  /* use random number generator in every frame to make it less predictable */
+  if (game.engine_version >= VERSION_IDENT(3,1,1,0))
+    RND(1);
+#endif
 }
 
 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
@@ -8994,6 +9964,11 @@ static boolean canFallDown(struct PlayerInfo *player)
 
   return (IN_LEV_FIELD(jx, jy + 1) &&
          (IS_FREE(jx, jy + 1) ||
+#if USE_NEW_BLOCK_STYLE
+#if USE_GRAVITY_BUGFIX_OLD
+          Feld[jx][jy + 1] == EL_PLAYER_IS_LEAVING ||
+#endif
+#endif
           (Feld[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
          IS_WALKABLE_FROM(Feld[jx][jy], MV_DOWN) &&
          !IS_WALKABLE_INSIDE(Feld[jx][jy]));
@@ -9027,6 +10002,16 @@ static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
   int nexty = newy + dy;
 #endif
 
+#if 1
+  return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
+         IS_GRAVITY_REACHABLE(Feld[newx][newy]) &&
+#if 0
+         (!IS_SP_PORT(Feld[newx][newy]) || move_dir == MV_UP) &&
+#endif
+         (IS_DIGGABLE(Feld[newx][newy]) ||
+          IS_WALKABLE_FROM(Feld[newx][newy], opposite_dir) ||
+          canPassField(newx, newy, move_dir)));
+#else
 #if 1
   return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
          IS_GRAVITY_REACHABLE(Feld[newx][newy]) &&
@@ -9050,6 +10035,7 @@ static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
            (level.can_pass_to_walkable || IS_FREE(nextx, nexty)))));
 #endif
 #endif
+#endif
 }
 
 static void CheckGravityMovement(struct PlayerInfo *player)
@@ -9261,7 +10247,7 @@ boolean MovePlayerOneStep(struct PlayerInfo *player,
 
   /* check if DigField() has caused relocation of the player */
   if (player->jx != jx || player->jy != jy)
-    return MF_NO_ACTION;
+    return MF_NO_ACTION;       /* <-- !!! CHECK THIS [-> MF_ACTION ?] !!! */
 
   StorePlayer[jx][jy] = 0;
   player->last_jx = jx;
@@ -9286,7 +10272,7 @@ boolean MovePlayerOneStep(struct PlayerInfo *player,
 #if 0
   if (IS_CUSTOM_ELEMENT(Feld[jx][jy]))
   {
-    CheckTriggeredElementChangeBySide(jx, jy, Feld[jx][jy], CE_OTHER_GETS_LEFT,
+    CheckTriggeredElementChangeBySide(jx, jy, Feld[jx][jy], CE_PLAYER_LEAVES_X,
                                      leave_side);
     CheckElementChangeBySide(jx,jy, Feld[jx][jy],CE_LEFT_BY_PLAYER,leave_side);
   }
@@ -9294,7 +10280,7 @@ boolean MovePlayerOneStep(struct PlayerInfo *player,
   if (IS_CUSTOM_ELEMENT(Feld[new_jx][new_jy]))
   {
     CheckTriggeredElementChangeBySide(new_jx, new_jy, Feld[new_jx][new_jy],
-                                     CE_OTHER_GETS_ENTERED, enter_side);
+                                     CE_PLAYER_ENTERS_X, enter_side);
     CheckElementChangeBySide(new_jx, new_jy, Feld[new_jx][new_jy],
                             CE_ENTERED_BY_PLAYER, enter_side);
   }
@@ -9338,14 +10324,38 @@ boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
 #else
 
 #if 1
+
+#if 0
+  printf("::: %d <= %d < %d ?\n", player->move_delay, FrameCounter,
+        player->move_delay + player->move_delay_value);
+#endif
+
+#if USE_NEW_MOVE_DELAY
+  if (player->move_delay > 0)
+#else
   if (!FrameReached(&player->move_delay, player->move_delay_value))
+#endif
+  {
+#if 0
+    printf("::: can NOT move\n");
+#endif
+
     return FALSE;
+  }
 #else
   if (!FrameReached(&player->move_delay, player->move_delay_value) &&
       !(tape.playing && tape.file_version < FILE_VERSION_2_0))
     return FALSE;
 #endif
 
+#endif
+
+#if 0
+  printf("::: COULD move now\n");
+#endif
+
+#if USE_NEW_MOVE_DELAY
+  player->move_delay = -1;             /* set to "uninitialized" value */
 #endif
 
   /* store if player is automatically moved to next field */
@@ -9373,7 +10383,13 @@ boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
     {
       ScrollPlayer(player, SCROLL_GO_ON);
       ScrollScreen(NULL, SCROLL_GO_ON);
+
+#if USE_NEW_MOVE_DELAY
+      AdvanceFrameAndPlayerCounters(player->index_nr);
+#else
       FrameCounter++;
+#endif
+
       DrawAllPlayers();
       BackToFront();
     }
@@ -9477,6 +10493,10 @@ boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
 
   if (moved & MF_MOVING)
   {
+#if 0
+    printf("::: REALLY moves now\n");
+#endif
+
     if (old_jx != jx && old_jy == jy)
       player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
     else if (old_jx == jx && old_jy != jy)
@@ -9504,6 +10524,11 @@ boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
     if (game.engine_version < VERSION_IDENT(3,1,0,0))
 #endif
     {
+      int move_direction = player->MovDir;
+#if 1
+      int enter_side = MV_DIR_OPPOSITE(move_direction);
+      int leave_side = move_direction;
+#else
       static int trigger_sides[4][2] =
       {
        /* enter side           leave side */
@@ -9512,9 +10537,9 @@ boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
        { CH_SIDE_BOTTOM,       CH_SIDE_TOP     },      /* moving up    */
        { CH_SIDE_TOP,          CH_SIDE_BOTTOM  }       /* moving down  */
       };
-      int move_direction = player->MovDir;
       int enter_side = trigger_sides[MV_DIR_BIT(move_direction)][0];
       int leave_side = trigger_sides[MV_DIR_BIT(move_direction)][1];
+#endif
       int old_element = Feld[old_jx][old_jy];
       int new_element = Feld[jx][jy];
 
@@ -9526,7 +10551,7 @@ boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
                                   player->index_bit, leave_side);
 
       CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
-                                         CE_OTHER_GETS_LEFT,
+                                         CE_PLAYER_LEAVES_X,
                                          player->index_bit, leave_side);
 
       if (IS_CUSTOM_ELEMENT(new_element))
@@ -9534,7 +10559,7 @@ boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
                                   player->index_bit, enter_side);
 
       CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
-                                         CE_OTHER_GETS_ENTERED,
+                                         CE_PLAYER_ENTERS_X,
                                          player->index_bit, enter_side);
 #endif
 
@@ -9551,8 +10576,23 @@ boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
     player->last_move_dir = MV_NO_MOVING;
     */
     player->is_moving = FALSE;
+
+#if USE_NEW_MOVE_STYLE
+    /* player is ALLOWED to move, but CANNOT move (something blocks his way) */
+    /* ensure that the player is also allowed to move in the next frame */
+    /* (currently, the player is forced to wait eight frames before he can try
+       again!!!) */
+
+    if (game.engine_version >= VERSION_IDENT(3,1,1,0))
+      player->move_delay = 0;  /* allow direct movement in the next frame */
+#endif
   }
 
+#if USE_NEW_MOVE_DELAY
+  if (player->move_delay == -1)                /* not yet initialized by DigField() */
+    player->move_delay = player->move_delay_value;
+#endif
+
   if (game.engine_version < VERSION_IDENT(3,0,7,0))
   {
     TestIfHeroTouchesBadThing(jx, jy);
@@ -9571,16 +10611,82 @@ void ScrollPlayer(struct PlayerInfo *player, int mode)
   int last_jx = player->last_jx, last_jy = player->last_jy;
   int move_stepsize = TILEX / player->move_delay_value;
 
-  if (!player->active || !player->MovPos)
-    return;
+  if (!player->active || !player->MovPos)
+    return;
+
+  if (mode == SCROLL_INIT)
+  {
+    player->actual_frame_counter = FrameCounter;
+    player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
+
+#if 0
+    printf("::: %06d: %d,%d: %d (%d) [%d]\n",
+          FrameCounter,
+          last_jx, last_jy, Feld[last_jx][last_jy], EL_EXPLOSION,
+          player->block_delay);
+#endif
+
+#if USE_NEW_BLOCK_STYLE
+
+#if 0
+    if (player->block_delay <= 0)
+      printf("::: ALERT! block_delay == %d\n", player->block_delay);
+#endif
+
+    if ((player->block_last_field || player->block_delay_adjustment > 0) &&
+       Feld[last_jx][last_jy] == EL_EMPTY)
+    {
+      int last_field_block_delay = 0;  /* start with no blocking at all */
+      int block_delay_adjustment = player->block_delay_adjustment;
+
+      /* if player blocks last field, add delay for exactly one move */
+      if (player->block_last_field)
+      {
+       last_field_block_delay += player->move_delay_value;
+
+#if USE_GRAVITY_BUGFIX_NEW
+       /* when blocking enabled, prevent moving up despite gravity */
+       if (game.gravity && player->MovDir == MV_UP)
+         block_delay_adjustment = -1;
+#endif
+      }
+
+      /* add block delay adjustment (also possible when not blocking) */
+      last_field_block_delay += block_delay_adjustment;
+
+#if 0
+#if USE_BLOCK_DELAY_BUGFIX
+      /* when blocking enabled, correct block delay for fast movement */
+      if (player->block_last_field &&
+         player->move_delay_value < MOVE_DELAY_NORMAL_SPEED)
+       last_field_block_delay =
+         player->move_delay_value + player->block_delay_adjustment;
+#endif
+#endif
 
-  if (mode == SCROLL_INIT)
-  {
-    player->actual_frame_counter = FrameCounter;
-    player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
+#if 0
+#if USE_GRAVITY_BUGFIX_NEW
+      /* when blocking enabled, correct block delay for gravity movement */
+      if (player->block_last_field &&
+         game.gravity && player->MovDir == MV_UP)
+       last_field_block_delay = player->move_delay_value - 1;
+#endif
+#endif
 
+      Feld[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
+      MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
+    }
+#else
+#if USE_NEW_MOVE_STYLE
+    if ((game.engine_version < VERSION_IDENT(3,1,1,0) ||
+        player->block_last_field) &&
+       Feld[last_jx][last_jy] == EL_EMPTY)
+      Feld[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
+#else
     if (Feld[last_jx][last_jy] == EL_EMPTY)
       Feld[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
+#endif
+#endif
 
 #if 0
     DrawPlayer(player);
@@ -9594,9 +10700,16 @@ void ScrollPlayer(struct PlayerInfo *player, int mode)
   player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
   player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
 
+#if USE_NEW_BLOCK_STYLE
+#else
   if (!player->block_last_field &&
       Feld[last_jx][last_jy] == EL_PLAYER_IS_LEAVING)
+#if 1
+    RemoveField(last_jx, last_jy);
+#else
     Feld[last_jx][last_jy] = EL_EMPTY;
+#endif
+#endif
 
   /* before DrawPlayer() to draw correct player graphic for this case */
   if (player->MovPos == 0)
@@ -9633,9 +10746,16 @@ void ScrollPlayer(struct PlayerInfo *player, int mode)
     }
 #endif
 
+#if USE_NEW_BLOCK_STYLE
+#else
     if (player->block_last_field &&
        Feld[last_jx][last_jy] == EL_PLAYER_IS_LEAVING)
+#if 1
+      RemoveField(last_jx, last_jy);
+#else
       Feld[last_jx][last_jy] = EL_EMPTY;
+#endif
+#endif
 
     player->last_jx = jx;
     player->last_jy = jy;
@@ -9659,6 +10779,11 @@ void ScrollPlayer(struct PlayerInfo *player, int mode)
     if (game.engine_version >= VERSION_IDENT(3,1,0,0))
 #endif
     {
+      int move_direction = player->MovDir;
+#if 1
+      int enter_side = MV_DIR_OPPOSITE(move_direction);
+      int leave_side = move_direction;
+#else
       static int trigger_sides[4][2] =
       {
        /* enter side           leave side */
@@ -9667,9 +10792,9 @@ void ScrollPlayer(struct PlayerInfo *player, int mode)
        { CH_SIDE_BOTTOM,       CH_SIDE_TOP     },      /* moving up    */
        { CH_SIDE_TOP,          CH_SIDE_BOTTOM  }       /* moving down  */
       };
-      int move_direction = player->MovDir;
       int enter_side = trigger_sides[MV_DIR_BIT(move_direction)][0];
       int leave_side = trigger_sides[MV_DIR_BIT(move_direction)][1];
+#endif
       int old_jx = last_jx;
       int old_jy = last_jy;
       int old_element = Feld[old_jx][old_jy];
@@ -9683,7 +10808,7 @@ void ScrollPlayer(struct PlayerInfo *player, int mode)
                                   player->index_bit, leave_side);
 
       CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
-                                         CE_OTHER_GETS_LEFT,
+                                         CE_PLAYER_LEAVES_X,
                                          player->index_bit, leave_side);
 
       if (IS_CUSTOM_ELEMENT(new_element))
@@ -9691,7 +10816,7 @@ void ScrollPlayer(struct PlayerInfo *player, int mode)
                                   player->index_bit, enter_side);
 
       CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
-                                         CE_OTHER_GETS_ENTERED,
+                                         CE_PLAYER_ENTERS_X,
                                          player->index_bit, enter_side);
 #endif
 
@@ -9703,7 +10828,12 @@ void ScrollPlayer(struct PlayerInfo *player, int mode)
       TestIfHeroTouchesBadThing(jx, jy);
       TestIfPlayerTouchesCustomElement(jx, jy);
 #if 1
-      TestIfElementTouchesCustomElement(jx, jy);       /* for empty space */
+#if 1
+      /* needed because pushed element has not yet reached its destination,
+        so it would trigger a change event at its previous field location */
+      if (!player->is_pushing)
+#endif
+       TestIfElementTouchesCustomElement(jx, jy);      /* for empty space */
 #endif
 
       if (!player->active)
@@ -9823,11 +10953,11 @@ void TestIfPlayerTouchesCustomElement(int x, int y)
       CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
                                 player->index_bit, border_side);
       CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
-                                         CE_OTHER_GETS_TOUCHED,
+                                         CE_PLAYER_TOUCHES_X,
                                          player->index_bit, border_side);
 #else
       CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
-                                         CE_OTHER_GETS_TOUCHED,
+                                         CE_PLAYER_TOUCHES_X,
                                          player->index_bit, border_side);
       CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
                                 player->index_bit, border_side);
@@ -9848,11 +10978,11 @@ void TestIfPlayerTouchesCustomElement(int x, int y)
       CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
                                 player->index_bit, center_side);
       CheckTriggeredElementChangeByPlayer(x, y, center_element,
-                                         CE_OTHER_GETS_TOUCHED,
+                                         CE_PLAYER_TOUCHES_X,
                                          player->index_bit, center_side);
 #else
       CheckTriggeredElementChangeByPlayer(x, y, center_element,
-                                         CE_OTHER_GETS_TOUCHED,
+                                         CE_PLAYER_TOUCHES_X,
                                          player->index_bit, center_side);
       CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
                                 player->index_bit, center_side);
@@ -9890,7 +11020,7 @@ void TestIfElementTouchesCustomElement(int x, int y)
   boolean change_center_element = FALSE;
   int center_element_change_page = 0;
   int center_element = Feld[x][y];     /* should always be non-moving! */
-  int border_trigger_element;
+  int border_trigger_element = EL_UNDEFINED;
   int i, j;
 
   for (i = 0; i < NUM_DIRECTIONS; i++)
@@ -9915,7 +11045,7 @@ void TestIfElementTouchesCustomElement(int x, int y)
 
     /* check for change of center element (but change it only once) */
     if (IS_CUSTOM_ELEMENT(center_element) &&
-       HAS_ANY_CHANGE_EVENT(center_element, CE_OTHER_IS_TOUCHING) &&
+       HAS_ANY_CHANGE_EVENT(center_element, CE_TOUCHING_X) &&
        !change_center_element)
     {
       for (j = 0; j < element_info[center_element].num_change_pages; j++)
@@ -9924,7 +11054,7 @@ void TestIfElementTouchesCustomElement(int x, int y)
          &element_info[center_element].change_page[j];
 
        if (change->can_change &&
-           change->events & CH_EVENT_BIT(CE_OTHER_IS_TOUCHING) &&
+           change->has_event[CE_TOUCHING_X] &&
            change->trigger_side & border_side &&
 #if 1
            IS_EQUAL_OR_IN_GROUP(border_element, change->trigger_element)
@@ -9944,7 +11074,7 @@ void TestIfElementTouchesCustomElement(int x, int y)
 
     /* check for change of border element */
     if (IS_CUSTOM_ELEMENT(border_element) &&
-       HAS_ANY_CHANGE_EVENT(border_element, CE_OTHER_IS_TOUCHING))
+       HAS_ANY_CHANGE_EVENT(border_element, CE_TOUCHING_X))
     {
       for (j = 0; j < element_info[border_element].num_change_pages; j++)
       {
@@ -9952,7 +11082,7 @@ void TestIfElementTouchesCustomElement(int x, int y)
          &element_info[border_element].change_page[j];
 
        if (change->can_change &&
-           change->events & CH_EVENT_BIT(CE_OTHER_IS_TOUCHING) &&
+           change->has_event[CE_TOUCHING_X] &&
            change->trigger_side & center_side &&
 #if 1
            IS_EQUAL_OR_IN_GROUP(center_element, change->trigger_element)
@@ -9966,7 +11096,7 @@ void TestIfElementTouchesCustomElement(int x, int y)
 #endif
 
          CheckElementChangeByPage(xx, yy, border_element, center_element,
-                                  CE_OTHER_IS_TOUCHING, j);
+                                  CE_TOUCHING_X, j);
          break;
        }
       }
@@ -9980,7 +11110,7 @@ void TestIfElementTouchesCustomElement(int x, int y)
 #endif
 
     CheckElementChangeByPage(x, y, center_element, border_trigger_element,
-                            CE_OTHER_IS_TOUCHING, center_element_change_page);
+                            CE_TOUCHING_X, center_element_change_page);
   }
 }
 
@@ -10010,8 +11140,11 @@ void TestIfElementHitsCustomElement(int x, int y, int direction)
   touched_element = (IN_LEV_FIELD(hitx, hity) ?
                     MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
 
+#if !USE_HITTING_SOMETHING_BUGFIX
+  /* "hitting something" is also true when hitting the playfield border */
   CheckElementChangeBySide(x, y, hitting_element, touched_element,
                           CE_HITTING_SOMETHING, direction);
+#endif
 
   if (IN_LEV_FIELD(hitx, hity))
   {
@@ -10033,11 +11166,13 @@ void TestIfElementHitsCustomElement(int x, int y, int direction)
     {
       int i;
 
+#if !USE_HIT_BY_SOMETHING_BUGFIX
       CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
                               CE_HIT_BY_SOMETHING, opposite_direction);
+#endif
 
       if (IS_CUSTOM_ELEMENT(hitting_element) &&
-         HAS_ANY_CHANGE_EVENT(hitting_element, CE_OTHER_IS_HITTING))
+         HAS_ANY_CHANGE_EVENT(hitting_element, CE_HITTING_X))
       {
        for (i = 0; i < element_info[hitting_element].num_change_pages; i++)
        {
@@ -10045,7 +11180,7 @@ void TestIfElementHitsCustomElement(int x, int y, int direction)
            &element_info[hitting_element].change_page[i];
 
          if (change->can_change &&
-             change->events & CH_EVENT_BIT(CE_OTHER_IS_HITTING) &&
+             change->has_event[CE_HITTING_X] &&
              change->trigger_side & touched_side &&
          
 #if 1
@@ -10056,14 +11191,14 @@ void TestIfElementHitsCustomElement(int x, int y, int direction)
              )
          {
            CheckElementChangeByPage(x, y, hitting_element, touched_element,
-                                    CE_OTHER_IS_HITTING, i);
+                                    CE_HITTING_X, i);
            break;
          }
        }
       }
 
       if (IS_CUSTOM_ELEMENT(touched_element) &&
-         HAS_ANY_CHANGE_EVENT(touched_element, CE_OTHER_GETS_HIT))
+         HAS_ANY_CHANGE_EVENT(touched_element, CE_HIT_BY_X))
       {
        for (i = 0; i < element_info[touched_element].num_change_pages; i++)
        {
@@ -10071,7 +11206,7 @@ void TestIfElementHitsCustomElement(int x, int y, int direction)
            &element_info[touched_element].change_page[i];
 
          if (change->can_change &&
-             change->events & CH_EVENT_BIT(CE_OTHER_GETS_HIT) &&
+             change->has_event[CE_HIT_BY_X] &&
              change->trigger_side & hitting_side &&
 #if 1
              IS_EQUAL_OR_IN_GROUP(hitting_element, change->trigger_element)
@@ -10081,13 +11216,24 @@ void TestIfElementHitsCustomElement(int x, int y, int direction)
              )
          {
            CheckElementChangeByPage(hitx, hity, touched_element,
-                                    hitting_element, CE_OTHER_GETS_HIT, i);
+                                    hitting_element, CE_HIT_BY_X, i);
            break;
          }
        }
       }
+
+#if USE_HIT_BY_SOMETHING_BUGFIX
+      CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
+                              CE_HIT_BY_SOMETHING, opposite_direction);
+#endif
     }
   }
+
+#if USE_HITTING_SOMETHING_BUGFIX
+  /* "hitting something" is also true when hitting the playfield border */
+  CheckElementChangeBySide(x, y, hitting_element, touched_element,
+                          CE_HITTING_SOMETHING, direction);
+#endif
 }
 
 #if 0
@@ -10152,7 +11298,7 @@ void TestIfElementSmashesCustomElement(int x, int y, int direction)
            &element_info[hitting_element].change_page[i];
 
          if (change->can_change &&
-             change->events & CH_EVENT_BIT(CE_OTHER_IS_SMASHING) &&
+             change->has_event[CE_OTHER_IS_SMASHING] &&
              change->trigger_side & touched_side &&
          
 #if 1
@@ -10178,7 +11324,7 @@ void TestIfElementSmashesCustomElement(int x, int y, int direction)
            &element_info[touched_element].change_page[i];
 
          if (change->can_change &&
-             change->events & CH_EVENT_BIT(CE_OTHER_GETS_SMASHED) &&
+             change->has_event[CE_OTHER_GETS_SMASHED] &&
              change->trigger_side & hitting_side &&
 #if 1
              IS_EQUAL_OR_IN_GROUP(hitting_element, change->trigger_element)
@@ -10549,16 +11695,11 @@ int DigField(struct PlayerInfo *player,
             int oldx, int oldy, int x, int y,
             int real_dx, int real_dy, int mode)
 {
-  static int trigger_sides[4] =
-  {
-    CH_SIDE_RIGHT,     /* moving left  */
-    CH_SIDE_LEFT,      /* moving right */
-    CH_SIDE_BOTTOM,    /* moving up    */
-    CH_SIDE_TOP,       /* moving down  */
-  };
 #if 0
   boolean use_spring_bug = (game.engine_version < VERSION_IDENT(2,2,0,0));
 #endif
+  boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
+  boolean player_was_pushing = player->is_pushing;
   int jx = oldx, jy = oldy;
   int dx = x - jx, dy = y - jy;
   int nextx = x + dx, nexty = y + dy;
@@ -10567,25 +11708,43 @@ int DigField(struct PlayerInfo *player,
                        dy == -1 ? MV_UP :
                        dy == +1 ? MV_DOWN : MV_NO_MOVING);
   int opposite_direction = MV_DIR_OPPOSITE(move_direction);
+#if 1
+  int dig_side = MV_DIR_OPPOSITE(move_direction);
+#else
+  static int trigger_sides[4] =
+  {
+    CH_SIDE_RIGHT,     /* moving left  */
+    CH_SIDE_LEFT,      /* moving right */
+    CH_SIDE_BOTTOM,    /* moving up    */
+    CH_SIDE_TOP,       /* moving down  */
+  };
   int dig_side = trigger_sides[MV_DIR_BIT(move_direction)];
+#endif
   int old_element = Feld[jx][jy];
   int element;
 
-  if (player->MovPos == 0)
+  if (is_player)               /* function can also be called by EL_PENGUIN */
   {
-    player->is_digging = FALSE;
-    player->is_collecting = FALSE;
-  }
+    if (player->MovPos == 0)
+    {
+      player->is_digging = FALSE;
+      player->is_collecting = FALSE;
+    }
 
-  if (player->MovPos == 0)     /* last pushing move finished */
-    player->is_pushing = FALSE;
+    if (player->MovPos == 0)   /* last pushing move finished */
+      player->is_pushing = FALSE;
 
-  if (mode == DF_NO_PUSH)      /* player just stopped pushing */
-  {
-    player->is_switching = FALSE;
-    player->push_delay = 0;
+    if (mode == DF_NO_PUSH)    /* player just stopped pushing */
+    {
+      player->is_switching = FALSE;
+#if USE_NEW_PUSH_DELAY
+      player->push_delay = -1;
+#else
+      player->push_delay = 0;
+#endif
 
-    return MF_NO_ACTION;
+      return MF_NO_ACTION;
+    }
   }
 
   if (IS_MOVING(x, y) || IS_PLAYER(x, y))
@@ -10633,6 +11792,13 @@ int DigField(struct PlayerInfo *player,
 
   if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
     old_element = Back[jx][jy];
+#if USE_BACK_WALKABLE_BUGFIX
+
+  /* in case of element dropped at player position, check background */
+  else if (Back[jx][jy] != EL_EMPTY &&
+          game.engine_version >= VERSION_IDENT(2,2,0,0))
+    old_element = Back[jx][jy];
+#endif
 
 #endif
 
@@ -10644,12 +11810,15 @@ int DigField(struct PlayerInfo *player,
 
   element = Feld[x][y];
 
+  if (!is_player && !IS_COLLECTIBLE(element))  /* penguin cannot collect it */
+    return MF_NO_ACTION;
+
   if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
       game.engine_version >= VERSION_IDENT(2,2,0,0))
     return MF_NO_ACTION;
 
 #if 1
-  if (game.gravity && !player->is_auto_moving &&
+  if (game.gravity && is_player && !player->is_auto_moving &&
       canFallDown(player) && move_direction != MV_DOWN &&
       !canMoveToValidFieldWithGravity(jx, jy, move_direction))
     return MF_NO_ACTION;       /* player cannot walk here due to gravity */
@@ -10802,14 +11971,14 @@ int DigField(struct PlayerInfo *player,
          return MF_NO_ACTION;  /* player cannot walk here due to gravity */
 #endif
 
-       if (IS_GATE(element))
+       if (IS_RND_GATE(element))
        {
-         if (!player->key[element - EL_GATE_1])
+         if (!player->key[RND_GATE_NR(element)])
            return MF_NO_ACTION;
        }
-       else if (IS_GATE_GRAY(element))
+       else if (IS_RND_GATE_GRAY(element))
        {
-         if (!player->key[element - EL_GATE_1_GRAY])
+         if (!player->key[RND_GATE_GRAY_NR(element)])
            return MF_NO_ACTION;
        }
        else if (element == EL_EXIT_OPEN ||
@@ -10872,12 +12041,12 @@ int DigField(struct PlayerInfo *player,
 
        if (IS_EM_GATE(element))
        {
-         if (!player->key[element - EL_EM_GATE_1])
+         if (!player->key[EM_GATE_NR(element)])
            return MF_NO_ACTION;
        }
        else if (IS_EM_GATE_GRAY(element))
        {
-         if (!player->key[element - EL_EM_GATE_1_GRAY])
+         if (!player->key[EM_GATE_GRAY_NR(element)])
            return MF_NO_ACTION;
        }
        else if (IS_SP_PORT(element))
@@ -10887,6 +12056,16 @@ int DigField(struct PlayerInfo *player,
              element == EL_SP_GRAVITY_PORT_UP ||
              element == EL_SP_GRAVITY_PORT_DOWN)
            game.gravity = !game.gravity;
+         else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
+                  element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
+                  element == EL_SP_GRAVITY_ON_PORT_UP ||
+                  element == EL_SP_GRAVITY_ON_PORT_DOWN)
+           game.gravity = TRUE;
+         else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
+                  element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
+                  element == EL_SP_GRAVITY_OFF_PORT_UP ||
+                  element == EL_SP_GRAVITY_OFF_PORT_DOWN)
+           game.gravity = FALSE;
        }
 
        /* automatically move to the next field with double speed */
@@ -10925,7 +12104,7 @@ int DigField(struct PlayerInfo *player,
 
        PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
 
-       CheckTriggeredElementChangeByPlayer(x, y, element,CE_OTHER_GETS_DIGGED,
+       CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
                                            player->index_bit, dig_side);
 
 #if 1
@@ -10939,14 +12118,16 @@ int DigField(struct PlayerInfo *player,
       {
        RemoveField(x, y);
 
-       if (mode != DF_SNAP)
+       if (is_player && mode != DF_SNAP)
        {
          GfxElement[x][y] = element;
          player->is_collecting = TRUE;
        }
 
        if (element == EL_SPEED_PILL)
+       {
          player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
+       }
        else if (element == EL_EXTRA_TIME && level.time > 0)
        {
          TimeLeft += 10;
@@ -10978,15 +12159,11 @@ int DigField(struct PlayerInfo *player,
        {
          player->dynabomb_xl = TRUE;
        }
-       else if ((element >= EL_KEY_1 && element <= EL_KEY_4) ||
-                (element >= EL_EM_KEY_1 && element <= EL_EM_KEY_4))
+       else if (IS_KEY(element))
        {
-         int key_nr = (element >= EL_KEY_1 && element <= EL_KEY_4 ?
-                       element - EL_KEY_1 : element - EL_EM_KEY_1);
+         player->key[KEY_NR(element)] = TRUE;
 
-         player->key[key_nr] = TRUE;
-
-         DrawGameValue_Keys(player);
+         DrawGameValue_Keys(player->key);
 
          redraw_mask |= REDRAW_DOOR_1;
        }
@@ -11025,9 +12202,10 @@ int DigField(struct PlayerInfo *player,
        RaiseScoreElement(element);
        PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
 
-       CheckTriggeredElementChangeByPlayer(x, y, element,
-                                           CE_OTHER_GETS_COLLECTED,
-                                           player->index_bit, dig_side);
+       if (is_player)
+         CheckTriggeredElementChangeByPlayer(x, y, element,
+                                             CE_PLAYER_COLLECTS_X,
+                                             player->index_bit, dig_side);
 
 #if 1
        if (mode == DF_SNAP)
@@ -11074,11 +12252,25 @@ int DigField(struct PlayerInfo *player,
 #endif
 
 #if 1
-       if (game.engine_version >= VERSION_IDENT(3,0,7,1))
+
+#if 1
+       if (game.engine_version >= VERSION_IDENT(3,1,0,0))
+       {
+         if (player->push_delay_value == -1 || !player_was_pushing)
+           player->push_delay_value = GET_NEW_PUSH_DELAY(element);
+       }
+       else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
        {
          if (player->push_delay_value == -1)
            player->push_delay_value = GET_NEW_PUSH_DELAY(element);
        }
+#else
+       if (game.engine_version >= VERSION_IDENT(3,0,7,1))
+       {
+         if (player->push_delay_value == -1 || !player_was_pushing)
+           player->push_delay_value = GET_NEW_PUSH_DELAY(element);
+       }
+#endif
        else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
        {
          if (!player->is_pushing)
@@ -11098,9 +12290,12 @@ int DigField(struct PlayerInfo *player,
 #endif
 
 #if 0
-       printf("::: push delay: %ld [%d, %d] [%d]\n",
-              player->push_delay_value, FrameCounter, game.engine_version,
-              player->is_pushing);
+       printf("::: push delay: %ld -> %ld [%d, %d] [%d / %d] [%d '%s': %d]\n",
+              player->push_delay, player->push_delay_value,
+              FrameCounter, game.engine_version,
+              player_was_pushing, player->is_pushing,
+              element, element_info[element].token_name,
+              GET_NEW_PUSH_DELAY(element));
 #endif
 
        player->is_pushing = TRUE;
@@ -11114,16 +12309,56 @@ int DigField(struct PlayerInfo *player,
        if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
          return MF_NO_ACTION;
 
+#if USE_NEW_PUSH_DELAY
+
+#if 0
+       if ( (player->push_delay == -1) != (player->push_delay2 == 0) )
+         printf("::: ALERT: %d, %d [%d / %d]\n",
+                player->push_delay, player->push_delay2,
+                FrameCounter, FrameCounter / 50);
+#endif
+
+       if (player->push_delay == -1)   /* new pushing; restart delay */
+         player->push_delay = 0;
+#else
        if (player->push_delay == 0)    /* new pushing; restart delay */
          player->push_delay = FrameCounter;
+#endif
+
+#if USE_NEW_PUSH_DELAY
+#if 0
+       if ( (player->push_delay > 0) != (!xxx_fr) )
+         printf("::: PUSH BUG! %d, (%d -> %d) %d [%d / %d]\n",
+                player->push_delay,
+                xxx_pdv2, player->push_delay2, player->push_delay_value,
+                FrameCounter, FrameCounter / 50);
+#endif
+
+#if 0
+       if (player->push_delay > 0 &&
+           !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
+           element != EL_SPRING && element != EL_BALLOON)
+#else
+       /* !!! */
+       if (player->push_delay < player->push_delay_value &&
+           !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
+           element != EL_SPRING && element != EL_BALLOON)
+#endif
 
+#else
        if (!FrameReached(&player->push_delay, player->push_delay_value) &&
            !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
            element != EL_SPRING && element != EL_BALLOON)
+#endif
        {
          /* make sure that there is no move delay before next try to push */
+#if USE_NEW_MOVE_DELAY
+         if (game.engine_version >= VERSION_IDENT(3,0,7,1))
+           player->move_delay = 0;
+#else
          if (game.engine_version >= VERSION_IDENT(3,0,7,1))
            player->move_delay = INITIAL_MOVE_DELAY_OFF;
+#endif
 
          return MF_NO_ACTION;
        }
@@ -11183,22 +12418,40 @@ int DigField(struct PlayerInfo *player,
        else
          player->push_delay_value = -1;        /* get new value later */
 
+#if USE_PUSH_BUGFIX
+       /* now: check for element change _after_ element has been pushed! */
+#if 1
+       if (game.use_change_when_pushing_bug)
+#else
+       if (game.engine_version < VERSION_IDENT(3,1,0,0))
+#endif
+       {
+         CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
+                                    player->index_bit, dig_side);
+         CheckTriggeredElementChangeByPlayer(x,y, element, CE_PLAYER_PUSHES_X,
+                                             player->index_bit, dig_side);
+       }
+
+#else
+
 #if 1
        /* check for element change _after_ element has been pushed! */
 #else
 
 #if 1
-      /* !!! TEST ONLY !!! */
+       /* !!! TEST ONLY !!! */
        CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
                                   player->index_bit, dig_side);
-       CheckTriggeredElementChangeByPlayer(x, y, element,CE_OTHER_GETS_PUSHED,
+       CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
                                            player->index_bit, dig_side);
 #else
-       CheckTriggeredElementChangeByPlayer(x, y, element,CE_OTHER_GETS_PUSHED,
+       CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
                                            player->index_bit, dig_side);
        CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
                                   player->index_bit, dig_side);
 #endif
+#endif
+
 #endif
 
        break;
@@ -11206,7 +12459,13 @@ int DigField(struct PlayerInfo *player,
       else if (IS_SWITCHABLE(element))
       {
        if (PLAYER_SWITCHING(player, x, y))
+       {
+         CheckTriggeredElementChangeByPlayer(x,y, element,
+                                             CE_PLAYER_PRESSES_X,
+                                             player->index_bit, dig_side);
+
          return MF_ACTION;
+       }
 
        player->is_switching = TRUE;
        player->switch_x = x;
@@ -11278,6 +12537,7 @@ int DigField(struct PlayerInfo *player,
          Feld[x][y] = EL_LAMP_ACTIVE;
          local_player->lights_still_needed--;
 
+         ResetGfxAnimation(x, y);
          DrawLevelField(x, y);
        }
        else if (element == EL_TIME_ORB_FULL)
@@ -11286,6 +12546,7 @@ int DigField(struct PlayerInfo *player,
          TimeLeft += 10;
          DrawGameValue_Time(TimeLeft);
 
+         ResetGfxAnimation(x, y);
          DrawLevelField(x, y);
 
 #if 0
@@ -11293,6 +12554,13 @@ int DigField(struct PlayerInfo *player,
 #endif
        }
 
+       CheckTriggeredElementChangeByPlayer(x, y, element,
+                                           CE_SWITCH_OF_X,
+                                           player->index_bit, dig_side);
+
+       CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
+                                           player->index_bit, dig_side);
+
        return MF_ACTION;
       }
       else
@@ -11308,11 +12576,11 @@ int DigField(struct PlayerInfo *player,
          CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
                                     player->index_bit, dig_side);
          CheckTriggeredElementChangeByPlayer(x, y, element,
-                                             CE_OTHER_IS_SWITCHING,
+                                             CE_SWITCH_OF_X,
                                              player->index_bit, dig_side);
 #else
          CheckTriggeredElementChangeByPlayer(x, y, element,
-                                             CE_OTHER_IS_SWITCHING,
+                                             CE_SWITCH_OF_X,
                                              player->index_bit, dig_side);
          CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
                                     player->index_bit, dig_side);
@@ -11323,10 +12591,10 @@ int DigField(struct PlayerInfo *player,
        /* !!! TEST ONLY !!! (this breaks "machine", level 000) */
        CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
                                   player->index_bit, dig_side);
-       CheckTriggeredElementChangeByPlayer(x,y, element,CE_OTHER_GETS_PRESSED,
+       CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
                                            player->index_bit, dig_side);
 #else
-       CheckTriggeredElementChangeByPlayer(x,y, element,CE_OTHER_GETS_PRESSED,
+       CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
                                            player->index_bit, dig_side);
        CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
                                   player->index_bit, dig_side);
@@ -11336,10 +12604,19 @@ int DigField(struct PlayerInfo *player,
       return MF_NO_ACTION;
   }
 
+#if USE_NEW_PUSH_DELAY
+  player->push_delay = -1;
+#else
   player->push_delay = 0;
+#endif
 
-  if (Feld[x][y] != element)           /* really digged/collected something */
-    player->is_collecting = !player->is_digging;
+#if USE_PENGUIN_COLLECT_BUGFIX
+  if (is_player)               /* function can also be called by EL_PENGUIN */
+#endif
+  {
+    if (Feld[x][y] != element)         /* really digged/collected something */
+      player->is_collecting = !player->is_digging;
+  }
 
   return MF_MOVING;
 }
@@ -11430,6 +12707,12 @@ boolean SnapField(struct PlayerInfo *player, int dx, int dy)
 
 boolean DropElement(struct PlayerInfo *player)
 {
+  int old_element, new_element;
+  int dropx = player->jx, dropy = player->jy;
+  int drop_direction = player->MovDir;
+#if 1
+  int drop_side = drop_direction;
+#else
   static int trigger_sides[4] =
   {
     CH_SIDE_LEFT,      /* dropping left  */
@@ -11437,10 +12720,8 @@ boolean DropElement(struct PlayerInfo *player)
     CH_SIDE_TOP,       /* dropping up    */
     CH_SIDE_BOTTOM,    /* dropping down  */
   };
-  int old_element, new_element;
-  int dropx = player->jx, dropy = player->jy;
-  int drop_direction = player->MovDir;
   int drop_side = trigger_sides[MV_DIR_BIT(drop_direction)];
+#endif
   int drop_element = (player->inventory_size > 0 ?
                      player->inventory_element[player->inventory_size - 1] :
                      player->inventory_infinite_element != EL_UNDEFINED ?
@@ -11449,6 +12730,15 @@ boolean DropElement(struct PlayerInfo *player)
                      EL_DYNABOMB_PLAYER_1_ACTIVE + player->index_nr :
                      EL_UNDEFINED);
 
+#if USE_DROP_BUGFIX
+  /* do not drop an element on top of another element; when holding drop key
+     pressed without moving, dropped element must move away before the next
+     element can be dropped (this is especially important if the next element
+     is dynamite, which can be placed on background for historical reasons) */
+  if (PLAYER_DROPPING(player, dropx, dropy) && Feld[dropx][dropy] != EL_EMPTY)
+    return MF_ACTION;
+#endif
+
   if (IS_THROWABLE(drop_element))
   {
     dropx += GET_DX_FROM_DIR(drop_direction);
@@ -11526,7 +12816,7 @@ boolean DropElement(struct PlayerInfo *player)
 
 #if 1
     /* needed if previous element just changed to "empty" in the last frame */
-    Changed[dropx][dropy] = 0;         /* allow another change */
+    Changed[dropx][dropy] = FALSE;             /* allow another change */
 #endif
 
 #if 1
@@ -11534,11 +12824,11 @@ boolean DropElement(struct PlayerInfo *player)
     CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
                               player->index_bit, drop_side);
     CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
-                                       CE_OTHER_GETS_DROPPED,
+                                       CE_PLAYER_DROPS_X,
                                        player->index_bit, drop_side);
 #else
     CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
-                                       CE_OTHER_GETS_DROPPED,
+                                       CE_PLAYER_DROPS_X,
                                        player->index_bit, drop_side);
     CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
                               player->index_bit, drop_side);
@@ -11596,11 +12886,11 @@ boolean DropElement(struct PlayerInfo *player)
     nexty = dropy + GET_DY_FROM_DIR(move_direction);
 
 #if 1
-      Changed[dropx][dropy] = 0;       /* allow another change */
-      CheckCollision[dropx][dropy] = 2;
+    Changed[dropx][dropy] = FALSE;             /* allow another change */
+    CheckCollision[dropx][dropy] = 2;
 #else
 
-    if (IN_LEV_FIELD(nextx, nexty) && IS_FREE(nextx, nexty))
+    if (IN_LEV_FIELD_AND_IS_FREE(nextx, nexty))
     {
 #if 0
       WasJustMoving[dropx][dropy] = 3;
@@ -11611,10 +12901,11 @@ boolean DropElement(struct PlayerInfo *player)
 #endif
 #endif
     }
-#if 1
+#if 0
+    /* !!! commented out from 3.1.0-4 to 3.1.0-5 !!! */
     else
     {
-      Changed[dropx][dropy] = 0;       /* allow another change */
+      Changed[dropx][dropy] = FALSE;   /* allow another change */
 
 #if 1
       TestIfElementHitsCustomElement(dropx, dropy, move_direction);
@@ -11644,6 +12935,10 @@ boolean DropElement(struct PlayerInfo *player)
 
   player->is_dropping = TRUE;
 
+#if USE_DROP_BUGFIX
+  player->drop_x = dropx;
+  player->drop_y = dropy;
+#endif
 
   return TRUE;
 }
@@ -11766,6 +13061,168 @@ static void PlayLevelMusic()
     PlayMusic(MAP_NOCONF_MUSIC(level_nr));     /* from music dir */
 }
 
+void PlayLevelSound_EM(int x, int y, int element_em, int sample)
+{
+  int element = (element_em > -1 ? map_element_EM_to_RND(element_em) : 0);
+
+#if 0
+  if (sample == SAMPLE_bug)
+    printf("::: PlayLevelSound_EM: %d, %d: %d\n", x, y, sample);
+#endif
+
+  switch (sample)
+  {
+    case SAMPLE_blank:
+      PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
+      break;
+
+    case SAMPLE_roll:
+      PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
+      break;
+
+    case SAMPLE_stone:
+      PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
+      break;
+
+    case SAMPLE_nut:
+      PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
+      break;
+
+    case SAMPLE_crack:
+      PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
+      break;
+
+    case SAMPLE_bug:
+      PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
+      break;
+
+    case SAMPLE_tank:
+      PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
+      break;
+
+    case SAMPLE_android_clone:
+      PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
+      break;
+
+    case SAMPLE_android_move:
+      PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
+      break;
+
+    case SAMPLE_spring:
+      PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
+      break;
+
+    case SAMPLE_slurp:
+      PlayLevelSoundElementAction(x, y, element, ACTION_SLURPED_BY_SPRING);
+      break;
+
+    case SAMPLE_eater:
+      PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
+      break;
+
+    case SAMPLE_eater_eat:
+      PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
+      break;
+
+    case SAMPLE_alien:
+      PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
+      break;
+
+    case SAMPLE_collect:
+      PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
+      break;
+
+    case SAMPLE_diamond:
+      PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
+      break;
+
+    case SAMPLE_squash:
+      /* !!! CHECK THIS !!! */
+#if 1
+      PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
+#else
+      PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
+#endif
+      break;
+
+    case SAMPLE_wonderfall:
+      PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
+      break;
+
+    case SAMPLE_drip:
+      PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
+      break;
+
+    case SAMPLE_push:
+      PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
+      break;
+
+    case SAMPLE_dirt:
+      PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
+      break;
+
+    case SAMPLE_acid:
+      PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
+      break;
+
+    case SAMPLE_ball:
+      PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
+      break;
+
+    case SAMPLE_grow:
+      PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
+      break;
+
+    case SAMPLE_wonder:
+      PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
+      break;
+
+    case SAMPLE_door:
+      PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
+      break;
+
+    case SAMPLE_exit_open:
+      PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
+      break;
+
+    case SAMPLE_exit_leave:
+      PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
+      break;
+
+    case SAMPLE_dynamite:
+      PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
+      break;
+
+    case SAMPLE_tick:
+      PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
+      break;
+
+    case SAMPLE_press:
+      PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
+      break;
+
+    case SAMPLE_wheel:
+      PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
+      break;
+
+    case SAMPLE_boom:
+      PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
+      break;
+
+    case SAMPLE_die:
+      PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
+      break;
+
+    case SAMPLE_time:
+      PlaySoundStereo(SND_GAME_RUNNING_OUT_OF_TIME, SOUND_MIDDLE);
+      break;
+
+    default:
+      PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
+      break;
+  }
+}
+
 void RaiseScore(int value)
 {
   local_player->score += value;
@@ -11835,6 +13292,14 @@ void RaiseScoreElement(int element)
     case EL_KEY_2:
     case EL_KEY_3:
     case EL_KEY_4:
+    case EL_EM_KEY_1:
+    case EL_EM_KEY_2:
+    case EL_EM_KEY_3:
+    case EL_EM_KEY_4:
+    case EL_EMC_KEY_5:
+    case EL_EMC_KEY_6:
+    case EL_EMC_KEY_7:
+    case EL_EMC_KEY_8:
       RaiseScore(level.score[SC_KEY]);
       break;
     default:
@@ -11851,7 +13316,7 @@ void RequestQuitGame(boolean ask_if_really_quit)
       Request("Do you really want to quit the game ?",
              REQ_ASK | REQ_STAY_CLOSED))
   {
-#if defined(PLATFORM_UNIX)
+#if defined(NETWORK_AVALIABLE)
     if (options.network)
       SendToServer_StopPlaying();
     else
@@ -12042,7 +13507,7 @@ static void HandleGameButtons(struct GadgetInfo *gi)
     case GAME_CTRL_ID_PAUSE:
       if (options.network)
       {
-#if defined(PLATFORM_UNIX)
+#if defined(NETWORK_AVALIABLE)
        if (tape.pausing)
          SendToServer_ContinuePlaying();
        else
@@ -12056,7 +13521,7 @@ static void HandleGameButtons(struct GadgetInfo *gi)
     case GAME_CTRL_ID_PLAY:
       if (tape.pausing)
       {
-#if defined(PLATFORM_UNIX)
+#if defined(NETWORK_AVALIABLE)
        if (options.network)
          SendToServer_ContinuePlaying();
        else