rnd-20031019-4-src
[rocksndiamonds.git] / src / files.c
index 4ef0174af5d82563ac31646637060ea587327f72..36751f4630437fb12df9e828268a5e960e02760a 100644 (file)
 #define LEVEL_HEADER_UNUSED    13      /* 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 LEVEL_CHUNK_CNT3_UNUSED        10      /* unused CNT3 chunk bytes    */
 #define LEVEL_CPART_CUS3_SIZE  134     /* size of CUS3 chunk part    */
 #define LEVEL_CPART_CUS3_UNUSED        15      /* unused CUS3 bytes / part   */
 #define TAPE_HEADER_SIZE       20      /* size of tape file header   */
 #define TAPE_HEADER_UNUSED     3       /* unused tape header bytes   */
 
-#define LEVEL_CHUNK_CUS3_SIZE(x) (2 + x * LEVEL_CPART_CUS3_SIZE)
+#define LEVEL_CHUNK_CUS3_SIZE(x) (2 + (x) * LEVEL_CPART_CUS3_SIZE)
+#define LEVEL_CHUNK_CUS4_SIZE(x) (48 + 48 + (x) * 48)
 
 /* file identifier strings */
 #define LEVEL_COOKIE_TMPL      "ROCKSNDIAMONDS_LEVEL_FILE_VERSION_x.x"
@@ -69,11 +72,13 @@ void setElementChangeInfoToDefaults(struct ElementChangeInfo *change)
   change->can_change = FALSE;
 
   change->events = CE_BITMASK_DEFAULT;
+  change->sides = CH_SIDE_ANY;
+
   change->target_element = EL_EMPTY_SPACE;
 
   change->delay_fixed = 0;
   change->delay_random = 0;
-  change->delay_frames = -1;   /* later set to reliable default value */
+  change->delay_frames = 1;
 
   change->trigger_element = EL_EMPTY_SPACE;
 
@@ -81,15 +86,14 @@ void setElementChangeInfoToDefaults(struct ElementChangeInfo *change)
   change->use_content = FALSE;
   change->only_complete = FALSE;
   change->use_random_change = FALSE;
-  change->random = 0;
+  change->random = 100;
   change->power = CP_NON_DESTRUCTIVE;
 
   for(x=0; x<3; x++)
     for(y=0; y<3; y++)
       change->content[x][y] = EL_EMPTY_SPACE;
 
-  change->player_action = 0;
-  change->collide_action = 0;
+  change->direct_action = 0;
   change->other_action = 0;
 
   change->pre_change_function = NULL;
@@ -124,7 +128,7 @@ static void setLevelInfoToDefaults(struct LevelInfo *level)
   level->time_timegate = 10;
   level->amoeba_content = EL_DIAMOND;
   level->double_speed = FALSE;
-  level->gravity = FALSE;
+  level->initial_gravity = FALSE;
   level->em_slippery_gems = FALSE;
 
   level->use_custom_template = FALSE;
@@ -137,9 +141,12 @@ static void setLevelInfoToDefaults(struct LevelInfo *level)
   strcpy(level->name, NAMELESS_LEVEL_NAME);
   strcpy(level->author, ANONYMOUS_NAME);
 
-  level->envelope[0] = '\0';
-  level->envelope_xsize = MAX_ENVELOPE_XSIZE;
-  level->envelope_ysize = MAX_ENVELOPE_YSIZE;
+  for (i=0; i<4; i++)
+  {
+    level->envelope_text[i][0] = '\0';
+    level->envelope_xsize[i] = MAX_ENVELOPE_XSIZE;
+    level->envelope_ysize[i] = MAX_ENVELOPE_YSIZE;
+  }
 
   for(i=0; i<LEVEL_SCORE_ELEMENTS; i++)
     level->score[i] = 10;
@@ -179,8 +186,8 @@ static void setLevelInfoToDefaults(struct LevelInfo *level)
     element_info[element].collect_score = 10;          /* special default */
     element_info[element].collect_count = 1;           /* special default */
 
-    element_info[element].push_delay_fixed = 2;                /* special default */
-    element_info[element].push_delay_random = 8;       /* special default */
+    element_info[element].push_delay_fixed = -1;       /* initialize later */
+    element_info[element].push_delay_random = -1;      /* initialize later */
     element_info[element].move_delay_fixed = 0;
     element_info[element].move_delay_random = 0;
 
