fixed ignoring clicks on global animations after executing event actions
[rocksndiamonds.git] / src / files.c
index 4cc8065647a2c5daf566b48f5010b6051931f22a..ac75c2afc8151a97e03cd7428bb08ab5265c8b59 100644 (file)
@@ -18,6 +18,7 @@
 
 #include "files.h"
 #include "init.h"
+#include "screens.h"
 #include "tools.h"
 #include "tape.h"
 #include "config.h"
@@ -57,7 +58,7 @@
 
 #define TAPE_CHUNK_VERS_SIZE   8       /* size of file version chunk */
 #define TAPE_CHUNK_HEAD_SIZE   20      /* size of tape file header   */
-#define TAPE_CHUNK_HEAD_UNUSED 3       /* unused tape header bytes   */
+#define TAPE_CHUNK_HEAD_UNUSED 2       /* unused tape header bytes   */
 
 #define LEVEL_CHUNK_CNT3_SIZE(x)        (LEVEL_CHUNK_CNT3_HEADER + (x))
 #define LEVEL_CHUNK_CUS3_SIZE(x)        (2 + (x) * LEVEL_CPART_CUS3_SIZE)
@@ -244,6 +245,12 @@ static struct LevelFileConfigInfo chunk_config_INFO[] =
     &li.auto_exit_sokoban,             FALSE
   },
 
+  {
+    -1,                                        -1,
+    TYPE_BOOLEAN,                      CONF_VALUE_8_BIT(10),
+    &li.auto_count_gems,               FALSE
+  },
+
   {
     -1,                                        -1,
     -1,                                        -1,
@@ -800,16 +807,69 @@ static struct LevelFileConfigInfo chunk_config_ELEM[] =
     &li.num_ball_contents,             4, MAX_ELEMENT_CONTENTS
   },
 
-  /* ---------- unused values ----------------------------------------------- */
+  {
+    EL_MM_MCDUFFIN,                    -1,
+    TYPE_BOOLEAN,                      CONF_VALUE_8_BIT(1),
+    &li.mm_laser_red,                  FALSE
+  },
+  {
+    EL_MM_MCDUFFIN,                    -1,
+    TYPE_BOOLEAN,                      CONF_VALUE_8_BIT(2),
+    &li.mm_laser_green,                        FALSE
+  },
+  {
+    EL_MM_MCDUFFIN,                    -1,
+    TYPE_BOOLEAN,                      CONF_VALUE_8_BIT(3),
+    &li.mm_laser_blue,                 TRUE
+  },
 
   {
-    EL_UNKNOWN,                                SAVE_CONF_NEVER,
+    EL_DF_LASER,                       -1,
+    TYPE_BOOLEAN,                      CONF_VALUE_8_BIT(1),
+    &li.df_laser_red,                  TRUE
+  },
+  {
+    EL_DF_LASER,                       -1,
+    TYPE_BOOLEAN,                      CONF_VALUE_8_BIT(2),
+    &li.df_laser_green,                        TRUE
+  },
+  {
+    EL_DF_LASER,                       -1,
+    TYPE_BOOLEAN,                      CONF_VALUE_8_BIT(3),
+    &li.df_laser_blue,                 FALSE
+  },
+
+  {
+    EL_MM_FUSE_ACTIVE,                 -1,
     TYPE_INTEGER,                      CONF_VALUE_16_BIT(1),
-    &li.score[SC_UNKNOWN_14],          10
+    &li.mm_time_fuse,                  25
   },
+  {
+    EL_MM_BOMB,                                -1,
+    TYPE_INTEGER,                      CONF_VALUE_16_BIT(1),
+    &li.mm_time_bomb,                  75
+  },
+  {
+    EL_MM_GRAY_BALL,                   -1,
+    TYPE_INTEGER,                      CONF_VALUE_16_BIT(1),
+    &li.mm_time_ball,                  75
+  },
+  {
+    EL_MM_STEEL_BLOCK,                 -1,
+    TYPE_INTEGER,                      CONF_VALUE_16_BIT(1),
+    &li.mm_time_block,                 75
+  },
+  {
+    EL_MM_LIGHTBALL,                   -1,
+    TYPE_INTEGER,                      CONF_VALUE_16_BIT(1),
+    &li.score[SC_ELEM_BONUS],          10
+  },
+
+  /* ---------- unused values ----------------------------------------------- */
+
   {
     EL_UNKNOWN,                                SAVE_CONF_NEVER,
-    TYPE_INTEGER,                      CONF_VALUE_16_BIT(2),
+    TYPE_INTEGER,                      CONF_VALUE_16_BIT(1),
     &li.score[SC_UNKNOWN_15],          10
   },
 
@@ -1603,6 +1663,7 @@ static void setLevelInfoToDefaults_Level(struct LevelInfo *level)
 
   setLevelInfoToDefaults_EM();
   setLevelInfoToDefaults_SP();
