rnd-20051126-1-src
[rocksndiamonds.git] / src / files.c
index fa71b1771f72e6b4d7c21558cdb5da454061d264..1c9d6e9bacfffabbc897ae265dc1c73a33b4c76f 100644 (file)
@@ -29,7 +29,7 @@
 #define CHUNK_SIZE_NONE                -1      /* do not write chunk size    */
 #define FILE_VERS_CHUNK_SIZE   8       /* size of file version chunk */
 #define LEVEL_HEADER_SIZE      80      /* size of level file header  */
-#define LEVEL_HEADER_UNUSED    1       /* unused level header bytes  */
+#define LEVEL_HEADER_UNUSED    0       /* unused level header bytes  */
 #define LEVEL_CHUNK_CNT2_SIZE  160     /* size of level CNT2 chunk   */
 #define LEVEL_CHUNK_CNT2_UNUSED        11      /* unused CNT2 chunk bytes    */
 #define LEVEL_CHUNK_CNT3_HEADER        16      /* size of level CNT3 header  */
 #define TAPE_COOKIE_TMPL       "ROCKSNDIAMONDS_TAPE_FILE_VERSION_x.x"
 #define SCORE_COOKIE           "ROCKSNDIAMONDS_SCORE_FILE_VERSION_1.2"
 
-/* values for level file type identifier */
-#define LEVEL_FILE_TYPE_UNKNOWN                0
-#define LEVEL_FILE_TYPE_RND            1
-#define LEVEL_FILE_TYPE_BD             2
-#define LEVEL_FILE_TYPE_EM             3
-#define LEVEL_FILE_TYPE_SP             4
-#define LEVEL_FILE_TYPE_DX             5
-#define LEVEL_FILE_TYPE_SB             6
-#define LEVEL_FILE_TYPE_DC             7
-
-#define LEVEL_FILE_TYPE_RND_PACKED     (10 + LEVEL_FILE_TYPE_RND)
-#define LEVEL_FILE_TYPE_EM_PACKED      (10 + LEVEL_FILE_TYPE_EM)
-
-#define IS_SINGLE_LEVEL_FILE(x)                (x < 10)
-#define IS_PACKED_LEVEL_FILE(x)                (x > 10)
+static struct
+{
+  int filetype;
+  char *id;
+}
+filetype_id_list[] =
+{
+  { LEVEL_FILE_TYPE_RND,       "RND"   },
+  { LEVEL_FILE_TYPE_BD,                "BD"    },
+  { LEVEL_FILE_TYPE_EM,                "EM"    },
+  { LEVEL_FILE_TYPE_SP,                "SP"    },
+  { LEVEL_FILE_TYPE_DX,                "DX"    },
+  { LEVEL_FILE_TYPE_SB,                "SB"    },
+  { LEVEL_FILE_TYPE_DC,                "DC"    },
+  { -1,                                NULL    },
+};
 
 
 /* ========================================================================= */
@@ -87,11 +88,12 @@ void setElementChangePages(struct ElementInfo *ei, int change_pages)
 
 void setElementChangeInfoToDefaults(struct ElementChangeInfo *change)
 {
-  int x, y;
+  int i, x, y;
 
   change->can_change = FALSE;
 
-  change->events = CE_BITMASK_DEFAULT;
+  for (i = 0; i < NUM_CHANGE_EVENTS; i++)
+    change->has_event[i] = FALSE;
 
   change->trigger_player = CH_PLAYER_ANY;
   change->trigger_side = CH_SIDE_ANY;
@@ -112,6 +114,11 @@ void setElementChangeInfoToDefaults(struct ElementChangeInfo *change)
   change->random_percentage = 100;
   change->replace_when = CP_WHEN_EMPTY;
 
+  change->has_action = FALSE;
+  change->action_type = CA_NO_ACTION;
+  change->action_mode = CA_MODE_UNDEFINED;
+  change->action_arg = CA_ARG_UNDEFINED;
+
   for (x = 0; x < 3; x++)
     for (y = 0; y < 3; y++)
       change->target_content[x][y] = EL_EMPTY_SPACE;
@@ -127,9 +134,14 @@ void setElementChangeInfoToDefaults(struct ElementChangeInfo *change)
 static void setLevelInfoToDefaults(struct LevelInfo *level)
 {
   static boolean clipboard_elements_initialized = FALSE;
-
   int i, j, x, y;
 
+  setLevelInfoToDefaults_EM();
+
+  level->native_em_level = &native_em_level;
+
+  level->game_engine_type = GAME_ENGINE_TYPE_RND;
+
   level->file_version = FILE_VERSION_ACTUAL;
   level->game_version = GAME_VERSION_ACTUAL;
 
@@ -159,18 +171,38 @@ static void setLevelInfoToDefaults(struct LevelInfo *level)
   level->double_speed = FALSE;
   level->initial_gravity = FALSE;
   level->em_slippery_gems = FALSE;
-  level->block_last_field = FALSE;
-  level->sp_block_last_field = TRUE;
   level->instant_relocation = FALSE;
   level->can_pass_to_walkable = FALSE;
   level->grow_into_diggable = TRUE;
 
+  level->block_last_field = FALSE;     /* EM does not block by default */
+  level->sp_block_last_field = TRUE;   /* SP blocks the last field */
+
   level->can_move_into_acid_bits = ~0; /* everything can move into acid */
-  level->dont_collide_with_bits = ~0;  /* always deadly when colliding  */
+  level->dont_collide_with_bits = ~0;  /* always deadly when colliding */
 
   level->use_spring_bug = FALSE;
   level->use_step_counter = FALSE;
 
+  /* values for the new EMC elements */
+  level->android_move_time = 10;
+  level->android_clone_time = 10;
+  level->ball_random = FALSE;
+  level->ball_state_initial = FALSE;
+  level->ball_time = 10;
+  level->lenses_score = 10;
+  level->magnify_score = 10;
+  level->slurp_score = 10;
+  level->lenses_time = 10;
+  level->magnify_time = 10;
+  level->wind_direction_initial = MV_NO_MOVING;
+  for (i = 0; i < NUM_MAGIC_BALL_CONTENTS; i++)
+    for (x = 0; x < 3; x++)
+      for (y = 0; y < 3; y++)
+       level->ball_content[i][x][y] = EL_EMPTY;
+  for (i = 0; i < 16; i++)
+    level->android_array[i] = FALSE;
+
   level->use_custom_template = FALSE;
 
   for (i = 0; i < MAX_LEVEL_NAME_LEN; i++)
@@ -228,6 +260,8 @@ static void setLevelInfoToDefaults(struct LevelInfo *level)
 
       element_info[element].use_gfx_element = FALSE;
       element_info[element].gfx_element = EL_EMPTY_SPACE;
+
+      element_info[element].modified_settings = FALSE;
     }
 
     if (IS_CUSTOM_ELEMENT(element) ||
@@ -259,6 +293,9 @@ static void setLevelInfoToDefaults(struct LevelInfo *level)
       element_info[element].explosion_delay = 16;
       element_info[element].ignition_delay = 8;
 
+      element_info[element].counter_initial = 0;
+      element_info[element].counter = 0;
+
       for (x = 0; x < 3; x++)
        for (y = 0; y < 3; y++)
          element_info[element].content[x][y] = EL_EMPTY_SPACE;
@@ -282,8 +319,6 @@ static void setLevelInfoToDefaults(struct LevelInfo *level)
 
       /* now set default properties */
       SET_PROPERTY(element, EP_CAN_MOVE_INTO_ACID, TRUE);
-
-      element_info[element].modified_settings = FALSE;
     }
 
     if (IS_GROUP_ELEMENT(element) ||
@@ -310,6 +345,8 @@ static void setLevelInfoToDefaults(struct LevelInfo *level)
 
   level->no_valid_file = FALSE;
 
+  level->changed = FALSE;
+
   if (leveldir_current == NULL)                /* only when dumping level */
     return;
 
@@ -398,34 +435,14 @@ static int getFileTypeFromBasename(char *basename)
   return LEVEL_FILE_TYPE_UNKNOWN;
 }
 
-static char *getSingleLevelBasename(int nr, int type)
+static char *getSingleLevelBasename(int nr)
 {
   static char basename[MAX_FILENAME_LEN];
-  char *level_filename = getStringCopy(leveldir_current->level_filename);
-
-  if (level_filename == NULL)
-    level_filename = getStringCat2("%03d.", LEVELFILE_EXTENSION);
 
-  switch (type)
-  {
-    case LEVEL_FILE_TYPE_RND:
-      if (nr < 0)
-       sprintf(basename, "template.%s", LEVELFILE_EXTENSION);
-      else
-       sprintf(basename, "%03d.%s", nr, LEVELFILE_EXTENSION);
-      break;
-
-    case LEVEL_FILE_TYPE_EM:
-      sprintf(basename, "%d", nr);
-      break;
-
-    case LEVEL_FILE_TYPE_UNKNOWN:
-    default:
-      sprintf(basename, level_filename, nr);
-      break;
-  }
-
-  free(level_filename);
+  if (nr < 0)
+    sprintf(basename, "template.%s", LEVELFILE_EXTENSION);
+  else
+    sprintf(basename, "%03d.%s", nr, LEVELFILE_EXTENSION);
 
   return basename;
 }
@@ -468,9 +485,9 @@ static char *getPackedLevelBasename(int type)
   return basename;
 }
 