@@ -235,12 +242,12 @@ static void setLevelInfoToDefaults(struct LevelInfo *level)
        strcpy(level->author, PROGRAM_AUTHOR_STRING);
        break;
 
-      case LEVELCLASS_CONTRIBUTION:
-       strncpy(level->author, leveldir_current->name,MAX_LEVEL_AUTHOR_LEN);
+      case LEVELCLASS_CONTRIB:
+       strncpy(level->author, leveldir_current->name, MAX_LEVEL_AUTHOR_LEN);
        level->author[MAX_LEVEL_AUTHOR_LEN] = '\0';
        break;
 
-      case LEVELCLASS_USER:
+      case LEVELCLASS_PRIVATE:
        strncpy(level->author, getRealName(), MAX_LEVEL_AUTHOR_LEN);
        level->author[MAX_LEVEL_AUTHOR_LEN] = '\0';
        break;
@@ -268,15 +275,63 @@ boolean LevelFileExists(int level_nr)
 
 static int checkLevelElement(int element)
 {
+  /* map some (historic, now obsolete) elements */
+
+#if 1
+  switch (element)
+  {
+    case EL_PLAYER_OBSOLETE:
+      element = EL_PLAYER_1;
+      break;
+
+    case EL_KEY_OBSOLETE:
+      element = EL_KEY_1;
+
+    case EL_EM_KEY_1_FILE_OBSOLETE:
+      element = EL_EM_KEY_1;
+      break;
+
+    case EL_EM_KEY_2_FILE_OBSOLETE:
+      element = EL_EM_KEY_2;
+      break;
+
+    case EL_EM_KEY_3_FILE_OBSOLETE:
+      element = EL_EM_KEY_3;
+      break;
+
+    case EL_EM_KEY_4_FILE_OBSOLETE:
+      element = EL_EM_KEY_4;
+      break;
+
+    case EL_ENVELOPE_OBSOLETE:
+      element = EL_ENVELOPE_1;
+      break;
+
+    case EL_SP_EMPTY:
+      element = EL_EMPTY;
+      break;
+
+    default:
+      if (element >= NUM_FILE_ELEMENTS)
+      {
+       Error(ERR_WARN, "invalid level element %d", element);
+
+       element = EL_CHAR_QUESTION;
+      }
+      break;
+  }
+#else
   if (element >= NUM_FILE_ELEMENTS)
   {
     Error(ERR_WARN, "invalid level element %d", element);
+
     element = EL_CHAR_QUESTION;
   }
   else if (element == EL_PLAYER_OBSOLETE)
     element = EL_PLAYER_1;
   else if (element == EL_KEY_OBSOLETE)
     element = EL_KEY_1;
+#endif
 
   return element;
 }
@@ -317,7 +372,7 @@ static int LoadLevel_HEAD(FILE *file, int chunk_size, struct LevelInfo *level)
   level->time_wheel            = getFile8Bit(file);
   level->amoeba_content                = checkLevelElement(getFile8Bit(file));
   level->double_speed          = (getFile8Bit(file) == 1 ? TRUE : FALSE);
-  level->gravity               = (getFile8Bit(file) == 1 ? TRUE : FALSE);
+  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);
 
@@ -417,6 +472,7 @@ static int LoadLevel_CNT2(FILE *file, int chunk_size, struct LevelInfo *level)
   num_contents = getFile8Bit(file);
   content_xsize = getFile8Bit(file);
   content_ysize = getFile8Bit(file);
+
   ReadUnusedBytesFromFile(file, LEVEL_CHUNK_CNT2_UNUSED);
 
   for(i=0; i<MAX_ELEMENT_CONTENTS; i++)
@@ -449,6 +505,41 @@ static int LoadLevel_CNT2(FILE *file, int chunk_size, struct LevelInfo *level)
   return chunk_size;
 }
 