+  setLevelInfoToDefaults_MM();
 
   level->native_em_level = &native_em_level;
   level->native_sp_level = &native_sp_level;
@@ -1637,6 +1698,9 @@ static void setLevelInfoToDefaults_Level(struct LevelInfo *level)
 
   BorderElement = EL_STEELWALL;
 
+  /* detect custom elements when loading them */
+  level->file_has_custom_elements = FALSE;
+
   /* set all bug compatibility flags to "false" => do not emulate this bug */
   level->use_action_after_change_bug = FALSE;
 
@@ -1918,6 +1982,26 @@ static int getFileTypeFromBasename(char *basename)
   return LEVEL_FILE_TYPE_UNKNOWN;
 }
 
+static int getFileTypeFromMagicBytes(char *filename, int type)
+{
+  File *file;
+
+  if ((file = openFile(filename, MODE_READ)))
+  {
+    char chunk_name[CHUNK_ID_LEN + 1];
+
+    getFileChunkBE(file, chunk_name, NULL);
+
+    if (strEqual(chunk_name, "MMII") ||
+       strEqual(chunk_name, "MIRR"))
+      type = LEVEL_FILE_TYPE_MM;
+
+    closeFile(file);
+  }
+
+  return type;
+}
+
 static boolean checkForPackageFromBasename(char *basename)
 {
   /* !!! WON'T WORK ANYMORE IF getFileTypeFromBasename() ALSO DETECTS !!!
@@ -2184,6 +2268,9 @@ static void determineLevelFileInfo_Filetype(struct LevelFileInfo *lfi)
 {
   if (lfi->type == LEVEL_FILE_TYPE_UNKNOWN)
     lfi->type = getFileTypeFromBasename(lfi->basename);
+
+  if (lfi->type == LEVEL_FILE_TYPE_RND)
+    lfi->type = getFileTypeFromMagicBytes(lfi->filename, lfi->type);
 }
 
 static void setLevelFileInfo(struct LevelFileInfo *level_file_info, int nr)
@@ -2558,6 +2645,8 @@ static int LoadLevel_CUS1(File *file, int chunk_size, struct LevelInfo *level)
     element_info[element].push_delay_random = 8;
   }
 
+  level->file_has_custom_elements = TRUE;
+
   return chunk_size;
 }
 
@@ -2584,6 +2673,8 @@ static int LoadLevel_CUS2(File *file, int chunk_size, struct LevelInfo *level)
       Error(ERR_WARN, "invalid custom element number %d", element);
   }
 
+  level->file_has_custom_elements = TRUE;
+
   return chunk_size;
 }
 
@@ -2675,6 +2766,8 @@ static int LoadLevel_CUS3(File *file, int chunk_size, struct LevelInfo *level)
     ei->modified_settings = TRUE;
   }
 
+  level->file_has_custom_elements = TRUE;
+
   return chunk_size;
 }
 
@@ -2823,6 +2916,8 @@ static int LoadLevel_CUS4(File *file, int chunk_size, struct LevelInfo *level)
   /* mark this custom element as modified */
   ei->modified_settings = TRUE;
 
+  level->file_has_custom_elements = TRUE;
+
   return chunk_size;
 }
 
@@ -2867,6 +2962,8 @@ static int LoadLevel_GRP1(File *file, int chunk_size, struct LevelInfo *level)
   /* mark this group element as modified */
   element_info[element].modified_settings = TRUE;
 
+  level->file_has_custom_elements = TRUE;
+
   return chunk_size;
 }
 
@@ -3159,6 +3256,8 @@ static int LoadLevel_CUSX(File *file, int chunk_size, struct LevelInfo *level)
       break;
   }
 
+  level->file_has_custom_elements = TRUE;
+
   return real_chunk_size;
 }
 
@@ -3184,6 +3283,8 @@ static int LoadLevel_GRPX(File *file, int chunk_size, struct LevelInfo *level)
   *ei = xx_ei;
   *group = xx_group;
 
+  level->file_has_custom_elements = TRUE;
+
   return real_chunk_size;
 }
 
@@ -3891,33 +3992,36 @@ void CopyNativeLevel_RND_to_MM(struct LevelInfo *level)
   struct LevelInfo_MM *level_mm = level->native_mm_level;
   int x, y;
 
-  level_mm->file_version = level->file_version;
-  level_mm->game_version = level->game_version;
-  level_mm->encoding_16bit_field = level->encoding_16bit_field;
-
   level_mm->fieldx = MIN(level->fieldx, MM_MAX_PLAYFIELD_WIDTH);
-  level_mm->fieldy = MIN(level->fieldx, MM_MAX_PLAYFIELD_HEIGHT);
+  level_mm->fieldy = MIN(level->fieldy, MM_MAX_PLAYFIELD_HEIGHT);
 
   level_mm->time = level->time;
   level_mm->kettles_needed = level->gems_needed;
