rnd-20040303-B-src
[rocksndiamonds.git] / src / files.c
index f364ae95b93295dbdf6eb60082fba96ce82a4522..bf3b77e7d063e9662243e6afdd0dbeeeb2a19d69 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    11      /* unused level header bytes  */
+#define LEVEL_HEADER_UNUSED          /* 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  */
@@ -92,7 +92,10 @@ void setElementChangeInfoToDefaults(struct ElementChangeInfo *change)
   change->can_change = FALSE;
 
   change->events = CE_BITMASK_DEFAULT;
-  change->sides = CH_SIDE_ANY;
+
+  change->trigger_player = CH_PLAYER_ANY;
+  change->trigger_side = CH_SIDE_ANY;
+  change->trigger_page = CH_PAGE_ANY;
 
   change->target_element = EL_EMPTY_SPACE;
 
@@ -141,18 +144,28 @@ static void setLevelInfoToDefaults(struct LevelInfo *level)
 
   level->time = 100;
   level->gems_needed = 0;
+
   level->amoeba_speed = 10;
+
   level->time_magic_wall = 10;
   level->time_wheel = 10;
   level->time_light = 10;
   level->time_timegate = 10;
+
   level->amoeba_content = EL_DIAMOND;
+
   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->use_spring_bug = FALSE;
+
+  level->can_move_into_acid_bits = ~0; /* everything can move into acid */
+
+  level->use_step_counter = FALSE;
+
   level->use_custom_template = FALSE;
 
   for (i = 0; i < MAX_LEVEL_NAME_LEN; i++)
@@ -208,6 +221,8 @@ static void setLevelInfoToDefaults(struct LevelInfo *level)
 
     if (IS_CUSTOM_ELEMENT(element))
     {
+      element_info[element].access_direction = MV_ALL_DIRECTIONS;
+
       element_info[element].collect_score = 10;                /* special default */
       element_info[element].collect_count = 1;         /* special default */
 
@@ -217,7 +232,7 @@ static void setLevelInfoToDefaults(struct LevelInfo *level)
       element_info[element].move_delay_random = 0;
 
       element_info[element].move_pattern = MV_ALL_DIRECTIONS;
-      element_info[element].move_direction_initial = MV_AUTOMATIC;
+      element_info[element].move_direction_initial = MV_START_AUTOMATIC;
       element_info[element].move_stepsize = TILEX / 8;
       element_info[element].move_enter_element = EL_EMPTY_SPACE;
       element_info[element].move_leave_element = EL_EMPTY_SPACE;
@@ -225,12 +240,16 @@ static void setLevelInfoToDefaults(struct LevelInfo *level)
 
       element_info[element].slippery_type = SLIPPERY_ANY_RANDOM;
 
+      element_info[element].explosion_delay = 18;
+      element_info[element].ignition_delay = 8;
+
       for (x = 0; x < 3; x++)
        for (y = 0; y < 3; y++)
          element_info[element].content[x][y] = EL_EMPTY_SPACE;
 
       element_info[element].access_type = 0;
       element_info[element].access_layer = 0;
+      element_info[element].access_protected = 0;
       element_info[element].walk_to_action = 0;
       element_info[element].smash_targets = 0;
       element_info[element].deadliness = 0;
@@ -246,6 +265,9 @@ static void setLevelInfoToDefaults(struct LevelInfo *level)
       for (j = 0; j < NUM_EP_BITFIELDS; j++)
        Properties[element][j] = EP_BITMASK_DEFAULT;
 
+      /* now set default properties */
+      SET_PROPERTY(element, EP_CAN_MOVE_INTO_ACID, TRUE);
+
       element_info[element].modified_settings = FALSE;
     }
     else if (IS_GROUP_ELEMENT(element) || element == EL_INTERNAL_EDITOR)
@@ -267,7 +289,7 @@ static void setLevelInfoToDefaults(struct LevelInfo *level)
 
   BorderElement = EL_STEELWALL;
 
-  level->no_level_file = FALSE;
+  level->no_valid_file = FALSE;
 
   if (leveldir_current == NULL)                /* only when dumping level */
     return;
@@ -527,7 +549,7 @@ static struct LevelFileInfo *getLevelFileInfo(int nr)
 
 int getMappedElement(int element)
 {
-  /* map some (historic, now obsolete) elements */
+  /* remap some (historic, now obsolete) elements */
 
 #if 1
   switch (element)
@@ -568,7 +590,7 @@ int getMappedElement(int element)
       {
        Error(ERR_WARN, "invalid level element %d", element);
 
-       element = EL_CHAR_QUESTION;
+       element = EL_UNKNOWN;
       }
       break;
   }
@@ -577,7 +599,7 @@ int getMappedElement(int element)
   {
     Error(ERR_WARN, "invalid level element %d", element);
 
-    element = EL_CHAR_QUESTION;
+    element = EL_UNKNOWN;
   }
   else if (element == EL_PLAYER_OBSOLETE)
     element = EL_PLAYER_1;
@@ -588,6 +610,32 @@ int getMappedElement(int element)
   return element;
 }
 
