rnd-20100624-1-src
[rocksndiamonds.git] / src / game.c
index 71d83bbc815de1eea134facc57cec36018580851..a88b3f04ec1a8525e904b45e13418c94580ea9c3 100644 (file)
 #define USE_FIX_KILLED_BY_NON_WALKABLE (USE_NEW_STUFF          * 1)
 #define USE_FIX_IMPACT_COLLISION       (USE_NEW_STUFF          * 1)
 #define USE_FIX_CE_ACTION_WITH_PLAYER  (USE_NEW_STUFF          * 1)
+#define USE_FIX_NO_ACTION_AFTER_CHANGE (USE_NEW_STUFF          * 1)
+
+#define USE_PLAYER_REANIMATION         (USE_NEW_STUFF          * 1)
 
 #define USE_GFX_RESET_WHEN_NOT_MOVING  (USE_NEW_STUFF          * 1)
 
-#define USE_DELAYED_GFX_REDRAW         (USE_NEW_STUFF          * 1)
+#define USE_NEW_PLAYER_ASSIGNMENTS     (USE_NEW_STUFF          * 1)
+
+#define USE_DELAYED_GFX_REDRAW         (USE_NEW_STUFF          * 0)
 
 #if USE_DELAYED_GFX_REDRAW
 #define TEST_DrawLevelField(x, y)                              \
        GfxRedraw[x][y] |= GFX_REDRAW_TILE
-#define TEST_DrawLevelFieldCrumbledSand(x, y)                  \
+#define TEST_DrawLevelFieldCrumbled(x, y)                      \
        GfxRedraw[x][y] |= GFX_REDRAW_TILE_CRUMBLED
-#define TEST_DrawLevelFieldCrumbledSandNeighbours(x, y)                \
+#define TEST_DrawLevelFieldCrumbledNeighbours(x, y)            \
        GfxRedraw[x][y] |= GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS
 #define TEST_DrawTwinkleOnField(x, y)                          \
        GfxRedraw[x][y] |= GFX_REDRAW_TILE_TWINKLED
 #else
 #define TEST_DrawLevelField(x, y)                              \
             DrawLevelField(x, y)
-#define TEST_DrawLevelFieldCrumbledSand(x, y)                  \
-            DrawLevelFieldCrumbledSand(x, y)
-#define TEST_DrawLevelFieldCrumbledSandNeighbours(x, y)                \
-            DrawLevelFieldCrumbledSandNeighbours(x, y)
+#define TEST_DrawLevelFieldCrumbled(x, y)                      \
+            DrawLevelFieldCrumbled(x, y)
+#define TEST_DrawLevelFieldCrumbledNeighbours(x, y)            \
+            DrawLevelFieldCrumbledNeighbours(x, y)
 #define TEST_DrawTwinkleOnField(x, y)                          \
             DrawTwinkleOnField(x, y)
 #endif
 #define GAME_PANEL_TIME_HH                     32
 #define GAME_PANEL_TIME_MM                     33
 #define GAME_PANEL_TIME_SS                     34
-#define GAME_PANEL_SHIELD_NORMAL               35
-#define GAME_PANEL_SHIELD_NORMAL_TIME          36
-#define GAME_PANEL_SHIELD_DEADLY               37
-#define GAME_PANEL_SHIELD_DEADLY_TIME          38
-#define GAME_PANEL_EXIT                                39
-#define GAME_PANEL_EMC_MAGIC_BALL              40
-#define GAME_PANEL_EMC_MAGIC_BALL_SWITCH       41
-#define GAME_PANEL_LIGHT_SWITCH                        42
-#define GAME_PANEL_LIGHT_SWITCH_TIME           43
-#define GAME_PANEL_TIMEGATE_SWITCH             44
-#define GAME_PANEL_TIMEGATE_SWITCH_TIME                45
-#define GAME_PANEL_SWITCHGATE_SWITCH           46
-#define GAME_PANEL_EMC_LENSES                  47
-#define GAME_PANEL_EMC_LENSES_TIME             48
-#define GAME_PANEL_EMC_MAGNIFIER               49
-#define GAME_PANEL_EMC_MAGNIFIER_TIME          50
-#define GAME_PANEL_BALLOON_SWITCH              51
-#define GAME_PANEL_DYNABOMB_NUMBER             52
-#define GAME_PANEL_DYNABOMB_SIZE               53
-#define GAME_PANEL_DYNABOMB_POWER              54
-#define GAME_PANEL_PENGUINS                    55
-#define GAME_PANEL_SOKOBAN_OBJECTS             56
-#define GAME_PANEL_SOKOBAN_FIELDS              57
-#define GAME_PANEL_ROBOT_WHEEL                 58
-#define GAME_PANEL_CONVEYOR_BELT_1             59
-#define GAME_PANEL_CONVEYOR_BELT_2             60
-#define GAME_PANEL_CONVEYOR_BELT_3             61
-#define GAME_PANEL_CONVEYOR_BELT_4             62
-#define GAME_PANEL_CONVEYOR_BELT_1_SWITCH      63
-#define GAME_PANEL_CONVEYOR_BELT_2_SWITCH      64
-#define GAME_PANEL_CONVEYOR_BELT_3_SWITCH      65
-#define GAME_PANEL_CONVEYOR_BELT_4_SWITCH      66
-#define GAME_PANEL_MAGIC_WALL                  67
-#define GAME_PANEL_MAGIC_WALL_TIME             68
-#define GAME_PANEL_GRAVITY_STATE               69
-#define GAME_PANEL_GRAPHIC_1                   70
-#define GAME_PANEL_GRAPHIC_2                   71
-#define GAME_PANEL_GRAPHIC_3                   72
-#define GAME_PANEL_GRAPHIC_4                   73
-#define GAME_PANEL_GRAPHIC_5                   74
-#define GAME_PANEL_GRAPHIC_6                   75
-#define GAME_PANEL_GRAPHIC_7                   76
-#define GAME_PANEL_GRAPHIC_8                   77
-#define GAME_PANEL_ELEMENT_1                   78
-#define GAME_PANEL_ELEMENT_2                   79
-#define GAME_PANEL_ELEMENT_3                   80
-#define GAME_PANEL_ELEMENT_4                   81
-#define GAME_PANEL_ELEMENT_5                   82
-#define GAME_PANEL_ELEMENT_6                   83
-#define GAME_PANEL_ELEMENT_7                   84
-#define GAME_PANEL_ELEMENT_8                   85
-#define GAME_PANEL_ELEMENT_COUNT_1             86
-#define GAME_PANEL_ELEMENT_COUNT_2             87
-#define GAME_PANEL_ELEMENT_COUNT_3             88
-#define GAME_PANEL_ELEMENT_COUNT_4             89
-#define GAME_PANEL_ELEMENT_COUNT_5             90
-#define GAME_PANEL_ELEMENT_COUNT_6             91
-#define GAME_PANEL_ELEMENT_COUNT_7             92
-#define GAME_PANEL_ELEMENT_COUNT_8             93
-#define GAME_PANEL_CE_SCORE_1                  94
-#define GAME_PANEL_CE_SCORE_2                  95
-#define GAME_PANEL_CE_SCORE_3                  96
-#define GAME_PANEL_CE_SCORE_4                  97
-#define GAME_PANEL_CE_SCORE_5                  98
-#define GAME_PANEL_CE_SCORE_6                  99
-#define GAME_PANEL_CE_SCORE_7                  100
-#define GAME_PANEL_CE_SCORE_8                  101
-#define GAME_PANEL_CE_SCORE_1_ELEMENT          102
-#define GAME_PANEL_CE_SCORE_2_ELEMENT          103
-#define GAME_PANEL_CE_SCORE_3_ELEMENT          104
-#define GAME_PANEL_CE_SCORE_4_ELEMENT          105
-#define GAME_PANEL_CE_SCORE_5_ELEMENT          106
-#define GAME_PANEL_CE_SCORE_6_ELEMENT          107
-#define GAME_PANEL_CE_SCORE_7_ELEMENT          108
-#define GAME_PANEL_CE_SCORE_8_ELEMENT          109
-#define GAME_PANEL_PLAYER_NAME                 110
-#define GAME_PANEL_LEVEL_NAME                  111
-#define GAME_PANEL_LEVEL_AUTHOR                        112
-
-#define NUM_GAME_PANEL_CONTROLS                        113
+#define GAME_PANEL_FRAME                       35
+#define GAME_PANEL_SHIELD_NORMAL               36
+#define GAME_PANEL_SHIELD_NORMAL_TIME          37
+#define GAME_PANEL_SHIELD_DEADLY               38
+#define GAME_PANEL_SHIELD_DEADLY_TIME          39
+#define GAME_PANEL_EXIT                                40
+#define GAME_PANEL_EMC_MAGIC_BALL              41
+#define GAME_PANEL_EMC_MAGIC_BALL_SWITCH       42
+#define GAME_PANEL_LIGHT_SWITCH                        43
+#define GAME_PANEL_LIGHT_SWITCH_TIME           44
+#define GAME_PANEL_TIMEGATE_SWITCH             45
+#define GAME_PANEL_TIMEGATE_SWITCH_TIME                46
+#define GAME_PANEL_SWITCHGATE_SWITCH           47
+#define GAME_PANEL_EMC_LENSES                  48
+#define GAME_PANEL_EMC_LENSES_TIME             49
+#define GAME_PANEL_EMC_MAGNIFIER               50
+#define GAME_PANEL_EMC_MAGNIFIER_TIME          51
+#define GAME_PANEL_BALLOON_SWITCH              52
+#define GAME_PANEL_DYNABOMB_NUMBER             53
+#define GAME_PANEL_DYNABOMB_SIZE               54
+#define GAME_PANEL_DYNABOMB_POWER              55
+#define GAME_PANEL_PENGUINS                    56
+#define GAME_PANEL_SOKOBAN_OBJECTS             57
+#define GAME_PANEL_SOKOBAN_FIELDS              58
+#define GAME_PANEL_ROBOT_WHEEL                 59
+#define GAME_PANEL_CONVEYOR_BELT_1             60
+#define GAME_PANEL_CONVEYOR_BELT_2             61
+#define GAME_PANEL_CONVEYOR_BELT_3             62
+#define GAME_PANEL_CONVEYOR_BELT_4             63
+#define GAME_PANEL_CONVEYOR_BELT_1_SWITCH      64
+#define GAME_PANEL_CONVEYOR_BELT_2_SWITCH      65
+#define GAME_PANEL_CONVEYOR_BELT_3_SWITCH      66
+#define GAME_PANEL_CONVEYOR_BELT_4_SWITCH      67
+#define GAME_PANEL_MAGIC_WALL                  68
+#define GAME_PANEL_MAGIC_WALL_TIME             69
+#define GAME_PANEL_GRAVITY_STATE               70
+#define GAME_PANEL_GRAPHIC_1                   71
+#define GAME_PANEL_GRAPHIC_2                   72
+#define GAME_PANEL_GRAPHIC_3                   73
+#define GAME_PANEL_GRAPHIC_4                   74
+#define GAME_PANEL_GRAPHIC_5                   75
+#define GAME_PANEL_GRAPHIC_6                   76
+#define GAME_PANEL_GRAPHIC_7                   77
+#define GAME_PANEL_GRAPHIC_8                   78
+#define GAME_PANEL_ELEMENT_1                   79
+#define GAME_PANEL_ELEMENT_2                   80
+#define GAME_PANEL_ELEMENT_3                   81
+#define GAME_PANEL_ELEMENT_4                   82
+#define GAME_PANEL_ELEMENT_5                   83
+#define GAME_PANEL_ELEMENT_6                   84
+#define GAME_PANEL_ELEMENT_7                   85
+#define GAME_PANEL_ELEMENT_8                   86
+#define GAME_PANEL_ELEMENT_COUNT_1             87
+#define GAME_PANEL_ELEMENT_COUNT_2             88
+#define GAME_PANEL_ELEMENT_COUNT_3             89
+#define GAME_PANEL_ELEMENT_COUNT_4             90
+#define GAME_PANEL_ELEMENT_COUNT_5             91
+#define GAME_PANEL_ELEMENT_COUNT_6             92
+#define GAME_PANEL_ELEMENT_COUNT_7             93
+#define GAME_PANEL_ELEMENT_COUNT_8             94
+#define GAME_PANEL_CE_SCORE_1                  95
+#define GAME_PANEL_CE_SCORE_2                  96
+#define GAME_PANEL_CE_SCORE_3                  97
+#define GAME_PANEL_CE_SCORE_4                  98
+#define GAME_PANEL_CE_SCORE_5                  99
+#define GAME_PANEL_CE_SCORE_6                  100
+#define GAME_PANEL_CE_SCORE_7                  101
+#define GAME_PANEL_CE_SCORE_8                  102
+#define GAME_PANEL_CE_SCORE_1_ELEMENT          103
+#define GAME_PANEL_CE_SCORE_2_ELEMENT          104
+#define GAME_PANEL_CE_SCORE_3_ELEMENT          105
+#define GAME_PANEL_CE_SCORE_4_ELEMENT          106
+#define GAME_PANEL_CE_SCORE_5_ELEMENT          107
+#define GAME_PANEL_CE_SCORE_6_ELEMENT          108
+#define GAME_PANEL_CE_SCORE_7_ELEMENT          109
+#define GAME_PANEL_CE_SCORE_8_ELEMENT          110
+#define GAME_PANEL_PLAYER_NAME                 111
+#define GAME_PANEL_LEVEL_NAME                  112
+#define GAME_PANEL_LEVEL_AUTHOR                        113
+
+#define NUM_GAME_PANEL_CONTROLS                        114
 
 struct GamePanelOrderInfo
 {
@@ -468,6 +474,11 @@ static struct GamePanelControlInfo game_panel_controls[] =
     &game.panel.time_ss,
     TYPE_INTEGER,
   },