-  level_mm->auto_count_kettles = FALSE;
-  level_mm->laser_red = FALSE;
-  level_mm->laser_green = FALSE;
-  level_mm->laser_blue = TRUE;
+  level_mm->auto_count_kettles = level->auto_count_gems;
+
+  level_mm->laser_red = level->mm_laser_red;
+  level_mm->laser_green = level->mm_laser_green;
+  level_mm->laser_blue = level->mm_laser_blue;
 
   strcpy(level_mm->name, level->name);
   strcpy(level_mm->author, level->author);
 
+  level_mm->score[SC_EMERALD]    = level->score[SC_EMERALD];
   level_mm->score[SC_PACMAN]     = level->score[SC_PACMAN];
-  level_mm->score[SC_KEY]        = level->score[SC_PACMAN];
+  level_mm->score[SC_KEY]        = level->score[SC_KEY];
   level_mm->score[SC_TIME_BONUS] = level->score[SC_TIME_BONUS];
+  level_mm->score[SC_ELEM_BONUS] = level->score[SC_ELEM_BONUS];
 
   level_mm->amoeba_speed = level->amoeba_speed;
-  level_mm->time_fuse = 0;
+  level_mm->time_fuse    = level->mm_time_fuse;
+  level_mm->time_bomb    = level->mm_time_bomb;
+  level_mm->time_ball    = level->mm_time_ball;
+  level_mm->time_block   = level->mm_time_block;
 
-  for (y = 0; y < level_mm->fieldx; y++)
-    for (x = 0; x < level_mm->fieldy; x++)
-      level_mm->field[x][y] = map_element_RND_to_MM(level->field[x][y]);
+  for (x = 0; x < level->fieldx; x++)
+    for (y = 0; y < level->fieldy; y++)
+      Ur[x][y] =
+       level_mm->field[x][y] = map_element_RND_to_MM(level->field[x][y]);
 }
 
 void CopyNativeLevel_MM_to_RND(struct LevelInfo *level)
@@ -3925,27 +4029,37 @@ void CopyNativeLevel_MM_to_RND(struct LevelInfo *level)
   struct LevelInfo_MM *level_mm = level->native_mm_level;
   int x, y;
 
-  level->file_version = level_mm->file_version;
-  level->game_version = level_mm->game_version;
-  level->encoding_16bit_field = level_mm->encoding_16bit_field;
-
   level->fieldx = MIN(level_mm->fieldx, MAX_LEV_FIELDX);
-  level->fieldy = MIN(level_mm->fieldx, MAX_LEV_FIELDY);
+  level->fieldy = MIN(level_mm->fieldy, MAX_LEV_FIELDY);
 
   level->time = level_mm->time;
   level->gems_needed = level_mm->kettles_needed;
+  level->auto_count_gems = level_mm->auto_count_kettles;
+
+  level->mm_laser_red = level_mm->laser_red;
+  level->mm_laser_green = level_mm->laser_green;
+  level->mm_laser_blue = level_mm->laser_blue;
 
   strcpy(level->name, level_mm->name);
-  strcpy(level->author, level_mm->author);
 
+  /* only overwrite author from 'levelinfo.conf' if author defined in level */
+  if (!strEqual(level_mm->author, ANONYMOUS_NAME))
+    strcpy(level->author, level_mm->author);
+
+  level->score[SC_EMERALD]    = level_mm->score[SC_EMERALD];
   level->score[SC_PACMAN]     = level_mm->score[SC_PACMAN];
-  level->score[SC_KEY]        = level_mm->score[SC_PACMAN];
+  level->score[SC_KEY]        = level_mm->score[SC_KEY];
   level->score[SC_TIME_BONUS] = level_mm->score[SC_TIME_BONUS];
+  level->score[SC_ELEM_BONUS] = level_mm->score[SC_ELEM_BONUS];
 
-  level->amoeba_speed = level_mm->amoeba_speed;
+  level->amoeba_speed  = level_mm->amoeba_speed;
+  level->mm_time_fuse  = level_mm->time_fuse;
+  level->mm_time_bomb  = level_mm->time_bomb;
+  level->mm_time_ball  = level_mm->time_ball;
+  level->mm_time_block = level_mm->time_block;
 
-  for (y = 0; y < level->fieldx; y++)
-    for (x = 0; x < level->fieldy; x++)
+  for (x = 0; x < level->fieldx; x++)
+    for (y = 0; y < level->fieldy; y++)
       level->field[x][y] = map_element_MM_to_RND(level_mm->field[x][y]);
 }
 
@@ -6265,9 +6379,25 @@ static void LoadLevel_InitVersion(struct LevelInfo *level, char *filename)
     level->em_explodes_by_fire = TRUE;
 }
 