+int getMappedElementByVersion(int element, int game_version)
+{
+  /* remap some elements due to certain game version */
+
+  if (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 (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);
+  }
+
+  return element;
+}
+
 static int LoadLevel_VERS(FILE *file, int chunk_size, struct LevelInfo *level)
 {
   level->file_version = getFileVersion(file);
@@ -627,10 +675,17 @@ static int LoadLevel_HEAD(FILE *file, int chunk_size, struct LevelInfo *level)
   level->initial_gravity       = (getFile8Bit(file) == 1 ? TRUE : FALSE);
   level->encoding_16bit_field  = (getFile8Bit(file) == 1 ? TRUE : FALSE);
   level->em_slippery_gems      = (getFile8Bit(file) == 1 ? TRUE : FALSE);
+
+  level->use_custom_template   = (getFile8Bit(file) == 1 ? TRUE : FALSE);
+
   level->block_last_field      = (getFile8Bit(file) == 1 ? TRUE : FALSE);
   level->sp_block_last_field   = (getFile8Bit(file) == 1 ? TRUE : FALSE);
 
-  level->use_custom_template   = (getFile8Bit(file) == 1 ? TRUE : FALSE);
+  level->use_spring_bug                = (getFile8Bit(file) == 1 ? TRUE : FALSE);
+
+  level->can_move_into_acid_bits = getFile16BitBE(file);
+
+  level->use_step_counter      = (getFile8Bit(file) == 1 ? TRUE : FALSE);
 
   ReadUnusedBytesFromFile(file, LEVEL_HEADER_UNUSED);
 
@@ -986,6 +1041,7 @@ static int LoadLevel_CUS4(FILE *file, int chunk_size, struct LevelInfo *level)
   ei->move_delay_fixed = getFile16BitBE(file);
   ei->move_delay_random = getFile16BitBE(file);
 
+  /* bits 0 - 15 of "move_pattern" ... */
   ei->move_pattern = getFile16BitBE(file);
   ei->move_direction_initial = getFile8Bit(file);
   ei->move_stepsize = getFile8Bit(file);
@@ -1000,8 +1056,16 @@ static int LoadLevel_CUS4(FILE *file, int chunk_size, struct LevelInfo *level)
   ei->move_leave_element = getMappedElement(getFile16BitBE(file));
   ei->move_leave_type = getFile8Bit(file);
 
+  /* ... bits 16 - 31 of "move_pattern" (not nice, but downward compatible) */
+  ei->move_pattern |= (getFile16BitBE(file) << 16);
+
+  ei->access_direction = getFile8Bit(file);
+
+  ei->explosion_delay = getFile8Bit(file);
+  ei->ignition_delay = getFile8Bit(file);
+
   /* some free bytes for future custom property values and padding */
-  ReadUnusedBytesFromFile(file, 7);
+  ReadUnusedBytesFromFile(file, 2);
 
   /* read change property values */
 
@@ -1038,13 +1102,23 @@ static int LoadLevel_CUS4(FILE *file, int chunk_size, struct LevelInfo *level)
 
     change->can_change = getFile8Bit(file);
 
-    change->sides = getFile8Bit(file);
+    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);
 
-    if (change->sides == CH_SIDE_NONE) /* correct empty sides field */
-      change->sides = CH_SIDE_ANY;
+#else
 
     /* some free bytes for future change property values and padding */
     ReadUnusedBytesFromFile(file, 8);
+#endif
   }
 
   /* mark this custom element as modified */