+  {
+    GAME_PANEL_FRAME,
+    &game.panel.frame,
+    TYPE_INTEGER,
+  },
   {
     GAME_PANEL_SHIELD_NORMAL,
     &game.panel.shield_normal,
@@ -1126,6 +1137,7 @@ void TestIfBadThingRunsIntoPlayer(int, int, int);
 void TestIfFriendTouchesBadThing(int, int);
 void TestIfBadThingTouchesFriend(int, int);
 void TestIfBadThingTouchesOtherBadThing(int, int);
+void TestIfGoodThingGetsHitByBadThing(int, int, int);
 
 void KillPlayer(struct PlayerInfo *);
 void BuryPlayer(struct PlayerInfo *);
@@ -1163,6 +1175,8 @@ static int recursion_loop_depth;
 static boolean recursion_loop_detected;
 static boolean recursion_loop_element;
 
+static int map_player_action[MAX_PLAYERS];
+
 
 /* ------------------------------------------------------------------------- */
 /* definition of elements that automatically change to other elements after  */
@@ -1780,6 +1794,17 @@ static void InitPlayerField(int x, int y, int element, boolean init_game)
     player->jx = player->last_jx = x;
     player->jy = player->last_jy = y;
   }
+
+#if USE_PLAYER_REANIMATION
+  if (!init_game)
+  {
+    int player_nr = GET_PLAYER_NR(element);
+    struct PlayerInfo *player = &stored_player[player_nr];
+
+    if (player->active && player->killed)
+      player->reanimated = TRUE; /* if player was just killed, reanimate him */
+  }
+#endif
 }
 
 static void InitField(int x, int y, boolean init_game)
@@ -2202,17 +2227,25 @@ void UpdateGameControlValues()
              local_player->LevelSolved_CountingTime :
              level.game_engine_type == GAME_ENGINE_TYPE_EM ?
              level.native_em_level->lev->time :
+             level.game_engine_type == GAME_ENGINE_TYPE_SP ?
+             level.native_sp_level->game_sp->time_played :
              level.time == 0 ? TimePlayed : TimeLeft);
   int score = (local_player->LevelSolved ?
               local_player->LevelSolved_CountingScore :
               level.game_engine_type == GAME_ENGINE_TYPE_EM ?
               level.native_em_level->lev->score :
+              level.game_engine_type == GAME_ENGINE_TYPE_SP ?
+              level.native_sp_level->game_sp->score :
               local_player->score);
   int gems = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
              level.native_em_level->lev->required :
+             level.game_engine_type == GAME_ENGINE_TYPE_SP ?
+             level.native_sp_level->game_sp->infotrons_still_needed :
              local_player->gems_still_needed);
   int exit_closed = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
                     level.native_em_level->lev->required > 0 :
+                    level.game_engine_type == GAME_ENGINE_TYPE_SP ?
+                    level.native_sp_level->game_sp->infotrons_still_needed > 0 :
                     local_player->gems_still_needed > 0 ||
                     local_player->sokobanfields_still_needed > 0 ||
                     local_player->lights_still_needed > 0);