-static char *getSingleLevelFilename(int nr, int type)
+static char *getSingleLevelFilename(int nr)
 {
-  return getLevelFilenameFromBasename(getSingleLevelBasename(nr, type));
+  return getLevelFilenameFromBasename(getSingleLevelBasename(nr));
 }
 
 #if 0
@@ -482,9 +499,10 @@ static char *getPackedLevelFilename(int type)
 
 char *getDefaultLevelFilename(int nr)
 {
-  return getSingleLevelFilename(nr, LEVEL_FILE_TYPE_RND);
+  return getSingleLevelFilename(nr);
 }
 
+#if 0
 static void setLevelFileInfo_SingleLevelFilename(struct LevelFileInfo *lfi,
                                                 int type)
 {
@@ -493,6 +511,23 @@ static void setLevelFileInfo_SingleLevelFilename(struct LevelFileInfo *lfi,
   lfi->basename = getSingleLevelBasename(lfi->nr, lfi->type);
   lfi->filename = getLevelFilenameFromBasename(lfi->basename);
 }
+#endif
+
+static void setLevelFileInfo_FormatLevelFilename(struct LevelFileInfo *lfi,
+                                                int type, char *format, ...)
+{
+  static char basename[MAX_FILENAME_LEN];
+  va_list ap;
+
+  va_start(ap, format);
+  vsprintf(basename, format, ap);
+  va_end(ap);
+
+  lfi->type = type;
+  lfi->packed = FALSE;
+  lfi->basename = basename;
+  lfi->filename = getLevelFilenameFromBasename(lfi->basename);
+}
 
 static void setLevelFileInfo_PackedLevelFilename(struct LevelFileInfo *lfi,
                                                 int type)
@@ -503,31 +538,94 @@ static void setLevelFileInfo_PackedLevelFilename(struct LevelFileInfo *lfi,
   lfi->filename = getLevelFilenameFromBasename(lfi->basename);
 }
 
+static int getFiletypeFromID(char *filetype_id)
+{
+  char *filetype_id_lower;
+  int filetype = LEVEL_FILE_TYPE_UNKNOWN;
+  int i;
+
+  if (filetype_id == NULL)
+    return LEVEL_FILE_TYPE_UNKNOWN;
+
+  filetype_id_lower = getStringToLower(filetype_id);
+
+  for (i = 0; filetype_id_list[i].id != NULL; i++)
+  {
+    char *id_lower = getStringToLower(filetype_id_list[i].id);
+    
+    if (strcmp(filetype_id_lower, id_lower) == 0)
+      filetype = filetype_id_list[i].filetype;
+
+    free(id_lower);
+
+    if (filetype != LEVEL_FILE_TYPE_UNKNOWN)
+      break;
+  }
+
+  free(filetype_id_lower);
+
+  return filetype;
+}
+
 static void determineLevelFileInfo_Filename(struct LevelFileInfo *lfi)
 {
+  int nr = lfi->nr;
+
   /* special case: level number is negative => check for level template file */
-  if (lfi->nr < 0)
+  if (nr < 0)
   {
-    setLevelFileInfo_SingleLevelFilename(lfi, LEVEL_FILE_TYPE_RND);
+    setLevelFileInfo_FormatLevelFilename(lfi, LEVEL_FILE_TYPE_RND,
+                                        "template.%s", LEVELFILE_EXTENSION);
 
+    /* no fallback if template file not existing */
     return;
   }
 
+  /* special case: check for file name/pattern specified in "levelinfo.conf" */
   if (leveldir_current->level_filename != NULL)
   {
-    /* check for file name/pattern specified in "levelinfo.conf" */
-    setLevelFileInfo_SingleLevelFilename(lfi, LEVEL_FILE_TYPE_UNKNOWN);
+    int filetype = getFiletypeFromID(leveldir_current->level_filetype);
+
+    setLevelFileInfo_FormatLevelFilename(lfi, filetype,
+                                        leveldir_current->level_filename, nr);
     if (fileExists(lfi->filename))
       return;
   }
 
   /* check for native Rocks'n'Diamonds level file */
-  setLevelFileInfo_SingleLevelFilename(lfi, LEVEL_FILE_TYPE_RND);
+  setLevelFileInfo_FormatLevelFilename(lfi, LEVEL_FILE_TYPE_RND,
+                                      "%03d.%s", nr, LEVELFILE_EXTENSION);
+  if (fileExists(lfi->filename))
+    return;
+
+  /* check for Emerald Mine level file (V1) */
+  setLevelFileInfo_FormatLevelFilename(lfi, LEVEL_FILE_TYPE_EM, "a%c%c",
+                                      'a' + (nr / 10) % 26, '0' + nr % 10);
+  if (fileExists(lfi->filename))
+    return;
+  setLevelFileInfo_FormatLevelFilename(lfi, LEVEL_FILE_TYPE_EM, "A%c%c",
+                                      'A' + (nr / 10) % 26, '0' + nr % 10);
+  if (fileExists(lfi->filename))
+    return;
+
+  /* check for Emerald Mine level file (V2 to V5) */
+  setLevelFileInfo_FormatLevelFilename(lfi, LEVEL_FILE_TYPE_EM, "%d", nr);
   if (fileExists(lfi->filename))
     return;
 
-  /* check for classic Emerald Mine level file */
-  setLevelFileInfo_SingleLevelFilename(lfi, LEVEL_FILE_TYPE_EM);
+  /* check for Emerald Mine level file (V6 / single mode) */
+  setLevelFileInfo_FormatLevelFilename(lfi, LEVEL_FILE_TYPE_EM, "%02ds", nr);
+  if (fileExists(lfi->filename))
+    return;
+  setLevelFileInfo_FormatLevelFilename(lfi, LEVEL_FILE_TYPE_EM, "%02dS", nr);
+  if (fileExists(lfi->filename))
+    return;
+
+  /* check for Emerald Mine level file (V6 / teamwork mode) */
+  setLevelFileInfo_FormatLevelFilename(lfi, LEVEL_FILE_TYPE_EM, "%02dt", nr);
+  if (fileExists(lfi->filename))
+    return;
+  setLevelFileInfo_FormatLevelFilename(lfi, LEVEL_FILE_TYPE_EM, "%02dT", nr);
   if (fileExists(lfi->filename))
     return;
 
@@ -536,8 +634,9 @@ static void determineLevelFileInfo_Filename(struct LevelFileInfo *lfi)
   if (fileExists(lfi->filename))
     return;
 
-  /* no known level file found -- try to use default values */
-  setLevelFileInfo_SingleLevelFilename(lfi, LEVEL_FILE_TYPE_UNKNOWN);
+  /* no known level file found -- use default values (and fail later) */
+  setLevelFileInfo_FormatLevelFilename(lfi, LEVEL_FILE_TYPE_RND,
+                                      "%03d.%s", nr, LEVELFILE_EXTENSION);
 }
 
 static void determineLevelFileInfo_Filetype(struct LevelFileInfo *lfi)
@@ -546,22 +645,17 @@ static void determineLevelFileInfo_Filetype(struct LevelFileInfo *lfi)
     lfi->type = getFileTypeFromBasename(lfi->basename);
 }
 
-static struct LevelFileInfo *getLevelFileInfo(int nr)
+static void setLevelFileInfo(struct LevelFileInfo *level_file_info, int nr)
 {
-  static struct LevelFileInfo level_file_info;
-
   /* always start with reliable default values */
-  setFileInfoToDefaults(&level_file_info);
-
-  level_file_info.nr = nr;     /* set requested level number */
+  setFileInfoToDefaults(level_file_info);
 
-  determineLevelFileInfo_Filename(&level_file_info);
-  determineLevelFileInfo_Filetype(&level_file_info);
+  level_file_info->nr = nr;    /* set requested level number */
 
-  return &level_file_info;
+  determineLevelFileInfo_Filename(level_file_info);
+  determineLevelFileInfo_Filetype(level_file_info);
 }
 
-
 /* ------------------------------------------------------------------------- */
 /* functions for loading R'n'D level                                         */
 /* ------------------------------------------------------------------------- */
@@ -570,7 +664,6 @@ int getMappedElement(int element)
 {
   /* remap some (historic, now obsolete) elements */
 
-#if 1
   switch (element)
   {
     case EL_PLAYER_OBSOLETE:
@@ -613,18 +706,6 @@ int getMappedElement(int element)
       }
       break;
   }
-#else
-  if (element >= NUM_FILE_ELEMENTS)
-  {
-    Error(ERR_WARN, "invalid level element %d", element);
-
-    element = EL_UNKNOWN;
-  }
-  else if (element == EL_PLAYER_OBSOLETE)
-    element = EL_PLAYER_1;
-  else if (element == EL_KEY_OBSOLETE)
-    element = EL_KEY_1;
-#endif
 
   return element;
 }
@@ -709,6 +790,8 @@ static int LoadLevel_HEAD(FILE *file, int chunk_size, struct LevelInfo *level)
   level->can_pass_to_walkable  = (getFile8Bit(file) == 1 ? TRUE : FALSE);
   level->grow_into_diggable    = (getFile8Bit(file) == 1 ? TRUE : FALSE);
 
+  level->game_engine_type      = getFile8Bit(file);
+
   ReadUnusedBytesFromFile(file, LEVEL_HEADER_UNUSED);
 
   return chunk_size;