@@ -1108,7 +1182,7 @@ static void LoadLevelFromFileInfo_RND(struct LevelInfo *level,
 
   if (!(file = fopen(filename, MODE_READ)))
   {
-    level->no_level_file = TRUE;
+    level->no_valid_file = TRUE;
 
     if (level != &level_template)
       Error(ERR_WARN, "cannot read level '%s' -- using empty level", filename);
@@ -1124,6 +1198,8 @@ static void LoadLevelFromFileInfo_RND(struct LevelInfo *level,
     getFileChunkBE(file, chunk_name, NULL);
     if (strcmp(chunk_name, "CAVE") != 0)
     {
+      level->no_valid_file = TRUE;
+
       Error(ERR_WARN, "unknown format of level file '%s'", filename);
       fclose(file);
       return;
@@ -1138,6 +1214,8 @@ static void LoadLevelFromFileInfo_RND(struct LevelInfo *level,
 
     if (!checkCookieString(cookie, LEVEL_COOKIE_TMPL))
     {
+      level->no_valid_file = TRUE;
+
       Error(ERR_WARN, "unknown format of level file '%s'", filename);
       fclose(file);
       return;
@@ -1145,6 +1223,8 @@ static void LoadLevelFromFileInfo_RND(struct LevelInfo *level,
 
     if ((level->file_version = getFileVersionFromCookieString(cookie)) == -1)
     {
+      level->no_valid_file = TRUE;
+
       Error(ERR_WARN, "unsupported version of level file '%s'", filename);
       fclose(file);
       return;
@@ -1276,7 +1356,7 @@ static int map_em_element_yam(int element)
 
     default:
       Error(ERR_WARN, "invalid level element %d", element);
-      return EL_CHAR_QUESTION;
+      return EL_UNKNOWN;
   }
 }
 
@@ -1290,10 +1370,13 @@ static int map_em_element_field(int element)
   switch (element)
   {
     case 0x00: return EL_ROCK;
+    case 0x01: return EL_ROCK;                         /* EMC */
     case 0x02: return EL_DIAMOND;
     case 0x03: return EL_DIAMOND;
     case 0x04: return EL_ROBOT;
     case 0x05: return EL_ROBOT;                        /* EMC */
+    case 0x06: return EL_EMPTY_SPACE;                  /* EMC */
+    case 0x07: return EL_EMPTY_SPACE;                  /* EMC */
     case 0x08: return EL_SPACESHIP_UP;
     case 0x09: return EL_SPACESHIP_RIGHT;
     case 0x0a: return EL_SPACESHIP_DOWN;
@@ -1302,7 +1385,9 @@ static int map_em_element_field(int element)
     case 0x0d: return EL_SPACESHIP_RIGHT;
     case 0x0e: return EL_SPACESHIP_DOWN;
     case 0x0f: return EL_SPACESHIP_LEFT;
+
     case 0x10: return EL_BOMB;
+    case 0x11: return EL_BOMB;                         /* EMC */
     case 0x12: return EL_EMERALD;
     case 0x13: return EL_EMERALD;
     case 0x14: return EL_BUG_UP;
@@ -1314,34 +1399,115 @@ static int map_em_element_field(int element)
     case 0x1a: return EL_BUG_DOWN;
     case 0x1b: return EL_BUG_LEFT;
     case 0x1c: return EL_AMOEBA_DROP;
+    case 0x1d: return EL_AMOEBA_DROP;                  /* EMC */
+    case 0x1e: return EL_AMOEBA_DROP;                  /* EMC */
+    case 0x1f: return EL_AMOEBA_DROP;                  /* EMC */
+
     case 0x20: return EL_ROCK;
+    case 0x21: return EL_BOMB;                         /* EMC */
+    case 0x22: return EL_DIAMOND;                      /* EMC */
+    case 0x23: return EL_EMERALD;                      /* EMC */
     case 0x24: return EL_MAGIC_WALL;
     case 0x25: return EL_NUT;
+    case 0x26: return EL_NUT;                          /* EMC */
+    case 0x27: return EL_NUT;                          /* EMC */
 
       /* looks like magic wheel, but is _always_ activated */
     case 0x28: return EL_ROBOT_WHEEL;                  /* EMC */
 
-    case 0x29: return EL_YAMYAM;
-    case 0x2a: return EL_YAMYAM;
-    case 0x2b: return EL_YAMYAM;                       /* EMC */
-    case 0x2c: return EL_YAMYAM;                       /* EMC */
+    case 0x29: return EL_YAMYAM;       /* up */
+    case 0x2a: return EL_YAMYAM;       /* down */
+    case 0x2b: return EL_YAMYAM;       /* left */      /* EMC */
+    case 0x2c: return EL_YAMYAM;       /* right */     /* EMC */
     case 0x2d: return EL_QUICKSAND_FULL;
+    case 0x2e: return EL_EMPTY_SPACE;                  /* EMC */
+    case 0x2f: return EL_EMPTY_SPACE;                  /* EMC */
+
+    case 0x30: return EL_EMPTY_SPACE;                  /* EMC */
+    case 0x31: return EL_SAND;                         /* EMC */
+    case 0x32: return EL_SAND;                         /* EMC */
+    case 0x33: return EL_SAND;                         /* EMC */
+    case 0x34: return EL_QUICKSAND_FULL;               /* EMC */
+    case 0x35: return EL_QUICKSAND_FULL;               /* EMC */
+    case 0x36: return EL_QUICKSAND_FULL;               /* EMC */
+    case 0x37: return EL_SAND;                         /* EMC */
+    case 0x38: return EL_ROCK;                         /* EMC */
     case 0x39: return EL_EXPANDABLE_WALL_HORIZONTAL;   /* EMC */
     case 0x3a: return EL_EXPANDABLE_WALL_VERTICAL;     /* EMC */
-    case 0x3b: return EL_DYNAMITE_ACTIVE;
-    case 0x3c: return EL_DYNAMITE_ACTIVE;
-    case 0x3d: return EL_DYNAMITE_ACTIVE;
-    case 0x3e: return EL_DYNAMITE_ACTIVE;
+    case 0x3b: return EL_DYNAMITE_ACTIVE;      /* 1 */
+    case 0x3c: return EL_DYNAMITE_ACTIVE;      /* 2 */
+    case 0x3d: return EL_DYNAMITE_ACTIVE;      /* 3 */
+    case 0x3e: return EL_DYNAMITE_ACTIVE;      /* 4 */
     case 0x3f: return EL_ACID_POOL_BOTTOM;
-    case 0x40: return EL_EXIT_OPEN;
-    case 0x41: return EL_EXIT_OPEN;
-    case 0x42: return EL_EXIT_OPEN;
-    case 0x43: return EL_BALLOON;
-    case 0x4e: return EL_INVISIBLE_WALL;
-    case 0x65: return EL_ACID;                         /* EMC */
-    case 0x73: return EL_SAND;                         /* EMC */
+
+    case 0x40: return EL_EXIT_OPEN;    /* 1 */
+    case 0x41: return EL_EXIT_OPEN;    /* 2 */
+    case 0x42: return EL_EXIT_OPEN;    /* 3 */
+    case 0x43: return EL_BALLOON;                      /* EMC */
+    case 0x44: return EL_UNKNOWN;                      /* EMC ("plant") */
+    case 0x45: return EL_SPRING;                       /* EMC */
+    case 0x46: return EL_SPRING;       /* falling */   /* EMC */
+    case 0x47: return EL_SPRING;       /* left */      /* EMC */
+    case 0x48: return EL_SPRING;       /* right */     /* EMC */
+    case 0x49: return EL_UNKNOWN;                      /* EMC ("ball 1") */
+    case 0x4a: return EL_UNKNOWN;                      /* EMC ("ball 2") */
+    case 0x4b: return EL_UNKNOWN;                      /* EMC ("android") */
+    case 0x4c: return EL_EMPTY_SPACE;                  /* EMC */
+    case 0x4d: return EL_UNKNOWN;                      /* EMC ("android") */
+    case 0x4e: return EL_INVISIBLE_WALL;               /* EMC (? "android") */
+    case 0x4f: return EL_UNKNOWN;                      /* EMC ("android") */
+
+    case 0x50: return EL_UNKNOWN;                      /* EMC ("android") */
+    case 0x51: return EL_UNKNOWN;                      /* EMC ("android") */
+    case 0x52: return EL_UNKNOWN;                      /* EMC ("android") */
+    case 0x53: return EL_UNKNOWN;                      /* EMC ("android") */
+    case 0x54: return EL_UNKNOWN;                      /* EMC ("android") */
+    case 0x55: return EL_EMPTY_SPACE;                  /* EMC */
+    case 0x56: return EL_EMPTY_SPACE;                  /* EMC */
+    case 0x57: return EL_EMPTY_SPACE;                  /* EMC */
+    case 0x58: return EL_EMPTY_SPACE;                  /* EMC */
+    case 0x59: return EL_EMPTY_SPACE;                  /* EMC */
+    case 0x5a: return EL_EMPTY_SPACE;                  /* EMC */
+    case 0x5b: return EL_EMPTY_SPACE;                  /* EMC */
+    case 0x5c: return EL_EMPTY_SPACE;                  /* EMC */
+    case 0x5d: return EL_EMPTY_SPACE;                  /* EMC */
+    case 0x5e: return EL_EMPTY_SPACE;                  /* EMC */
+    case 0x5f: return EL_EMPTY_SPACE;                  /* EMC */
+
+    case 0x60: return EL_EMPTY_SPACE;                  /* EMC */
+    case 0x61: return EL_EMPTY_SPACE;                  /* EMC */
+    case 0x62: return EL_EMPTY_SPACE;                  /* EMC */
+    case 0x63: return EL_SPRING;       /* left */      /* EMC */
+    case 0x64: return EL_SPRING;       /* right */     /* EMC */
+    case 0x65: return EL_ACID;         /* 1 */         /* EMC */
+    case 0x66: return EL_ACID;         /* 2 */         /* EMC */
+    case 0x67: return EL_ACID;         /* 3 */         /* EMC */
+    case 0x68: return EL_ACID;         /* 4 */         /* EMC */
+    case 0x69: return EL_ACID;         /* 5 */         /* EMC */
+    case 0x6a: return EL_ACID;         /* 6 */         /* EMC */
+    case 0x6b: return EL_ACID;         /* 7 */         /* EMC */
+    case 0x6c: return EL_ACID;         /* 8 */         /* EMC */
+    case 0x6d: return EL_EMPTY_SPACE;                  /* EMC */
+    case 0x6e: return EL_EMPTY_SPACE;                  /* EMC */
+    case 0x6f: return EL_EMPTY_SPACE;                  /* EMC */
+
+    case 0x70: return EL_EMPTY_SPACE;                  /* EMC */
+    case 0x71: return EL_EMPTY_SPACE;                  /* EMC */
+    case 0x72: return EL_NUT;          /* left */      /* EMC */
+    case 0x73: return EL_SAND;                         /* EMC (? "nut") */
     case 0x74: return EL_STEELWALL;
-    case 0x7b: return EL_ACID;
+    case 0x75: return EL_EMPTY_SPACE;                  /* EMC */
+    case 0x76: return EL_EMPTY_SPACE;                  /* EMC */
+    case 0x77: return EL_BOMB;         /* left */      /* EMC */
+    case 0x78: return EL_BOMB;         /* right */     /* EMC */
+    case 0x79: return EL_ROCK;         /* left */      /* EMC */
+    case 0x7a: return EL_ROCK;         /* right */     /* EMC */
+    case 0x7b: return EL_ACID;                         /* (? EMC "blank") */
+    case 0x7c: return EL_EMPTY_SPACE;                  /* EMC */
+    case 0x7d: return EL_EMPTY_SPACE;                  /* EMC */
+    case 0x7e: return EL_EMPTY_SPACE;                  /* EMC */
+    case 0x7f: return EL_EMPTY_SPACE;                  /* EMC */
+
     case 0x80: return EL_EMPTY;
     case 0x81: return EL_WALL_SLIPPERY;
     case 0x82: return EL_SAND;
@@ -1355,63 +1521,101 @@ static int map_em_element_field(int element)
     case 0x8a: return EL_EM_GATE_2;
     case 0x8b: return EL_EM_GATE_4;
     case 0x8c: return EL_EM_GATE_3;
-    case 0x8d: return EL_INVISIBLE_WALL;               /* EMC */
+    case 0x8d: return EL_INVISIBLE_WALL;               /* EMC (? "dripper") */
     case 0x8e: return EL_EM_GATE_1_GRAY;
     case 0x8f: return EL_EM_GATE_2_GRAY;
+
     case 0x90: return EL_EM_GATE_4_GRAY;
     case 0x91: return EL_EM_GATE_3_GRAY;
     case 0x92: return EL_MAGIC_WALL;
-    case 0x94: return EL_QUICKSAND_EMPTY;
+    case 0x93: return EL_ROBOT_WHEEL;
+    case 0x94: return EL_QUICKSAND_EMPTY;              /* (? EMC "sand") */
     case 0x95: return EL_ACID_POOL_TOPLEFT;
     case 0x96: return EL_ACID_POOL_TOPRIGHT;
     case 0x97: return EL_ACID_POOL_BOTTOMLEFT;
     case 0x98: return EL_ACID_POOL_BOTTOMRIGHT;
-    case 0x99: return EL_ACID;
-    case 0x9a: return EL_AMOEBA_DEAD;
-    case 0x9b: return EL_AMOEBA_DEAD;
-    case 0x9c: return EL_AMOEBA_DEAD;
-    case 0x9d: return EL_AMOEBA_DEAD;
+    case 0x99: return EL_ACID;                 /* (? EMC "fake blank") */
+    case 0x9a: return EL_AMOEBA_DEAD;          /* 1 */
+    case 0x9b: return EL_AMOEBA_DEAD;          /* 2 */
+    case 0x9c: return EL_AMOEBA_DEAD;          /* 3 */
+    case 0x9d: return EL_AMOEBA_DEAD;          /* 4 */
     case 0x9e: return EL_EXIT_CLOSED;
-    case 0x9f: return EL_CHAR_LESS;                    /* EMC */
-    case 0x93: return EL_ROBOT_WHEEL;
-
-      /* looks like normal dust, but behaves like wall */
-    case 0xa0: return EL_WALL;                         /* EMC */
-
-    case 0xa8: return EL_EMC_WALL_1;                   /* EMC */
-    case 0xa9: return EL_EMC_WALL_2;                   /* EMC */
-    case 0xaa: return EL_EMC_WALL_3;                   /* EMC */
-    case 0xab: return EL_EMC_WALL_7;                   /* EMC */
+    case 0x9f: return EL_CHAR_LESS;            /* arrow left */
+
+      /* looks like normal sand, but behaves like wall */
+    case 0xa0: return EL_UNKNOWN;              /* EMC ("fake grass") */
+    case 0xa1: return EL_UNKNOWN;              /* EMC ("lenses") */
+    case 0xa2: return EL_UNKNOWN;              /* EMC ("magnify") */
+    case 0xa3: return EL_UNKNOWN;              /* EMC ("fake blank") */
+    case 0xa4: return EL_UNKNOWN;              /* EMC ("fake grass") */
+    case 0xa5: return EL_UNKNOWN;              /* EMC ("switch") */
+    case 0xa6: return EL_UNKNOWN;              /* EMC ("switch") */
+    case 0xa7: return EL_EMPTY_SPACE;                  /* EMC */
+    case 0xa8: return EL_EMC_WALL_1;                   /* EMC ("decor 8") */
+    case 0xa9: return EL_EMC_WALL_2;                   /* EMC ("decor 9") */
+    case 0xaa: return EL_EMC_WALL_3;                   /* EMC ("decor 10") */
+    case 0xab: return EL_EMC_WALL_7;                   /* EMC ("decor 5") */
+    case 0xac: return EL_CHAR_COMMA;                   /* EMC */
+    case 0xad: return EL_CHAR_QUOTEDBL;                /* EMC */
     case 0xae: return EL_CHAR_MINUS;                   /* EMC */
     case 0xaf: return EL_DYNAMITE;
-    case 0xb0: return EL_EMC_STEELWALL_1;              /* EMC */
-    case 0xb1: return EL_EMC_WALL_8;                   /* EMC */
 
-      /* (exact steel wall) */
-    case 0xb3: return EL_STEELWALL;                    /* EMC */
-
-    case 0xb4: return EL_WALL_SLIPPERY;                /* EMC */
-    case 0xb5: return EL_EMC_WALL_6;                   /* EMC */
-    case 0xb6: return EL_EMC_WALL_5;                   /* EMC */
-    case 0xb7: return EL_EMC_WALL_4;                   /* EMC */
+    case 0xb0: return EL_EMC_STEELWALL_1;              /* EMC ("steel 3") */
+    case 0xb1: return EL_EMC_WALL_8;                   /* EMC ("decor 6") */
+    case 0xb2: return EL_UNKNOWN;                      /* EMC ("decor 7") */
+    case 0xb3: return EL_STEELWALL;            /* 2 */ /* EMC */
+    case 0xb4: return EL_WALL_SLIPPERY;        /* 2 */ /* EMC */
+    case 0xb5: return EL_EMC_WALL_6;                   /* EMC ("decor 2") */
+    case 0xb6: return EL_EMC_WALL_5;                   /* EMC ("decor 4") */
+    case 0xb7: return EL_EMC_WALL_4;                   /* EMC ("decor 3") */
     case 0xb8: return EL_BALLOON_SWITCH_ANY;           /* EMC */
     case 0xb9: return EL_BALLOON_SWITCH_RIGHT;         /* EMC */
     case 0xba: return EL_BALLOON_SWITCH_DOWN;          /* EMC */
     case 0xbb: return EL_BALLOON_SWITCH_LEFT;          /* EMC */
     case 0xbc: return EL_BALLOON_SWITCH_UP;            /* EMC */
-    case 0xbd: return EL_SAND;                         /* EMC */
+    case 0xbd: return EL_SAND;                         /* EMC ("dirt") */
+    case 0xbe: return EL_UNKNOWN;                      /* EMC ("plant") */
+    case 0xbf: return EL_UNKNOWN;                      /* EMC ("key 5") */
+
+    case 0xc0: return EL_UNKNOWN;                      /* EMC ("key 6") */
+    case 0xc1: return EL_UNKNOWN;                      /* EMC ("key 7") */
+    case 0xc2: return EL_UNKNOWN;                      /* EMC ("key 8") */
+    case 0xc3: return EL_UNKNOWN;                      /* EMC ("door 5") */
+    case 0xc4: return EL_UNKNOWN;                      /* EMC ("door 6") */
+    case 0xc5: return EL_UNKNOWN;                      /* EMC ("door 7") */
+    case 0xc6: return EL_UNKNOWN;                      /* EMC ("door 8") */
+    case 0xc7: return EL_UNKNOWN;                      /* EMC ("bumper") */
+
+      /* characters: see above */
+
     case 0xec: return EL_CHAR_PERIOD;
     case 0xed: return EL_CHAR_EXCLAM;
     case 0xee: return EL_CHAR_COLON;
     case 0xef: return EL_CHAR_QUESTION;
-    case 0xf0: return EL_CHAR_GREATER;
-    case 0xf1: return EL_CHAR_COPYRIGHT;
-    case 0xfe: return EL_PLAYER_1;
-    case 0xff: return EL_PLAYER_2;
+
+    case 0xf0: return EL_CHAR_GREATER;                 /* arrow right */
+    case 0xf1: return EL_CHAR_COPYRIGHT;               /* EMC: "decor 1" */
+    case 0xf2: return EL_UNKNOWN;              /* EMC ("fake door 5") */
+    case 0xf3: return EL_UNKNOWN;              /* EMC ("fake door 6") */
+    case 0xf4: return EL_UNKNOWN;              /* EMC ("fake door 7") */
+    case 0xf5: return EL_UNKNOWN;              /* EMC ("fake door 8") */
+    case 0xf6: return EL_EMPTY_SPACE;                  /* EMC */
+    case 0xf7: return EL_EMPTY_SPACE;                  /* EMC */
+
+    case 0xf8: return EL_EMPTY_SPACE;                  /* EMC */
+    case 0xf9: return EL_EMPTY_SPACE;                  /* EMC */
+    case 0xfa: return EL_EMPTY_SPACE;                  /* EMC */
+    case 0xfb: return EL_EMPTY_SPACE;                  /* EMC */
+    case 0xfc: return EL_EMPTY_SPACE;                  /* EMC */
+    case 0xfd: return EL_EMPTY_SPACE;                  /* EMC */
+
+    case 0xfe: return EL_PLAYER_1;                     /* EMC: "blank" */
+    case 0xff: return EL_PLAYER_2;                     /* EMC: "blank" */
 
     default:
+      /* should never happen (all 8-bit value cases should be handled) */
       Error(ERR_WARN, "invalid level element %d", element);
-      return EL_CHAR_QUESTION;
+      return EL_UNKNOWN;
   }
 }
 
@@ -1434,7 +1638,7 @@ static void LoadLevelFromFileInfo_EM(struct LevelInfo *level,
 
   if (!(file = fopen(filename, MODE_READ)))
   {
-    level->no_level_file = TRUE;
+    level->no_valid_file = TRUE;
 
     Error(ERR_WARN, "cannot read level '%s' -- using empty level", filename);
 
@@ -1560,7 +1764,7 @@ static void LoadLevelFromFileStream_SP(FILE *file, struct LevelInfo *level,
        Error(ERR_WARN, "in level %d, at position %d, %d:", nr, x, y);
        Error(ERR_WARN, "invalid level element %d", element_old);
 
-       element_new = EL_CHAR_QUESTION;
+       element_new = EL_UNKNOWN;
       }
 
       level->field[x][y] = element_new;
@@ -1626,7 +1830,7 @@ static void LoadLevelFromFileInfo_SP(struct LevelInfo *level,
 
   if (!(file = fopen(filename, MODE_READ)))
   {
-    level->no_level_file = TRUE;
+    level->no_valid_file = TRUE;
 
     Error(ERR_WARN, "cannot read level '%s' -- using empty level", filename);
 
@@ -1636,7 +1840,7 @@ static void LoadLevelFromFileInfo_SP(struct LevelInfo *level,
   /* position file stream to the requested level inside the level package */
   if (fseek(file, nr * SP_LEVEL_SIZE, SEEK_SET) != 0)
   {
-    level->no_level_file = TRUE;
+    level->no_valid_file = TRUE;
 
     Error(ERR_WARN, "cannot fseek level '%s' -- using empty level", filename);
 
@@ -1881,6 +2085,38 @@ static void LoadLevel_InitVersion(struct LevelInfo *level, char *filename)
     /* 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;
+
+    if (level->game_version < VERSION_IDENT(2,2,0,0))
+      level->use_spring_bug = TRUE;
+
+    if (level->game_version < VERSION_IDENT(3,0,9,0))
+    {
+      int i, j;
+
+      level->can_move_into_acid_bits = 0; /* nothing can move into acid */
+
+      setMoveIntoAcidProperty(level, EL_ROBOT,     TRUE);
+      setMoveIntoAcidProperty(level, EL_SATELLITE, TRUE);
+      setMoveIntoAcidProperty(level, EL_PENGUIN,   TRUE);
+      setMoveIntoAcidProperty(level, EL_BALLOON,   TRUE);
+
+      for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
+       SET_PROPERTY(EL_CUSTOM_START + i, EP_CAN_MOVE_INTO_ACID, TRUE);
+
+      for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
+      {
+       int element = EL_CUSTOM_START + i;
+       struct ElementInfo *ei = &element_info[element];
+
+       for (j = 0; j < ei->num_change_pages; j++)
+       {
+         struct ElementChangeInfo *change = &ei->change_page[j];
+
+         change->trigger_player = CH_PLAYER_ANY;
+         change->trigger_page = CH_PAGE_ANY;
+       }
+      }
+    }
   }
   else
   {
@@ -1926,7 +2162,7 @@ static void LoadLevel_InitVersion(struct LevelInfo *level, char *filename)
 
 static void LoadLevel_InitElements(struct LevelInfo *level, char *filename)
 {
-  int i, j;
+  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) */
@@ -1985,6 +2221,24 @@ 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++)
+  {
+    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;
+
+    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 0
   /* set default push delay values (corrected since version 3.0.7-1) */
   if (level->game_version < VERSION_IDENT(3,0,7,1))
@@ -2010,6 +2264,16 @@ static void LoadLevel_InitElements(struct LevelInfo *level, char *filename)
   }
 #endif
 
+  /* map elements that have changed in newer versions */
+  level->amoeba_content = getMappedElementByVersion(level->amoeba_content,
+                                                   level->game_version);
+  for (i = 0; i < MAX_ELEMENT_CONTENTS; i++)
+    for (x = 0; x < 3; x++)
+      for (y = 0; y < 3; y++)
+       level->yamyam_content[i][x][y] =
+         getMappedElementByVersion(level->yamyam_content[i][x][y],
+                                   level->game_version);
+
   /* initialize element properties for level editor etc. */
   InitElementPropertiesEngine(level->game_version);
 }
@@ -2025,6 +2289,9 @@ static void LoadLevel_InitPlayfield(struct LevelInfo *level, char *filename)
     {
       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 */
@@ -2043,6 +2310,7 @@ static void LoadLevel_InitPlayfield(struct LevelInfo *level, char *filename)
                   element == EL_SP_GRAVITY_PORT_DOWN  ? EL_SP_PORT_DOWN  :
                   element);
       }
+#endif
 
       level->field[x][y] = element;
     }
@@ -2141,10 +2409,17 @@ static void SaveLevel_HEAD(FILE *file, struct LevelInfo *level)
   putFile8Bit(file, (level->initial_gravity ? 1 : 0));
   putFile8Bit(file, (level->encoding_16bit_field ? 1 : 0));
   putFile8Bit(file, (level->em_slippery_gems ? 1 : 0));
+
+  putFile8Bit(file, (level->use_custom_template ? 1 : 0));
+
   putFile8Bit(file, (level->block_last_field ? 1 : 0));
   putFile8Bit(file, (level->sp_block_last_field ? 1 : 0));
 
-  putFile8Bit(file, (level->use_custom_template ? 1 : 0));
+  putFile8Bit(file, (level->use_spring_bug ? 1 : 0));
+
+  putFile16BitBE(file, level->can_move_into_acid_bits);
+
+  putFile8Bit(file, (level->use_step_counter ? 1 : 0));
 
   WriteUnusedBytesToFile(file, LEVEL_HEADER_UNUSED);
 }
@@ -2428,7 +2703,8 @@ static void SaveLevel_CUS4(FILE *file, struct LevelInfo *level, int element)
   putFile16BitBE(file, ei->move_delay_fixed);
   putFile16BitBE(file, ei->move_delay_random);
 
-  putFile16BitBE(file, ei->move_pattern);
+  /* bits 0 - 15 of "move_pattern" ... */
+  putFile16BitBE(file, ei->move_pattern & 0xffff);
   putFile8Bit(file, ei->move_direction_initial);
   putFile8Bit(file, ei->move_stepsize);
 
@@ -2442,8 +2718,16 @@ static void SaveLevel_CUS4(FILE *file, struct LevelInfo *level, int element)
   putFile16BitBE(file, ei->move_leave_element);
   putFile8Bit(file, ei->move_leave_type);
 
+  /* ... bits 16 - 31 of "move_pattern" (not nice, but downward compatible) */
+  putFile16BitBE(file, (ei->move_pattern >> 16) & 0xffff);
+
+  putFile8Bit(file, ei->access_direction);
+
+  putFile8Bit(file, ei->explosion_delay);
+  putFile8Bit(file, ei->ignition_delay);
+
   /* some free bytes for future custom property values and padding */
-  WriteUnusedBytesToFile(file, 7);
+  WriteUnusedBytesToFile(file, 2);
 
   /* write change property values */
 
@@ -2475,10 +2759,21 @@ static void SaveLevel_CUS4(FILE *file, struct LevelInfo *level, int element)
 
     putFile8Bit(file, change->can_change);
 
-    putFile8Bit(file, change->sides);
+    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
 
     /* some free bytes for future change property values and padding */
     WriteUnusedBytesToFile(file, 8);
+#endif
   }
 }
 
@@ -2639,6 +2934,13 @@ void SaveLevelTemplate()
 
 void DumpLevel(struct LevelInfo *level)
 {
+  if (level->no_valid_file)
+  {
+    Error(ERR_WARN, "cannot dump -- no valid level file found");
+
+    return;
+  }
+
   printf_line("-", 79);
   printf("Level xxx (file version %08d, game version %08d)\n",
         level->file_version, level->game_version);
@@ -2664,6 +2966,8 @@ void DumpLevel(struct LevelInfo *level)
   printf("EM style slippery gems:      %s\n", (level->em_slippery_gems ? "yes" : "no"));
   printf("Player blocks last field:    %s\n", (level->block_last_field ? "yes" : "no"));
   printf("SP player blocks last field: %s\n", (level->sp_block_last_field ? "yes" : "no"));
+  printf("use spring bug: %s\n", (level->use_spring_bug ? "yes" : "no"));
+  printf("use step counter: %s\n", (level->use_step_counter ? "yes" : "no"));
 
   printf_line("-", 79);
 }
@@ -2695,6 +2999,8 @@ static void setTapeInfoToDefaults()
   tape.recording = FALSE;
   tape.playing = FALSE;
   tape.pausing = FALSE;
+
+  tape.no_valid_file = FALSE;
 }
 
 static int LoadTape_VERS(FILE *file, int chunk_size, struct TapeInfo *tape)
@@ -2859,7 +3165,13 @@ void LoadTapeFromFilename(char *filename)
   setTapeInfoToDefaults();
 
   if (!(file = fopen(filename, MODE_READ)))
+  {
+    tape.no_valid_file = TRUE;
+
+    Error(ERR_WARN, "cannot read tape '%s' -- using empty tape", filename);
+
     return;
+  }
 
   getFileChunkBE(file, chunk_name, NULL);
   if (strcmp(chunk_name, "RND1") == 0)
@@ -2869,6 +3181,8 @@ void LoadTapeFromFilename(char *filename)
     getFileChunkBE(file, chunk_name, NULL);
     if (strcmp(chunk_name, "TAPE") != 0)
     {
+      tape.no_valid_file = TRUE;
+
       Error(ERR_WARN, "unknown format of tape file '%s'", filename);
       fclose(file);
       return;
@@ -2883,6 +3197,8 @@ void LoadTapeFromFilename(char *filename)
 
     if (!checkCookieString(cookie, TAPE_COOKIE_TMPL))
     {
+      tape.no_valid_file = TRUE;
+
       Error(ERR_WARN, "unknown format of tape file '%s'", filename);
       fclose(file);
       return;
@@ -2890,6 +3206,8 @@ void LoadTapeFromFilename(char *filename)
 
     if ((tape.file_version = getFileVersionFromCookieString(cookie)) == -1)
     {
+      tape.no_valid_file = TRUE;
+
       Error(ERR_WARN, "unsupported version of tape file '%s'", filename);
       fclose(file);
       return;
@@ -3106,11 +3424,21 @@ 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",