+static int LoadLevel_CNT3(FILE *file, int chunk_size, struct LevelInfo *level)
+{
+  int i;
+  int element;
+  int envelope_nr;
+  int envelope_len;
+  int chunk_size_expected;
+
+  element = checkLevelElement(getFile16BitBE(file));
+  if (!IS_ENVELOPE(element))
+    element = EL_ENVELOPE_1;
+
+  envelope_nr = element - EL_ENVELOPE_1;
+
+  envelope_len = getFile16BitBE(file);
+
+  level->envelope_xsize[envelope_nr] = getFile8Bit(file);
+  level->envelope_ysize[envelope_nr] = getFile8Bit(file);
+
+  ReadUnusedBytesFromFile(file, LEVEL_CHUNK_CNT3_UNUSED);
+
+  chunk_size_expected = LEVEL_CHUNK_CNT3_HEADER + envelope_len;
+
+  if (chunk_size_expected != chunk_size)
+  {
+    ReadUnusedBytesFromFile(file, chunk_size - LEVEL_CHUNK_CNT3_HEADER);
+    return chunk_size_expected;
+  }
+
+  for(i=0; i < envelope_len; i++)
+    level->envelope_text[envelope_nr][i] = getFile8Bit(file);
+
+  return chunk_size;
+}
+
 static int LoadLevel_CUS1(FILE *file, int chunk_size, struct LevelInfo *level)
 {
   int num_changed_custom_elements = getFile16BitBE(file);
@@ -591,6 +682,119 @@ static int LoadLevel_CUS3(FILE *file, int chunk_size, struct LevelInfo *level)
   return chunk_size;
 }
 
+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;
+
+  element = getFile16BitBE(file);
+
+  if (!IS_CUSTOM_ELEMENT(element))
+  {
+    Error(ERR_WARN, "invalid custom element number %d", element);
+
+    element = EL_DEFAULT;      /* dummy element used for artwork config */
+  }
+
+  ei = &element_info[element];
+
+  for(i=0; i < MAX_ELEMENT_NAME_LEN; i++)
+    ei->description[i] = getFile8Bit(file);
+  ei->description[MAX_ELEMENT_NAME_LEN] = 0;
+
+  Properties[element][EP_BITFIELD_BASE] = getFile32BitBE(file);
+  ReadUnusedBytesFromFile(file, 4);    /* reserved for more base properties */
+
+  ei->num_change_pages = getFile8Bit(file);
+
+  /* some free bytes for future base property values and padding */
+  ReadUnusedBytesFromFile(file, 5);
+
+  chunk_size_expected = LEVEL_CHUNK_CUS4_SIZE(ei->num_change_pages);
+  if (chunk_size_expected != chunk_size)
+  {
+    ReadUnusedBytesFromFile(file, chunk_size - 48);
+    return chunk_size_expected;
+  }
+
+  /* read custom property values */
+
+  ei->use_gfx_element = getFile8Bit(file);
+  ei->gfx_element = checkLevelElement(getFile16BitBE(file));
+
+  ei->collect_score = getFile8Bit(file);
+  ei->collect_count = getFile8Bit(file);
+
+  ei->push_delay_fixed = getFile16BitBE(file);
+  ei->push_delay_random = getFile16BitBE(file);
+  ei->move_delay_fixed = getFile16BitBE(file);
+  ei->move_delay_random = getFile16BitBE(file);
+
+  ei->move_pattern = getFile16BitBE(file);
+  ei->move_direction_initial = getFile8Bit(file);
+  ei->move_stepsize = getFile8Bit(file);
+
+  ei->slippery_type = getFile8Bit(file);
+
+  for(y=0; y<3; y++)
+    for(x=0; x<3; x++)
+      ei->content[x][y] = checkLevelElement(getFile16BitBE(file));
+
+  /* some free bytes for future custom property values and padding */
+  ReadUnusedBytesFromFile(file, 12);
+
+  /* read change property values */
+
+  setElementChangePages(ei, ei->num_change_pages);
+
+  for (i=0; i < ei->num_change_pages; i++)
+  {
+    struct ElementChangeInfo *change = &ei->change_page[i];
+
+    /* always start with reliable default values */
+    setElementChangeInfoToDefaults(change);
+
+    change->events = getFile32BitBE(file);
+
+    change->target_element = checkLevelElement(getFile16BitBE(file));
+
+    change->delay_fixed = getFile16BitBE(file);
+    change->delay_random = getFile16BitBE(file);
+    change->delay_frames = getFile16BitBE(file);
+
+    change->trigger_element = checkLevelElement(getFile16BitBE(file));
+
+    change->explode = getFile8Bit(file);
+    change->use_content = getFile8Bit(file);
+    change->only_complete = getFile8Bit(file);
+    change->use_random_change = getFile8Bit(file);
+
+    change->random = getFile8Bit(file);
+    change->power = getFile8Bit(file);
+
+    for(y=0; y<3; y++)
+      for(x=0; x<3; x++)
+       change->content[x][y] = checkLevelElement(getFile16BitBE(file));
+
+    change->can_change = getFile8Bit(file);
+
+    change->sides = getFile8Bit(file);
+
+    if (change->sides == CH_SIDE_NONE) /* correct empty sides field */
+      change->sides = CH_SIDE_ANY;
+
+    /* some free bytes for future change property values and padding */
+    ReadUnusedBytesFromFile(file, 8);
+  }
+
+  /* mark this custom element as modified */
+  ei->modified_settings = TRUE;
+
+  return chunk_size;
+}
+
 void LoadLevelFromFilename(struct LevelInfo *level, char *filename)
 {
   char cookie[MAX_LINE_LEN];
@@ -671,9 +875,11 @@ void LoadLevelFromFilename(struct LevelInfo *level, char *filename)
       { "BODY", -1,                    LoadLevel_BODY },
       { "CONT", -1,                    LoadLevel_CONT },
       { "CNT2", LEVEL_CHUNK_CNT2_SIZE, LoadLevel_CNT2 },
+      { "CNT3", -1,                    LoadLevel_CNT3 },
       { "CUS1", -1,                    LoadLevel_CUS1 },
       { "CUS2", -1,                    LoadLevel_CUS2 },
       { "CUS3", -1,                    LoadLevel_CUS3 },
+      { "CUS4", -1,                    LoadLevel_CUS4 },
       {  NULL,  0,                     NULL }
     };
 