@@ -2234,6 +2267,10 @@ void UpdateGameControlValues()
   {
     for (i = 0; i < MAX_PLAYERS; i++)
     {
+      /* only one player in Supaplex game engine */
+      if (level.game_engine_type == GAME_ENGINE_TYPE_SP && i > 0)
+       break;
+
       for (k = 0; k < MAX_NUM_KEYS; k++)
       {
        if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
@@ -2250,6 +2287,9 @@ void UpdateGameControlValues()
       if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
        game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
          level.native_em_level->ply[i]->dynamite;
+      else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
+       game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
+         level.native_sp_level->game_sp->red_disk_count;
       else
        game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
          stored_player[i].inventory_size;
@@ -2282,6 +2322,9 @@ void UpdateGameControlValues()
     if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
       game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
        level.native_em_level->ply[player_nr]->dynamite;
+    else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
+      game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
+       level.native_sp_level->game_sp->red_disk_count;
     else
       game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
        stored_player[player_nr].inventory_size;
@@ -2310,6 +2353,8 @@ void UpdateGameControlValues()
   game_panel_controls[GAME_PANEL_TIME_MM].value = (time / 60) % 60;
   game_panel_controls[GAME_PANEL_TIME_SS].value = time % 60;
 
+  game_panel_controls[GAME_PANEL_FRAME].value = FrameCounter;
+
   game_panel_controls[GAME_PANEL_SHIELD_NORMAL].value =
     (local_player->shield_normal_time_left > 0 ? EL_SHIELD_NORMAL_ACTIVE :
      EL_EMPTY);
@@ -2494,8 +2539,15 @@ void DisplayGameControlValues()
     return;
 
   /* copy default game door content to main double buffer */
+#if 1
+  /* !!! CHECK AGAIN !!! */
+  SetPanelBackground();
+  // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
+  DrawBackground(DX, DY, DXSIZE, DYSIZE);
+#else
   BlitBitmap(graphic_info[IMG_GLOBAL_DOOR].bitmap, drawto,
             DOOR_GFX_PAGEX5, DOOR_GFX_PAGEY1, DXSIZE, DYSIZE, DX, DY);
+#endif
 
   /* redraw game control buttons */
 #if 1
@@ -3436,8 +3488,8 @@ static void InitGameEngine()
     for (j = 0; j < ei->num_change_pages; j++)
     {
       ei->change_page[j].actual_trigger_element = EL_EMPTY;
-      ei->change_page[j].actual_trigger_player = EL_PLAYER_1;
-      ei->change_page[j].actual_trigger_player_bits = CH_PLAYER_1;
+      ei->change_page[j].actual_trigger_player = EL_EMPTY;
+      ei->change_page[j].actual_trigger_player_bits = CH_PLAYER_NONE;
       ei->change_page[j].actual_trigger_side = CH_SIDE_NONE;
       ei->change_page[j].actual_trigger_ce_value = 0;
       ei->change_page[j].actual_trigger_ce_score = 0;
@@ -3665,11 +3717,25 @@ void InitGame()
   boolean emulate_sp = TRUE;   /* unless non-SUPAPLEX    elements found */
 #if 0
   boolean do_fading = (game_status == GAME_MODE_MAIN);
+#endif
+#if 1
+  int initial_move_dir = MV_DOWN;
+#else
+  int initial_move_dir = MV_NONE;
 #endif
   int i, j, x, y;
 
   game_status = GAME_MODE_PLAYING;
 
+#if 1
+  /* needed if different viewport properties defined for playing */
+  ChangeViewportPropertiesIfNeeded();
+#endif
+
+#if 1
+  DrawCompleteVideoDisplay();
+#endif
+
   InitGameEngine();
   InitGameControlValues();
 
@@ -3686,7 +3752,10 @@ void InitGame()
 
     player->present = FALSE;
     player->active = FALSE;
+    player->mapped = FALSE;
+
     player->killed = FALSE;
+    player->reanimated = FALSE;
 
     player->action = 0;
     player->effective_action = 0;
@@ -3710,10 +3779,10 @@ void InitGame()
     player->dynabombs_left = 0;
     player->dynabomb_xl = FALSE;
 
-    player->MovDir = MV_NONE;
+    player->MovDir = initial_move_dir;
     player->MovPos = 0;
     player->GfxPos = 0;
-    player->GfxDir = MV_NONE;
+    player->GfxDir = initial_move_dir;
     player->GfxAction = ACTION_DEFAULT;
     player->Frame = 0;
     player->StepFrame = 0;
@@ -3735,7 +3804,7 @@ void InitGame()
 
     player->step_counter = 0;
 
-    player->last_move_dir = MV_NONE;
+    player->last_move_dir = initial_move_dir;
 
     player->is_active = FALSE;
 
@@ -3759,7 +3828,7 @@ void InitGame()
     player->anim_delay_counter = 0;
     player->post_delay_counter = 0;
 
-    player->dir_waiting = MV_NONE;
+    player->dir_waiting = initial_move_dir;
     player->action_waiting = ACTION_DEFAULT;
     player->last_action_waiting = ACTION_DEFAULT;
     player->special_action_bored = ACTION_DEFAULT;
@@ -3792,6 +3861,26 @@ void InitGame()
     player->inventory_infinite_element = EL_UNDEFINED;
     player->inventory_size = 0;
 
+    if (level.use_initial_inventory[i])
+    {
+      for (j = 0; j < level.initial_inventory_size[i]; j++)
+      {
+       int element = level.initial_inventory_content[i][j];
+       int collect_count = element_info[element].collect_count_initial;
+       int k;
+
+       if (!IS_CUSTOM_ELEMENT(element))
+         collect_count = 1;
+
+       if (collect_count == 0)
+         player->inventory_infinite_element = element;
+       else
+         for (k = 0; k < collect_count; k++)
+           if (player->inventory_size < MAX_INVENTORY_SIZE)
+             player->inventory_element[player->inventory_size++] = element;
+      }
+    }
+
     DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
     SnapField(player, 0, 0);
 
@@ -3805,6 +3894,8 @@ void InitGame()
     player->LevelSolved_SaveScore = FALSE;
     player->LevelSolved_CountingTime = 0;
     player->LevelSolved_CountingScore = 0;
+
+    map_player_action[i] = i;
   }
 
   network_player_action_received = FALSE;
@@ -3997,6 +4088,124 @@ void InitGame()
     if (game.belt_dir[i] == MV_NONE)
       game.belt_dir_nr[i] = 3;         /* not moving, next moving left */
 
+#if USE_NEW_PLAYER_ASSIGNMENTS
+  /* !!! SAME AS init.c:InitPlayerInfo() -- FIX THIS !!! */
+  /* choose default local player */
+  local_player = &stored_player[0];
+
+  for (i = 0; i < MAX_PLAYERS; i++)
+    stored_player[i].connected = FALSE;
+
+  local_player->connected = TRUE;
+  /* !!! SAME AS init.c:InitPlayerInfo() -- FIX THIS !!! */
+
+  if (tape.playing)
+  {
+    /* try to guess locally connected team mode players (needed for correct
+       assignment of player figures from level to locally playing players) */
+
+    for (i = 0; i < MAX_PLAYERS; i++)
+      if (tape.player_participates[i])
+       stored_player[i].connected = TRUE;
+  }
+  else if (setup.team_mode && !options.network)
+  {
+    /* try to guess locally connected team mode players (needed for correct
+       assignment of player figures from level to locally playing players) */
+
+    for (i = 0; i < MAX_PLAYERS; i++)
+      if (setup.input[i].use_joystick ||
+         setup.input[i].key.left != KSYM_UNDEFINED)
+       stored_player[i].connected = TRUE;
+  }
+
+#if 0
+  for (i = 0; i < MAX_PLAYERS; i++)
+    printf("::: player %d: %s\n", i,
+          (stored_player[i].connected ? "connected" : "not connected"));
+
+  for (i = 0; i < MAX_PLAYERS; i++)
+    printf("::: player %d: %s\n", i,
+          (stored_player[i].present ? "present" : "not present"));
+#endif
+
+  /* check if any connected player was not found in playfield */
+  for (i = 0; i < MAX_PLAYERS; i++)
+  {
+    struct PlayerInfo *player = &stored_player[i];
+
+    if (player->connected && !player->present)
+    {
+      struct PlayerInfo *field_player = NULL;
+
+#if 0
+      printf("::: looking for field player for player %d ...\n", i);
+#endif
+
+      /* assign first free player found that is present in the playfield */
+
+      /* first try: look for unmapped playfield player that is not connected */
+      if (field_player == NULL)
+       for (j = 0; j < MAX_PLAYERS; j++)
+         if (stored_player[j].present &&
+             !stored_player[j].mapped &&
+             !stored_player[j].connected)
+           field_player = &stored_player[j];
+
+      /* second try: look for *any* unmapped playfield player */
+      if (field_player == NULL)
+       for (j = 0; j < MAX_PLAYERS; j++)
+         if (stored_player[j].present &&
+             !stored_player[j].mapped)
+           field_player = &stored_player[j];
+
+      if (field_player != NULL)
+      {
+       int jx = field_player->jx, jy = field_player->jy;
+
+#if 0
+       printf("::: found player figure %d\n", field_player->index_nr);
+#endif
+
+       player->present = FALSE;
+       player->active = FALSE;
+
+       field_player->present = TRUE;
+       field_player->active = TRUE;
+
+       /*
+       player->initial_element = field_player->initial_element;
+       player->artwork_element = field_player->artwork_element;
+
+       player->block_last_field       = field_player->block_last_field;
+       player->block_delay_adjustment = field_player->block_delay_adjustment;
+       */
+
+       StorePlayer[jx][jy] = field_player->element_nr;
+
+       field_player->jx = field_player->last_jx = jx;
+       field_player->jy = field_player->last_jy = jy;
+
+       if (local_player == player)
+         local_player = field_player;
+
+       map_player_action[field_player->index_nr] = i;
+
+       field_player->mapped = TRUE;
+
+#if 0
+       printf("::: map_player_action[%d] == %d\n",
+              field_player->index_nr, i);
+#endif
+      }
+    }
+
+    if (player->connected && player->present)
+      player->mapped = TRUE;
+  }
+
+#else
+
   /* check if any connected player was not found in playfield */
   for (i = 0; i < MAX_PLAYERS; i++)
   {
@@ -4006,25 +4215,26 @@ void InitGame()
     {
       for (j = 0; j < MAX_PLAYERS; j++)
       {
-       struct PlayerInfo *some_player = &stored_player[j];
-       int jx = some_player->jx, jy = some_player->jy;
+       struct PlayerInfo *field_player = &stored_player[j];
+       int jx = field_player->jx, jy = field_player->jy;
 
        /* assign first free player found that is present in the playfield */
-       if (some_player->present && !some_player->connected)
+       if (field_player->present && !field_player->connected)
        {
          player->present = TRUE;
          player->active = TRUE;
 
-         some_player->present = FALSE;
-         some_player->active = FALSE;
+         field_player->present = FALSE;
+         field_player->active = FALSE;
 
-         player->initial_element = some_player->initial_element;
-         player->artwork_element = some_player->artwork_element;
+         player->initial_element = field_player->initial_element;
+         player->artwork_element = field_player->artwork_element;
 
-         player->block_last_field       = some_player->block_last_field;
-         player->block_delay_adjustment = some_player->block_delay_adjustment;
+         player->block_last_field       = field_player->block_last_field;
+         player->block_delay_adjustment = field_player->block_delay_adjustment;
 
          StorePlayer[jx][jy] = player->element_nr;
+
          player->jx = player->last_jx = jx;
          player->jy = player->last_jy = jy;
 
@@ -4033,14 +4243,35 @@ void InitGame()
       }
     }
   }
+#endif
+
+#if 0
+  printf("::: local_player->present == %d\n", local_player->present);
+#endif
 
   if (tape.playing)
   {
     /* when playing a tape, eliminate all players who do not participate */
 
+#if USE_NEW_PLAYER_ASSIGNMENTS
+    for (i = 0; i < MAX_PLAYERS; i++)
+    {
+      if (stored_player[i].active &&
+         !tape.player_participates[map_player_action[i]])
+      {
+       struct PlayerInfo *player = &stored_player[i];
+       int jx = player->jx, jy = player->jy;
+
+       player->active = FALSE;
+       StorePlayer[jx][jy] = 0;
+       Feld[jx][jy] = EL_EMPTY;
+      }
+    }
+#else
     for (i = 0; i < MAX_PLAYERS; i++)
     {
-      if (stored_player[i].active && !tape.player_participates[i])
+      if (stored_player[i].active &&
+         !tape.player_participates[i])
       {
        struct PlayerInfo *player = &stored_player[i];
        int jx = player->jx, jy = player->jy;
@@ -4050,6 +4281,7 @@ void InitGame()
        Feld[jx][jy] = EL_EMPTY;
       }
     }
+#endif
   }
   else if (!options.network && !setup.team_mode)       /* && !tape.playing */
   {
@@ -4080,9 +4312,15 @@ void InitGame()
   /* when recording the game, store which players take part in the game */
   if (tape.recording)
   {
+#if USE_NEW_PLAYER_ASSIGNMENTS
+    for (i = 0; i < MAX_PLAYERS; i++)
+      if (stored_player[i].connected)
+       tape.player_participates[i] = TRUE;
+#else
     for (i = 0; i < MAX_PLAYERS; i++)
       if (stored_player[i].active)
        tape.player_participates[i] = TRUE;
+#endif
   }
 
   if (options.debug)
@@ -4272,6 +4510,13 @@ void InitGame()
     /* blit playfield from scroll buffer to normal back buffer for fading in */
     BlitScreenToBitmap_EM(backbuffer);
   }
+  else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
+  {
+    InitGameEngine_SP();
+
+    /* blit playfield from scroll buffer to normal back buffer for fading in */
+    BlitScreenToBitmap_SP(backbuffer);
+  }
   else
   {
     DrawLevel();
@@ -4301,8 +4546,24 @@ void InitGame()
   if (!game.restart_level)
   {
     /* copy default game door content to main double buffer */
+#if 1
+#if 1
+    /* !!! CHECK AGAIN !!! */
+    SetPanelBackground();
+    // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
+    DrawBackground(DX, DY, DXSIZE, DYSIZE);
+#else
+    struct GraphicInfo *gfx = &graphic_info[IMG_BACKGROUND_PANEL];
+
+    /* (ClearRectangle() only needed if panel bitmap is smaller than panel) */
+    ClearRectangle(drawto, DX, DY, DXSIZE, DYSIZE);
+    BlitBitmap(gfx->bitmap, drawto, gfx->src_x, gfx->src_y,
+              MIN(gfx->width, DXSIZE), MIN(gfx->height, DYSIZE), DX, DY);
+#endif
+#else
     BlitBitmap(graphic_info[IMG_GLOBAL_DOOR].bitmap, drawto,
               DOOR_GFX_PAGEX5, DOOR_GFX_PAGEY1, DXSIZE, DYSIZE, DX, DY);
+#endif
   }
 
   SetPanelBackground();
@@ -5140,7 +5401,10 @@ static void RemoveField(int x, int y)
   GfxElement[x][y] = EL_UNDEFINED;
   GfxAction[x][y] = ACTION_DEFAULT;
   GfxDir[x][y] = MV_NONE;
+#if 0
+  /* !!! this would prevent the removed tile from being redrawn !!! */
   GfxRedraw[x][y] = GFX_REDRAW_NONE;
+#endif
 }
 
 void RemoveMovingField(int x, int y)
@@ -5304,8 +5568,6 @@ void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
 
   if (quick_relocation)
   {
-    int offset = game.scroll_delay_value;
-
     if (!IN_VIS_FIELD(SCREENX(x), SCREENY(y)) || center_screen)
     {
       if (!level.shifted_relocation || center_screen)
@@ -5346,8 +5608,47 @@ void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
     }
     else
     {
+#if 1
+      if (!level.shifted_relocation || center_screen)
+      {
+       /* quick relocation (without scrolling), with centering of screen */
+
+       scroll_x = (x < SBX_Left  + MIDPOSX ? SBX_Left :
+                   x > SBX_Right + MIDPOSX ? SBX_Right :
+                   x - MIDPOSX);
+
+       scroll_y = (y < SBY_Upper + MIDPOSY ? SBY_Upper :
+                   y > SBY_Lower + MIDPOSY ? SBY_Lower :
+                   y - MIDPOSY);
+      }
+      else
+      {
+       /* quick relocation (without scrolling), but do not center screen */
+
+       int center_scroll_x = (old_x < SBX_Left  + MIDPOSX ? SBX_Left :
+                              old_x > SBX_Right + MIDPOSX ? SBX_Right :
+                              old_x - MIDPOSX);
+
+       int center_scroll_y = (old_y < SBY_Upper + MIDPOSY ? SBY_Upper :
+                              old_y > SBY_Lower + MIDPOSY ? SBY_Lower :
+                              old_y - MIDPOSY);
+
+       int offset_x = x + (scroll_x - center_scroll_x);
+       int offset_y = y + (scroll_y - center_scroll_y);
+
+       scroll_x = (offset_x < SBX_Left  + MIDPOSX ? SBX_Left :
+                   offset_x > SBX_Right + MIDPOSX ? SBX_Right :
+                   offset_x - MIDPOSX);
+
+       scroll_y = (offset_y < SBY_Upper + MIDPOSY ? SBY_Upper :
+                   offset_y > SBY_Lower + MIDPOSY ? SBY_Lower :
+                   offset_y - MIDPOSY);
+      }
+#else
       /* quick relocation (without scrolling), inside visible screen area */
 
+      int offset = game.scroll_delay_value;
+
       if ((move_dir == MV_LEFT  && scroll_x > x - MIDPOSX + offset) ||
          (move_dir == MV_RIGHT && scroll_x < x - MIDPOSX - offset))
        scroll_x = x - MIDPOSX + (scroll_x < x - MIDPOSX ? -offset : +offset);
@@ -5363,6 +5664,7 @@ void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
       /* don't scroll over playfield boundaries */
       if (scroll_y < SBY_Upper || scroll_y > SBY_Lower)
        scroll_y = (scroll_y < SBY_Upper ? SBY_Upper : SBY_Lower);
+#endif
     }
 
     RedrawPlayfield(TRUE, 0,0,0,0);
@@ -5528,10 +5830,19 @@ void RelocatePlayer(int jx, int jy, int el_player_raw)
   Feld[jx][jy] = el_player;
   InitPlayerField(jx, jy, el_player, TRUE);
 
+  /* "InitPlayerField()" above sets Feld[jx][jy] to EL_EMPTY, but it may be
+     possible that the relocation target field did not contain a player element,
+     but a walkable element, to which the new player was relocated -- in this
+     case, restore that (already initialized!) element on the player field */
   if (!ELEM_IS_PLAYER(element))        /* player may be set on walkable element */
   {
-    Feld[jx][jy] = element;
+    Feld[jx][jy] = element;    /* restore previously existing element */
+#if 0
+    /* !!! do not initialize already initialized element a second time !!! */
+    /* (this causes at least problems with "element creation" CE trigger for
+       already existing elements, and existing Sokoban fields counted twice) */
     InitField(jx, jy, FALSE);
+#endif
   }
 
   /* only visually relocate centered player */
@@ -5547,6 +5858,21 @@ void RelocatePlayer(int jx, int jy, int el_player_raw)
 
   CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
                                      player->index_bit, enter_side);
+
+#if 1
+  if (player->is_switching)
+  {
+    /* ensure that relocation while still switching an element does not cause
+       a new element to be treated as also switched directly after relocation
+       (this is important for teleporter switches that teleport the player to
+       a place where another teleporter switch is in the same direction, which
+       would then incorrectly be treated as immediately switched before the
+       direction key that caused the switch was released) */
+
+    player->switch_x += jx - old_jx;
+    player->switch_y += jy - old_jy;
+  }
+#endif
 }
 
 void Explode(int ex, int ey, int phase, int mode)
@@ -5864,7 +6190,7 @@ void Explode(int ex, int ey, int phase, int mode)
     TestIfElementTouchesCustomElement(x, y);
 
     if (GFX_CRUMBLED(element))
-      TEST_DrawLevelFieldCrumbledSandNeighbours(x, y);
+      TEST_DrawLevelFieldCrumbledNeighbours(x, y);
 
     if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
       StorePlayer[x][y] = 0;
@@ -5878,7 +6204,7 @@ void Explode(int ex, int ey, int phase, int mode)
     int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
 
     if (phase == delay)
-      TEST_DrawLevelFieldCrumbledSand(x, y);
+      TEST_DrawLevelFieldCrumbled(x, y);
 
     if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
     {
@@ -6336,7 +6662,7 @@ static void RedrawAllLightSwitchesAndInvisibleElements()
 
       /* uncrumble neighbour fields, if needed */
       if (element == EL_INVISIBLE_SAND)
-       TEST_DrawLevelFieldCrumbledSandNeighbours(x, y);
+       TEST_DrawLevelFieldCrumbledNeighbours(x, y);
     }
     else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
             element == EL_INVISIBLE_WALL_ACTIVE ||
@@ -6349,7 +6675,7 @@ static void RedrawAllLightSwitchesAndInvisibleElements()
 
       /* re-crumble neighbour fields, if needed */
       if (element == EL_INVISIBLE_SAND)
-       TEST_DrawLevelFieldCrumbledSandNeighbours(x, y);
+       TEST_DrawLevelFieldCrumbledNeighbours(x, y);
     }
   }
 }