@@ -937,6 +1020,7 @@ static int LoadLevel_CUS3(FILE *file, int chunk_size, struct LevelInfo *level)
   for (i = 0; i < num_changed_custom_elements; i++)
   {
     int element = getFile16BitBE(file);
+    unsigned long event_bits;
 
     if (!IS_CUSTOM_ELEMENT(element))
     {
@@ -975,7 +1059,10 @@ static int LoadLevel_CUS3(FILE *file, int chunk_size, struct LevelInfo *level)
        element_info[element].content[x][y] =
          getMappedElement(getFile16BitBE(file));
 
-    element_info[element].change->events = getFile32BitBE(file);
+    event_bits = getFile32BitBE(file);
+    for (j = 0; j < NUM_CHANGE_EVENTS; j++)
+      if (event_bits & (1 << j))
+       element_info[element].change->has_event[j] = TRUE;
 
     element_info[element].change->target_element =
       getMappedElement(getFile16BitBE(file));
@@ -1017,7 +1104,7 @@ static int LoadLevel_CUS4(FILE *file, int chunk_size, struct LevelInfo *level)
   struct ElementInfo *ei;
   int chunk_size_expected;
   int element;
-  int i, x, y;
+  int i, j, x, y;
 
   element = getFile16BitBE(file);
 
@@ -1099,11 +1186,15 @@ static int LoadLevel_CUS4(FILE *file, int chunk_size, struct LevelInfo *level)
   for (i = 0; i < ei->num_change_pages; i++)
   {
     struct ElementChangeInfo *change = &ei->change_page[i];
+    unsigned long event_bits;
 
     /* always start with reliable default values */
     setElementChangeInfoToDefaults(change);
 
-    change->events = getFile32BitBE(file);
+    event_bits = getFile32BitBE(file);
+    for (j = 0; j < NUM_CHANGE_EVENTS; j++)
+      if (event_bits & (1 << j))
+       change->has_event[j] = TRUE;
 
     change->target_element = getMappedElement(getFile16BitBE(file));
 
@@ -1129,21 +1220,19 @@ static int LoadLevel_CUS4(FILE *file, int chunk_size, struct LevelInfo *level)
 
     change->trigger_side = getFile8Bit(file);
 
-#if 1
     change->trigger_player = getFile8Bit(file);
     change->trigger_page = getFile8Bit(file);
 
     change->trigger_page = (change->trigger_page == CH_PAGE_ANY_FILE ?
                            CH_PAGE_ANY : (1 << change->trigger_page));
 
-    /* some free bytes for future change property values and padding */
-    ReadUnusedBytesFromFile(file, 6);
-
-#else
+    change->has_action = getFile8Bit(file);
+    change->action_type = getFile8Bit(file);
+    change->action_mode = getFile8Bit(file);
+    change->action_arg = getFile16BitBE(file);
 
     /* some free bytes for future change property values and padding */
-    ReadUnusedBytesFromFile(file, 8);
-#endif
+    ReadUnusedBytesFromFile(file, 1);
   }
 
   /* mark this custom element as modified */
@@ -1336,6 +1425,8 @@ static void LoadLevelFromFileInfo_RND(struct LevelInfo *level,
 /* functions for loading EM level                                            */
 /* ------------------------------------------------------------------------- */
 
+#if 0
+
 static int map_em_element_yam(int element)
 {
   switch (element)
@@ -1648,16 +1739,13 @@ static int map_em_element_field(int element)
 #define EM_LEVEL_XSIZE                 64
 #define EM_LEVEL_YSIZE                 32
 
-static void LoadLevelFromFileInfo_EM(struct LevelInfo *level,
-                                    struct LevelFileInfo *level_file_info)
+static void OLD_LoadLevelFromFileInfo_EM(struct LevelInfo *level,
+                                        struct LevelFileInfo *level_file_info)
 {
   char *filename = level_file_info->filename;
   FILE *file;
   unsigned char leveldata[EM_LEVEL_SIZE];
   unsigned char *header = &leveldata[EM_LEVEL_XSIZE * EM_LEVEL_YSIZE];
-  unsigned char code0 = 0x65;
-  unsigned char code1 = 0x11;
-  boolean level_is_crypted = FALSE;
   int nr = level_file_info->nr;
   int i, x, y;
 
@@ -1670,7 +1758,7 @@ static void LoadLevelFromFileInfo_EM(struct LevelInfo *level,
     return;
   }
 
-  for(i = 0; i < EM_LEVEL_SIZE; i++)
+  for (i = 0; i < EM_LEVEL_SIZE; i++)
     leveldata[i] = fgetc(file);
 
   fclose(file);
@@ -1681,20 +1769,20 @@ static void LoadLevelFromFileInfo_EM(struct LevelInfo *level,
   if ((leveldata[0] == 0xf1 ||
        leveldata[0] == 0xf5) && leveldata[2] == 0xe7 && leveldata[3] == 0xee)
   {
-    level_is_crypted = TRUE;
+    unsigned char code0 = 0x65;
+    unsigned char code1 = 0x11;
 
     if (leveldata[0] == 0xf5)  /* error in crypted Emerald Mine 2 levels */
       leveldata[0] = 0xf1;
-  }
 
-  if (level_is_crypted)                /* decode crypted level data */
-  {
-    for(i = 0; i < EM_LEVEL_SIZE; i++)
+    /* decode crypted level data */
+
+    for (i = 0; i < EM_LEVEL_SIZE; i++)
     {
       leveldata[i] ^= code0;
       leveldata[i] -= code1;
 
-      code0  = (code0 + 7) & 0xff;
+      code0 = (code0 + 7) & 0xff;
     }
   }
 
@@ -1727,9 +1815,9 @@ static void LoadLevelFromFileInfo_EM(struct LevelInfo *level,
 
   level->num_yamyam_contents = 4;
 
-  for(i = 0; i < level->num_yamyam_contents; i++)
-    for(y = 0; y < 3; y++)
-      for(x = 0; x < 3; x++)
+  for (i = 0; i < level->num_yamyam_contents; i++)
+    for (y = 0; y < 3; y++)
+      for (x = 0; x < 3; x++)
        level->yamyam_content[i][x][y] =
          map_em_element_yam(header[i * 9 + y * 3 + x]);
 
@@ -1757,6 +1845,229 @@ static void LoadLevelFromFileInfo_EM(struct LevelInfo *level,
   level->field[x][y] = EL_PLAYER_2;
 }
 
+#endif
+
+void CopyNativeLevel_RND_to_EM(struct LevelInfo *level)
+{
+  static int ball_xy[8][2] =
+  {
+    { 0, 0 },
+    { 1, 0 },
+    { 2, 0 },
+    { 0, 1 },
+    { 2, 1 },
+    { 0, 2 },
+    { 1, 2 },
+    { 2, 2 },
+  };
+  struct LevelInfo_EM *level_em = level->native_em_level;
+  struct LEVEL *lev = level_em->lev;
+  struct PLAYER *ply1 = level_em->ply1;
+  struct PLAYER *ply2 = level_em->ply2;
+  int i, j, x, y;
+
+  lev->width  = MIN(level->fieldx, EM_MAX_CAVE_WIDTH);
+  lev->height = MIN(level->fieldy, EM_MAX_CAVE_HEIGHT);
+
+  lev->time_seconds     = level->time;
+  lev->required_initial = level->gems_needed;
+
+  lev->emerald_score   = level->score[SC_EMERALD];
+  lev->diamond_score   = level->score[SC_DIAMOND];
+  lev->alien_score     = level->score[SC_ROBOT];
+  lev->tank_score      = level->score[SC_SPACESHIP];
+  lev->bug_score       = level->score[SC_BUG];
+  lev->eater_score     = level->score[SC_YAMYAM];
+  lev->nut_score       = level->score[SC_NUT];
+  lev->dynamite_score  = level->score[SC_DYNAMITE];
+  lev->key_score       = level->score[SC_KEY];
+  lev->exit_score      = level->score[SC_TIME_BONUS];
+
+  for (i = 0; i < MAX_ELEMENT_CONTENTS; i++)
+    for (y = 0; y < 3; y++)
+      for (x = 0; x < 3; x++)
+       lev->eater_array[i][y * 3 + x] =
+         map_element_RND_to_EM(level->yamyam_content[i][x][y]);
+
+  lev->amoeba_time             = level->amoeba_speed;
+  lev->wonderwall_time_initial = level->time_magic_wall;
+  lev->wheel_time              = level->time_wheel;
+
+  lev->android_move_time       = level->android_move_time;
+  lev->android_clone_time      = level->android_clone_time;
+  lev->ball_random             = level->ball_random;
+  lev->ball_state_initial      = level->ball_state_initial;
+  lev->ball_time               = level->ball_time;
+
+  lev->lenses_score            = level->lenses_score;
+  lev->magnify_score           = level->magnify_score;
+  lev->slurp_score             = level->slurp_score;
+
+  lev->lenses_time             = level->lenses_time;
+  lev->magnify_time            = level->magnify_time;
+  lev->wind_direction_initial  = level->wind_direction_initial;
+
+  for (i = 0; i < NUM_MAGIC_BALL_CONTENTS; i++)
+    for (j = 0; j < 8; j++)
+      lev->ball_array[i][j] =
+       map_element_RND_to_EM(level->
+                             ball_content[i][ball_xy[j][0]][ball_xy[j][1]]);
+
+  for (i = 0; i < 16; i++)
+    lev->android_array[i] = FALSE;     /* !!! YET TO COME !!! */
+
+  /* first fill the complete playfield with the default border element */
+  for (y = 0; y < EM_MAX_CAVE_HEIGHT; y++)
+    for (x = 0; x < EM_MAX_CAVE_WIDTH; x++)
+      level_em->cave[x][y] = ZBORDER;
+
+  /* then copy the real level contents from level file into the playfield */
+  for (y = 0; y < lev->height; y++) for (x = 0; x < lev->width; x++)
+  {
+    int new_element = map_element_RND_to_EM(level->field[x][y]);
+
+    if (level->field[x][y] == EL_AMOEBA_DEAD)
+      new_element = map_element_RND_to_EM(EL_AMOEBA_WET);
+
+    level_em->cave[x + 1][y + 1] = new_element;
+  }
+
+  ply1->x_initial = 0;
+  ply1->y_initial = 0;
+
+  ply2->x_initial = 0;
+  ply2->y_initial = 0;
+
+  /* initialize player positions and delete players from the playfield */
+  for (y = 0; y < lev->height; y++) for (x = 0; x < lev->width; x++)
+  {
+    if (level->field[x][y] == EL_PLAYER_1)
+    {
+      ply1->x_initial = x + 1;
+      ply1->y_initial = y + 1;
+      level_em->cave[x + 1][y + 1] = map_element_RND_to_EM(EL_EMPTY);
+    }
+    else if (level->field[x][y] == EL_PLAYER_2)
+    {
+      ply2->x_initial = x + 1;
+      ply2->y_initial = y + 1;
+      level_em->cave[x + 1][y + 1] = map_element_RND_to_EM(EL_EMPTY);
+    }
+  }
+}
+
+void CopyNativeLevel_EM_to_RND(struct LevelInfo *level)
+{
+  static int ball_xy[8][2] =
+  {
+    { 0, 0 },
+    { 1, 0 },
+    { 2, 0 },
+    { 0, 1 },
+    { 2, 1 },
+    { 0, 2 },
+    { 1, 2 },
+    { 2, 2 },
+  };
+  struct LevelInfo_EM *level_em = level->native_em_level;
+  struct LEVEL *lev = level_em->lev;
+  struct PLAYER *ply1 = level_em->ply1;
+  struct PLAYER *ply2 = level_em->ply2;
+  int i, j, x, y;
+
+  level->fieldx = MIN(lev->width,  MAX_LEV_FIELDX);
+  level->fieldy = MIN(lev->height, MAX_LEV_FIELDY);
+
+  level->time        = lev->time_seconds;
+  level->gems_needed = lev->required_initial;
+
+  sprintf(level->name, "Level %d", level->file_info.nr);
+
+  level->score[SC_EMERALD]     = lev->emerald_score;
+  level->score[SC_DIAMOND]     = lev->diamond_score;
+  level->score[SC_ROBOT]       = lev->alien_score;
+  level->score[SC_SPACESHIP]   = lev->tank_score;
+  level->score[SC_BUG]         = lev->bug_score;
+  level->score[SC_YAMYAM]      = lev->eater_score;
+  level->score[SC_NUT]         = lev->nut_score;
+  level->score[SC_DYNAMITE]    = lev->dynamite_score;
+  level->score[SC_KEY]         = lev->key_score;
+  level->score[SC_TIME_BONUS]  = lev->exit_score;
+
+  level->num_yamyam_contents = MAX_ELEMENT_CONTENTS;
+
+  for (i = 0; i < level->num_yamyam_contents; i++)
+    for (y = 0; y < 3; y++)
+      for (x = 0; x < 3; x++)
+       level->yamyam_content[i][x][y] =
+         map_element_EM_to_RND(lev->eater_array[i][y * 3 + x]);
+
+  level->amoeba_speed          = lev->amoeba_time;
+  level->time_magic_wall       = lev->wonderwall_time_initial;
+  level->time_wheel            = lev->wheel_time;
+
+  level->android_move_time     = lev->android_move_time;
+  level->android_clone_time    = lev->android_clone_time;
+  level->ball_random           = lev->ball_random;
+  level->ball_state_initial    = lev->ball_state_initial;
+  level->ball_time             = lev->ball_time;
+
+  level->lenses_score          = lev->lenses_score;
+  level->magnify_score         = lev->magnify_score;
+  level->slurp_score           = lev->slurp_score;
+
+  level->lenses_time           = lev->lenses_time;
+  level->magnify_time          = lev->magnify_time;
+  level->wind_direction_initial        = lev->wind_direction_initial;
+
+  for (i = 0; i < NUM_MAGIC_BALL_CONTENTS; i++)
+    for (j = 0; j < 8; j++)
+      level->ball_content[i][ball_xy[j][0]][ball_xy[j][1]] =
+       map_element_EM_to_RND(lev->ball_array[i][j]);
+
+  for (i = 0; i < 16; i++)
+    level->android_array[i] = FALSE;   /* !!! YET TO COME !!! */
+
+  /* convert the playfield (some elements need special treatment) */
+  for (y = 0; y < level->fieldy; y++) for (x = 0; x < level->fieldx; x++)
+  {
+    int new_element = map_element_EM_to_RND(level_em->cave[x + 1][y + 1]);
+
+    if (new_element == EL_AMOEBA_WET && level->amoeba_speed == 0)
+      new_element = EL_AMOEBA_DEAD;
+
+    level->field[x][y] = new_element;
+  }
+
+  /* in case of both players set to the same field, use the first player */
+  level->field[ply2->x_initial - 1][ply2->y_initial - 1] = EL_PLAYER_2;
+  level->field[ply1->x_initial - 1][ply1->y_initial - 1] = EL_PLAYER_1;
+
+#if 0
+  printf("::: native Emerald Mine file version: %d\n", level_em->file_version);
+#endif
+}
+
+static void LoadLevelFromFileInfo_EM(struct LevelInfo *level,
+                                    struct LevelFileInfo *level_file_info)
+{
+  if (!LoadNativeLevel_EM(level_file_info->filename))
+    level->no_valid_file = TRUE;
+}
+
+void CopyNativeLevel_RND_to_Native(struct LevelInfo *level)
+{
+  if (level->game_engine_type == GAME_ENGINE_TYPE_EM)
+    CopyNativeLevel_RND_to_EM(level);
+}
+
+void CopyNativeLevel_Native_to_RND(struct LevelInfo *level)
+{
+  if (level->game_engine_type == GAME_ENGINE_TYPE_EM)
+    CopyNativeLevel_EM_to_RND(level);
+}
+
+
 /* ------------------------------------------------------------------------- */
 /* functions for loading SP level                                            */
 /* ------------------------------------------------------------------------- */
@@ -1770,8 +2081,12 @@ static void LoadLevelFromFileInfo_EM(struct LevelInfo *level,
 static void LoadLevelFromFileStream_SP(FILE *file, struct LevelInfo *level,
                                       int nr)
 {
+  int num_special_ports;
   int i, x, y;
 
+  /* for details of the Supaplex level format, see Herman Perk's Supaplex
+     documentation file "SPFIX63.DOC" from his Supaplex "SpeedFix" package */
+
   /* read level body (width * height == 60 * 24 tiles == 1440 bytes) */
   for (y = 0; y < SP_LEVEL_YSIZE; y++)
   {
@@ -1796,28 +2111,104 @@ static void LoadLevelFromFileStream_SP(FILE *file, struct LevelInfo *level,
     }
   }
 
-  ReadUnusedBytesFromFile(file, 4);
+  ReadUnusedBytesFromFile(file, 4);    /* (not used by Supaplex engine) */
 
-  /* Initial gravitation: 1 == "on", anything else (0) == "off" */
+  /* initial gravity: 1 == "on", anything else (0) == "off" */
   level->initial_gravity = (fgetc(file) == 1 ? TRUE : FALSE);
 
-  ReadUnusedBytesFromFile(file, 1);
+  ReadUnusedBytesFromFile(file, 1);    /* (not used by Supaplex engine) */
 
   /* level title in uppercase letters, padded with dashes ("-") (23 bytes) */
   for (i = 0; i < SP_LEVEL_NAME_LEN; i++)
     level->name[i] = fgetc(file);
   level->name[SP_LEVEL_NAME_LEN] = '\0';
 
-  /* initial "freeze zonks": 2 == "on", anything else (0) == "off" */
-  ReadUnusedBytesFromFile(file, 1);    /* !!! NOT SUPPORTED YET !!! */
+  /* initial "freeze zonks": 2 == "on", anything else (0, 1) == "off" */
+  ReadUnusedBytesFromFile(file, 1);    /* (not used by R'n'D engine) */
 
   /* number of infotrons needed; 0 means that Supaplex will count the total
-     amount of infotrons in the level and use the low byte of that number.
+     amount of infotrons in the level and use the low byte of that number
      (a multiple of 256 infotrons will result in "0 infotrons needed"!) */
   level->gems_needed = fgetc(file);
 
-  /* information about special gravity port entries */
-  ReadUnusedBytesFromFile(file, 65);   /* !!! NOT SUPPORTED YET !!! */
+  /* number of special ("gravity") port entries below (maximum 10 allowed) */
+  num_special_ports = fgetc(file);
+
+  /* database of properties of up to 10 special ports (6 bytes per port) */
+  for (i = 0; i < 10; i++)
+  {
+    int port_location, port_x, port_y, port_element;
+    int gravity;
+
+    /* high and low byte of the location of a special port; if (x, y) are the
+       coordinates of a port in the field and (0, 0) is the top-left corner,
+       the 16 bit value here calculates as 2 * (x + (y * 60)) (this is twice
+       of what may be expected: Supaplex works with a game field in memory
+       which is 2 bytes per tile) */
+    port_location = getFile16BitBE(file);
+
+    /* change gravity: 1 == "turn on", anything else (0) == "turn off" */
+    gravity = fgetc(file);
+
+    /* "freeze zonks": 2 == "turn on", anything else (0, 1) == "turn off" */
+    ReadUnusedBytesFromFile(file, 1);  /* (not used by R'n'D engine) */
+
+    /* "freeze enemies": 1 == "turn on", anything else (0) == "turn off" */
+    ReadUnusedBytesFromFile(file, 1);  /* (not used by R'n'D engine) */
+
+    ReadUnusedBytesFromFile(file, 1);  /* (not used by Supaplex engine) */
+
+    if (i >= num_special_ports)
+      continue;
+
+    port_x = (port_location / 2) % SP_LEVEL_XSIZE;
+    port_y = (port_location / 2) / SP_LEVEL_XSIZE;
+
+    if (port_x < 0 || port_x >= SP_LEVEL_XSIZE ||
+       port_y < 0 || port_y >= SP_LEVEL_YSIZE)
+    {
+      Error(ERR_WARN, "special port position (%d, %d) out of bounds",
+           port_x, port_y);
+
+      continue;
+    }
+
+    port_element = level->field[port_x][port_y];
+
+    if (port_element < EL_SP_GRAVITY_PORT_RIGHT ||
+       port_element > EL_SP_GRAVITY_PORT_UP)
+    {
+      Error(ERR_WARN, "no special port at position (%d, %d)", port_x, port_y);
+
+      continue;
+    }
+
+    /* change previous (wrong) gravity inverting special port to either
+       gravity enabling special port or gravity disabling special port */
+    level->field[port_x][port_y] +=
+      (gravity == 1 ? EL_SP_GRAVITY_ON_PORT_RIGHT :
+       EL_SP_GRAVITY_OFF_PORT_RIGHT) - EL_SP_GRAVITY_PORT_RIGHT;
+  }
+
+  ReadUnusedBytesFromFile(file, 4);    /* (not used by Supaplex engine) */
+
+  /* change special gravity ports without database entries to normal ports */
+  for (y = 0; y < SP_LEVEL_YSIZE; y++)
+    for (x = 0; x < SP_LEVEL_XSIZE; x++)
+      if (level->field[x][y] >= EL_SP_GRAVITY_PORT_RIGHT &&
+         level->field[x][y] <= EL_SP_GRAVITY_PORT_UP)
+       level->field[x][y] += EL_SP_PORT_RIGHT - EL_SP_GRAVITY_PORT_RIGHT;
+
+  /* auto-determine number of infotrons if it was stored as "0" -- see above */
+  if (level->gems_needed == 0)
+  {
+    for (y = 0; y < SP_LEVEL_YSIZE; y++)
+      for (x = 0; x < SP_LEVEL_XSIZE; x++)
+       if (level->field[x][y] == EL_SP_INFOTRON)
+         level->gems_needed++;
+
+    level->gems_needed &= 0xff;                /* only use low byte -- see above */
+  }
 
   level->fieldx = SP_LEVEL_XSIZE;
   level->fieldy = SP_LEVEL_YSIZE;
@@ -1828,13 +2219,13 @@ static void LoadLevelFromFileStream_SP(FILE *file, struct LevelInfo *level,
   level->time_wheel = 0;
   level->amoeba_content = EL_EMPTY;
 
-  for(i = 0; i < LEVEL_SCORE_ELEMENTS; i++)
+  for (i = 0; i < LEVEL_SCORE_ELEMENTS; i++)
     level->score[i] = 0;               /* !!! CORRECT THIS !!! */
 
   /* there are no yamyams in supaplex levels */
-  for(i = 0; i < level->num_yamyam_contents; i++)
-    for(y = 0; y < 3; y++)
-      for(x = 0; x < 3; x++)
+  for (i = 0; i < level->num_yamyam_contents; i++)
+    for (y = 0; y < 3; y++)
+      for (x = 0; x < 3; x++)
        level->yamyam_content[i][x][y] = EL_EMPTY;
 }
 
@@ -2035,6 +2426,7 @@ void LoadLevelFromFileInfo(struct LevelInfo *level,
 
     case LEVEL_FILE_TYPE_EM:
       LoadLevelFromFileInfo_EM(level, level_file_info);
+      level->game_engine_type = GAME_ENGINE_TYPE_EM;
       break;
 
     case LEVEL_FILE_TYPE_SP:
@@ -2045,6 +2437,18 @@ void LoadLevelFromFileInfo(struct LevelInfo *level,
       LoadLevelFromFileInfo_RND(level, level_file_info);
       break;
   }
+
+  /* if level file is invalid, restore level structure to default values */
+  if (level->no_valid_file)
+    setLevelInfoToDefaults(level);
+
+  if (level->game_engine_type == GAME_ENGINE_TYPE_UNKNOWN)
+    level->game_engine_type = GAME_ENGINE_TYPE_RND;
+
+  if (level_file_info->type == LEVEL_FILE_TYPE_RND)
+    CopyNativeLevel_RND_to_Native(level);
+  else
+    CopyNativeLevel_Native_to_RND(level);
 }
 
 void LoadLevelFromFilename(struct LevelInfo *level, char *filename)
@@ -2071,22 +2475,8 @@ static void LoadLevel_InitVersion(struct LevelInfo *level, char *filename)
 #endif
 
   /* determine correct game engine version of current level */
-#if 1
   if (!leveldir_current->latest_engine)
-#else
-  if (IS_LEVELCLASS_CONTRIB(leveldir_current) ||
-      IS_LEVELCLASS_PRIVATE(leveldir_current) ||
-      IS_LEVELCLASS_UNDEFINED(leveldir_current))
-#endif
   {
-#if 0
-    printf("\n::: This level is private or contributed: '%s'\n", filename);
-#endif
-
-#if 0
-    printf("\n::: Use the stored game engine version for this level\n");
-#endif
-
     /* For all levels which are not forced to use the latest game engine
        version (normally user contributed, private and undefined levels),
        use the version of the game engine the levels were created for.
@@ -2097,33 +2487,26 @@ static void LoadLevel_InitVersion(struct LevelInfo *level, char *filename)
        pre-2.0 levels, where the game version is still taken from the
        file format version used to store the level -- see above). */
 
-#if 1
     /* player was faster than enemies in 1.0.0 and before */
     if (level->file_version == FILE_VERSION_1_0)
       level->double_speed = TRUE;
-#else
-    /* do some special adjustments to support older level versions */
-    if (level->file_version == FILE_VERSION_1_0)
-    {
-      Error(ERR_WARN, "level file '%s' has version number 1.0", filename);
-      Error(ERR_WARN, "using high speed movement for player");
-
-      /* player was faster than monsters in (pre-)1.0 levels */
-      level->double_speed = TRUE;
-    }
-#endif
 
     /* default behaviour for EM style gems was "slippery" only in 2.0.1 */
     if (level->game_version == VERSION_IDENT(2,0,1,0))
       level->em_slippery_gems = TRUE;
 
+    /* springs could be pushed over pits before (pre-release version) 2.2.0 */
     if (level->game_version < VERSION_IDENT(2,2,0,0))
       level->use_spring_bug = TRUE;
 
+    /* only few elements were able to actively move into acid before 3.1.0 */
+    /* trigger settings did not exist before 3.1.0; set to default "any" */
     if (level->game_version < VERSION_IDENT(3,1,0,0))
     {
       int i, j;
 
+      /* correct "can move into acid" settings (all zero in old levels) */
+
       level->can_move_into_acid_bits = 0; /* nothing can move into acid */
       level->dont_collide_with_bits = 0; /* nothing is deadly when colliding */
 
@@ -2135,6 +2518,8 @@ static void LoadLevel_InitVersion(struct LevelInfo *level, char *filename)
       for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
        SET_PROPERTY(EL_CUSTOM_START + i, EP_CAN_MOVE_INTO_ACID, TRUE);
 
+      /* correct trigger settings (stored as zero == "none" in old levels) */
+
       for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
       {
        int element = EL_CUSTOM_START + i;
@@ -2150,17 +2535,8 @@ static void LoadLevel_InitVersion(struct LevelInfo *level, char *filename)
       }
     }
   }
-  else
+  else         /* always use the latest game engine version */
   {
-#if 0
-    printf("\n::: ALWAYS USE LATEST ENGINE FOR THIS LEVEL: [%d] '%s'\n",
-          leveldir_current->sort_priority, filename);
-#endif
-
-#if 0
-    printf("\n::: Use latest game engine version for this level.\n");
-#endif
-
     /* For all levels which are forced to use the latest game engine version
        (normally all but user contributed, private and undefined levels), set
        the game engine version to the actual version; this allows for actual
@@ -2169,11 +2545,6 @@ static void LoadLevel_InitVersion(struct LevelInfo *level, char *filename)
        of the corresponding game more accurate, while (hopefully) not breaking
        existing levels created from other players. */
 
-#if 0
-    printf("::: changing engine from %d to %d\n",
-          level->game_version, GAME_VERSION_ACTUAL);
-#endif
-
     level->game_version = GAME_VERSION_ACTUAL;
 
     /* Set special EM style gems behaviour: EM style gems slip down from
@@ -2186,10 +2557,6 @@ static void LoadLevel_InitVersion(struct LevelInfo *level, char *filename)
     if (level->file_version < FILE_VERSION_2_0)
       level->em_slippery_gems = TRUE;
   }
-
-#if 0
-  printf("::: => %d\n", level->game_version);
-#endif
 }
 
 static void LoadLevel_InitElements(struct LevelInfo *level, char *filename)
@@ -2197,7 +2564,8 @@ static void LoadLevel_InitElements(struct LevelInfo *level, char *filename)
   int i, j, x, y;
 
   /* map custom element change events that have changed in newer versions
-     (these following values were accidentally changed in version 3.0.1) */
+     (these following values were accidentally changed in version 3.0.1)
+     (this seems to be needed only for 'ab_levelset3' and 'ab_levelset4') */
   if (level->game_version <= VERSION_IDENT(3,0,0,0))
   {
     for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
@@ -2205,7 +2573,7 @@ static void LoadLevel_InitElements(struct LevelInfo *level, char *filename)
       int element = EL_CUSTOM_START + i;
 
       /* order of checking and copying events to be mapped is important */
-      for (j = CE_BY_OTHER_ACTION; j >= CE_BY_PLAYER_OBSOLETE; j--)
+      for (j = CE_BY_OTHER_ACTION; j >= CE_COUNT_AT_ZERO; j--)
       {
        if (HAS_CHANGE_EVENT(element, j - 2))
        {
@@ -2215,7 +2583,7 @@ static void LoadLevel_InitElements(struct LevelInfo *level, char *filename)
       }
 
       /* order of checking and copying events to be mapped is important */
-      for (j = CE_OTHER_GETS_COLLECTED; j >= CE_HITTING_SOMETHING; j--)
+      for (j = CE_PLAYER_COLLECTS_X; j >= CE_HITTING_SOMETHING; j--)
       {
        if (HAS_CHANGE_EVENT(element, j - 1))
        {
@@ -2226,21 +2594,6 @@ static void LoadLevel_InitElements(struct LevelInfo *level, char *filename)
     }
   }
 
-  /* some custom element change events get mapped since version 3.0.3 */
-  for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
-  {
-    int element = EL_CUSTOM_START + i;
-
-    if (HAS_CHANGE_EVENT(element, CE_BY_PLAYER_OBSOLETE) ||
-       HAS_CHANGE_EVENT(element, CE_BY_COLLISION_OBSOLETE))
-    {
-      SET_CHANGE_EVENT(element, CE_BY_PLAYER_OBSOLETE, FALSE);
-      SET_CHANGE_EVENT(element, CE_BY_COLLISION_OBSOLETE, FALSE);
-
-      SET_CHANGE_EVENT(element, CE_BY_DIRECT_ACTION, TRUE);
-    }
-  }
-
   /* initialize "can_change" field for old levels with only one change page */
   if (level->game_version <= VERSION_IDENT(3,0,2,0))
   {
@@ -2254,25 +2607,29 @@ static void LoadLevel_InitElements(struct LevelInfo *level, char *filename)
   }
 
   /* correct custom element values (for old levels without these options) */
-  for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
+  if (level->game_version < VERSION_IDENT(3,1,1,0))
   {
-    int element = EL_CUSTOM_START + i;
-    struct ElementInfo *ei = &element_info[element];
+    for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
+    {
+      int element = EL_CUSTOM_START + i;
+      struct ElementInfo *ei = &element_info[element];
 
-    if (ei->access_direction == MV_NO_MOVING)
-      ei->access_direction = MV_ALL_DIRECTIONS;
+      if (ei->access_direction == MV_NO_MOVING)
+       ei->access_direction = MV_ALL_DIRECTIONS;
 
-    for (j = 0; j < ei->num_change_pages; j++)
-    {
-      struct ElementChangeInfo *change = &ei->change_page[j];
+      for (j = 0; j < ei->num_change_pages; j++)
+      {
+       struct ElementChangeInfo *change = &ei->change_page[j];
 
-      if (change->trigger_side == CH_SIDE_NONE)
-       change->trigger_side = CH_SIDE_ANY;
+       if (change->trigger_side == CH_SIDE_NONE)
+         change->trigger_side = CH_SIDE_ANY;
+      }
     }
   }
 
   /* initialize "can_explode" field for old levels which did not store this */
-  if (level->game_version <= VERSION_IDENT(3,1,0,2))
+  /* !!! CHECK THIS -- "<= 3,1,0,0" IS PROBABLY WRONG !!! */
+  if (level->game_version <= VERSION_IDENT(3,1,0,0))
   {
     for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
     {
@@ -2287,30 +2644,21 @@ static void LoadLevel_InitElements(struct LevelInfo *level, char *filename)
     }
   }
 
-#if 0
-  /* set default push delay values (corrected since version 3.0.7-1) */
-  if (level->game_version < VERSION_IDENT(3,0,7,1))
+  /* correct previously hard-coded move delay values for maze runner style */
+  if (level->game_version < VERSION_IDENT(3,1,1,0))
   {
-    game.default_push_delay_fixed = 2;
-    game.default_push_delay_random = 8;
-  }
-  else
-  {
-    game.default_push_delay_fixed = 8;
-    game.default_push_delay_random = 8;
-  }
-
-  /* set uninitialized push delay values of custom elements in older levels */
-  for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
-  {
-    int element = EL_CUSTOM_START + i;
+    for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
+    {
+      int element = EL_CUSTOM_START + i;
 
-    if (element_info[element].push_delay_fixed == -1)
-      element_info[element].push_delay_fixed = game.default_push_delay_fixed;
-    if (element_info[element].push_delay_random == -1)
-      element_info[element].push_delay_random = game.default_push_delay_random;
+      if (element_info[element].move_pattern & MV_MAZE_RUNNER_STYLE)
+      {
+       /* previously hard-coded and therefore ignored */
+       element_info[element].move_delay_fixed = 9;
+       element_info[element].move_delay_random = 0;
+      }
+    }
   }
-#endif
 
   /* map elements that have changed in newer versions */
   level->amoeba_content = getMappedElementByVersion(level->amoeba_content,
@@ -2332,37 +2680,9 @@ static void LoadLevel_InitPlayfield(struct LevelInfo *level, char *filename)
 
   /* map elements that have changed in newer versions */
   for (y = 0; y < level->fieldy; y++)
-  {
     for (x = 0; x < level->fieldx; x++)
-    {
-      int element = level->field[x][y];
-
-#if 1
-      element = getMappedElementByVersion(element, level->game_version);
-#else
-      if (level->game_version <= VERSION_IDENT(2,2,0,0))
-      {
-       /* map game font elements */
-       element = (element == EL_CHAR('[')  ? EL_CHAR_AUMLAUT :
-                  element == EL_CHAR('\\') ? EL_CHAR_OUMLAUT :
-                  element == EL_CHAR(']')  ? EL_CHAR_UUMLAUT :
-                  element == EL_CHAR('^')  ? EL_CHAR_COPYRIGHT : element);
-      }
-
-      if (level->game_version < VERSION_IDENT(3,0,0,0))
-      {
-       /* map Supaplex gravity tube elements */
-       element = (element == EL_SP_GRAVITY_PORT_LEFT  ? EL_SP_PORT_LEFT  :
-                  element == EL_SP_GRAVITY_PORT_RIGHT ? EL_SP_PORT_RIGHT :
-                  element == EL_SP_GRAVITY_PORT_UP    ? EL_SP_PORT_UP    :
-                  element == EL_SP_GRAVITY_PORT_DOWN  ? EL_SP_PORT_DOWN  :
-                  element);
-      }
-#endif
-
-      level->field[x][y] = element;
-    }
-  }
+      level->field[x][y] = getMappedElementByVersion(level->field[x][y],
+                                                    level->game_version);
 
   /* copy elements to runtime playfield array */
   for (x = 0; x < MAX_LEV_FIELDX; x++)
@@ -2379,46 +2699,34 @@ static void LoadLevel_InitPlayfield(struct LevelInfo *level, char *filename)
 
 void LoadLevelTemplate(int nr)
 {
-#if 1
-  struct LevelFileInfo *level_file_info = getLevelFileInfo(nr);
-  char *filename = level_file_info->filename;
+  char *filename;
 
-  LoadLevelFromFileInfo(&level_template, level_file_info);
-#else
-  char *filename = getDefaultLevelFilename(nr);
+  setLevelFileInfo(&level_template.file_info, nr);
+  filename = level_template.file_info.filename;
 
-  LoadLevelFromFilename_RND(&level_template, filename);
-#endif
+  LoadLevelFromFileInfo(&level_template, &level_template.file_info);
 
-  LoadLevel_InitVersion(&level, filename);
-  LoadLevel_InitElements(&level, filename);
+  LoadLevel_InitVersion(&level_template, filename);
+  LoadLevel_InitElements(&level_template, filename);
 
   ActivateLevelTemplate();
 }
 
 void LoadLevel(int nr)
 {
-#if 1
-  struct LevelFileInfo *level_file_info = getLevelFileInfo(nr);
-  char *filename = level_file_info->filename;
+  char *filename;
 
-  LoadLevelFromFileInfo(&level, level_file_info);
-#else
-  char *filename = getLevelFilename(nr);
+  setLevelFileInfo(&level.file_info, nr);
+  filename = level.file_info.filename;
 
-  LoadLevelFromFilename_RND(&level, filename);
-#endif
+  LoadLevelFromFileInfo(&level, &level.file_info);
 
   if (level.use_custom_template)
     LoadLevelTemplate(-1);
 
-#if 1
   LoadLevel_InitVersion(&level, filename);
   LoadLevel_InitElements(&level, filename);
   LoadLevel_InitPlayfield(&level, filename);
-#else
-  LoadLevel_InitLevel(&level, filename);
-#endif
 }
 
 static void SaveLevel_VERS(FILE *file, struct LevelInfo *level)
@@ -2472,6 +2780,8 @@ static void SaveLevel_HEAD(FILE *file, struct LevelInfo *level)
   putFile8Bit(file, (level->can_pass_to_walkable ? 1 : 0));
   putFile8Bit(file, (level->grow_into_diggable ? 1 : 0));
 
+  putFile8Bit(file, level->game_engine_type);
+
   WriteUnusedBytesToFile(file, LEVEL_HEADER_UNUSED);
 }
 
@@ -2726,7 +3036,7 @@ static void SaveLevel_CUS3(FILE *file, struct LevelInfo *level,
 static void SaveLevel_CUS4(FILE *file, struct LevelInfo *level, int element)
 {
   struct ElementInfo *ei = &element_info[element];
-  int i, x, y;
+  int i, j, x, y;
 
   putFile16BitBE(file, element);
 
@@ -2788,8 +3098,13 @@ static void SaveLevel_CUS4(FILE *file, struct LevelInfo *level, int element)
   for (i = 0; i < ei->num_change_pages; i++)
   {
     struct ElementChangeInfo *change = &ei->change_page[i];
+    unsigned long event_bits = 0;
 
-    putFile32BitBE(file, change->events);
+    for (j = 0; j < NUM_CHANGE_EVENTS; j++)
+      if (change->has_event[j])
+       event_bits |= (1 << j);
+
+    putFile32BitBE(file, event_bits);
 
     putFile16BitBE(file, change->target_element);
 
@@ -2815,19 +3130,17 @@ static void SaveLevel_CUS4(FILE *file, struct LevelInfo *level, int element)
 
     putFile8Bit(file, change->trigger_side);
 
-#if 1
     putFile8Bit(file, change->trigger_player);
     putFile8Bit(file, (change->trigger_page == CH_PAGE_ANY ? CH_PAGE_ANY_FILE :
                       log_2(change->trigger_page)));
 
-    /* some free bytes for future change property values and padding */
-    WriteUnusedBytesToFile(file, 6);
-
-#else
+    putFile8Bit(file, change->has_action);
+    putFile8Bit(file, change->action_type);
+    putFile8Bit(file, change->action_mode);
+    putFile16BitBE(file, change->action_arg);
 
     /* some free bytes for future change property values and padding */
-    WriteUnusedBytesToFile(file, 8);
-#endif
+    WriteUnusedBytesToFile(file, 1);
   }
 }
 
@@ -3138,7 +3451,7 @@ static int LoadTape_BODY(FILE *file, int chunk_size, struct TapeInfo *tape)
 
   for (i = 0; i < tape->length; i++)
   {
-    if (i >= MAX_TAPELEN)
+    if (i >= MAX_TAPE_LEN)
       break;
 
     for (j = 0; j < MAX_PLAYERS; j++)
@@ -3222,10 +3535,6 @@ void LoadTapeFromFilename(char *filename)
   {
     tape.no_valid_file = TRUE;
 
-#if 0
-    Error(ERR_WARN, "cannot read tape '%s' -- using empty tape", filename);
-#endif
-
     return;
   }
 
@@ -3427,7 +3736,7 @@ void SaveTape(int nr)
   InitTapeDirectory(leveldir_current->subdir);
 
   /* if a tape still exists, ask to overwrite it */
-  if (access(filename, F_OK) == 0)
+  if (fileExists(filename))
   {
     new_tape = FALSE;
     if (!Request("Replace old tape ?", REQ_ASK))
@@ -3473,38 +3782,31 @@ void SaveTape(int nr)
   tape.changed = FALSE;
 
   if (new_tape)
-    Request("tape saved !", REQ_CONFIRM);
+    Request("Tape saved !", REQ_CONFIRM);
 }
 
 void DumpTape(struct TapeInfo *tape)
 {
   int i, j;
 
-#if 1
   if (tape->no_valid_file)
   {
     Error(ERR_WARN, "cannot dump -- no valid tape file found");
 
     return;
   }
-#else
-  if (TAPE_IS_EMPTY(*tape))
-  {
-    Error(ERR_WARN, "no tape available for level %d", tape->level_nr);
-
-    return;
-  }
-#endif
 
   printf_line("-", 79);
   printf("Tape of Level %03d (file version %08d, game version %08d)\n",
         tape->level_nr, tape->file_version, tape->game_version);
+  printf("                  (effective engine version %08d)\n",
+        tape->engine_version);
   printf("Level series identifier: '%s'\n", tape->level_identifier);
   printf_line("-", 79);
 
   for (i = 0; i < tape->length; i++)
   {
-    if (i >= MAX_TAPELEN)
+    if (i >= MAX_TAPE_LEN)
       break;
 
     printf("%03d: ", i);
@@ -3635,33 +3937,35 @@ void SaveScore(int nr)
 #define SETUP_TOKEN_QUICK_DOORS                        10
 #define SETUP_TOKEN_TEAM_MODE                  11
 #define SETUP_TOKEN_HANDICAP                   12
-#define SETUP_TOKEN_TIME_LIMIT                 13
-#define SETUP_TOKEN_FULLSCREEN                 14
-#define SETUP_TOKEN_ASK_ON_ESCAPE              15
-#define SETUP_TOKEN_GRAPHICS_SET               16
-#define SETUP_TOKEN_SOUNDS_SET                 17
-#define SETUP_TOKEN_MUSIC_SET                  18
-#define SETUP_TOKEN_OVERRIDE_LEVEL_GRAPHICS    19
-#define SETUP_TOKEN_OVERRIDE_LEVEL_SOUNDS      20
-#define SETUP_TOKEN_OVERRIDE_LEVEL_MUSIC       21
-
-#define NUM_GLOBAL_SETUP_TOKENS                        22
+#define SETUP_TOKEN_SKIP_LEVELS                        13
+#define SETUP_TOKEN_TIME_LIMIT                 14
+#define SETUP_TOKEN_FULLSCREEN                 15
+#define SETUP_TOKEN_ASK_ON_ESCAPE              16
+#define SETUP_TOKEN_GRAPHICS_SET               17
+#define SETUP_TOKEN_SOUNDS_SET                 18
+#define SETUP_TOKEN_MUSIC_SET                  19
+#define SETUP_TOKEN_OVERRIDE_LEVEL_GRAPHICS    20
+#define SETUP_TOKEN_OVERRIDE_LEVEL_SOUNDS      21
+#define SETUP_TOKEN_OVERRIDE_LEVEL_MUSIC       22
+
+#define NUM_GLOBAL_SETUP_TOKENS                        23
 
 /* editor setup */
 #define SETUP_TOKEN_EDITOR_EL_BOULDERDASH      0
 #define SETUP_TOKEN_EDITOR_EL_EMERALD_MINE     1
-#define SETUP_TOKEN_EDITOR_EL_MORE             2
-#define SETUP_TOKEN_EDITOR_EL_SOKOBAN          3
-#define SETUP_TOKEN_EDITOR_EL_SUPAPLEX         4
-#define SETUP_TOKEN_EDITOR_EL_DIAMOND_CAVES    5
-#define SETUP_TOKEN_EDITOR_EL_DX_BOULDERDASH   6
-#define SETUP_TOKEN_EDITOR_EL_CHARS            7
-#define SETUP_TOKEN_EDITOR_EL_CUSTOM           8
-#define SETUP_TOKEN_EDITOR_EL_CUSTOM_MORE      9
-#define SETUP_TOKEN_EDITOR_EL_HEADLINES                10
-#define SETUP_TOKEN_EDITOR_EL_USER_DEFINED     11
-
-#define NUM_EDITOR_SETUP_TOKENS                        12
+#define SETUP_TOKEN_EDITOR_EL_EMERALD_MINE_CLUB        2
+#define SETUP_TOKEN_EDITOR_EL_MORE             3
+#define SETUP_TOKEN_EDITOR_EL_SOKOBAN          4
+#define SETUP_TOKEN_EDITOR_EL_SUPAPLEX         5
+#define SETUP_TOKEN_EDITOR_EL_DIAMOND_CAVES    6
+#define SETUP_TOKEN_EDITOR_EL_DX_BOULDERDASH   7
+#define SETUP_TOKEN_EDITOR_EL_CHARS            8
+#define SETUP_TOKEN_EDITOR_EL_CUSTOM           9
+#define SETUP_TOKEN_EDITOR_EL_CUSTOM_MORE      10
+#define SETUP_TOKEN_EDITOR_EL_HEADLINES                11
+#define SETUP_TOKEN_EDITOR_EL_USER_DEFINED     12
+
+#define NUM_EDITOR_SETUP_TOKENS                        13
 
 /* shortcut setup */
 #define SETUP_TOKEN_SHORTCUT_SAVE_GAME         0
@@ -3724,6 +4028,7 @@ static struct TokenInfo global_setup_tokens[] =
   { TYPE_SWITCH, &si.quick_doors,      "quick_doors"                   },
   { TYPE_SWITCH, &si.team_mode,                "team_mode"                     },
   { TYPE_SWITCH, &si.handicap,         "handicap"                      },
+  { TYPE_SWITCH, &si.skip_levels,      "skip_levels"                   },
   { TYPE_SWITCH, &si.time_limit,       "time_limit"                    },
   { TYPE_SWITCH, &si.fullscreen,       "fullscreen"                    },
   { TYPE_SWITCH, &si.ask_on_escape,    "ask_on_escape"                 },
@@ -3739,6 +4044,7 @@ static struct TokenInfo editor_setup_tokens[] =
 {
   { TYPE_SWITCH, &sei.el_boulderdash,  "editor.el_boulderdash"         },
   { TYPE_SWITCH, &sei.el_emerald_mine, "editor.el_emerald_mine"        },
+  { TYPE_SWITCH, &sei.el_emerald_mine_club,"editor.el_emerald_mine_club"},
   { TYPE_SWITCH, &sei.el_more,         "editor.el_more"                },
   { TYPE_SWITCH, &sei.el_sokoban,      "editor.el_sokoban"             },
   { TYPE_SWITCH, &sei.el_supaplex,     "editor.el_supaplex"            },
@@ -3824,6 +4130,7 @@ static void setSetupInfoToDefaults(struct SetupInfo *si)
   si->quick_doors = FALSE;
   si->team_mode = FALSE;
   si->handicap = TRUE;
+  si->skip_levels = TRUE;
   si->time_limit = TRUE;
   si->fullscreen = FALSE;
   si->ask_on_escape = TRUE;
@@ -3835,16 +4142,17 @@ static void setSetupInfoToDefaults(struct SetupInfo *si)
   si->override_level_sounds = FALSE;
   si->override_level_music = FALSE;
 
-  si->editor.el_boulderdash = TRUE;
-  si->editor.el_emerald_mine = TRUE;
-  si->editor.el_more = TRUE;
-  si->editor.el_sokoban = TRUE;
-  si->editor.el_supaplex = TRUE;
-  si->editor.el_diamond_caves = TRUE;
-  si->editor.el_dx_boulderdash = TRUE;
-  si->editor.el_chars = TRUE;
-  si->editor.el_custom = TRUE;
-  si->editor.el_custom_more = FALSE;
+  si->editor.el_boulderdash       = TRUE;
+  si->editor.el_emerald_mine      = TRUE;
+  si->editor.el_emerald_mine_club = TRUE;
+  si->editor.el_more              = TRUE;
+  si->editor.el_sokoban           = TRUE;
+  si->editor.el_supaplex          = TRUE;
+  si->editor.el_diamond_caves     = TRUE;
+  si->editor.el_dx_boulderdash    = TRUE;
+  si->editor.el_chars             = TRUE;
+  si->editor.el_custom            = TRUE;
+  si->editor.el_custom_more       = FALSE;
 
   si->editor.el_headlines = TRUE;
   si->editor.el_user_defined = FALSE;
@@ -4151,7 +4459,19 @@ void LoadUserDefinedEditorElementList(int **elements, int *num_elements)
   {
     char *value = getHashEntry(element_hash, list->token);
 
-    if (value)
+    if (value == NULL)         /* try to find obsolete token mapping */
+    {
+      char *mapped_token = get_mapped_token(list->token);
+
+      if (mapped_token != NULL)
+      {
+       value = getHashEntry(element_hash, mapped_token);
+
+       free(mapped_token);
+      }
+    }
+
+    if (value != NULL)
     {
       (*elements)[(*num_elements)++] = atoi(value);
     }
@@ -4180,7 +4500,6 @@ void LoadUserDefinedEditorElementList(int **elements, int *num_elements)
   freeSetupFileHash(element_hash);
 
 #if 0
-  /* TEST-ONLY */
   for (i = 0; i < *num_elements; i++)
     printf("editor: element '%s' [%d]\n",
           element_info[(*elements)[i]].token_name, (*elements)[i]);
@@ -4345,18 +4664,10 @@ void LoadMusicInfo()
 
   new = &music_file_info;
 
-#if 0
-  printf("::: num_music == %d\n", num_music);
-#endif
-
   for (i = 0; i < num_music; i++)
   {
     music = getMusicListEntry(i);
 
-#if 0
-    printf("::: %d [%08x]\n", i, music->filename);
-#endif
-
     if (music->filename == NULL)
       continue;
 
@@ -4455,7 +4766,6 @@ void LoadMusicInfo()
   }
 
 #if 0
-  /* TEST-ONLY */
   for (next = music_file_info; next != NULL; next = next->next)
     printf("::: title == '%s'\n", next->title);
 #endif
@@ -4696,7 +5006,6 @@ void LoadHelpAnimInfo()
   freeSetupFileHash(direction_hash);
 
 #if 0
-  /* TEST ONLY */
   for (i = 0; i < num_list_entries; i++)
     printf("::: %d, %d, %d => %d\n",
           helpanim_info[i].element,
@@ -4732,7 +5041,6 @@ void LoadHelpTextInfo()
   }
 
 #if 0
-  /* TEST ONLY */
   BEGIN_HASH_ITERATION(helptext_info, itr)
   {
     printf("::: '%s' => '%s'\n",
@@ -4741,3 +5049,117 @@ void LoadHelpTextInfo()
   END_HASH_ITERATION(hash, itr)
 #endif
 }
+
+
+/* ------------------------------------------------------------------------- *
+ * convert levels
+ * ------------------------------------------------------------------------- */
+
+#define MAX_NUM_CONVERT_LEVELS         1000
+
+void ConvertLevels()
+{
+  static LevelDirTree *convert_leveldir = NULL;
+  static int convert_level_nr = -1;
+  static int num_levels_handled = 0;
+  static int num_levels_converted = 0;
+  static boolean levels_failed[MAX_NUM_CONVERT_LEVELS];
+  int i;
+
+  convert_leveldir = getTreeInfoFromIdentifier(leveldir_first,
+                                              global.convert_leveldir);
+
+  if (convert_leveldir == NULL)
+    Error(ERR_EXIT, "no such level identifier: '%s'",
+         global.convert_leveldir);
+
+  leveldir_current = convert_leveldir;
+
+  if (global.convert_level_nr != -1)
+  {
+    convert_leveldir->first_level = global.convert_level_nr;
+    convert_leveldir->last_level  = global.convert_level_nr;
+  }
+
+  convert_level_nr = convert_leveldir->first_level;
+
+  printf_line("=", 79);
+  printf("Converting levels\n");
+  printf_line("-", 79);
+  printf("Level series identifier: '%s'\n", convert_leveldir->identifier);
+  printf("Level series name:       '%s'\n", convert_leveldir->name);
+  printf("Level series author:     '%s'\n", convert_leveldir->author);
+  printf("Number of levels:        %d\n",   convert_leveldir->levels);
+  printf_line("=", 79);
+  printf("\n");
+
+  for (i = 0; i < MAX_NUM_CONVERT_LEVELS; i++)
+    levels_failed[i] = FALSE;
+
+  while (convert_level_nr <= convert_leveldir->last_level)
+  {
+    char *level_filename;
+    boolean new_level;
+
+    level_nr = convert_level_nr++;
+
+    printf("Level %03d: ", level_nr);
+
+    LoadLevel(level_nr);
+    if (level.no_valid_file)
+    {
+      printf("(no level)\n");
+      continue;
+    }
+
+    printf("converting level ... ");
+
+    level_filename = getDefaultLevelFilename(level_nr);
+    new_level = !fileExists(level_filename);
+
+    if (new_level)
+    {
+      SaveLevel(level_nr);
+
+      num_levels_converted++;
+
+      printf("converted.\n");
+    }
+    else
+    {
+      if (level_nr >= 0 && level_nr < MAX_NUM_CONVERT_LEVELS)
+       levels_failed[level_nr] = TRUE;
+
+      printf("NOT CONVERTED -- LEVEL ALREADY EXISTS.\n");
+    }
+
+    num_levels_handled++;
+  }
+
+  printf("\n");
+  printf_line("=", 79);
+  printf("Number of levels handled: %d\n", num_levels_handled);
+  printf("Number of levels converted: %d (%d%%)\n", num_levels_converted,
+        (num_levels_handled ?
+         num_levels_converted * 100 / num_levels_handled : 0));
+  printf_line("-", 79);
+  printf("Summary (for automatic parsing by scripts):\n");
+  printf("LEVELDIR '%s', CONVERTED %d/%d (%d%%)",
+        convert_leveldir->identifier, num_levels_converted,
+        num_levels_handled,
+        (num_levels_handled ?
+         num_levels_converted * 100 / num_levels_handled : 0));
+
+  if (num_levels_handled != num_levels_converted)
+  {
+    printf(", FAILED:");
+    for (i = 0; i < MAX_NUM_CONVERT_LEVELS; i++)
+      if (levels_failed[i])
+       printf(" %03d", i);
+  }
+
+  printf("\n");
+  printf_line("=", 79);
+
+  CloseAllAndExit(0);
+}