@@ -719,23 +925,36 @@ void LoadLevelFromFilename(struct LevelInfo *level, char *filename)
   fclose(file);
 }
 
-#if 1
-
 static void LoadLevel_InitVersion(struct LevelInfo *level, char *filename)
 {
   if (leveldir_current == NULL)                /* only when dumping level */
     return;
 
+#if 0
+  printf("::: sort_priority: %d\n", leveldir_current->sort_priority);
+#endif
+
   /* determine correct game engine version of current level */
-  if (IS_LEVELCLASS_CONTRIBUTION(leveldir_current) ||
-      IS_LEVELCLASS_USER(leveldir_current))
+#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
 
-    /* For user contributed and private levels, use the version of
-       the game engine the levels were created for.
+#if 1
+    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.
+
        Since 2.0.1, the game engine version is now directly stored
        in the level file (chunk "VERS"), so there is no need anymore
        to set the game version from the file version (except for old,
@@ -745,7 +964,7 @@ static void LoadLevel_InitVersion(struct LevelInfo *level, char *filename)
     /* 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, "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 */
@@ -763,12 +982,22 @@ static void LoadLevel_InitVersion(struct LevelInfo *level, char *filename)
           leveldir_current->sort_priority, filename);
 #endif
 
-    /* Always use the latest version of the game engine for all but
-       user contributed and private levels; this allows for actual
-       corrections in the game engine to take effect for existing,
-       converted levels (from "classic" or other existing games) to
-       make the game emulation more accurate, while (hopefully) not
-       breaking existing levels created from other players. */
+#if 1
+    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
+       corrections in the game engine to take effect for existing, converted
+       levels (from "classic" or other existing games) to make the emulation
+       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;
 