-static void LoadLevel_InitElements(struct LevelInfo *level, char *filename)
+static void LoadLevel_InitStandardElements(struct LevelInfo *level)
 {
-  int i, j, x, y;
+  int i, x, y;
+
+  /* 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].e[x][y] =
+         getMappedElementByVersion(level->yamyam_content[i].e[x][y],
+                                   level->game_version);
+
+}
+
+static void LoadLevel_InitCustomElements(struct LevelInfo *level)
+{
+  int i, j;
 
   /* map custom element change events that have changed in newer versions
      (these following values were accidentally changed in version 3.0.1)
@@ -6381,19 +6511,30 @@ static void LoadLevel_InitElements(struct LevelInfo *level, char *filename)
     }
   }
 
-  /* 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].e[x][y] =
-         getMappedElementByVersion(level->yamyam_content[i].e[x][y],
-                                   level->game_version);
+  /* set some other uninitialized values of custom elements in older levels */
+  if (level->game_version < VERSION_IDENT(3,1,0,0))
+  {
+    for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
+    {
+      int element = EL_CUSTOM_START + i;
+
+      element_info[element].access_direction = MV_ALL_DIRECTIONS;
+
+      element_info[element].explosion_delay = 17;
+      element_info[element].ignition_delay = 8;
+    }
+  }
+}
+
+static void LoadLevel_InitElements(struct LevelInfo *level, char *filename)
+{
+  LoadLevel_InitStandardElements(level);
+
+  if (level->file_has_custom_elements)
+    LoadLevel_InitCustomElements(level);
 
   /* initialize element properties for level editor etc. */
   InitElementPropertiesEngine(level->game_version);
-  InitElementPropertiesAfterLoading(level->game_version);
   InitElementPropertiesGfxElement();
 }
 
@@ -7441,6 +7582,8 @@ static int LoadTape_HEAD(File *file, int chunk_size, struct TapeInfo *tape)
       }
     }
 
+    tape->use_mouse = (getFile8Bit(file) == 1 ? TRUE : FALSE);
+
     ReadUnusedBytesFromFile(file, TAPE_CHUNK_HEAD_UNUSED);
 
     engine_version = getFileVersion(file);
@@ -7476,8 +7619,9 @@ static int LoadTape_INFO(File *file, int chunk_size, struct TapeInfo *tape)
 static int LoadTape_BODY(File *file, int chunk_size, struct TapeInfo *tape)
 {
   int i, j;
-  int chunk_size_expected =
-    (tape->num_participating_players + 1) * tape->length;
+  int tape_pos_size =
+    (tape->use_mouse ? 3 : tape->num_participating_players) + 1;
+  int chunk_size_expected = tape_pos_size * tape->length;
 
   if (chunk_size_expected != chunk_size)
   {
@@ -7499,12 +7643,23 @@ static int LoadTape_BODY(File *file, int chunk_size, struct TapeInfo *tape)
       break;
     }
 
-    for (j = 0; j < MAX_PLAYERS; j++)
+    if (tape->use_mouse)
     {
-      tape->pos[i].action[j] = MV_NONE;
+      tape->pos[i].action[TAPE_ACTION_LX]     = getFile8Bit(file);
+      tape->pos[i].action[TAPE_ACTION_LY]     = getFile8Bit(file);
+      tape->pos[i].action[TAPE_ACTION_BUTTON] = getFile8Bit(file);
 
-      if (tape->player_participates[j])
-       tape->pos[i].action[j] = getFile8Bit(file);
+      tape->pos[i].action[TAPE_ACTION_UNUSED] = 0;
+    }
+    else
+    {
+      for (j = 0; j < MAX_PLAYERS; j++)
+      {
+       tape->pos[i].action[j] = MV_NONE;
+
+       if (tape->player_participates[j])
+         tape->pos[i].action[j] = getFile8Bit(file);
+      }
     }
 
     tape->pos[i].delay = getFile8Bit(file);
@@ -7561,7 +7716,7 @@ static int LoadTape_BODY(File *file, int chunk_size, struct TapeInfo *tape)
   }
 
   if (i != tape->length)
-    chunk_size = (tape->num_participating_players + 1) * i;
+    chunk_size = tape_pos_size * i;
 
   return chunk_size;
 }
@@ -7830,6 +7985,8 @@ static void SaveTape_HEAD(FILE *file, struct TapeInfo *tape)
 
   putFile8Bit(file, store_participating_players);
 
+  putFile8Bit(file, (tape->use_mouse ? 1 : 0));
+
   /* unused bytes not at the end here for 4-byte alignment of engine_version */
   WriteUnusedBytesToFile(file, TAPE_CHUNK_HEAD_UNUSED);
 
@@ -7855,9 +8012,18 @@ static void SaveTape_BODY(FILE *file, struct TapeInfo *tape)
 
   for (i = 0; i < tape->length; i++)
   {
-    for (j = 0; j < MAX_PLAYERS; j++)
-      if (tape->player_participates[j])
-       putFile8Bit(file, tape->pos[i].action[j]);
+    if (tape->use_mouse)
+    {
+      putFile8Bit(file, tape->pos[i].action[TAPE_ACTION_LX]);
+      putFile8Bit(file, tape->pos[i].action[TAPE_ACTION_LY]);
+      putFile8Bit(file, tape->pos[i].action[TAPE_ACTION_BUTTON]);
+    }
+    else
+    {
+      for (j = 0; j < MAX_PLAYERS; j++)
+       if (tape->player_participates[j])
+         putFile8Bit(file, tape->pos[i].action[j]);
+    }
 
     putFile8Bit(file, tape->pos[i].delay);
   }