@@ -6385,7 +6711,7 @@ static void RedrawAllInvisibleElementsForLenses()
 
       /* uncrumble neighbour fields, if needed */
       if (element == EL_INVISIBLE_SAND)
-       TEST_DrawLevelFieldCrumbledSandNeighbours(x, y);
+       TEST_DrawLevelFieldCrumbledNeighbours(x, y);
     }
     else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
             element == EL_INVISIBLE_WALL_ACTIVE ||
@@ -6398,7 +6724,7 @@ static void RedrawAllInvisibleElementsForLenses()
 
       /* re-crumble neighbour fields, if needed */
       if (element == EL_INVISIBLE_SAND)
-       TEST_DrawLevelFieldCrumbledSandNeighbours(x, y);
+       TEST_DrawLevelFieldCrumbledNeighbours(x, y);
     }
   }
 }
@@ -6432,6 +6758,8 @@ static void RedrawAllInvisibleElementsForMagnifier()
                    element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
                    IS_EMC_GATE_GRAY(element) ?
                    element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
+                   IS_DC_GATE_GRAY(element) ?
+                   EL_DC_GATE_WHITE_GRAY_ACTIVE :
                    element);
       TEST_DrawLevelField(x, y);
     }
@@ -6444,6 +6772,8 @@ static void RedrawAllInvisibleElementsForMagnifier()
                    element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
                    IS_EMC_GATE_GRAY_ACTIVE(element) ?
                    element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
+                   IS_DC_GATE_GRAY_ACTIVE(element) ?
+                   EL_DC_GATE_WHITE_GRAY :
                    element);
       TEST_DrawLevelField(x, y);
     }