@@ -782,6 +1011,10 @@ 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)
@@ -789,15 +1022,15 @@ static void LoadLevel_InitElements(struct LevelInfo *level, char *filename)
   int i, j;
 
   /* map custom element change events that have changed in newer versions
-     (these following values have accidentally changed in version 3.0.1) */
+     (these following values were accidentally changed in version 3.0.1) */
   if (level->game_version <= VERSION_IDENT(3,0,0))
   {
     for (i=0; i < NUM_CUSTOM_ELEMENTS; i++)
     {
       int element = EL_CUSTOM_START + i;
 
-      /* order of checking events to be mapped is important */
-      for (j=CE_BY_OTHER; j >= CE_BY_PLAYER; j--)
+      /* order of checking and copying events to be mapped is important */
+      for (j=CE_BY_OTHER_ACTION; j >= CE_BY_PLAYER; j--)
       {
        if (HAS_CHANGE_EVENT(element, j - 2))
        {
@@ -806,7 +1039,7 @@ static void LoadLevel_InitElements(struct LevelInfo *level, char *filename)
        }
       }
 
-      /* order of checking events to be mapped is important */
+      /* order of checking and copying events to be mapped is important */
       for (j=CE_OTHER_GETS_COLLECTED; j >= CE_COLLISION; j--)
       {
        if (HAS_CHANGE_EVENT(element, j - 1))
@@ -818,134 +1051,63 @@ static void LoadLevel_InitElements(struct LevelInfo *level, char *filename)
     }
   }
 
-  /* initialize "can_change" field for old levels with only one change page */
-  if (level->game_version <= VERSION_IDENT(3,0,2))
+  /* some custom element change events get mapped since version 3.0.3 */
+  for (i=0; i < NUM_CUSTOM_ELEMENTS; i++)
   {
-    for (i=0; i < NUM_CUSTOM_ELEMENTS; i++)
+    int element = EL_CUSTOM_START + i;
+
+    if (HAS_CHANGE_EVENT(element, CE_BY_PLAYER) ||
+       HAS_CHANGE_EVENT(element, CE_BY_COLLISION))
     {
-      int element = EL_CUSTOM_START + i;
+      SET_CHANGE_EVENT(element, CE_BY_PLAYER, FALSE);
+      SET_CHANGE_EVENT(element, CE_BY_COLLISION, FALSE);
 
-      if (CAN_CHANGE(element))
-       element_info[element].change->can_change = TRUE;
+      SET_CHANGE_EVENT(element, CE_BY_DIRECT_ACTION, TRUE);
     }
   }
 
-  /* initialize element properties for level editor etc. */
-  InitElementPropertiesEngine(level->game_version);
-}
-
-static void LoadLevel_InitPlayfield(struct LevelInfo *level, char *filename)
-{
-  int x, y;
-
-  /* map elements that have changed in newer versions */
-  for(y=0; y<level->fieldy; y++)
+  /* initialize "can_change" field for old levels with only one change page */
+  if (level->game_version <= VERSION_IDENT(3,0,2))
   {
-    for(x=0; x<level->fieldx; x++)
+    for (i=0; i < NUM_CUSTOM_ELEMENTS; i++)
     {
-      int element = level->field[x][y];
-
-      if (level->game_version <= VERSION_IDENT(2,2,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))
-      {
-       /* 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);
-      }
+      int element = EL_CUSTOM_START + i;
 
-      level->field[x][y] = element;
+      if (CAN_CHANGE(element))
+       element_info[element].change->can_change = TRUE;
     }
   }
 
-  /* copy elements to runtime playfield array */
-  for(x=0; x<MAX_LEV_FIELDX; x++)
-    for(y=0; y<MAX_LEV_FIELDY; y++)
-      Feld[x][y] = level->field[x][y];
-
-  /* initialize level size variables for faster access */
-  lev_fieldx = level->fieldx;
-  lev_fieldy = level->fieldy;
-
-  /* determine border element for this level */
-  SetBorderElement();
-}
-
-#else
-
-static void LoadLevel_InitLevel(struct LevelInfo *level, char *filename)
-{
-  int i, j, x, y;
-
-  if (leveldir_current == NULL)                /* only when dumping level */
-    return;
-
-  /* determine correct game engine version of current level */
-  if (IS_LEVELCLASS_CONTRIBUTION(leveldir_current) ||
-      IS_LEVELCLASS_USER(leveldir_current))
+  /* set default push delay values (corrected since version 3.0.7) */
+  if (level->game_version < VERSION_IDENT(3,0,7))
   {
-#if 0
-    printf("\n::: This level is private or contributed: '%s'\n", filename);
-#endif
-
-    /* For user contributed and private levels, use the version of
-       the game engine the levels were created for.
-       Since 2.0.1, the game engine version is now directly stored
-       in the level file (chunk "VERS"), so there is no need anymore
-       to set the game version from the file version (except for old,
-       pre-2.0 levels, where the game version is still taken from the
-       file format version used to store the level -- see above). */
-
-    /* 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;
-    }
-
-    /* Default behaviour for EM style gems was "slippery" only in 2.0.1 */
-    if (level->game_version == VERSION_IDENT(2,0,1))
-      level->em_slippery_gems = TRUE;
+    game.default_push_delay_fixed = 2;
+    game.default_push_delay_random = 8;
   }
   else
   {
-#if 0
-    printf("\n::: ALWAYS USE LATEST ENGINE FOR THIS LEVEL: [%d] '%s'\n",
-          leveldir_current->sort_priority, filename);
-#endif
+    game.default_push_delay_fixed = 8;
+    game.default_push_delay_random = 8;
+  }
 
-    /* Always use the latest version of the game engine for all but
-       user contributed and private levels; this allows for actual
-       corrections in the game engine to take effect for existing,
-       converted levels (from "classic" or other existing games) to
-       make the game emulation more accurate, while (hopefully) not
-       breaking existing levels created from other players. */
+  /* 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;
 
-    level->game_version = GAME_VERSION_ACTUAL;
+    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;
+  }
 
-    /* Set special EM style gems behaviour: EM style gems slip down from
-       normal, steel and growing wall. As this is a more fundamental change,
-       it seems better to set the default behaviour to "off" (as it is more
-       natural) and make it configurable in the level editor (as a property
-       of gem style elements). Already existing converted levels (neither
-       private nor contributed levels) are changed to the new behaviour. */
+  /* initialize element properties for level editor etc. */
+  InitElementPropertiesEngine(level->game_version);
+}
 
-    if (level->file_version < FILE_VERSION_2_0)
-      level->em_slippery_gems = TRUE;
-  }
+static void LoadLevel_InitPlayfield(struct LevelInfo *level, char *filename)
+{
+  int x, y;
 
   /* map elements that have changed in newer versions */
   for(y=0; y<level->fieldy; y++)
@@ -977,48 +1139,6 @@ static void LoadLevel_InitLevel(struct LevelInfo *level, char *filename)
     }
   }
 
-  /* map custom element change events that have changed in newer versions
-     (these following values have accidentally changed in version 3.0.1) */
-  if (level->game_version <= VERSION_IDENT(3,0,0))
-  {
-    for (i=0; i < NUM_CUSTOM_ELEMENTS; i++)
-    {
-      int element = EL_CUSTOM_START + i;
-
-      /* order of checking events to be mapped is important */
-      for (j=CE_BY_OTHER; j >= CE_BY_PLAYER; j--)
-      {
-       if (HAS_CHANGE_EVENT(element, j - 2))
-       {
-         SET_CHANGE_EVENT(element, j - 2, FALSE);
-         SET_CHANGE_EVENT(element, j, TRUE);
-       }
-      }
-
-      /* order of checking events to be mapped is important */
-      for (j=CE_OTHER_GETS_COLLECTED; j >= CE_COLLISION; j--)
-      {
-       if (HAS_CHANGE_EVENT(element, j - 1))
-       {
-         SET_CHANGE_EVENT(element, j - 1, FALSE);
-         SET_CHANGE_EVENT(element, j, TRUE);
-       }
-      }
-    }
-  }
-
-  /* initialize "can_change" field for old levels with only one change page */
-  if (level->game_version <= VERSION_IDENT(3,0,2))
-  {
-    for (i=0; i < NUM_CUSTOM_ELEMENTS; i++)
-    {
-      int element = EL_CUSTOM_START + i;
-
-      if (CAN_CHANGE(element))
-       element_info[element].change->can_change = TRUE;
-    }
-  }
-
   /* copy elements to runtime playfield array */
   for(x=0; x<MAX_LEV_FIELDX; x++)
     for(y=0; y<MAX_LEV_FIELDY; y++)
@@ -1030,13 +1150,8 @@ static void LoadLevel_InitLevel(struct LevelInfo *level, char *filename)
 
   /* determine border element for this level */
   SetBorderElement();
-
-  /* initialize element properties for level editor etc. */
-  InitElementPropertiesEngine(level->game_version);
 }
 