@@ -7868,6 +8034,7 @@ void SaveTape(int nr)
   char *filename = getTapeFilename(nr);
   FILE *file;
   int num_participating_players = 0;
+  int tape_pos_size;
   int info_chunk_size;
   int body_chunk_size;
   int i;
@@ -7888,8 +8055,10 @@ void SaveTape(int nr)
     if (tape.player_participates[i])
       num_participating_players++;
 
+  tape_pos_size = (tape.use_mouse ? 3 : num_participating_players) + 1;
+
   info_chunk_size = 2 + (strlen(tape.level_identifier) + 1) + 2;
-  body_chunk_size = (num_participating_players + 1) * tape.length;
+  body_chunk_size = tape_pos_size * tape.length;
 
   putFileChunkBE(file, "RND1", CHUNK_SIZE_UNDEFINED);
   putFileChunkBE(file, "TAPE", CHUNK_SIZE_NONE);
@@ -7913,18 +8082,18 @@ void SaveTape(int nr)
   tape.changed = FALSE;
 }
 
-boolean SaveTapeChecked(int nr)
+static boolean SaveTapeCheckedExt(int nr, char *msg_replace, char *msg_saved)
 {
   char *filename = getTapeFilename(nr);
   boolean new_tape = !fileExists(filename);
   boolean tape_saved = FALSE;
 
-  if (new_tape || Request("Replace old tape?", REQ_ASK))
+  if (new_tape || Request(msg_replace, REQ_ASK))
   {
     SaveTape(nr);
 
     if (new_tape)
-      Request("Tape saved!", REQ_CONFIRM);
+      Request(msg_saved, REQ_CONFIRM);
 
     tape_saved = TRUE;
   }
@@ -7932,6 +8101,17 @@ boolean SaveTapeChecked(int nr)
   return tape_saved;
 }
 
+boolean SaveTapeChecked(int nr)
+{
+  return SaveTapeCheckedExt(nr, "Replace old tape?", "Tape saved!");
+}
+
+boolean SaveTapeChecked_LevelSolved(int nr)
+{
+  return SaveTapeCheckedExt(nr, "Level solved! Replace old tape?",
+                               "Level solved! Tape saved!");
+}
+
 void DumpTape(struct TapeInfo *tape)
 {
   int tape_frame_counter;
@@ -8125,8 +8305,20 @@ void SaveScore(int nr)
 #define SETUP_TOKEN_TOUCH_CONTROL_TYPE         41
 #define SETUP_TOKEN_TOUCH_MOVE_DISTANCE                42
 #define SETUP_TOKEN_TOUCH_DROP_DISTANCE                43
+#define SETUP_TOKEN_TOUCH_TRANSPARENCY         44
+#define SETUP_TOKEN_TOUCH_DRAW_OUTLINED                45
+#define SETUP_TOKEN_TOUCH_DRAW_PRESSED         46
+#define SETUP_TOKEN_TOUCH_GRID_XSIZE_0         47
+#define SETUP_TOKEN_TOUCH_GRID_YSIZE_0         48
+#define SETUP_TOKEN_TOUCH_GRID_XSIZE_1         49
+#define SETUP_TOKEN_TOUCH_GRID_YSIZE_1         50
+
+#define NUM_GLOBAL_SETUP_TOKENS                        51
 
-#define NUM_GLOBAL_SETUP_TOKENS                        44
+/* auto setup */
+#define SETUP_TOKEN_AUTO_EDITOR_ZOOM_TILESIZE  0
+
+#define NUM_AUTO_SETUP_TOKENS                  1
 
 /* editor setup */
 #define SETUP_TOKEN_EDITOR_EL_CLASSIC          0
@@ -8266,6 +8458,7 @@ void SaveScore(int nr)
 
 
 static struct SetupInfo si;
+static struct SetupAutoSetupInfo sasi;
 static struct SetupEditorInfo sei;
 static struct SetupEditorCascadeInfo seci;
 static struct SetupShortcutInfo ssi;
@@ -8321,6 +8514,18 @@ static struct TokenInfo global_setup_tokens[] =
   { TYPE_STRING, &si.touch.control_type,      "touch.control_type"     },
   { TYPE_INTEGER,&si.touch.move_distance,     "touch.move_distance"    },
   { TYPE_INTEGER,&si.touch.drop_distance,     "touch.drop_distance"    },
+  { TYPE_INTEGER,&si.touch.transparency,      "touch.transparency"     },
+  { TYPE_INTEGER,&si.touch.draw_outlined,     "touch.draw_outlined"    },
+  { TYPE_INTEGER,&si.touch.draw_pressed,      "touch.draw_pressed"     },
+  { TYPE_INTEGER,&si.touch.grid_xsize[0],     "touch.virtual_buttons.0.xsize" },
+  { TYPE_INTEGER,&si.touch.grid_ysize[0],     "touch.virtual_buttons.0.ysize" },
+  { TYPE_INTEGER,&si.touch.grid_xsize[1],     "touch.virtual_buttons.1.xsize" },
+  { TYPE_INTEGER,&si.touch.grid_ysize[1],     "touch.virtual_buttons.1.ysize" },
+};
+
+static struct TokenInfo auto_setup_tokens[] =
+{
+  { TYPE_INTEGER,&sasi.editor_zoom_tilesize,   "editor.zoom_tilesize"  },
 };
 
 static struct TokenInfo editor_setup_tokens[] =