@@ -7808,7 +8138,7 @@ void StartMoving(int x, int y)
       else if (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
       {
        if (!MovDelay[x][y])
-         MovDelay[x][y] = TILEY/4 + 1;
+         MovDelay[x][y] = TILEY / 4 + 1;
 
        if (MovDelay[x][y])
        {
@@ -7836,7 +8166,7 @@ void StartMoving(int x, int y)
       else if (Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
       {
        if (!MovDelay[x][y])
-         MovDelay[x][y] = TILEY/4 + 1;
+         MovDelay[x][y] = TILEY / 4 + 1;
 
        if (MovDelay[x][y])
        {
@@ -7864,7 +8194,7 @@ void StartMoving(int x, int y)
       else if (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
       {
        if (!MovDelay[x][y])
-         MovDelay[x][y] = TILEY/4 + 1;
+         MovDelay[x][y] = TILEY / 4 + 1;
 
        if (MovDelay[x][y])
        {
@@ -8211,7 +8541,7 @@ void StartMoving(int x, int y)
 
            if (IN_SCR_FIELD(sx, sy))
            {
-             TEST_DrawLevelFieldCrumbledSand(xx, yy);
+             TEST_DrawLevelFieldCrumbled(xx, yy);
              DrawGraphic(sx, sy, flame_graphic, frame);
            }
          }
@@ -8714,7 +9044,7 @@ void ContinueMoving(int x, int y)
   {
     Feld[x][y] = EL_SAND;
 
-    TEST_DrawLevelFieldCrumbledSandNeighbours(x, y);
+    TEST_DrawLevelFieldCrumbledNeighbours(x, y);
   }
   else if (element == EL_QUICKSAND_FILLING)
   {
@@ -8881,7 +9211,7 @@ void ContinueMoving(int x, int y)
     InitField(x, y, FALSE);
 
     if (GFX_CRUMBLED(Feld[x][y]))
-      TEST_DrawLevelFieldCrumbledSandNeighbours(x, y);
+      TEST_DrawLevelFieldCrumbledNeighbours(x, y);
 
     if (ELEM_IS_PLAYER(move_leave_element))
       RelocatePlayer(x, y, move_leave_element);
@@ -8943,6 +9273,11 @@ void ContinueMoving(int x, int y)
   else if (element == EL_PENGUIN)
     TestIfFriendTouchesBadThing(newx, newy);
 
+  if (DONT_GET_HIT_BY(element))
+  {
+    TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
+  }
+
   /* give the player one last chance (one more frame) to move away */
   if (CAN_FALL(element) && direction == MV_DOWN &&
       (last_line || (!IS_FREE(x, newy + 1) &&
@@ -10070,7 +10405,7 @@ static void ChangeActiveTrap(int x, int y)
 
   /* if new animation frame was drawn, correct crumbled sand border */
   if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
-    TEST_DrawLevelFieldCrumbledSand(x, y);
+    TEST_DrawLevelFieldCrumbled(x, y);
 }
 
 static int getSpecialActionElement(int element, int number, int base_element)
@@ -10104,6 +10439,7 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
   int action_type = change->action_type;
   int action_mode = change->action_mode;
   int action_arg = change->action_arg;
+  int action_element = change->action_element;
   int i;
 
   if (!change->has_action)
@@ -10115,11 +10451,21 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
     (level.time > 0 ? TimeLeft :
      TimePlayed);
 
-  int action_arg_element =
+  int action_arg_element_raw =
     (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 :
+     action_arg == CA_ARG_ELEMENT_ACTION  ? change->action_element :
+     action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
+     action_arg == CA_ARG_INVENTORY_RM_TARGET  ? change->target_element :
+     action_arg == CA_ARG_INVENTORY_RM_ACTION  ? change->action_element :
      EL_EMPTY);
+  int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
+
+#if 0
+  if (action_arg_element_raw == EL_GROUP_START)
+    printf("::: %d,%d: %d ('%s')\n", x, y, element, EL_NAME(element));
+#endif
 
   int action_arg_direction =
     (action_arg >= CA_ARG_DIRECTION_LEFT &&
@@ -10176,10 +10522,13 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
      action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? local_player->score :
      action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
      action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
+     action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
      action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
      action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
+     action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
      action_arg == CA_ARG_ELEMENT_NR_TARGET  ? change->target_element :
      action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
+     action_arg == CA_ARG_ELEMENT_NR_ACTION  ? change->action_element :
      -1);
 
   int action_arg_number_old =
@@ -10196,7 +10545,9 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
                            action_arg_number_min, action_arg_number_max);
 
 #if 1
-  int trigger_player_bits = change->actual_trigger_player_bits;
+  int trigger_player_bits =
+    (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
+     change->actual_trigger_player_bits : change->trigger_player);
 #else
   int trigger_player_bits =
     (change->actual_trigger_player >= EL_PLAYER_1 &&
@@ -10209,6 +10560,7 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
     (action_arg >= CA_ARG_PLAYER_1 &&
      action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
      action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
+     action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
      PLAYER_BITS_ANY);
 
   /* ---------- execute action  -------------------------------------------- */
@@ -10310,6 +10662,33 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
       break;
     }
 
+    case CA_SET_LEVEL_RANDOM_SEED:
+    {
+#if 1
+      /* ensure that setting a new random seed while playing is predictable */
+      InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
+#else
+      InitRND(action_arg_number_new);
+#endif
+
+#if 0
+      printf("::: %d -> %d\n", action_arg_number_new, RND(10));
+#endif
+
+#if 0
+      {
+       int i;
+
+       printf("::: ");
+       for (i = 0; i < 9; i++)
+         printf("%d, ", RND(2));
+       printf("\n");
+      }
+#endif
+
+      break;
+    }
+
     /* ---------- player actions  ------------------------------------------ */
 
     case CA_MOVE_PLAYER:
@@ -10364,6 +10743,10 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
 
     case CA_SET_PLAYER_SPEED:
     {
+#if 0
+      printf("::: trigger_player_bits == %d\n", trigger_player_bits);
+#endif
+
       for (i = 0; i < MAX_PLAYERS; i++)
       {
        if (trigger_player_bits & (1 << i))
@@ -10481,6 +10864,104 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
       break;
     }
 
+    case CA_SET_PLAYER_INVENTORY:
+    {
+      for (i = 0; i < MAX_PLAYERS; i++)
+      {
+       struct PlayerInfo *player = &stored_player[i];
+       int j, k;
+
+       if (trigger_player_bits & (1 << i))
+       {
+         int inventory_element = action_arg_element;
+
+         if (action_arg == CA_ARG_ELEMENT_TARGET ||
+             action_arg == CA_ARG_ELEMENT_TRIGGER ||
+             action_arg == CA_ARG_ELEMENT_ACTION)
+         {
+           int element = inventory_element;
+           int collect_count = element_info[element].collect_count_initial;
+
+           if (!IS_CUSTOM_ELEMENT(element))
+             collect_count = 1;
+
+           if (collect_count == 0)
+             player->inventory_infinite_element = element;
+           else
+             for (k = 0; k < collect_count; k++)
+               if (player->inventory_size < MAX_INVENTORY_SIZE)
+                 player->inventory_element[player->inventory_size++] =
+                   element;
+         }
+         else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
+                  action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
+                  action_arg == CA_ARG_INVENTORY_RM_ACTION)
+         {
+           if (player->inventory_infinite_element != EL_UNDEFINED &&
+               IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
+                                    action_arg_element_raw))
+             player->inventory_infinite_element = EL_UNDEFINED;
+
+           for (k = 0, j = 0; j < player->inventory_size; j++)
+           {
+             if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
+                                       action_arg_element_raw))
+               player->inventory_element[k++] = player->inventory_element[j];
+           }
+
+           player->inventory_size = k;
+         }
+         else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
+         {
+           if (player->inventory_size > 0)
+           {
+             for (j = 0; j < player->inventory_size - 1; j++)
+               player->inventory_element[j] = player->inventory_element[j + 1];
+
+             player->inventory_size--;
+           }
+         }
+         else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
+         {
+           if (player->inventory_size > 0)
+             player->inventory_size--;
+         }
+         else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
+         {
+           player->inventory_infinite_element = EL_UNDEFINED;
+           player->inventory_size = 0;
+         }
+         else if (action_arg == CA_ARG_INVENTORY_RESET)
+         {
+           player->inventory_infinite_element = EL_UNDEFINED;
+           player->inventory_size = 0;
+
+           if (level.use_initial_inventory[i])
+           {
+             for (j = 0; j < level.initial_inventory_size[i]; j++)
+             {
+               int element = level.initial_inventory_content[i][j];
+               int collect_count = element_info[element].collect_count_initial;
+
+               if (!IS_CUSTOM_ELEMENT(element))
+                 collect_count = 1;
+
+               if (collect_count == 0)
+                 player->inventory_infinite_element = element;
+               else
+                 for (k = 0; k < collect_count; k++)
+                   if (player->inventory_size < MAX_INVENTORY_SIZE)
+                     player->inventory_element[player->inventory_size++] =
+                       element;
+             }
+           }
+         }
+       }
+      }
+
+      break;
+    }
+
     /* ---------- CE actions  ---------------------------------------------- */
 
     case CA_SET_CE_VALUE:
@@ -10549,11 +11030,43 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
       break;
     }
 
-    /* ---------- engine actions  ------------------------------------------ */
-
-    case CA_SET_ENGINE_SCAN_MODE:
+    case CA_SET_CE_ARTWORK:
     {
-      InitPlayfieldScanMode(action_arg);
+      int artwork_element = action_arg_element;
+      boolean reset_frame = FALSE;
+      int xx, yy;
+
+      if (action_arg == CA_ARG_ELEMENT_RESET)
+       artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
+                          element);
+
+      if (ei->gfx_element != artwork_element)
+       reset_frame = TRUE;
+
+      ei->gfx_element = artwork_element;
+
+      SCAN_PLAYFIELD(xx, yy)
+      {
+       if (Feld[xx][yy] == element)
+       {
+         if (reset_frame)
+         {
+           ResetGfxAnimation(xx, yy);
+           ResetRandomAnimationValue(xx, yy);
+         }
+
+         TEST_DrawLevelField(xx, yy);
+       }
+      }
+
+      break;
+    }
+
+    /* ---------- engine actions  ------------------------------------------ */
+
+    case CA_SET_ENGINE_SCAN_MODE:
+    {
+      InitPlayfieldScanMode(action_arg);
 
       break;
     }
@@ -10628,7 +11141,7 @@ static void CreateFieldExt(int x, int y, int element, boolean is_change)
     TEST_DrawLevelField(x, y);
 
     if (GFX_CRUMBLED(new_element))
-      TEST_DrawLevelFieldCrumbledSandNeighbours(x, y);
+      TEST_DrawLevelFieldCrumbledNeighbours(x, y);
   }
 
 #if 1
@@ -10708,8 +11221,8 @@ static boolean ChangeElement(int x, int y, int element, int page)
   {
     /* reset actual trigger element, trigger player and action element */
     change->actual_trigger_element = EL_EMPTY;
-    change->actual_trigger_player = EL_PLAYER_1;
-    change->actual_trigger_player_bits = CH_PLAYER_1;
+    change->actual_trigger_player = EL_EMPTY;
+    change->actual_trigger_player_bits = CH_PLAYER_NONE;
     change->actual_trigger_side = CH_SIDE_NONE;
     change->actual_trigger_ce_value = 0;
     change->actual_trigger_ce_score = 0;
@@ -10869,6 +11382,7 @@ static void HandleElementChange(int x, int y, int page)
   int element = MovingOrBlocked2Element(x, y);
   struct ElementInfo *ei = &element_info[element];
   struct ElementChangeInfo *change = &ei->change_page[page];
+  boolean handle_action_before_change = FALSE;
 
 #ifdef DEBUG
   if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
@@ -10951,6 +11465,15 @@ static void HandleElementChange(int x, int y, int page)
       return;
     }
 
+#if 1
+    /* special case: set new level random seed before changing element */
+    if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
+      handle_action_before_change = TRUE;
+
+    if (change->has_action && handle_action_before_change)
+      ExecuteCustomElementAction(x, y, element, page);
+#endif
+
     if (change->can_change)
     {
       if (ChangeElement(x, y, element, page))
@@ -10960,7 +11483,7 @@ static void HandleElementChange(int x, int y, int page)
       }
     }
 
-    if (change->has_action)
+    if (change->has_action && !handle_action_before_change)
       ExecuteCustomElementAction(x, y, element, page);
   }
 }
@@ -11096,6 +11619,11 @@ static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
        change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
        change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
 
+#if 0
+       printf("::: TRIGGERED CHANGE FOUND: %d ['%s'], %d\n",
+              element, EL_NAME(element), p);
+#endif
+
        if ((change->can_change && !change_done) || change->has_action)
        {
          int x, y;
@@ -11106,6 +11634,21 @@ static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
            {
              if (change->can_change && !change_done)
              {
+#if USE_FIX_NO_ACTION_AFTER_CHANGE
+               /* if element already changed in this frame, not only prevent
+                  another element change (checked in ChangeElement()), but
+                  also prevent additional element actions for this element */
+
+               if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
+                   !level.use_action_after_change_bug)
+                 continue;
+#endif
+
+#if 0
+               printf("::: TRIGGERED CHANGE FOUND: %d ['%s'], %d -- CHANGE\n",
+                      element, EL_NAME(element), p);
+#endif
+
                ChangeDelay[x][y] = 1;
                ChangeEvent[x][y] = trigger_event;
 
@@ -11114,6 +11657,22 @@ static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
 #if USE_NEW_DELAYED_ACTION
              else if (change->has_action)
              {
+#if USE_FIX_NO_ACTION_AFTER_CHANGE
+               /* if element already changed in this frame, not only prevent
+                  another element change (checked in ChangeElement()), but
+                  also prevent additional element actions for this element */
+
+               if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
+                   !level.use_action_after_change_bug)
+                 continue;
+#endif
+
+
+#if 0
+               printf("::: TRIGGERED CHANGE FOUND: %d ['%s'], %d -- ACTION\n",
+                      element, EL_NAME(element), p);
+#endif
+
                ExecuteCustomElementAction(x, y, element, p);
                PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
              }
@@ -11131,6 +11690,12 @@ static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
          {
            change_done = TRUE;
            change_done_any = TRUE;
+
+#if 0
+           printf("::: TRIGGERED CHANGE FOUND: %d ['%s'], %d -- DONE\n",
+                  element, EL_NAME(element), p);
+#endif
+
          }
        }
       }