-#endif
-
 void LoadLevelTemplate(int level_nr)
 {
   char *filename = getLevelFilename(level_nr);
@@ -1100,7 +1215,7 @@ static void SaveLevel_HEAD(FILE *file, struct LevelInfo *level)
   putFile8Bit(file, (level->encoding_16bit_amoeba ? EL_EMPTY :
                     level->amoeba_content));
   putFile8Bit(file, (level->double_speed ? 1 : 0));
-  putFile8Bit(file, (level->gravity ? 1 : 0));
+  putFile8Bit(file, (level->initial_gravity ? 1 : 0));
   putFile8Bit(file, (level->encoding_16bit_field ? 1 : 0));
   putFile8Bit(file, (level->em_slippery_gems ? 1 : 0));
 
@@ -1200,6 +1315,23 @@ static void SaveLevel_CNT2(FILE *file, struct LevelInfo *level, int element)
        putFile16BitBE(file, content_array[i][x][y]);
 }
 
+static void SaveLevel_CNT3(FILE *file, struct LevelInfo *level, int element)
+{
+  int i;
+  int envelope_nr = element - EL_ENVELOPE_1;
+  int envelope_len = strlen(level->envelope_text[envelope_nr]) + 1;
+
+  putFile16BitBE(file, element);
+  putFile16BitBE(file, envelope_len);
+  putFile8Bit(file, level->envelope_xsize[envelope_nr]);
+  putFile8Bit(file, level->envelope_ysize[envelope_nr]);
+
+  WriteUnusedBytesToFile(file, LEVEL_CHUNK_CNT3_UNUSED);
+
+  for(i=0; i < envelope_len; i++)
+    putFile8Bit(file, level->envelope_text[envelope_nr][i]);
+}
+
 #if 0
 static void SaveLevel_CUS1(FILE *file, struct LevelInfo *level,
                           int num_changed_custom_elements)