@@ -8529,6 +8734,58 @@ static void setSetupInfoToDefaults(struct SetupInfo *si)
   si->touch.control_type = getStringCopy(TOUCH_CONTROL_DEFAULT);
   si->touch.move_distance = TOUCH_MOVE_DISTANCE_DEFAULT;       /* percent */
   si->touch.drop_distance = TOUCH_DROP_DISTANCE_DEFAULT;       /* percent */
+  si->touch.transparency = TOUCH_TRANSPARENCY_DEFAULT;         /* percent */
+  si->touch.draw_outlined = TRUE;
+  si->touch.draw_pressed = TRUE;
+
+  for (i = 0; i < 2; i++)
+  {
+    char *default_grid_button[6][2] =
+    {
+      { "      ", "  ^^  " },
+      { "      ", "  ^^  " },
+      { "      ", "<<  >>" },
+      { "      ", "<<  >>" },
+      { "111222", "  vv  " },
+      { "111222", "  vv  " }
+    };
+    int grid_xsize = DEFAULT_GRID_XSIZE(i);
+    int grid_ysize = DEFAULT_GRID_YSIZE(i);
+    int min_xsize = MIN(6, grid_xsize);
+    int min_ysize = MIN(6, grid_ysize);
+    int startx = grid_xsize - min_xsize;
+    int starty = grid_ysize - min_ysize;
+    int x, y;
+
+    // virtual buttons grid can only be set to defaults if video is initialized
+    // (this will be repeated if virtual buttons are not loaded from setup file)
+    if (video.initialized)
+    {
+      si->touch.grid_xsize[i] = grid_xsize;
+      si->touch.grid_ysize[i] = grid_ysize;
+    }
+    else
+    {
+      si->touch.grid_xsize[i] = -1;
+      si->touch.grid_ysize[i] = -1;
+    }
+
+    for (x = 0; x < MAX_GRID_XSIZE; x++)
+      for (y = 0; y < MAX_GRID_YSIZE; y++)
+       si->touch.grid_button[i][x][y] = CHAR_GRID_BUTTON_NONE;
+
+    for (x = 0; x < min_xsize; x++)
+      for (y = 0; y < min_ysize; y++)
+       si->touch.grid_button[i][x][starty + y] =
+         default_grid_button[y][0][x];
+
+    for (x = 0; x < min_xsize; x++)
+      for (y = 0; y < min_ysize; y++)
+       si->touch.grid_button[i][startx + x][starty + y] =
+         default_grid_button[y][1][x];
+  }
+
+  si->touch.grid_initialized           = video.initialized;
 
   si->editor.el_boulderdash            = TRUE;
   si->editor.el_emerald_mine           = TRUE;
@@ -8666,6 +8923,11 @@ static void setSetupInfoToDefaults(struct SetupInfo *si)
 #endif
 }
 
+static void setSetupInfoToDefaults_AutoSetup(struct SetupInfo *si)
+{
+  si->auto_setup.editor_zoom_tilesize = MINI_TILESIZE;
+}
+
 static void setSetupInfoToDefaults_EditorCascade(struct SetupInfo *si)
 {
   si->editor_cascade.el_bd             = TRUE;
@@ -8701,17 +8963,22 @@ static char *getHideSetupToken(void *setup_value)
   return hide_setup_token;
 }
 
-static void setHideSetupEntry(void *setup_value_raw)
+void setHideSetupEntry(void *setup_value)
 {
-  /* !!! DIRTY WORKAROUND; TO BE FIXED AFTER THE MM ENGINE RELEASE !!! */
-  void *setup_value = setup_value_raw - (void *)&si + (void *)&setup;
-
   char *hide_setup_token = getHideSetupToken(setup_value);
 
   if (setup_value != NULL)
     setHashEntry(hide_setup_hash, hide_setup_token, "");
 }
 