@@ -11185,6 +11750,10 @@ static boolean CheckElementChangeExt(int x, int y,
 
   RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
 
+#if 0
+  printf("::: X: trigger_player_bits == %d\n", trigger_player);
+#endif
+
   for (p = 0; p < element_info[element].num_change_pages; p++)
   {
     struct ElementChangeInfo *change = &element_info[element].change_page[p];
@@ -11452,6 +12021,20 @@ static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
   }
 }
 
+static void CheckSingleStepMode(struct PlayerInfo *player)
+{
+  if (tape.single_step && tape.recording && !tape.pausing)
+  {
+    /* as it is called "single step mode", just return to pause mode when the
+       player stopped moving after one tile (or never starts moving at all) */
+    if (!player->is_moving && !player->is_pushing)
+    {
+      TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
+      SnapField(player, 0, 0);                 /* stop snapping */
+    }
+  }
+}
+
 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
 {
   boolean moved = FALSE, snapped = FALSE, dropped = FALSE;
@@ -11479,14 +12062,7 @@ static byte PlayerActions(struct PlayerInfo *player, byte player_action)
       moved = MovePlayer(player, dx, dy);
     }
 
-    if (tape.single_step && tape.recording && !tape.pausing)
-    {
-      if (button1 || (dropped && !moved))
-      {
-       TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
-       SnapField(player, 0, 0);                /* stop snapping */
-      }
-    }
+    CheckSingleStepMode(player);
 
     SetPlayerWaiting(player, FALSE);
 
@@ -11510,6 +12086,8 @@ static byte PlayerActions(struct PlayerInfo *player, byte player_action)
     player->is_dropping_pressed = FALSE;
     player->drop_pressed_delay = 0;
 
+    CheckSingleStepMode(player);
+
     return 0;
   }
 }
@@ -11518,6 +12096,7 @@ static void CheckLevelTime()
 {
   int i;
 
+  /* !!! SAME CODE AS IN "GameActions()" -- FIX THIS !!! */
   if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
   {
     if (level.native_em_level->lev->home == 0) /* all players at home */
@@ -11535,6 +12114,21 @@ static void CheckLevelTime()
        level.native_em_level->ply[3]->alive == 0)      /* all dead */
       AllPlayersGone = TRUE;
   }
+  else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
+  {
+    if (game_sp.LevelSolved &&
+       !game_sp.GameOver)                              /* game won */
+    {
+      PlayerWins(local_player);
+
+      game_sp.GameOver = TRUE;
+
+      AllPlayersGone = TRUE;
+    }
+
+    if (game_sp.GameOver)                              /* game lost */
+      AllPlayersGone = TRUE;
+  }
 
   if (TimeFrames >= FRAMES_PER_SECOND)
   {
@@ -11710,8 +12304,9 @@ void GameActions()
   }
 
   if (game.restart_level)
-    StartGameActions(options.network, setup.autorecord, NEW_RANDOMIZE);
+    StartGameActions(options.network, setup.autorecord, level.random_seed);
 
+  /* !!! SAME CODE AS IN "CheckLevelTime()" -- FIX THIS !!! */
   if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
   {
     if (level.native_em_level->lev->home == 0) /* all players at home */
@@ -11729,6 +12324,21 @@ void GameActions()
        level.native_em_level->ply[3]->alive == 0)      /* all dead */
       AllPlayersGone = TRUE;
   }
+  else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
+  {
+    if (game_sp.LevelSolved &&
+       !game_sp.GameOver)                              /* game won */
+    {
+      PlayerWins(local_player);
+
+      game_sp.GameOver = TRUE;
+
+      AllPlayersGone = TRUE;
+    }
+
+    if (game_sp.GameOver)                              /* game lost */
+      AllPlayersGone = TRUE;
+  }
 
   if (local_player->LevelSolved && !local_player->LevelSolved_GameEnd)
     GameWon();
@@ -11830,10 +12440,26 @@ void GameActions()
   if (tape.recording)
     TapeRecordAction(tape_action);
 
+#if USE_NEW_PLAYER_ASSIGNMENTS
+  {
+    byte mapped_action[MAX_PLAYERS];
+
+    for (i = 0; i < MAX_PLAYERS; i++)
+      mapped_action[i] = stored_player[map_player_action[i]].effective_action;
+
+    for (i = 0; i < MAX_PLAYERS; i++)
+      stored_player[i].effective_action = mapped_action[i];
+  }
+#endif
+
   if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
   {
     GameActions_EM_Main();
   }
+  else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
+  {
+    GameActions_SP_Main();
+  }
   else
   {
     GameActions_RND();
@@ -11856,6 +12482,22 @@ void GameActions_EM_Main()
   AdvanceFrameAndPlayerCounters(-1);   /* advance counters for all players */
 }
 
+void GameActions_SP_Main()
+{
+  byte effective_action[MAX_PLAYERS];
+  boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
+  int i;
+
+  for (i = 0; i < MAX_PLAYERS; i++)
+    effective_action[i] = stored_player[i].effective_action;
+
+  GameActions_SP(effective_action, warp_mode);
+
+  CheckLevelTime();
+
+  AdvanceFrameAndPlayerCounters(-1);   /* advance counters for all players */
+}
+
 void GameActions_RND()
 {
   int magic_wall_x = 0, magic_wall_y = 0;
@@ -12561,14 +13203,17 @@ void GameActions_RND()
        GfxRedraw[x][y] != GFX_REDRAW_NONE)
 #endif
     {
+      /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
+        !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
+
       if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
        DrawLevelField(x, y);
 
       if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
-       DrawLevelFieldCrumbledSand(x, y);
+       DrawLevelFieldCrumbled(x, y);
 
       if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
-       DrawLevelFieldCrumbledSandNeighbours(x, y);
+       DrawLevelFieldCrumbledNeighbours(x, y);
 
       if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
        DrawTwinkleOnField(x, y);
@@ -12662,12 +13307,13 @@ static boolean AllPlayersInVisibleScreen()
 
 void ScrollLevel(int dx, int dy)
 {
-#if 1
+#if 0
+  /* (directly solved in BlitBitmap() now) */
   static Bitmap *bitmap_db_field2 = NULL;
   int softscroll_offset = (setup.soft_scrolling ? TILEX : 0);
   int x, y;
 #else
-  int i, x, y;
+  int x, y;
 #endif
 
 #if 0
@@ -12677,7 +13323,8 @@ void ScrollLevel(int dx, int dy)
     return;
 #endif
 
-#if 1
+#if 0
+  /* (directly solved in BlitBitmap() now) */
   if (bitmap_db_field2 == NULL)
     bitmap_db_field2 = CreateBitmap(FXSIZE, FYSIZE, DEFAULT_DEPTH);
 
@@ -12721,8 +13368,29 @@ void ScrollLevel(int dx, int dy)
 
 #else
 
+#if NEW_TILESIZE
+#if NEW_SCROLL
+  int softscroll_offset = (setup.soft_scrolling ? 2 * TILEX_VAR : 0);
+#else
+  int softscroll_offset = (setup.soft_scrolling ? TILEX_VAR : 0);
+#endif
+#else
+#if NEW_SCROLL
+  int softscroll_offset = (setup.soft_scrolling ? 2 * TILEX : 0);
+#else
   int softscroll_offset = (setup.soft_scrolling ? TILEX : 0);
+#endif
+#endif
 
+#if NEW_TILESIZE
+  BlitBitmap(drawto_field, drawto_field,
+            FX + TILEX_VAR * (dx == -1) - softscroll_offset,
+            FY + TILEY_VAR * (dy == -1) - softscroll_offset,
+            SXSIZE - TILEX_VAR * (dx != 0) + 2 * softscroll_offset,
+            SYSIZE - TILEY_VAR * (dy != 0) + 2 * softscroll_offset,
+            FX + TILEX_VAR * (dx == 1) - softscroll_offset,
+            FY + TILEY_VAR * (dy == 1) - softscroll_offset);
+#else
   BlitBitmap(drawto_field, drawto_field,
             FX + TILEX * (dx == -1) - softscroll_offset,
             FY + TILEY * (dy == -1) - softscroll_offset,
@@ -12731,6 +13399,8 @@ void ScrollLevel(int dx, int dy)
             FX + TILEX * (dx == 1) - softscroll_offset,
             FY + TILEY * (dy == 1) - softscroll_offset);
 #endif
+
+#endif
 #endif
 
   if (dx != 0)
@@ -13280,8 +13950,14 @@ void ScrollPlayer(struct PlayerInfo *player, int mode)
 
     if (Feld[jx][jy] == EL_EXIT_OPEN ||
        Feld[jx][jy] == EL_EM_EXIT_OPEN ||
+#if 1
+       Feld[jx][jy] == EL_EM_EXIT_OPENING ||
+#endif
        Feld[jx][jy] == EL_STEEL_EXIT_OPEN ||
        Feld[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
+#if 1
+       Feld[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
+#endif
        Feld[jx][jy] == EL_SP_EXIT_OPEN ||
        Feld[jx][jy] == EL_SP_EXIT_OPENING)     /* <-- special case */
     {
@@ -13586,6 +14262,8 @@ void TestIfElementTouchesCustomElement(int x, int y)
     /* check for change of border element */
     CheckElementChangeBySide(xx, yy, border_element, center_element,
                             CE_TOUCHING_X, center_side);
+
+    /* (center element cannot be player, so we dont have to check this here) */
   }
 
   for (i = 0; i < NUM_DIRECTIONS; i++)
@@ -13905,6 +14583,7 @@ void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
 
     test_x = bad_x + test_xy[i][0];
     test_y = bad_y + test_xy[i][1];
+
     if (!IN_LEV_FIELD(test_x, test_y))
       continue;
 
@@ -13935,12 +14614,14 @@ void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
 
        kill_x = test_x;
        kill_y = test_y;
+
        break;
       }
       else if (test_element == EL_PENGUIN)
       {
        kill_x = test_x;
        kill_y = test_y;
+
        break;
       }
     }
@@ -13963,6 +14644,63 @@ void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
   }
 }
 