@@ -1258,6 +1390,7 @@ static void SaveLevel_CUS2(FILE *file, struct LevelInfo *level,
 }
 #endif
 
+#if 0
 static void SaveLevel_CUS3(FILE *file, struct LevelInfo *level,
                           int num_changed_custom_elements)
 {
@@ -1337,12 +1470,92 @@ static void SaveLevel_CUS3(FILE *file, struct LevelInfo *level,
   if (check != num_changed_custom_elements)    /* should not happen */
     Error(ERR_WARN, "inconsistent number of custom element properties");
 }
+#endif
+
+static void SaveLevel_CUS4(FILE *file, struct LevelInfo *level, int element)
+{
+  struct ElementInfo *ei = &element_info[element];
+  int i, x, y;
+
+  putFile16BitBE(file, element);
+
+  for(i=0; i < MAX_ELEMENT_NAME_LEN; i++)
+    putFile8Bit(file, ei->description[i]);
+
+  putFile32BitBE(file, Properties[element][EP_BITFIELD_BASE]);
+  WriteUnusedBytesToFile(file, 4);     /* reserved for more base properties */
+
+  putFile8Bit(file, ei->num_change_pages);
+
+  /* some free bytes for future base property values and padding */
+  WriteUnusedBytesToFile(file, 5);
+
+  /* write custom property values */
+
+  putFile8Bit(file, ei->use_gfx_element);
+  putFile16BitBE(file, ei->gfx_element);
+
+  putFile8Bit(file, ei->collect_score);
+  putFile8Bit(file, ei->collect_count);
+
+  putFile16BitBE(file, ei->push_delay_fixed);
+  putFile16BitBE(file, ei->push_delay_random);
+  putFile16BitBE(file, ei->move_delay_fixed);
+  putFile16BitBE(file, ei->move_delay_random);
+
+  putFile16BitBE(file, ei->move_pattern);
+  putFile8Bit(file, ei->move_direction_initial);
+  putFile8Bit(file, ei->move_stepsize);
+
+  putFile8Bit(file, ei->slippery_type);
+
+  for(y=0; y<3; y++)
+    for(x=0; x<3; x++)
+      putFile16BitBE(file, ei->content[x][y]);
+
+  /* some free bytes for future custom property values and padding */
+  WriteUnusedBytesToFile(file, 12);
+
+  /* write change property values */
+
+  for (i=0; i < ei->num_change_pages; i++)
+  {
+    struct ElementChangeInfo *change = &ei->change_page[i];
+
+    putFile32BitBE(file, change->events);
+
+    putFile16BitBE(file, change->target_element);
+
+    putFile16BitBE(file, change->delay_fixed);
+    putFile16BitBE(file, change->delay_random);
+    putFile16BitBE(file, change->delay_frames);
+
+    putFile16BitBE(file, change->trigger_element);
+
+    putFile8Bit(file, change->explode);
+    putFile8Bit(file, change->use_content);
+    putFile8Bit(file, change->only_complete);
+    putFile8Bit(file, change->use_random_change);
+
+    putFile8Bit(file, change->random);
+    putFile8Bit(file, change->power);
+
+    for(y=0; y<3; y++)
+      for(x=0; x<3; x++)
+       putFile16BitBE(file, change->content[x][y]);
+
+    putFile8Bit(file, change->can_change);
+
+    putFile8Bit(file, change->sides);
+
+    /* some free bytes for future change property values and padding */
+    WriteUnusedBytesToFile(file, 8);
+  }
+}
 
 static void SaveLevelFromFilename(struct LevelInfo *level, char *filename)
 {
   int body_chunk_size;
-  int num_changed_custom_elements = 0;
-  int level_chunk_CUS3_size;
   int i, x, y;
   FILE *file;
 
@@ -1379,12 +1592,6 @@ static void SaveLevelFromFilename(struct LevelInfo *level, char *filename)
   body_chunk_size =
     level->fieldx * level->fieldy * (level->encoding_16bit_field ? 2 : 1);
 
-  /* check for non-standard custom elements and calculate "CUS3" chunk size */
-  for (i=0; i < NUM_CUSTOM_ELEMENTS; i++)
-    if (element_info[EL_CUSTOM_START + i].modified_settings)
-      num_changed_custom_elements++;
-  level_chunk_CUS3_size = LEVEL_CHUNK_CUS3_SIZE(num_changed_custom_elements);
-
   putFileChunkBE(file, "RND1", CHUNK_SIZE_UNDEFINED);
   putFileChunkBE(file, "CAVE", CHUNK_SIZE_NONE);
 
@@ -1413,10 +1620,33 @@ static void SaveLevelFromFilename(struct LevelInfo *level, char *filename)
     SaveLevel_CNT2(file, level, EL_BD_AMOEBA);
   }
 
-  if (num_changed_custom_elements > 0 && !level->use_custom_template)
+  /* check for envelope content */
+  for (i=0; i<4; i++)
   {
-    putFileChunkBE(file, "CUS3", level_chunk_CUS3_size);
-    SaveLevel_CUS3(file, level, num_changed_custom_elements);
+    if (strlen(level->envelope_text[i]) > 0)
+    {
+      int envelope_len = strlen(level->envelope_text[i]) + 1;
+
+      putFileChunkBE(file, "CNT3", LEVEL_CHUNK_CNT3_HEADER + envelope_len);
+      SaveLevel_CNT3(file, level, EL_ENVELOPE_1 + i);
+    }
+  }
+
+  /* check for non-default custom elements (unless using template level) */
+  if (!level->use_custom_template)
+  {
+    for (i=0; i < NUM_CUSTOM_ELEMENTS; i++)
+    {
+      int element = EL_CUSTOM_START + i;
+
+      if (element_info[element].modified_settings)
+      {
+       int num_change_pages = element_info[element].num_change_pages;
+
+       putFileChunkBE(file, "CUS4", LEVEL_CHUNK_CUS4_SIZE(num_change_pages));
+       SaveLevel_CUS4(file, level, element);
+      }
+    }
   }
 
   fclose(file);
@@ -1460,7 +1690,7 @@ void DumpLevel(struct LevelInfo *level)
   printf("\n");
   printf("Amoeba Speed: %d\n", level->amoeba_speed);
   printf("\n");
-  printf("Gravity:                %s\n", (level->gravity ? "yes" : "no"));
+  printf("Gravity:                %s\n", (level->initial_gravity ? "yes" : "no"));
   printf("Double Speed Movement:  %s\n", (level->double_speed ? "yes" : "no"));
   printf("EM style slippery gems: %s\n", (level->em_slippery_gems ? "yes" : "no"));
 
@@ -2065,9 +2295,10 @@ void SaveScore(int level_nr)
 #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_HEADLINES                9
+#define SETUP_TOKEN_EDITOR_EL_CUSTOM_MORE      9
+#define SETUP_TOKEN_EDITOR_EL_HEADLINES                10
 
-#define NUM_EDITOR_SETUP_TOKENS                        10
+#define NUM_EDITOR_SETUP_TOKENS                        11
 
 /* shortcut setup */
 #define SETUP_TOKEN_SHORTCUT_SAVE_GAME         0
@@ -2490,7 +2721,8 @@ void LoadSpecialMenuDesignSettings()
     for (j=0; image_config[j].token != NULL; j++)
       if (strcmp(image_config_vars[i].token, image_config[j].token) == 0)
        *image_config_vars[i].value =
-         get_integer_from_string(image_config[j].value);
+         get_auto_parameter_value(image_config_vars[i].token,
+                                  image_config[j].value);
 
   if ((setup_file_hash = loadSetupFileHash(filename)) == NULL)
     return;
@@ -2516,7 +2748,8 @@ void LoadSpecialMenuDesignSettings()
     char *value = getHashEntry(setup_file_hash, image_config_vars[i].token);
 
     if (value != NULL)
-      *image_config_vars[i].value = get_integer_from_string(value);
+      *image_config_vars[i].value =
+       get_auto_parameter_value(image_config_vars[i].token, value);
   }
 
   freeSetupFileHash(setup_file_hash);