+static void setHideSetupEntryRaw(char *token_text, void *setup_value_raw)
+{
+  /* !!! DIRTY WORKAROUND; TO BE FIXED AFTER THE MM ENGINE RELEASE !!! */
+  void *setup_value = setup_value_raw - (void *)&si + (void *)&setup;
+
+  setHideSetupEntry(setup_value);
+}
+
 boolean hideSetupEntry(void *setup_value)
 {
   char *hide_setup_token = getHideSetupToken(setup_value);
@@ -8732,7 +8999,7 @@ static void setSetupInfoFromTokenText(SetupFileHash *setup_file_hash,
 
   /* check if this setup option should be hidden in the setup menu */
   if (token_hide_value != NULL && get_boolean_from_string(token_hide_value))
-    setHideSetupEntry(token_info[token_nr].value);
+    setHideSetupEntryRaw(token_text, token_info[token_nr].value);
 }
 
 static void setSetupInfoFromTokenInfo(SetupFileHash *setup_file_hash,
@@ -8759,6 +9026,45 @@ static void decodeSetupFileHash(SetupFileHash *setup_file_hash)
     setSetupInfoFromTokenInfo(setup_file_hash, global_setup_tokens, i);
   setup = si;
 
+  /* virtual buttons setup */
+  setup.touch.grid_initialized = TRUE;
+  for (i = 0; i < 2; i++)
+  {
+    int grid_xsize = setup.touch.grid_xsize[i];
+    int grid_ysize = setup.touch.grid_ysize[i];
+    int x, y;
+
+    // if virtual buttons are not loaded from setup file, repeat initializing
+    // virtual buttons grid with default values later when video is initialized
+    if (grid_xsize == -1 ||
+       grid_ysize == -1)
+    {
+      setup.touch.grid_initialized = FALSE;
+
+      continue;
+    }
+
+    for (y = 0; y < grid_ysize; y++)
+    {
+      char token_string[MAX_LINE_LEN];
+
+      sprintf(token_string, "touch.virtual_buttons.%d.%02d", i, y);
+
+      char *value_string = getHashEntry(setup_file_hash, token_string);
+
+      if (value_string == NULL)
+       continue;
+
+      for (x = 0; x < grid_xsize; x++)
+      {
+       char c = value_string[x];
+
+       setup.touch.grid_button[i][x][y] =
+         (c == '.' ? CHAR_GRID_BUTTON_NONE : c);
+      }
+    }
+  }
+
   /* editor setup */
   sei = setup.editor;
   for (i = 0; i < NUM_EDITOR_SETUP_TOKENS; i++)
@@ -8813,6 +9119,24 @@ static void decodeSetupFileHash(SetupFileHash *setup_file_hash)
   for (i = 0; i < NUM_OPTIONS_SETUP_TOKENS; i++)
     setSetupInfoFromTokenInfo(setup_file_hash, options_setup_tokens, i);
   setup.options = soi;
+
+  setHideRelatedSetupEntries();
+}
+
+static void decodeSetupFileHash_AutoSetup(SetupFileHash *setup_file_hash)
+{
+  int i;
+
+  if (!setup_file_hash)
+    return;
+
+  /* auto setup */
+  sasi = setup.auto_setup;
+  for (i = 0; i < NUM_AUTO_SETUP_TOKENS; i++)
+    setSetupInfo(auto_setup_tokens, i,
+                getHashEntry(setup_file_hash,
+                             auto_setup_tokens[i].text));
+  setup.auto_setup = sasi;
 }
 
 static void decodeSetupFileHash_EditorCascade(SetupFileHash *setup_file_hash)
@@ -8889,6 +9213,26 @@ void LoadSetup()
   LoadSetup_SpecialPostProcessing();
 }
 
+void LoadSetup_AutoSetup()
+{
+  char *filename = getPath2(getSetupDir(), AUTOSETUP_FILENAME);
+  SetupFileHash *setup_file_hash = NULL;
+
+  /* always start with reliable default values */
+  setSetupInfoToDefaults_AutoSetup(&setup);
+
+  setup_file_hash = loadSetupFileHash(filename);
+
+  if (setup_file_hash)
+  {
+    decodeSetupFileHash_AutoSetup(setup_file_hash);
+
+    freeSetupFileHash(setup_file_hash);
+  }
+
+  free(filename);
+}
+
 void LoadSetup_EditorCascade()
 {
   char *filename = getPath2(getSetupDir(), EDITORCASCADE_FILENAME);
@@ -8982,12 +9326,43 @@ void SaveSetup()
     if (i == SETUP_TOKEN_PLAYER_NAME + 1 ||
        i == SETUP_TOKEN_GRAPHICS_SET ||
        i == SETUP_TOKEN_VOLUME_SIMPLE ||
-       i == SETUP_TOKEN_TOUCH_CONTROL_TYPE)
+       i == SETUP_TOKEN_TOUCH_CONTROL_TYPE ||
+       i == SETUP_TOKEN_TOUCH_GRID_XSIZE_0 ||
+       i == SETUP_TOKEN_TOUCH_GRID_XSIZE_1)
       fprintf(file, "\n");
 
     fprintf(file, "%s\n", getSetupLine(global_setup_tokens, "", i));
   }
 