+void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
+{
+  int bad_element = Feld[bad_x][bad_y];
+  int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
+  int dy = (bad_move_dir == MV_UP   ? -1 : bad_move_dir == MV_DOWN  ? +1 : 0);
+  int test_x = bad_x + dx, test_y = bad_y + dy;
+  int test_move_dir, test_element;
+  int kill_x = -1, kill_y = -1;
+
+  if (!IN_LEV_FIELD(test_x, test_y))
+    return;
+
+  test_move_dir =
+    (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
+
+  test_element = Feld[test_x][test_y];
+
+  if (test_move_dir != bad_move_dir)
+  {
+    /* good thing can be player or penguin that does not move away */
+    if (IS_PLAYER(test_x, test_y))
+    {
+      struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
+
+      /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
+        player as being hit when he is moving towards the bad thing, because
+        the "get hit by" condition would be lost after the player stops) */
+      if (player->MovPos != 0 && player->MovDir == bad_move_dir)
+       return;         /* player moves away from bad thing */
+
+      kill_x = test_x;
+      kill_y = test_y;
+    }
+    else if (test_element == EL_PENGUIN)
+    {
+      kill_x = test_x;
+      kill_y = test_y;
+    }
+  }
+
+  if (kill_x != -1 || kill_y != -1)
+  {
+    if (IS_PLAYER(kill_x, kill_y))
+    {
+      struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
+
+      if (player->shield_deadly_time_left > 0 &&
+         !IS_INDESTRUCTIBLE(bad_element))
+       Bang(bad_x, bad_y);
+      else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
+       KillPlayer(player);
+    }
+    else
+      Bang(kill_x, kill_y);
+  }
+}
+
 void TestIfPlayerTouchesBadThing(int x, int y)
 {
   TestIfGoodThingHitsBadThing(x, y, MV_NONE);
@@ -14034,6 +14772,11 @@ void KillPlayer(struct PlayerInfo *player)
   if (!player->active)
     return;
 
+#if 0
+  printf("::: 0: killed == %d, active == %d, reanimated == %d\n",
+        player->killed, player->active, player->reanimated);
+#endif
+
   /* the following code was introduced to prevent an infinite loop when calling
      -> Bang()
      -> CheckTriggeredElementChangeExt()
@@ -14058,8 +14801,31 @@ void KillPlayer(struct PlayerInfo *player)
   player->shield_normal_time_left = 0;
   player->shield_deadly_time_left = 0;
 
+#if 0
+  printf("::: 1: killed == %d, active == %d, reanimated == %d\n",
+        player->killed, player->active, player->reanimated);
+#endif
+
   Bang(jx, jy);
+
+#if 0
+  printf("::: 2: killed == %d, active == %d, reanimated == %d\n",
+        player->killed, player->active, player->reanimated);
+#endif
+
+#if USE_PLAYER_REANIMATION
+#if 1
+  if (player->reanimated)      /* killed player may have been reanimated */
+    player->killed = player->reanimated = FALSE;
+  else
+    BuryPlayer(player);
+#else
+  if (player->killed)          /* player may have been reanimated */
+    BuryPlayer(player);
+#endif
+#else
   BuryPlayer(player);
+#endif
 }
 
 static void KillPlayerUnlessEnemyProtected(int x, int y)
@@ -14328,8 +15094,14 @@ static int DigField(struct PlayerInfo *player,
     }
     else if (element == EL_EXIT_OPEN ||
             element == EL_EM_EXIT_OPEN ||
+#if 1
+            element == EL_EM_EXIT_OPENING ||
+#endif
             element == EL_STEEL_EXIT_OPEN ||
             element == EL_EM_STEEL_EXIT_OPEN ||
+#if 1
+            element == EL_EM_STEEL_EXIT_OPENING ||
+#endif
             element == EL_SP_EXIT_OPEN ||
             element == EL_SP_EXIT_OPENING)
     {
@@ -14707,8 +15479,13 @@ static int DigField(struct PlayerInfo *player,
        PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
                                    ACTION_FILLING);
 
+#if 1
+      if (local_player->sokobanfields_still_needed == 0 &&
+         (game.emulation == EMU_SOKOBAN || level.auto_exit_sokoban))
+#else
       if (local_player->sokobanfields_still_needed == 0 &&
          game.emulation == EMU_SOKOBAN)
+#endif
       {
        PlayerWins(player);
 
@@ -15494,6 +16271,21 @@ void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
   }
 }
 
+void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
+{
+  int element = map_element_SP_to_RND(element_sp);
+  int action = map_action_SP_to_RND(action_sp);
+  int offset = (setup.sp_show_border_elements ? 0 : 1);
+  int x = xx - offset;
+  int y = yy - offset;
+
+#if 0
+  printf("::: %d -> %d\n", element_sp, action_sp);
+#endif
+
+  PlayLevelSoundElementAction(x, y, element, action);
+}
+
 #if 0
 void ChangeTime(int value)
 {
@@ -15717,8 +16509,6 @@ unsigned int RND(int max)
 /* game engine snapshot handling functions                                   */
 /* ------------------------------------------------------------------------- */
 
-#define ARGS_ADDRESS_AND_SIZEOF(x)             (&(x)), (sizeof(x))
-
 struct EngineSnapshotInfo
 {
   /* runtime values for custom element collect score */
@@ -15728,32 +16518,14 @@ struct EngineSnapshotInfo
   int choice_pos[NUM_GROUP_ELEMENTS];
 
   /* runtime values for belt position animations */
-  int belt_graphic[4 * NUM_BELT_PARTS];
-  int belt_anim_mode[4 * NUM_BELT_PARTS];
-};
-
-struct EngineSnapshotNodeInfo
-{
-  void *buffer_orig;
-  void *buffer_copy;
-  int size;
+  int belt_graphic[4][NUM_BELT_PARTS];
+  int belt_anim_mode[4][NUM_BELT_PARTS];
 };
 
 static struct EngineSnapshotInfo engine_snapshot_rnd;
-static ListNode *engine_snapshot_list = NULL;
 static char *snapshot_level_identifier = NULL;
 static int snapshot_level_nr = -1;
 
-void FreeEngineSnapshot()
-{
-  while (engine_snapshot_list != NULL)
-    deleteNodeFromList(&engine_snapshot_list, engine_snapshot_list->key,
-                      checked_free);
-
-  setString(&snapshot_level_identifier, NULL);
-  snapshot_level_nr = -1;
-}
-
 static void SaveEngineSnapshotValues_RND()
 {
   static int belt_base_active_element[4] =
@@ -15787,8 +16559,8 @@ static void SaveEngineSnapshotValues_RND()
       int graphic = el2img(element);
       int anim_mode = graphic_info[graphic].anim_mode;
 
-      engine_snapshot_rnd.belt_graphic[i * 4 + j] = graphic;
-      engine_snapshot_rnd.belt_anim_mode[i * 4 + j] = anim_mode;
+      engine_snapshot_rnd.belt_graphic[i][j] = graphic;
+      engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
     }
   }
 }
@@ -15816,8 +16588,8 @@ static void LoadEngineSnapshotValues_RND()
   {
     for (j = 0; j < NUM_BELT_PARTS; j++)
     {
-      int graphic = engine_snapshot_rnd.belt_graphic[i * 4 + j];
-      int anim_mode = engine_snapshot_rnd.belt_anim_mode[i * 4 + j];
+      int graphic = engine_snapshot_rnd.belt_graphic[i][j];
+      int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
 
       graphic_info[graphic].anim_mode = anim_mode;
     }
@@ -15839,36 +16611,26 @@ static void LoadEngineSnapshotValues_RND()
   }
 }
 
-static void SaveEngineSnapshotBuffer(void *buffer, int size)
-{
-  struct EngineSnapshotNodeInfo *bi =
-    checked_calloc(sizeof(struct EngineSnapshotNodeInfo));
-
-  bi->buffer_orig = buffer;
-  bi->buffer_copy = checked_malloc(size);
-  bi->size = size;
-
-  memcpy(bi->buffer_copy, buffer, size);
-
-  addNodeToList(&engine_snapshot_list, NULL, bi);
-}
-
 void SaveEngineSnapshot()
 {
-  FreeEngineSnapshot();                /* free previous snapshot, if needed */
-
-  if (level_editor_test_game)  /* do not save snapshots from editor */
+  /* do not save snapshots from editor */
+  if (level_editor_test_game)
     return;
 
+  /* free previous snapshot buffers, if needed */
+  FreeEngineSnapshotBuffers();
+
   /* copy some special values to a structure better suited for the snapshot */
 
   SaveEngineSnapshotValues_RND();
   SaveEngineSnapshotValues_EM();
+  SaveEngineSnapshotValues_SP();
 
   /* save values stored in special snapshot structure */
 
   SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
   SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
+  SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
 
   /* save further RND engine values */
 
@@ -15942,7 +16704,7 @@ void SaveEngineSnapshot()
   snapshot_level_nr = level_nr;
 
 #if 0
-  ListNode *node = engine_snapshot_list;
+  ListNode *node = engine_snapshot_list_rnd;
   int num_bytes = 0;
 
   while (node != NULL)
@@ -15956,29 +16718,17 @@ void SaveEngineSnapshot()
 #endif
 }
 
-static void LoadEngineSnapshotBuffer(struct EngineSnapshotNodeInfo *bi)
-{
-  memcpy(bi->buffer_orig, bi->buffer_copy, bi->size);
-}
-
 void LoadEngineSnapshot()
 {
-  ListNode *node = engine_snapshot_list;
-
-  if (engine_snapshot_list == NULL)
-    return;
-
-  while (node != NULL)
-  {
-    LoadEngineSnapshotBuffer((struct EngineSnapshotNodeInfo *)node->content);
+  /* restore generically stored snapshot buffers */
 
-    node = node->next;
-  }
+  LoadEngineSnapshotBuffers();
 
   /* restore special values from snapshot structure */
 
   LoadEngineSnapshotValues_RND();
   LoadEngineSnapshotValues_EM();
+  LoadEngineSnapshotValues_SP();
 }
 
 boolean CheckEngineSnapshot()
@@ -15990,98 +16740,38 @@ boolean CheckEngineSnapshot()
 
 /* ---------- new game button stuff ---------------------------------------- */
 