+  /* virtual buttons setup */
+  for (i = 0; i < 2; i++)
+  {
+    int grid_xsize = setup.touch.grid_xsize[i];
+    int grid_ysize = setup.touch.grid_ysize[i];
+    int x, y;
+
+    fprintf(file, "\n");
+
+    for (y = 0; y < grid_ysize; y++)
+    {
+      char token_string[MAX_LINE_LEN];
+      char value_string[MAX_LINE_LEN];
+
+      sprintf(token_string, "touch.virtual_buttons.%d.%02d", i, y);
+
+      for (x = 0; x < grid_xsize; x++)
+      {
+       char c = setup.touch.grid_button[i][x][y];
+
+       value_string[x] = (c == CHAR_GRID_BUTTON_NONE ? '.' : c);
+      }
+
+      value_string[grid_xsize] = '\0';
+
+      fprintf(file, "%s\n", getFormattedSetupEntry(token_string, value_string));
+    }
+  }
+
   /* editor setup */
   sei = setup.editor;
   fprintf(file, "\n");
@@ -9039,6 +9414,34 @@ void SaveSetup()
   SetFilePermissions(filename, PERMS_PRIVATE);
 }
 
+void SaveSetup_AutoSetup()
+{
+  char *filename = getPath2(getSetupDir(), AUTOSETUP_FILENAME);
+  FILE *file;
+  int i;
+
+  InitUserDataDirectory();
+
+  if (!(file = fopen(filename, MODE_WRITE)))
+  {
+    Error(ERR_WARN, "cannot write auto setup file '%s'", filename);
+    free(filename);
+    return;
+  }
+
+  fprintFileHeader(file, AUTOSETUP_FILENAME);
+
+  sasi = setup.auto_setup;
+  for (i = 0; i < NUM_AUTO_SETUP_TOKENS; i++)
+    fprintf(file, "%s\n", getSetupLine(auto_setup_tokens, "", i));
+
+  fclose(file);
+
+  SetFilePermissions(filename, PERMS_PRIVATE);
+
+  free(filename);
+}
+
 void SaveSetup_EditorCascade()
 {
   char *filename = getPath2(getSetupDir(), EDITORCASCADE_FILENAME);
@@ -9500,6 +9903,40 @@ static void LoadMenuDesignSettingsFromFilename(char *filename)
       menu.draw_yoffset_setup[i] = get_integer_from_string(value_2);
   }
 
+  /* special case: initialize with default values that may be overwritten */
+  /* (eg, init "menu.line_spacing.INFO[XXX]" from "menu.line_spacing.INFO") */
+  for (i = 0; i < NUM_SPECIAL_GFX_INFO_ARGS; i++)
+  {
+    char *value_1 = getHashEntry(setup_file_hash,"menu.left_spacing.INFO");
+    char *value_2 = getHashEntry(setup_file_hash,"menu.right_spacing.INFO");
+    char *value_3 = getHashEntry(setup_file_hash,"menu.top_spacing.INFO");
+    char *value_4 = getHashEntry(setup_file_hash,"menu.bottom_spacing.INFO");
+    char *value_5 = getHashEntry(setup_file_hash,"menu.paragraph_spacing.INFO");
+    char *value_6 = getHashEntry(setup_file_hash,"menu.headline1_spacing.INFO");
+    char *value_7 = getHashEntry(setup_file_hash,"menu.headline2_spacing.INFO");
+    char *value_8 = getHashEntry(setup_file_hash,"menu.line_spacing.INFO");
+    char *value_9 = getHashEntry(setup_file_hash,"menu.extra_spacing.INFO");
+
+    if (value_1 != NULL)
+      menu.left_spacing_info[i]      = get_integer_from_string(value_1);
+    if (value_2 != NULL)
+      menu.right_spacing_info[i]     = get_integer_from_string(value_2);
+    if (value_3 != NULL)
+      menu.top_spacing_info[i]       = get_integer_from_string(value_3);
+    if (value_4 != NULL)
+      menu.bottom_spacing_info[i]    = get_integer_from_string(value_4);
+    if (value_5 != NULL)
+      menu.paragraph_spacing_info[i] = get_integer_from_string(value_5);
+    if (value_6 != NULL)
+      menu.headline1_spacing_info[i] = get_integer_from_string(value_6);
+    if (value_7 != NULL)
+      menu.headline2_spacing_info[i] = get_integer_from_string(value_7);
+    if (value_8 != NULL)
+      menu.line_spacing_info[i]      = get_integer_from_string(value_8);
+    if (value_9 != NULL)
+      menu.extra_spacing_info[i]     = get_integer_from_string(value_9);
+  }
+
   /* special case: initialize with default values that may be overwritten */
   /* (eg, init "menu.enter_screen.SCORES.xyz" from "menu.enter_screen.xyz") */
   for (i = 0; i < NUM_SPECIAL_GFX_ARGS; i++)