-/* graphic position values for game buttons */
-#define GAME_BUTTON_XSIZE      30
-#define GAME_BUTTON_YSIZE      30
-#define GAME_BUTTON_XPOS       5
-#define GAME_BUTTON_YPOS       215
-#define SOUND_BUTTON_XPOS      5
-#define SOUND_BUTTON_YPOS      (GAME_BUTTON_YPOS + GAME_BUTTON_YSIZE)
-
-#define GAME_BUTTON_STOP_XPOS  (GAME_BUTTON_XPOS + 0 * GAME_BUTTON_XSIZE)
-#define GAME_BUTTON_PAUSE_XPOS (GAME_BUTTON_XPOS + 1 * GAME_BUTTON_XSIZE)
-#define GAME_BUTTON_PLAY_XPOS  (GAME_BUTTON_XPOS + 2 * GAME_BUTTON_XSIZE)
-#define SOUND_BUTTON_MUSIC_XPOS        (SOUND_BUTTON_XPOS + 0 * GAME_BUTTON_XSIZE)
-#define SOUND_BUTTON_LOOPS_XPOS        (SOUND_BUTTON_XPOS + 1 * GAME_BUTTON_XSIZE)
-#define SOUND_BUTTON_SIMPLE_XPOS (SOUND_BUTTON_XPOS + 2 * GAME_BUTTON_XSIZE)
-
 static struct
 {
-  int *x, *y;
-  int gd_x, gd_y;
+  int graphic;
+  struct Rect *pos;
   int gadget_id;
   char *infotext;
 } gamebutton_info[NUM_GAME_BUTTONS] =
 {
-#if 1
   {
-    &game.button.stop.x,       &game.button.stop.y,
-    GAME_BUTTON_STOP_XPOS,     GAME_BUTTON_YPOS,
-    GAME_CTRL_ID_STOP,
-    "stop game"
+    IMG_GAME_BUTTON_GFX_STOP,          &game.button.stop,
+    GAME_CTRL_ID_STOP,                 "stop game"
   },
   {
-    &game.button.pause.x,      &game.button.pause.y,
-    GAME_BUTTON_PAUSE_XPOS,    GAME_BUTTON_YPOS,
-    GAME_CTRL_ID_PAUSE,
-    "pause game"
+    IMG_GAME_BUTTON_GFX_PAUSE,         &game.button.pause,
+    GAME_CTRL_ID_PAUSE,                        "pause game"
   },
   {
-    &game.button.play.x,       &game.button.play.y,
-    GAME_BUTTON_PLAY_XPOS,     GAME_BUTTON_YPOS,
-    GAME_CTRL_ID_PLAY,
-    "play game"
+    IMG_GAME_BUTTON_GFX_PLAY,          &game.button.play,
+    GAME_CTRL_ID_PLAY,                 "play game"
   },
   {
-    &game.button.sound_music.x,        &game.button.sound_music.y,
-    SOUND_BUTTON_MUSIC_XPOS,   SOUND_BUTTON_YPOS,
-    SOUND_CTRL_ID_MUSIC,
-    "background music on/off"
+    IMG_GAME_BUTTON_GFX_SOUND_MUSIC,   &game.button.sound_music,
+    SOUND_CTRL_ID_MUSIC,               "background music on/off"
   },
   {
-    &game.button.sound_loops.x,        &game.button.sound_loops.y,
-    SOUND_BUTTON_LOOPS_XPOS,   SOUND_BUTTON_YPOS,
-    SOUND_CTRL_ID_LOOPS,
-    "sound loops on/off"
+    IMG_GAME_BUTTON_GFX_SOUND_LOOPS,   &game.button.sound_loops,
+    SOUND_CTRL_ID_LOOPS,               "sound loops on/off"
   },
   {
-    &game.button.sound_simple.x,&game.button.sound_simple.y,
-    SOUND_BUTTON_SIMPLE_XPOS,  SOUND_BUTTON_YPOS,
-    SOUND_CTRL_ID_SIMPLE,
-    "normal sounds on/off"
+    IMG_GAME_BUTTON_GFX_SOUND_SIMPLE,  &game.button.sound_simple,
+    SOUND_CTRL_ID_SIMPLE,              "normal sounds on/off"
   }
-#else
-  {
-    GAME_BUTTON_STOP_XPOS,     GAME_BUTTON_YPOS,
-    GAME_CTRL_ID_STOP,
-    "stop game"
-  },
-  {
-    GAME_BUTTON_PAUSE_XPOS,    GAME_BUTTON_YPOS,
-    GAME_CTRL_ID_PAUSE,
-    "pause game"
-  },
-  {
-    GAME_BUTTON_PLAY_XPOS,     GAME_BUTTON_YPOS,
-    GAME_CTRL_ID_PLAY,
-    "play game"
-  },
-  {
-    SOUND_BUTTON_MUSIC_XPOS,   SOUND_BUTTON_YPOS,
-    SOUND_CTRL_ID_MUSIC,
-    "background music on/off"
-  },
-  {
-    SOUND_BUTTON_LOOPS_XPOS,   SOUND_BUTTON_YPOS,
-    SOUND_CTRL_ID_LOOPS,
-    "sound loops on/off"
-  },
-  {
-    SOUND_BUTTON_SIMPLE_XPOS,  SOUND_BUTTON_YPOS,
-    SOUND_CTRL_ID_SIMPLE,
-    "normal sounds on/off"
-  }
-#endif
 };
 
 void CreateGameButtons()
@@ -16090,23 +16780,22 @@ void CreateGameButtons()
 
   for (i = 0; i < NUM_GAME_BUTTONS; i++)
   {
-    Bitmap *gd_bitmap = graphic_info[IMG_GLOBAL_DOOR].bitmap;
+    struct GraphicInfo *gfx = &graphic_info[gamebutton_info[i].graphic];
+    struct Rect *pos = gamebutton_info[i].pos;
     struct GadgetInfo *gi;
     int button_type;
     boolean checked;
     unsigned long event_mask;
-    int x, y;
-    int gd_xoffset, gd_yoffset;
-    int gd_x1, gd_x2, gd_y1, gd_y2;
+    int gd_x   = gfx->src_x;
+    int gd_y   = gfx->src_y;
+    int gd_xp  = gfx->src_x + gfx->pressed_xoffset;
+    int gd_yp  = gfx->src_y + gfx->pressed_yoffset;
+    int gd_xa  = gfx->src_x + gfx->active_xoffset;
+    int gd_ya  = gfx->src_y + gfx->active_yoffset;
+    int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
+    int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
     int id = i;
 
-    x = DX + *gamebutton_info[i].x;
-    y = DY + *gamebutton_info[i].y;
-    gd_xoffset = gamebutton_info[i].gd_x;
-    gd_yoffset = gamebutton_info[i].gd_y;
-    gd_x1 = DOOR_GFX_PAGEX4 + gd_xoffset;
-    gd_x2 = DOOR_GFX_PAGEX3 + gd_xoffset;
-
     if (id == GAME_CTRL_ID_STOP ||
        id == GAME_CTRL_ID_PAUSE ||
        id == GAME_CTRL_ID_PLAY)
@@ -16114,8 +16803,6 @@ void CreateGameButtons()
       button_type = GD_TYPE_NORMAL_BUTTON;
       checked = FALSE;
       event_mask = GD_EVENT_RELEASED;
-      gd_y1  = DOOR_GFX_PAGEY1 + gd_yoffset - GAME_BUTTON_YSIZE;
-      gd_y2  = DOOR_GFX_PAGEY1 + gd_yoffset - GAME_BUTTON_YSIZE;
     }
     else
     {
@@ -16125,28 +16812,21 @@ void CreateGameButtons()
         (id == SOUND_CTRL_ID_LOOPS && setup.sound_loops) ||
         (id == SOUND_CTRL_ID_SIMPLE && setup.sound_simple) ? TRUE : FALSE);
       event_mask = GD_EVENT_PRESSED;
-      gd_y1  = DOOR_GFX_PAGEY1 + gd_yoffset;
-      gd_y2  = DOOR_GFX_PAGEY1 + gd_yoffset - GAME_BUTTON_YSIZE;
     }
 
     gi = CreateGadget(GDI_CUSTOM_ID, id,
                      GDI_INFO_TEXT, gamebutton_info[i].infotext,
-#if 1
-                     GDI_X, x,
-                     GDI_Y, y,
-#else
-                     GDI_X, DX + gd_xoffset,
-                     GDI_Y, DY + gd_yoffset,
-#endif
-                     GDI_WIDTH, GAME_BUTTON_XSIZE,
-                     GDI_HEIGHT, GAME_BUTTON_YSIZE,
+                     GDI_X, DX + pos->x,
+                     GDI_Y, DY + pos->y,
+                     GDI_WIDTH, gfx->width,
+                     GDI_HEIGHT, gfx->height,
                      GDI_TYPE, button_type,
                      GDI_STATE, GD_BUTTON_UNPRESSED,
                      GDI_CHECKED, checked,
-                     GDI_DESIGN_UNPRESSED, gd_bitmap, gd_x1, gd_y1,
-                     GDI_DESIGN_PRESSED, gd_bitmap, gd_x2, gd_y1,
-                     GDI_ALT_DESIGN_UNPRESSED, gd_bitmap, gd_x1, gd_y2,
-                     GDI_ALT_DESIGN_PRESSED, gd_bitmap, gd_x2, gd_y2,
+                     GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
+                     GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
+                     GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
+                     GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
                      GDI_DIRECT_DRAW, FALSE,
                      GDI_EVENT_MASK, event_mask,
                      GDI_CALLBACK_ACTION, HandleGameButtons,
@@ -16191,10 +16871,8 @@ void RedrawGameButtons()
     RedrawGadget(game_gadget[i]);
 }
 
-static void HandleGameButtons(struct GadgetInfo *gi)
+static void HandleGameButtonsExt(int id)
 {
-  int id = gi->custom_id;
-
   if (game_status != GAME_MODE_PLAYING)
     return;
 
@@ -16240,6 +16918,7 @@ static void HandleGameButtons(struct GadgetInfo *gi)
       if (setup.sound_music)
       { 
        setup.sound_music = FALSE;
+
        FadeMusic();
       }
       else if (audio.music_available)
@@ -16258,6 +16937,7 @@ static void HandleGameButtons(struct GadgetInfo *gi)
       else if (audio.loops_available)
       {
        setup.sound = setup.sound_loops = TRUE;
+
        SetAudioMode(setup.sound);
       }
       break;
@@ -16268,6 +16948,7 @@ static void HandleGameButtons(struct GadgetInfo *gi)
       else if (audio.sound_available)
       {
        setup.sound = setup.sound_simple = TRUE;
+
        SetAudioMode(setup.sound);
       }
       break;
@@ -16276,3 +16957,27 @@ static void HandleGameButtons(struct GadgetInfo *gi)
       break;
   }
 }
+
+static void HandleGameButtons(struct GadgetInfo *gi)
+{
+  HandleGameButtonsExt(gi->custom_id);
+}
+
+void HandleSoundButtonKeys(Key key)
+{
+#if 1
+  if (key == setup.shortcut.sound_simple)
+    ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
+  else if (key == setup.shortcut.sound_loops)
+    ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
+  else if (key == setup.shortcut.sound_music)
+    ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);
+#else
+  if (key == setup.shortcut.sound_simple)
+    HandleGameButtonsExt(SOUND_CTRL_ID_SIMPLE);
+  else if (key == setup.shortcut.sound_loops)
+    HandleGameButtonsExt(SOUND_CTRL_ID_LOOPS);
+  else if (key == setup.shortcut.sound_music)
+    HandleGameButtonsExt(SOUND_CTRL_ID_MUSIC);
+#endif
+}