fixed showing special toolbox buttons for empty space elements in editor
[rocksndiamonds.git] / src / editor.c
index 1713f04d9119d1044d351c73d42d4a3301ebbe30..4f29f028a7bc245c1b3abb4222aae4095c0f6be2 100644 (file)
@@ -459,6 +459,9 @@ enum
   GADGET_ID_INVENTORY_SIZE_DOWN,
   GADGET_ID_INVENTORY_SIZE_TEXT,
   GADGET_ID_INVENTORY_SIZE_UP,
+  GADGET_ID_MM_BALL_CONTENT_DOWN,
+  GADGET_ID_MM_BALL_CONTENT_TEXT,
+  GADGET_ID_MM_BALL_CONTENT_UP,
   GADGET_ID_CUSTOM_SCORE_DOWN,
   GADGET_ID_CUSTOM_SCORE_TEXT,
   GADGET_ID_CUSTOM_SCORE_UP,
@@ -539,6 +542,7 @@ enum
   GADGET_ID_ARTWORK_ELEMENT,
   GADGET_ID_EXPLOSION_ELEMENT,
   GADGET_ID_INVENTORY_CONTENT,
+  GADGET_ID_MM_BALL_CONTENT,
   GADGET_ID_CUSTOM_GRAPHIC,
   GADGET_ID_CUSTOM_CONTENT,
   GADGET_ID_CUSTOM_MOVE_ENTER,
@@ -570,6 +574,7 @@ enum
   GADGET_ID_LEVELSET_SAVE_MODE,
   GADGET_ID_WIND_DIRECTION,
   GADGET_ID_PLAYER_SPEED,
+  GADGET_ID_MM_BALL_CHOICE_MODE,
   GADGET_ID_CUSTOM_WALK_TO_ACTION,
   GADGET_ID_CUSTOM_EXPLOSION_TYPE,
   GADGET_ID_CUSTOM_DEADLINESS,
@@ -657,6 +662,7 @@ enum
   GADGET_ID_AUTO_EXIT_SOKOBAN,
   GADGET_ID_SOLVED_BY_ONE_PLAYER,
   GADGET_ID_FINISH_DIG_COLLECT,
+  GADGET_ID_KEEP_WALKABLE_CE,
   GADGET_ID_CONTINUOUS_SNAPPING,
   GADGET_ID_BLOCK_SNAP_FIELD,
   GADGET_ID_BLOCK_LAST_FIELD,
@@ -681,6 +687,8 @@ enum
   GADGET_ID_DF_LASER_RED,
   GADGET_ID_DF_LASER_GREEN,
   GADGET_ID_DF_LASER_BLUE,
+  GADGET_ID_ROTATE_MM_BALL_CONTENT,
+  GADGET_ID_EXPLODE_MM_BALL,
   GADGET_ID_CUSTOM_INDESTRUCTIBLE,
   GADGET_ID_CUSTOM_CAN_EXPLODE,
   GADGET_ID_CUSTOM_EXPLODE_FIRE,
@@ -749,6 +757,7 @@ enum
   ED_COUNTER_ID_ENVELOPE_XSIZE,
   ED_COUNTER_ID_ENVELOPE_YSIZE,
   ED_COUNTER_ID_INVENTORY_SIZE,
+  ED_COUNTER_ID_MM_BALL_CONTENT,
   ED_COUNTER_ID_CUSTOM_SCORE,
   ED_COUNTER_ID_CUSTOM_GEMCOUNT,
   ED_COUNTER_ID_CUSTOM_VALUE_FIX,
@@ -855,6 +864,7 @@ enum
   ED_SELECTBOX_ID_LEVELSET_SAVE_MODE,
   ED_SELECTBOX_ID_WIND_DIRECTION,
   ED_SELECTBOX_ID_PLAYER_SPEED,
+  ED_SELECTBOX_ID_MM_BALL_CHOICE_MODE,
   ED_SELECTBOX_ID_CUSTOM_ACCESS_TYPE,
   ED_SELECTBOX_ID_CUSTOM_ACCESS_LAYER,
   ED_SELECTBOX_ID_CUSTOM_ACCESS_PROTECTED,
@@ -967,6 +977,7 @@ enum
   ED_CHECKBUTTON_ID_AUTO_EXIT_SOKOBAN,
   ED_CHECKBUTTON_ID_SOLVED_BY_ONE_PLAYER,
   ED_CHECKBUTTON_ID_FINISH_DIG_COLLECT,
+  ED_CHECKBUTTON_ID_KEEP_WALKABLE_CE,
   ED_CHECKBUTTON_ID_CONTINUOUS_SNAPPING,
   ED_CHECKBUTTON_ID_BLOCK_SNAP_FIELD,
   ED_CHECKBUTTON_ID_BLOCK_LAST_FIELD,
@@ -991,6 +1002,8 @@ enum
   ED_CHECKBUTTON_ID_DF_LASER_RED,
   ED_CHECKBUTTON_ID_DF_LASER_GREEN,
   ED_CHECKBUTTON_ID_DF_LASER_BLUE,
+  ED_CHECKBUTTON_ID_ROTATE_MM_BALL_CONTENT,
+  ED_CHECKBUTTON_ID_EXPLODE_MM_BALL,
   ED_CHECKBUTTON_ID_CUSTOM_USE_GRAPHIC,
   ED_CHECKBUTTON_ID_CUSTOM_USE_TEMPLATE_1,
   ED_CHECKBUTTON_ID_CUSTOM_ACCESSIBLE,
@@ -1077,6 +1090,7 @@ enum
   ED_DRAWING_ID_ARTWORK_ELEMENT,
   ED_DRAWING_ID_EXPLOSION_ELEMENT,
   ED_DRAWING_ID_INVENTORY_CONTENT,
+  ED_DRAWING_ID_MM_BALL_CONTENT,
   ED_DRAWING_ID_CUSTOM_GRAPHIC,
   ED_DRAWING_ID_CUSTOM_CONTENT,
   ED_DRAWING_ID_CUSTOM_MOVE_ENTER,
@@ -1531,6 +1545,14 @@ static struct
     &level.initial_inventory_size[0],
     NULL,                              NULL, "number of inventory elements"
   },
+  {
+    ED_ELEMENT_SETTINGS_XPOS(0),       ED_ELEMENT_SETTINGS_YPOS(3),
+    MIN_ELEMENTS_IN_GROUP,             MAX_MM_BALL_CONTENTS,
+    GADGET_ID_MM_BALL_CONTENT_DOWN,    GADGET_ID_MM_BALL_CONTENT_UP,
+    GADGET_ID_MM_BALL_CONTENT_TEXT,    GADGET_ID_NONE,
+    &level.num_mm_ball_contents,
+    NULL,                              NULL, "number of content elements"
+  },
 
   // ---------- element settings: configure 1 (custom elements) ---------------
 
@@ -1741,7 +1763,7 @@ static struct
   int gadget_id;
   int xsize, ysize;
   char *value;
-  char *text_above, *infotext;
+  char *text_above, *text_above_cropped, *infotext;
 } textarea_info[ED_NUM_TEXTAREAS] =
 {
   {
@@ -1749,7 +1771,7 @@ static struct
     GADGET_ID_ENVELOPE_INFO,
     MAX_ENVELOPE_XSIZE, MAX_ENVELOPE_YSIZE,
     NULL,
-    "Envelope Content:", "Envelope Content"
+    "Envelope Content:", "Envelope Content (cropped):", "Envelope Content"
   }
 };
 
@@ -2007,6 +2029,9 @@ static struct ValueTextInfo options_change_direct_action[] =
   { CE_HEADLINE_SPECIAL_EVENTS,        "[mouse events]"                },
   { CE_CLICKED_BY_MOUSE,       "clicked by mouse"              },
   { CE_PRESSED_BY_MOUSE,       "pressed by mouse"              },
+  { CE_UNDEFINED,              " "                             },
+  { CE_HEADLINE_SPECIAL_EVENTS,        "[static states]"               },
+  { CE_NEXT_TO_PLAYER,         "next to player"                },
 
   { -1,                                NULL                            }
 };
@@ -2040,6 +2065,10 @@ static struct ValueTextInfo options_change_other_action[] =
   { CE_HEADLINE_SPECIAL_EVENTS,        "[mouse events]"                },
   { CE_MOUSE_CLICKED_ON_X,     "mouse clicked on"              },
   { CE_MOUSE_PRESSED_ON_X,     "mouse pressed on"              },
+  { CE_UNDEFINED,              " "                             },
+  { CE_HEADLINE_SPECIAL_EVENTS,        "[static states]"               },
+  { CE_PLAYER_NEXT_TO_X,       "player next to"                },
+  { CE_NEXT_TO_X,              "next to"                       },
 
   { -1,                                NULL                            }
 };
@@ -2070,38 +2099,38 @@ static struct ValueTextInfo options_change_trigger_player[] =
 
 static struct ValueTextInfo options_change_trigger_page[] =
 {
-  { (1 << 0),                  "1"                             },
-  { (1 << 1),                  "2"                             },
-  { (1 << 2),                  "3"                             },
-  { (1 << 3),                  "4"                             },
-  { (1 << 4),                  "5"                             },
-  { (1 << 5),                  "6"                             },
-  { (1 << 6),                  "7"                             },
-  { (1 << 7),                  "8"                             },
-  { (1 << 8),                  "9"                             },
-  { (1 << 9),                  "10"                            },
-  { (1 << 10),                 "11"                            },
-  { (1 << 11),                 "12"                            },
-  { (1 << 12),                 "13"                            },
-  { (1 << 13),                 "14"                            },
-  { (1 << 14),                 "15"                            },
-  { (1 << 15),                 "16"                            },
-  { (1 << 16),                 "17"                            },
-  { (1 << 17),                 "18"                            },
-  { (1 << 18),                 "19"                            },
-  { (1 << 19),                 "20"                            },
-  { (1 << 20),                 "21"                            },
-  { (1 << 21),                 "22"                            },
-  { (1 << 22),                 "23"                            },
-  { (1 << 23),                 "24"                            },
-  { (1 << 24),                 "25"                            },
-  { (1 << 25),                 "26"                            },
-  { (1 << 26),                 "27"                            },
-  { (1 << 27),                 "28"                            },
-  { (1 << 28),                 "29"                            },
-  { (1 << 29),                 "30"                            },
-  { (1 << 30),                 "31"                            },
-  { (1 << 31),                 "32"                            },
+  { (1u << 0),                 "1"                             },
+  { (1u << 1),                 "2"                             },
+  { (1u << 2),                 "3"                             },
+  { (1u << 3),                 "4"                             },
+  { (1u << 4),                 "5"                             },
+  { (1u << 5),                 "6"                             },
+  { (1u << 6),                 "7"                             },
+  { (1u << 7),                 "8"                             },
+  { (1u << 8),                 "9"                             },
+  { (1u << 9),                 "10"                            },
+  { (1u << 10),                        "11"                            },
+  { (1u << 11),                        "12"                            },
+  { (1u << 12),                        "13"                            },
+  { (1u << 13),                        "14"                            },
+  { (1u << 14),                        "15"                            },
+  { (1u << 15),                        "16"                            },
+  { (1u << 16),                        "17"                            },
+  { (1u << 17),                        "18"                            },
+  { (1u << 18),                        "19"                            },
+  { (1u << 19),                        "20"                            },
+  { (1u << 20),                        "21"                            },
+  { (1u << 21),                        "22"                            },
+  { (1u << 22),                        "23"                            },
+  { (1u << 23),                        "24"                            },
+  { (1u << 24),                        "25"                            },
+  { (1u << 25),                        "26"                            },
+  { (1u << 26),                        "27"                            },
+  { (1u << 27),                        "28"                            },
+  { (1u << 28),                        "29"                            },
+  { (1u << 29),                        "30"                            },
+  { (1u << 30),                        "31"                            },
+  { (1u << 31),                        "32"                            },
   { CH_PAGE_ANY,               "any"                           },
 
   { -1,                                NULL                            }
@@ -2546,6 +2575,14 @@ static struct
     &level.initial_player_stepsize[0],
     NULL, "initial player speed:", NULL,       "initial player speed"
   },
+  {
+    ED_ELEMENT_SETTINGS_XPOS(0),       ED_ELEMENT_SETTINGS_YPOS(4),
+    GADGET_ID_MM_BALL_CHOICE_MODE,     GADGET_ID_NONE,
+    -1,
+    options_group_choice_mode,
+    &level.mm_ball_choice_mode,
+    NULL, "choice type:", NULL,                "type of content choice"
+  },
 
   // ---------- element settings: configure 1 (custom elements) ---------------
 
@@ -3170,6 +3207,13 @@ static struct
     NULL, NULL,
     "CE action on finished dig/collect", "only finished dig/collect triggers CE"
   },
+  {
+    ED_ELEMENT_SETTINGS_XPOS(0),       ED_ELEMENT_SETTINGS_YPOS(4),
+    GADGET_ID_KEEP_WALKABLE_CE,                GADGET_ID_NONE,
+    &level.keep_walkable_ce,
+    NULL, NULL,
+    "keep walkable CE changed to player", "keep CE changing to player if walkable"
+  },
   {
     ED_ELEMENT_SETTINGS_XPOS(0),       ED_ELEMENT_SETTINGS_YPOS(9),
     GADGET_ID_CONTINUOUS_SNAPPING,     GADGET_ID_NONE,
@@ -3338,6 +3382,20 @@ static struct
     NULL, NULL,
     "blue",                            "use blue color components in laser"
   },
+  {
+    ED_ELEMENT_SETTINGS_XPOS(0),       ED_ELEMENT_SETTINGS_YPOS(5),
+    GADGET_ID_ROTATE_MM_BALL_CONTENT,  GADGET_ID_NONE,
+    &level.rotate_mm_ball_content,
+    NULL, NULL,
+    "randomly rotate created content", "randomly rotate newly created content"
+  },
+  {
+    ED_ELEMENT_SETTINGS_XPOS(0),       ED_ELEMENT_SETTINGS_YPOS(6),
+    GADGET_ID_EXPLODE_MM_BALL,         GADGET_ID_NONE,
+    &level.explode_mm_ball,
+    NULL, NULL,
+    "explode ball instead of melting", "use explosion to release ball content"
+  },
 
   // ---------- element settings: configure 1 (custom elements) ---------------
 
@@ -3724,6 +3782,16 @@ static struct
     NULL, NULL, NULL, NULL,            "content for initial inventory"
   },
 
+  // ---------- gray ball content -----------------------------------------
+
+  {
+    ED_AREA_1X1_SETTINGS_XPOS(0),      ED_AREA_1X1_SETTINGS_YPOS(2),
+    ED_AREA_1X1_SETTINGS_XOFF,         ED_AREA_1X1_SETTINGS_YOFF,
+    GADGET_ID_MM_BALL_CONTENT,         GADGET_ID_NONE,
+    &level.mm_ball_content[0],         MAX_MM_BALL_CONTENTS, 1,
+    "content:", NULL, NULL, NULL,      "content for gray ball"
+  },
+
   // ---------- element settings: configure 1 (custom elements) ---------------
 
   // ---------- custom graphic ------------------------------------------------
@@ -3732,7 +3800,7 @@ static struct
     -1,                                        ED_AREA_1X1_SETTINGS_YPOS(1),
     0,                                 ED_AREA_1X1_SETTINGS_YOFF,
     GADGET_ID_CUSTOM_GRAPHIC,          GADGET_ID_CUSTOM_USE_GRAPHIC,
-    &custom_element.gfx_element_initial,1, 1,
+    &custom_element.gfx_element_initial, 1, 1,
     NULL, NULL, NULL, NULL,            "custom graphic element"
   },
 
@@ -3846,7 +3914,7 @@ static int level_xpos = -1, level_ypos = -1;
 static int ed_tilesize = DEFAULT_EDITOR_TILESIZE;
 static int ed_tilesize_default = DEFAULT_EDITOR_TILESIZE;
 
-#define IN_ED_FIELD(x,y)       IN_FIELD(x, y, ed_fieldx, ed_fieldy)
+#define IN_ED_FIELD(x, y)      IN_FIELD(x, y, ed_fieldx, ed_fieldy)
 
 // drawing elements on the three mouse buttons
 static int new_element1 = EL_WALL;
@@ -3905,6 +3973,7 @@ static boolean getDrawModeHiRes(void);
 static int getTabulatorBarWidth(void);
 static int getTabulatorBarHeight(void);
 static Pixel getTabulatorBarColor(void);
+static int numHiresTiles(int);
 
 static int num_editor_gadgets = 0;     // dynamically determined
 
@@ -4594,7 +4663,12 @@ static int editor_el_mirror_magic[] =
   EL_MM_WOODEN_GRID_FIXED_1,
   EL_MM_WOODEN_GRID_FIXED_2,
   EL_MM_WOODEN_GRID_FIXED_3,
-  EL_MM_WOODEN_GRID_FIXED_4
+  EL_MM_WOODEN_GRID_FIXED_4,
+
+  EL_MM_ENVELOPE_1,
+  EL_MM_ENVELOPE_2,
+  EL_MM_ENVELOPE_3,
+  EL_MM_ENVELOPE_4
 };
 static int *editor_hl_mirror_magic_ptr = editor_hl_mirror_magic;
 static int *editor_el_mirror_magic_ptr = editor_el_mirror_magic;
@@ -5256,6 +5330,41 @@ static int *editor_el_group_ptr = editor_el_group;
 static int num_editor_hl_group = ARRAY_SIZE(editor_hl_group);
 static int num_editor_el_group = ARRAY_SIZE(editor_el_group);
 
+static int editor_hl_empty_space[] =
+{
+  EL_INTERNAL_CASCADE_ES_ACTIVE,
+  EL_CHAR('E'),
+  EL_CHAR('S'),
+  EL_EMPTY,
+};
+
+static int editor_el_empty_space[] =
+{
+  EL_EMPTY_SPACE_1,
+  EL_EMPTY_SPACE_2,
+  EL_EMPTY_SPACE_3,
+  EL_EMPTY_SPACE_4,
+
+  EL_EMPTY_SPACE_5,
+  EL_EMPTY_SPACE_6,
+  EL_EMPTY_SPACE_7,
+  EL_EMPTY_SPACE_8,
+
+  EL_EMPTY_SPACE_9,
+  EL_EMPTY_SPACE_10,
+  EL_EMPTY_SPACE_11,
+  EL_EMPTY_SPACE_12,
+
+  EL_EMPTY_SPACE_13,
+  EL_EMPTY_SPACE_14,
+  EL_EMPTY_SPACE_15,
+  EL_EMPTY_SPACE_16
+};
+static int *editor_hl_empty_space_ptr = editor_hl_empty_space;
+static int *editor_el_empty_space_ptr = editor_el_empty_space;
+static int num_editor_hl_empty_space = ARRAY_SIZE(editor_hl_empty_space);
+static int num_editor_el_empty_space = ARRAY_SIZE(editor_el_empty_space);
+
 static int editor_hl_reference[] =
 {
   EL_INTERNAL_CASCADE_REF_ACTIVE,
@@ -5468,6 +5577,12 @@ editor_elements_info[] =
     &editor_hl_group_ptr,              &num_editor_hl_group,
     &editor_el_group_ptr,              &num_editor_el_group
   },
+  {
+    &setup_editor_el_custom,
+    &setup.editor_cascade.el_es,
+    &editor_hl_empty_space_ptr,                &num_editor_hl_empty_space,
+    &editor_el_empty_space_ptr,                &num_editor_el_empty_space
+  },
   {
     &setup_editor_el_custom,
     &setup.editor_cascade.el_ref,
@@ -5500,6 +5615,14 @@ editor_elements_info[] =
   }
 };
 
+static struct XY xy_directions[] =
+{
+  { -1,  0 },
+  { +1,  0 },
+  {  0, -1 },
+  {  0, +1 }
+};
+
 
 // ----------------------------------------------------------------------------
 // functions
@@ -5553,7 +5676,7 @@ static char *getElementInfoText(int element)
 
 static char *getElementDescriptionFilenameExt(char *basename)
 {
-  char *elements_subdir = "elements";
+  char *elements_subdir = ELEMENTS_DIRECTORY;
   static char *elements_subdir2 = NULL;
   static char *filename = NULL;
 
@@ -5594,6 +5717,11 @@ static char *getElementDescriptionFilename(int element)
   if (filename != NULL)
     return filename;
 
+  // 3rd try: look for generic fallback text file for any element
+  filename = getElementDescriptionFilenameExt(FALLBACK_TEXT_FILENAME);
+  if (filename != NULL)
+    return filename;
+
   return NULL;
 }
 
@@ -5615,9 +5743,18 @@ static void InitDynamicEditorElementList(int **elements, int *num_elements)
 
   // find all elements used in current level
   for (y = 0; y < lev_fieldy; y++)
+  {
     for (x = 0; x < lev_fieldx; x++)
-      if (Tile[x][y] < NUM_FILE_ELEMENTS)      // should always be true
+    {
+      if (Tile[x][y] >= NUM_FILE_ELEMENTS)     // should never happen
+       continue;
+
+      if (IS_MM_WALL(Tile[x][y]))
+       element_found[map_mm_wall_element(Tile[x][y])] = TRUE;
+      else
        element_found[Tile[x][y]] = TRUE;
+    }
+  }
 
   *num_elements = 0;
 
@@ -5637,14 +5774,18 @@ static void InitDynamicEditorElementList(int **elements, int *num_elements)
 
   *num_elements = 0;
 
-  // add all elements used in current level (non-custom/group elements)
+  // add all elements used in current level (non-custom/group/empty elements)
   for (i = 0; i < NUM_FILE_ELEMENTS; i++)
-    if (element_found[i] && !(IS_CUSTOM_ELEMENT(i) || IS_GROUP_ELEMENT(i)))
+    if (element_found[i] && !(IS_CUSTOM_ELEMENT(i) ||
+                             IS_GROUP_ELEMENT(i) ||
+                             IS_EMPTY_ELEMENT(i)))
       (*elements)[(*num_elements)++] = i;
 
-  // add all elements used in current level (custom/group elements)
+  // add all elements used in current level (custom/group/empty elements)
   for (i = 0; i < NUM_FILE_ELEMENTS; i++)
-    if (element_found[i] && (IS_CUSTOM_ELEMENT(i) || IS_GROUP_ELEMENT(i)))
+    if (element_found[i] && (IS_CUSTOM_ELEMENT(i) ||
+                            IS_GROUP_ELEMENT(i) ||
+                            IS_EMPTY_ELEMENT(i)))
       (*elements)[(*num_elements)++] = i;
 
   while (*num_elements % 4)    // pad with empty elements, if needed
@@ -5822,10 +5963,10 @@ static void ReinitializeElementList(void)
   // determine size of element list
   for (i = 0; editor_elements_info[i].setup_value != NULL; i++)
   {
-    boolean found_inactive_cascade = FALSE;
-
     if (*editor_elements_info[i].setup_value)
     {
+      boolean found_inactive_cascade = FALSE;
+
       if (setup.editor.el_headlines)
       {
        // required for correct padding of palette headline buttons
@@ -6432,11 +6573,9 @@ static void CreateCounterButtons(void)
       int graphic;
       struct GraphicInfo *gd;
       int gd_x1, gd_x2, gd_y1, gd_y2;
-      unsigned int event_mask;
+      unsigned int event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
       char infotext[max_infotext_len + 1];
 
-      event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
-
       if (i == ED_COUNTER_ID_SELECT_LEVEL)
       {
        graphic = (j == 0 ?
@@ -6576,7 +6715,6 @@ static void CreateDrawingAreas(void)
   for (i = 0; i < ED_NUM_DRAWING_AREAS; i++)
   {
     struct GadgetInfo *gi;
-    unsigned int event_mask;
     int id = drawingarea_info[i].gadget_id;
     int x = SX + ED_AREA_SETTINGS_X(drawingarea_info[i]);
     int y = SY + ED_AREA_SETTINGS_Y(drawingarea_info[i]);
@@ -6584,8 +6722,7 @@ static void CreateDrawingAreas(void)
     int area_ysize = drawingarea_info[i].area_ysize;
     int item_size = (id == GADGET_ID_DRAWING_LEVEL ?
                     ed_tilesize : ED_DRAWINGAREA_TILE_SIZE);
-
-    event_mask =
+    unsigned int event_mask =
       GD_EVENT_PRESSED | GD_EVENT_RELEASED | GD_EVENT_MOVING |
       GD_EVENT_OFF_BORDERS | GD_EVENT_PIXEL_PRECISE;
 
@@ -6632,7 +6769,7 @@ static void CreateTextInputGadgets(void)
     int gd_x2 = gd->src_x + gd->active_xoffset;
     int gd_y2 = gd->src_y + gd->active_yoffset;
     struct GadgetInfo *gi;
-    unsigned int event_mask;
+    unsigned int event_mask = GD_EVENT_TEXT_RETURN | GD_EVENT_TEXT_LEAVING;
     char infotext[MAX_OUTPUT_LINESIZE + 1];
     int id = textinput_info[i].gadget_id;
     int x, y;
@@ -6659,8 +6796,6 @@ static void CreateTextInputGadgets(void)
       y = ED_SETTINGS_Y(textinput_info[i].y);
     }
 
-    event_mask = GD_EVENT_TEXT_RETURN | GD_EVENT_TEXT_LEAVING;
-
     sprintf(infotext, "Enter %s", textinput_info[i].infotext);
     infotext[max_infotext_len] = '\0';
 
@@ -6703,14 +6838,12 @@ static void CreateTextAreaGadgets(void)
     int gd_x2 = gd->src_x + gd->active_xoffset;
     int gd_y2 = gd->src_y + gd->active_yoffset;
     struct GadgetInfo *gi;
-    unsigned int event_mask;
+    unsigned int event_mask = GD_EVENT_TEXT_LEAVING;
     char infotext[MAX_OUTPUT_LINESIZE + 1];
     int id = textarea_info[i].gadget_id;
     int area_xsize = textarea_info[i].xsize;
     int area_ysize = textarea_info[i].ysize;
 
-    event_mask = GD_EVENT_TEXT_LEAVING;
-
     sprintf(infotext, "Enter %s", textarea_info[i].infotext);
     infotext[max_infotext_len] = '\0';
 
@@ -6754,11 +6887,12 @@ static void CreateSelectboxGadgets(void)
     int gd_y2 = gd->src_y + gd->active_yoffset;
     int selectbox_button_xsize = gd2->width;
     struct GadgetInfo *gi;
-    unsigned int event_mask;
     char infotext[MAX_OUTPUT_LINESIZE + 1];
     int id = selectbox_info[i].gadget_id;
     int x = SX + ED_SETTINGS_X(selectbox_info[i].x);
     int y = SY + ED_SETTINGS_Y(selectbox_info[i].y);
+    unsigned int event_mask =
+      GD_EVENT_RELEASED | GD_EVENT_TEXT_RETURN | GD_EVENT_TEXT_LEAVING;
 
     if (selectbox_info[i].size == -1)  // dynamically determine size
     {
@@ -6774,9 +6908,6 @@ static void CreateSelectboxGadgets(void)
       selectbox_info[i].size++;                // add one character empty space
     }
 
-    event_mask = GD_EVENT_RELEASED |
-      GD_EVENT_TEXT_RETURN | GD_EVENT_TEXT_LEAVING;
-
     // determine horizontal position to the right of specified gadget
     if (selectbox_info[i].gadget_id_align != GADGET_ID_NONE)
       x = (right_gadget_border[selectbox_info[i].gadget_id_align] +
@@ -6845,7 +6976,7 @@ static void CreateTextbuttonGadgets(void)
     int border_xsize = gd->border_size + gd->draw_xoffset;
     int border_ysize = gd->border_size;
     struct GadgetInfo *gi;
-    unsigned int event_mask;
+    unsigned int event_mask = GD_EVENT_RELEASED;
     char infotext[MAX_OUTPUT_LINESIZE + 1];
     int x = SX + ED_SETTINGS_X(textbutton_info[i].x);
     int y = SY + ED_SETTINGS_Y(textbutton_info[i].y);
@@ -6853,8 +6984,6 @@ static void CreateTextbuttonGadgets(void)
     if (textbutton_info[i].size == -1) // dynamically determine size
       textbutton_info[i].size = strlen(textbutton_info[i].text);
 
-    event_mask = GD_EVENT_RELEASED;
-
     sprintf(infotext, "%s", textbutton_info[i].infotext);
     infotext[max_infotext_len] = '\0';
 
@@ -6907,7 +7036,6 @@ static void CreateTextbuttonGadgets(void)
 static void CreateGraphicbuttonGadgets(void)
 {
   struct GadgetInfo *gi;
-  unsigned int event_mask;
   int i;
 
   // create buttons for scrolling of drawing area and element list
@@ -6922,8 +7050,7 @@ static void CreateGraphicbuttonGadgets(void)
     int gd_y1 = gd->src_y;
     int gd_x2 = gd->src_x + gd->pressed_xoffset;
     int gd_y2 = gd->src_y + gd->pressed_yoffset;
-
-    event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
+    unsigned int event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
 
     // determine horizontal position to the right of specified gadget
     if (graphicbutton_info[i].gadget_id_align != GADGET_ID_NONE)
@@ -7015,7 +7142,7 @@ static void CreateScrollbarGadgets(void)
     int gd_y2 = gd->src_y + gd->pressed_yoffset;
     struct GadgetInfo *gi;
     int items_max, items_visible, item_position;
-    unsigned int event_mask;
+    unsigned int event_mask = GD_EVENT_MOVING | GD_EVENT_OFF_BORDERS;
 
     if (i == ED_SCROLLBAR_ID_LIST_VERTICAL)
     {
@@ -7039,8 +7166,6 @@ static void CreateScrollbarGadgets(void)
       }
     }
 
-    event_mask = GD_EVENT_MOVING | GD_EVENT_OFF_BORDERS;
-
     gi = CreateGadget(GDI_CUSTOM_ID, id,
                      GDI_CUSTOM_TYPE_ID, i,
                      GDI_IMAGE_ID, graphic,
@@ -7076,11 +7201,8 @@ static void CreateScrollbarGadgets(void)
 static void CreateCheckbuttonGadgets(void)
 {
   struct GadgetInfo *gi;
-  unsigned int event_mask;
   int i;
 
-  event_mask = GD_EVENT_PRESSED;
-
   for (i = 0; i < ED_NUM_CHECKBUTTONS; i++)
   {
     int id = checkbutton_info[i].gadget_id;
@@ -7097,6 +7219,7 @@ static void CreateCheckbuttonGadgets(void)
     int gd_y2a = gd->src_y + gd->active_yoffset + gd->pressed_yoffset;
     int x = SX + ED_SETTINGS_X(checkbutton_info[i].x);
     int y = SY + ED_SETTINGS_Y(checkbutton_info[i].y);
+    unsigned int event_mask = GD_EVENT_PRESSED;
 
     // determine horizontal position to the right of specified gadget
     if (checkbutton_info[i].gadget_id_align != GADGET_ID_NONE)
@@ -7148,16 +7271,14 @@ static void CreateRadiobuttonGadgets(void)
   int gd_x2a = gd->src_x + gd->active_xoffset + gd->pressed_xoffset;
   int gd_y2a = gd->src_y + gd->active_yoffset + gd->pressed_yoffset;
   struct GadgetInfo *gi;
-  unsigned int event_mask;
   int i;
 
-  event_mask = GD_EVENT_PRESSED;
-
   for (i = 0; i < ED_NUM_RADIOBUTTONS; i++)
   {
     int id = radiobutton_info[i].gadget_id;
     int x = SX + ED_SETTINGS_X(radiobutton_info[i].x);
     int y = SY + ED_SETTINGS_Y(radiobutton_info[i].y);
+    unsigned int event_mask = GD_EVENT_PRESSED;
 
     int checked =
       (*radiobutton_info[i].value == radiobutton_info[i].checked_value);
@@ -7226,6 +7347,8 @@ void CreateLevelEditorGadgets(void)
 
   use_permanent_palette = !editor.palette.show_as_separate_screen;
 
+  InitGadgetScreenBorders(-1, INFOTEXT_YPOS);
+
   ReinitializeElementList();
 
   CreateControlButtons();
@@ -7418,9 +7541,13 @@ static void MapTextAreaGadget(int id)
   int yoffset_above = font_height + ED_GADGET_LINE_DISTANCE;
   int x_above = ED_SETTINGS_X(textarea_info[id].x);
   int y_above = ED_SETTINGS_Y(textarea_info[id].y) - yoffset_above;
+  char *text_above = textarea_info[id].text_above;
 
-  if (textarea_info[id].text_above)
-    DrawTextS(x_above, y_above, font_nr, textarea_info[id].text_above);
+  if (gi->textarea.cropped && textarea_info[id].text_above_cropped)
+    text_above = textarea_info[id].text_above_cropped;
+
+  if (text_above)
+    DrawTextS(x_above, y_above, font_nr, text_above);
 
   ModifyGadget(gi, GDI_TEXT_VALUE, textarea_info[id].value, GDI_END);
 
@@ -7549,7 +7676,7 @@ static void MapCheckbuttonGadget(int id)
 
   // set position for gadgets with dynamically determined position
   if (checkbutton_info[id].x != -1)    // do not change dynamic positions
-    ModifyGadget(gi, GDI_X, SX + ED_SETTINGS_X(checkbutton_info[id].x),GDI_END);
+    ModifyGadget(gi, GDI_X, SX + ED_SETTINGS_X(checkbutton_info[id].x), GDI_END);
   ModifyGadget(gi, GDI_Y, SY + ED_SETTINGS_Y(checkbutton_info[id].y), GDI_END);
 
   x_left = gi->x - xoffset_left;
@@ -8054,6 +8181,12 @@ static boolean CopyCustomElement(int element_old, int element_new,
 
     return FALSE;
   }
+  else if (IS_EMPTY_ELEMENT(element_old) && !IS_EMPTY_ELEMENT(element_new))
+  {
+    Request("Please choose empty element!", REQ_CONFIRM);
+
+    return FALSE;
+  }
   else
   {
     level.changed = TRUE;
@@ -8196,7 +8329,8 @@ static void CopyCustomElementPropertiesToEditor(int element)
 
   // set "change by direct action" selectbox help value
   custom_element_change.direct_action =
-    (HAS_CHANGE_EVENT(element, CE_TOUCHED_BY_PLAYER) ? CE_TOUCHED_BY_PLAYER :
+    (HAS_CHANGE_EVENT(element, CE_NEXT_TO_PLAYER) ? CE_NEXT_TO_PLAYER :
+     HAS_CHANGE_EVENT(element, CE_TOUCHED_BY_PLAYER) ? CE_TOUCHED_BY_PLAYER :
      HAS_CHANGE_EVENT(element, CE_PRESSED_BY_PLAYER) ? CE_PRESSED_BY_PLAYER :
      HAS_CHANGE_EVENT(element, CE_SWITCHED_BY_PLAYER) ? CE_SWITCHED_BY_PLAYER :
      HAS_CHANGE_EVENT(element, CE_SNAPPED_BY_PLAYER) ? CE_SNAPPED_BY_PLAYER :
@@ -8220,7 +8354,8 @@ static void CopyCustomElementPropertiesToEditor(int element)
 
   // set "change by other element action" selectbox help value
   custom_element_change.other_action =
-    (HAS_CHANGE_EVENT(element, CE_PLAYER_TOUCHES_X) ? CE_PLAYER_TOUCHES_X :
+    (HAS_CHANGE_EVENT(element, CE_PLAYER_NEXT_TO_X) ? CE_PLAYER_NEXT_TO_X :
+     HAS_CHANGE_EVENT(element, CE_PLAYER_TOUCHES_X) ? CE_PLAYER_TOUCHES_X :
      HAS_CHANGE_EVENT(element, CE_PLAYER_PRESSES_X) ? CE_PLAYER_PRESSES_X :
      HAS_CHANGE_EVENT(element, CE_PLAYER_SWITCHES_X) ? CE_PLAYER_SWITCHES_X :
      HAS_CHANGE_EVENT(element, CE_PLAYER_SNAPS_X) ? CE_PLAYER_SNAPS_X :
@@ -8230,6 +8365,7 @@ static void CopyCustomElementPropertiesToEditor(int element)
      HAS_CHANGE_EVENT(element, CE_PLAYER_DIGS_X) ? CE_PLAYER_DIGS_X :
      HAS_CHANGE_EVENT(element, CE_PLAYER_COLLECTS_X) ? CE_PLAYER_COLLECTS_X :
      HAS_CHANGE_EVENT(element, CE_PLAYER_DROPS_X) ? CE_PLAYER_DROPS_X :
+     HAS_CHANGE_EVENT(element, CE_NEXT_TO_X) ? CE_NEXT_TO_X :
      HAS_CHANGE_EVENT(element, CE_TOUCHING_X) ? CE_TOUCHING_X :
      HAS_CHANGE_EVENT(element, CE_HITTING_X) ? CE_HITTING_X :
      HAS_CHANGE_EVENT(element, CE_DIGGING_X) ? CE_DIGGING_X :
@@ -8254,9 +8390,14 @@ static void CopyGroupElementPropertiesToEditor(int element)
   custom_element = element_info[element];      // needed for description
 }
 
+static void CopyEmptyElementPropertiesToEditor(int element)
+{
+  custom_element = element_info[element];
+}
+
 static void CopyClassicElementPropertiesToEditor(int element)
 {
-  if (ELEM_IS_PLAYER(element) || COULD_MOVE_INTO_ACID(element))
+  if (IS_PLAYER_ELEMENT(element) || COULD_MOVE_INTO_ACID(element))
     custom_element_properties[EP_CAN_MOVE_INTO_ACID] =
       getMoveIntoAcidProperty(&level, element);
 
@@ -8271,6 +8412,8 @@ static void CopyElementPropertiesToEditor(int element)
     CopyCustomElementPropertiesToEditor(element);
   else if (IS_GROUP_ELEMENT(element))
     CopyGroupElementPropertiesToEditor(element);
+  else if (IS_EMPTY_ELEMENT(element))
+    CopyEmptyElementPropertiesToEditor(element);
   else
     CopyClassicElementPropertiesToEditor(element);
 }
@@ -8358,6 +8501,7 @@ static void CopyCustomElementPropertiesToGame(int element)
   // ---------- element settings: advanced (custom elements) ------------------
 
   // set player change event from checkbox and selectbox
+  custom_element_change_events[CE_NEXT_TO_PLAYER] = FALSE;
   custom_element_change_events[CE_TOUCHED_BY_PLAYER] = FALSE;
   custom_element_change_events[CE_PRESSED_BY_PLAYER] = FALSE;
   custom_element_change_events[CE_SWITCHED_BY_PLAYER] = FALSE;
@@ -8382,6 +8526,7 @@ static void CopyCustomElementPropertiesToGame(int element)
     custom_element_change_events[CE_BY_DIRECT_ACTION];
 
   // set other element action change event from checkbox and selectbox
+  custom_element_change_events[CE_PLAYER_NEXT_TO_X] = FALSE;
   custom_element_change_events[CE_PLAYER_TOUCHES_X] = FALSE;
   custom_element_change_events[CE_PLAYER_PRESSES_X] = FALSE;
   custom_element_change_events[CE_PLAYER_SWITCHES_X] = FALSE;
@@ -8392,6 +8537,7 @@ static void CopyCustomElementPropertiesToGame(int element)
   custom_element_change_events[CE_PLAYER_DIGS_X] = FALSE;
   custom_element_change_events[CE_PLAYER_COLLECTS_X] = FALSE;
   custom_element_change_events[CE_PLAYER_DROPS_X] = FALSE;
+  custom_element_change_events[CE_NEXT_TO_X] = FALSE;
   custom_element_change_events[CE_TOUCHING_X] = FALSE;
   custom_element_change_events[CE_HITTING_X] = FALSE;
   custom_element_change_events[CE_DIGGING_X] = FALSE;
@@ -8440,9 +8586,24 @@ static void CopyGroupElementPropertiesToGame(int element)
   InitElementPropertiesGfxElement();
 }
 
+static void CopyEmptyElementPropertiesToGame(int element)
+{
+  // mark that this empty element has been modified
+  custom_element.modified_settings = TRUE;
+  level.changed = TRUE;
+
+  if (level.use_custom_template)
+    AskToCopyAndModifyLevelTemplate();
+
+  element_info[element] = custom_element;
+
+  // needed here to restore runtime value "element_info[element].gfx_element"
+  InitElementPropertiesGfxElement();
+}
+
 static void CopyClassicElementPropertiesToGame(int element)
 {
-  if (ELEM_IS_PLAYER(element) || COULD_MOVE_INTO_ACID(element))
+  if (IS_PLAYER_ELEMENT(element) || COULD_MOVE_INTO_ACID(element))
     setMoveIntoAcidProperty(&level, element,
                            custom_element_properties[EP_CAN_MOVE_INTO_ACID]);
 
@@ -8457,6 +8618,8 @@ static void CopyElementPropertiesToGame(int element)
     CopyCustomElementPropertiesToGame(element);
   else if (IS_GROUP_ELEMENT(element))
     CopyGroupElementPropertiesToGame(element);
+  else if (IS_EMPTY_ELEMENT(element))
+    CopyEmptyElementPropertiesToGame(element);
   else
     CopyClassicElementPropertiesToGame(element);
 }
@@ -9112,7 +9275,7 @@ static void DrawPropertiesTabulatorGadgets(void)
   int i;
 
   // draw two config tabulators for player elements
-  if (ELEM_IS_PLAYER(properties_element))
+  if (IS_PLAYER_ELEMENT(properties_element))
     id_last = ED_TEXTBUTTON_ID_PROPERTIES_CONFIG_2;
 
   // draw two config and one "change" tabulator for custom elements
@@ -9127,7 +9290,7 @@ static void DrawPropertiesTabulatorGadgets(void)
 
     // use "config 1" and "config 2" instead of "config" for players and CEs
     if (i == ED_TEXTBUTTON_ID_PROPERTIES_CONFIG &&
-       (ELEM_IS_PLAYER(properties_element) ||
+       (IS_PLAYER_ELEMENT(properties_element) ||
         IS_CUSTOM_ELEMENT(properties_element)))
       continue;
 
@@ -9412,7 +9575,7 @@ static void DrawMagicBallContentAreas(void)
   DrawText(x, y + 2 * tilesize, "active",    font_nr);
 }
 
-static void DrawAndroidElementArea(int element)
+static void DrawAndroidElementArea(void)
 {
   int id = ED_DRAWING_ID_ANDROID_CONTENT;
   int num_elements = level.num_android_clone_elements;
@@ -9440,7 +9603,7 @@ static void DrawAndroidElementArea(int element)
   MapDrawingArea(id);
 }
 
-static void DrawGroupElementArea(int element)
+static void DrawGroupElementArea(void)
 {
   int id = ED_DRAWING_ID_GROUP_CONTENT;
   int num_elements = group_element_info.num_elements;
@@ -9497,13 +9660,40 @@ static void DrawPlayerInitialInventoryArea(int element)
   MapDrawingArea(id);
 }
 
+static void DrawMMBallContentArea(void)
+{
+  int id = ED_DRAWING_ID_MM_BALL_CONTENT;
+  int num_elements = level.num_mm_ball_contents;
+  int border_size = ED_DRAWINGAREA_BORDER_SIZE;
+  int sx = SX + ED_AREA_SETTINGS_X(drawingarea_info[id]) - border_size;
+  int sy = SY + ED_AREA_SETTINGS_Y(drawingarea_info[id]) - border_size;
+  int xsize = MAX_MM_BALL_CONTENTS;
+  int ysize = 1;
+
+  if (drawingarea_info[id].text_left != NULL)
+    sx += getTextWidthForDrawingArea(drawingarea_info[id].text_left);
+
+  UnmapDrawingArea(id);
+
+  ModifyEditorDrawingArea(id, num_elements, 1);
+
+  // delete content areas in case of reducing number of them
+  DrawBackground(sx, sy,
+                xsize * ED_DRAWINGAREA_TILE_SIZE + 2 * border_size,
+                ysize * ED_DRAWINGAREA_TILE_SIZE + 2 * border_size);
+
+  MapDrawingArea(id);
+}
+
 static void DrawEnvelopeTextArea(int envelope_nr)
 {
   int id = ED_TEXTAREA_ID_ENVELOPE_INFO;
   struct GadgetInfo *gi = level_editor_gadget[textarea_info[id].gadget_id];
 
   UnmapGadget(gi);
-  DrawBackground(gi->x, gi->y, gi->width, gi->height);
+
+  DrawBackground(gi->x, gi->y,
+                gi->textarea.crop_width, gi->textarea.crop_height);
 
   if (envelope_nr != -1)
     textarea_info[id].value = level.envelope[envelope_nr].text;
@@ -9577,13 +9767,16 @@ static void DrawPropertiesInfo(void)
     { -1,                      NULL                                    }
   };
   char *filename = getElementDescriptionFilename(properties_element);
-  char *percentage_text = "In this level: ";
+  char *num_elements_text = "In this level: ";
+  char *num_similar_text = "Similar tiles: ";
   char *properties_text = "Standard properties: ";
   char *description_text = "Description:";
   char *no_description_text = "No description available.";
   char *none_text = "None";
   float percentage;
-  int num_elements_in_level;
+  int num_elements_in_level = 0;
+  int num_similar_in_level = 0;
+  int num_hires_tiles_in_level = 0;
   int num_standard_properties = 0;
   int font1_nr = FONT_TEXT_1;
   int font2_nr = FONT_TEXT_2;
@@ -9592,7 +9785,8 @@ static void DrawPropertiesInfo(void)
   int font2_height = getFontHeight(font2_nr);
   int line1_height = font1_height + ED_GADGET_LINE_DISTANCE;
   int font2_yoffset = (font1_height - font2_height) / 2;
-  int percentage_text_len = strlen(percentage_text) * font1_width;
+  int num_elements_text_len = strlen(num_elements_text) * font1_width;
+  int num_similar_text_len = strlen(num_similar_text) * font1_width;
   int properties_text_len = strlen(properties_text) * font1_width;
   int xpos = ED_ELEMENT_SETTINGS_X(0);
   int ypos = ED_ELEMENT_SETTINGS_Y(0) + ED_GADGET_SMALL_DISTANCE;
@@ -9611,22 +9805,66 @@ static void DrawPropertiesInfo(void)
 
   // ----- print number of elements / percentage of this element in level
 
-  num_elements_in_level = 0;
-  for (y = 0; y < lev_fieldy; y++) 
+  for (y = 0; y < lev_fieldy; y++)
+  {
     for (x = 0; x < lev_fieldx; x++)
+    {
       if (Tile[x][y] == properties_element)
+      {
        num_elements_in_level++;
+      }
+      else if (IS_MM_WALL(Tile[x][y]) &&
+              map_mm_wall_element(Tile[x][y]) == properties_element)
+      {
+       num_hires_tiles_in_level += numHiresTiles(Tile[x][y]);
+      }
+    }
+  }
+
   percentage = num_elements_in_level * 100.0 / (lev_fieldx * lev_fieldy);
 
-  DrawTextS(xpos, ypos, font1_nr, percentage_text);
+  DrawTextS(xpos, ypos, font1_nr, num_elements_text);
 
-  if (num_elements_in_level > 0)
-    DrawTextF(xpos + percentage_text_len, ypos + font2_yoffset, font2_nr,
+  if (num_hires_tiles_in_level > 0)
+    DrawTextF(xpos + num_elements_text_len, ypos + font2_yoffset, font2_nr,
+             "%d wall tiles", num_hires_tiles_in_level);
+  else if (num_elements_in_level > 0)
+    DrawTextF(xpos + num_elements_text_len, ypos + font2_yoffset, font2_nr,
              "%d (%.2f %%)", num_elements_in_level, percentage);
   else
-    DrawTextF(xpos + percentage_text_len, ypos + font2_yoffset, font2_nr,
+    DrawTextF(xpos + num_elements_text_len, ypos + font2_yoffset, font2_nr,
              none_text);
 
+  // ----- print number of similar elements / percentage of them in level
+
+  for (y = 0; y < lev_fieldy; y++)
+  {
+    for (x = 0; x < lev_fieldx; x++)
+    {
+      if (strEqual(element_info[Tile[x][y]].class_name,
+                  element_info[properties_element].class_name))
+      {
+       num_similar_in_level++;
+      }
+    }
+  }
+
+  if (num_similar_in_level != num_elements_in_level)
+  {
+    ypos += 1 * MAX(font1_height, font2_height);
+
+    percentage = num_similar_in_level * 100.0 / (lev_fieldx * lev_fieldy);
+
+    DrawTextS(xpos, ypos, font1_nr, num_similar_text);
+
+    if (num_similar_in_level > 0)
+      DrawTextF(xpos + num_similar_text_len, ypos + font2_yoffset, font2_nr,
+               "%d (%.2f %%)", num_similar_in_level, percentage);
+    else
+      DrawTextF(xpos + num_similar_text_len, ypos + font2_yoffset, font2_nr,
+               none_text);
+  }
+
   ypos += 2 * MAX(font1_height, font2_height);
 
   // ----- print standard properties of this element
@@ -9675,6 +9913,7 @@ static void DrawPropertiesInfo(void)
 #define TEXT_DURATION          "Duration when activated"
 #define TEXT_DELAY_ON          "Delay before activating"
 #define TEXT_DELAY_OFF         "Delay before deactivating"
+#define TEXT_DELAY_CHANGING    "Delay before changing"
 #define TEXT_DELAY_EXPLODING   "Delay before exploding"
 #define TEXT_DELAY_MOVING      "Delay before moving"
 #define TEXT_BALL_DELAY                "Element generation delay"
@@ -9739,9 +9978,9 @@ static struct
   { EL_NUT,            &level.score[SC_NUT],           TEXT_CRACKING   },
   { EL_DYNAMITE,       &level.score[SC_DYNAMITE],      TEXT_COLLECTING },
   { EL_EM_DYNAMITE,    &level.score[SC_DYNAMITE],      TEXT_COLLECTING },
-  { EL_DYNABOMB_INCREASE_NUMBER,&level.score[SC_DYNAMITE],TEXT_COLLECTING },
-  { EL_DYNABOMB_INCREASE_SIZE, &level.score[SC_DYNAMITE],TEXT_COLLECTING },
-  { EL_DYNABOMB_INCREASE_POWER,        &level.score[SC_DYNAMITE],TEXT_COLLECTING },
+  { EL_DYNABOMB_INCREASE_NUMBER,&level.score[SC_DYNAMITE], TEXT_COLLECTING },
+  { EL_DYNABOMB_INCREASE_SIZE, &level.score[SC_DYNAMITE], TEXT_COLLECTING },
+  { EL_DYNABOMB_INCREASE_POWER,        &level.score[SC_DYNAMITE], TEXT_COLLECTING },
   { EL_SHIELD_NORMAL,  &level.score[SC_SHIELD],        TEXT_COLLECTING },
   { EL_SHIELD_DEADLY,  &level.score[SC_SHIELD],        TEXT_COLLECTING },
   { EL_EXTRA_TIME,     &level.extra_time_score,        TEXT_COLLECTING },
@@ -9806,7 +10045,7 @@ static struct
   { EL_EMC_MAGNIFIER,  &level.magnify_time,            TEXT_DURATION   },
   { EL_MM_FUSE_ACTIVE, &level.mm_time_fuse,            TEXT_DELAY_OFF  },
   { EL_MM_BOMB,                &level.mm_time_bomb,            TEXT_DELAY_EXPLODING },
-  { EL_MM_GRAY_BALL,   &level.mm_time_ball,            TEXT_DELAY_ON   },
+  { EL_MM_GRAY_BALL,   &level.mm_time_ball,            TEXT_DELAY_CHANGING },
   { EL_MM_STEEL_BLOCK, &level.mm_time_block,           TEXT_DELAY_MOVING },
   { EL_MM_WOODEN_BLOCK,        &level.mm_time_block,           TEXT_DELAY_MOVING },
 
@@ -9820,11 +10059,13 @@ static boolean checkPropertiesConfig(int element)
   if (IS_GEM(element) ||
       IS_CUSTOM_ELEMENT(element) ||
       IS_GROUP_ELEMENT(element) ||
+      IS_EMPTY_ELEMENT(element) ||
       IS_BALLOON_ELEMENT(element) ||
       IS_ENVELOPE(element) ||
+      IS_MM_ENVELOPE(element) ||
       IS_MM_MCDUFFIN(element) ||
       IS_DF_LASER(element) ||
-      ELEM_IS_PLAYER(element) ||
+      IS_PLAYER_ELEMENT(element) ||
       HAS_EDITOR_CONTENT(element) ||
       CAN_GROW(element) ||
       COULD_MOVE_INTO_ACID(element) ||
@@ -9978,10 +10219,19 @@ static void DrawPropertiesConfig(void)
       MapCheckbuttonGadget(ED_CHECKBUTTON_ID_INITIAL_BALL_ACTIVE);
     }
     else if (properties_element == EL_EMC_ANDROID)
-      DrawAndroidElementArea(properties_element);
+      DrawAndroidElementArea();
+    else if (properties_element == EL_MM_GRAY_BALL)
+    {
+      MapCounterButtons(ED_COUNTER_ID_MM_BALL_CONTENT);
+      MapSelectboxGadget(ED_SELECTBOX_ID_MM_BALL_CHOICE_MODE);
+      MapCheckbuttonGadget(ED_CHECKBUTTON_ID_ROTATE_MM_BALL_CONTENT);
+      MapCheckbuttonGadget(ED_CHECKBUTTON_ID_EXPLODE_MM_BALL);
+
+      DrawMMBallContentArea();
+    }
   }
 
-  if (ELEM_IS_PLAYER(properties_element))
+  if (IS_PLAYER_ELEMENT(properties_element))
   {
     int player_nr = GET_PLAYER_NR(properties_element);
 
@@ -10044,6 +10294,7 @@ static void DrawPropertiesConfig(void)
       // draw checkbutton gadgets
       MapCheckbuttonGadget(ED_CHECKBUTTON_ID_USE_INITIAL_INVENTORY);
       MapCheckbuttonGadget(ED_CHECKBUTTON_ID_FINISH_DIG_COLLECT);
+      MapCheckbuttonGadget(ED_CHECKBUTTON_ID_KEEP_WALKABLE_CE);
 
       // draw counter gadgets
       MapCounterButtons(ED_COUNTER_ID_INVENTORY_SIZE);
@@ -10060,7 +10311,7 @@ static void DrawPropertiesConfig(void)
     MapCheckbuttonGadget(ED_CHECKBUTTON_ID_EM_EXPLODES_BY_FIRE);
 
   if (COULD_MOVE_INTO_ACID(properties_element) &&
-      !ELEM_IS_PLAYER(properties_element) &&
+      !IS_PLAYER_ELEMENT(properties_element) &&
       (!IS_CUSTOM_ELEMENT(properties_element) ||
        edit_mode_properties == ED_MODE_PROPERTIES_CONFIG_2))
   {
@@ -10118,13 +10369,14 @@ static void DrawPropertiesConfig(void)
   if (IS_BALLOON_ELEMENT(properties_element))
     MapSelectboxGadget(ED_SELECTBOX_ID_WIND_DIRECTION);
 
-  if (IS_ENVELOPE(properties_element))
+  if (IS_ENVELOPE(properties_element) ||
+      IS_MM_ENVELOPE(properties_element))
   {
     int counter1_id = ED_COUNTER_ID_ENVELOPE_XSIZE;
     int counter2_id = ED_COUNTER_ID_ENVELOPE_YSIZE;
     int button1_id = ED_CHECKBUTTON_ID_ENVELOPE_AUTOWRAP;
     int button2_id = ED_CHECKBUTTON_ID_ENVELOPE_CENTERED;
-    int envelope_nr = properties_element - EL_ENVELOPE_1;
+    int envelope_nr = ENVELOPE_NR(properties_element);
 
     counterbutton_info[counter1_id].value = &level.envelope[envelope_nr].xsize;
     counterbutton_info[counter2_id].value = &level.envelope[envelope_nr].ysize;
@@ -10232,7 +10484,7 @@ static void DrawPropertiesConfig(void)
     MapTextbuttonGadget(ED_TEXTBUTTON_ID_SAVE_AS_TEMPLATE_1);
 
     // draw drawing area gadgets
-    DrawGroupElementArea(properties_element);
+    DrawGroupElementArea();
 
     // draw text input gadgets
     MapTextInputGadget(ED_TEXTINPUT_ID_ELEMENT_NAME);
@@ -10242,6 +10494,23 @@ static void DrawPropertiesConfig(void)
 
     draw_footer_line = TRUE;
   }
+  else if (IS_EMPTY_ELEMENT(properties_element))
+  {
+    // draw stickybutton gadget
+    MapCheckbuttonGadget(ED_CHECKBUTTON_ID_STICK_ELEMENT);
+
+    // draw checkbutton gadgets
+    MapCheckbuttonGadget(ED_CHECKBUTTON_ID_CUSTOM_USE_GRAPHIC);
+    MapCheckbuttonGadget(ED_CHECKBUTTON_ID_CUSTOM_USE_TEMPLATE_1);
+
+    // draw textbutton gadgets
+    MapTextbuttonGadget(ED_TEXTBUTTON_ID_SAVE_AS_TEMPLATE_1);
+
+    // draw drawing area gadgets
+    MapDrawingArea(ED_DRAWING_ID_CUSTOM_GRAPHIC);
+
+    draw_footer_line = TRUE;
+  }
 
   // draw little footer border line above CE/GE use/save template gadgets
   if (draw_footer_line)
@@ -10332,12 +10601,12 @@ static void DrawEditorElementName(int x, int y, int font_nr)
   int font_height = getFontHeight(font_nr);
   int max_text_width = SXSIZE - x - ED_ELEMENT_SETTINGS_X(0);
   int max_chars_per_line = max_text_width / font_width;
-  char buffer[max_chars_per_line + 1];
 
   if (strlen(element_name) <= max_chars_per_line)
     DrawTextS(x, y, font_nr, element_name);
   else
   {
+    char buffer[max_chars_per_line + 1];
     int next_pos = max_chars_per_line;
 
     strncpy(buffer, element_name, max_chars_per_line);
@@ -10398,12 +10667,12 @@ static void DrawPropertiesWindow(void)
     edit_mode_properties = ED_MODE_PROPERTIES_CONFIG_2;
 
   if (edit_mode_properties > ED_MODE_PROPERTIES_CONFIG &&
-      !ELEM_IS_PLAYER(properties_element) &&
+      !IS_PLAYER_ELEMENT(properties_element) &&
       !IS_CUSTOM_ELEMENT(properties_element))
     edit_mode_properties = ED_MODE_PROPERTIES_CONFIG;
 
   if (edit_mode_properties == ED_MODE_PROPERTIES_CONFIG &&
-      (ELEM_IS_PLAYER(properties_element) ||
+      (IS_PLAYER_ELEMENT(properties_element) ||
        IS_CUSTOM_ELEMENT(properties_element)))
     edit_mode_properties = ED_MODE_PROPERTIES_CONFIG_1;
 
@@ -10414,7 +10683,8 @@ static void DrawPropertiesWindow(void)
   UnmapLevelEditorToolboxCustomGadgets();
 
   if (IS_CUSTOM_ELEMENT(properties_element) ||
-      IS_GROUP_ELEMENT(properties_element))
+      IS_GROUP_ELEMENT(properties_element) ||
+      IS_EMPTY_ELEMENT(properties_element))
     MapLevelEditorToolboxCustomGadgets();
 
   SetMainBackgroundImage(IMG_BACKGROUND_EDITOR);
@@ -10724,13 +10994,7 @@ static int getChipFromOpenDirectionNotEmpty(int direction, int element_old)
 
 static int getClosedTube(int x, int y)
 {
-  static int xy[4][2] =
-  {
-    { -1, 0 },
-    { +1, 0 },
-    { 0, -1 },
-    { 0, +1 }
-  };
+  struct XY *xy = xy_directions;
   int element_old = IntelliDrawBuffer[x][y];
   int direction_old = getOpenDirectionFromTube(element_old);
   int direction_new = MV_NONE;
@@ -10738,8 +11002,8 @@ static int getClosedTube(int x, int y)
 
   for (i = 0; i < NUM_DIRECTIONS; i++)
   {
-    int xx = x + xy[i][0];
-    int yy = y + xy[i][1];
+    int xx = x + xy[i].x;
+    int yy = y + xy[i].y;
     int dir = MV_DIR_FROM_BIT(i);
     int dir_opposite = MV_DIR_OPPOSITE(dir);
 
@@ -10754,13 +11018,7 @@ static int getClosedTube(int x, int y)
 
 static int getClosedBelt(int x, int y)
 {
-  static int xy[4][2] =
-  {
-    { -1, 0 },
-    { +1, 0 },
-    { 0, -1 },
-    { 0, +1 }
-  };
+  struct XY *xy = xy_directions;
   int element_old = IntelliDrawBuffer[x][y];
   int nr = getBeltNrFromBeltElement(element_old);
   int direction_old = getOpenDirectionFromBelt(element_old);
@@ -10769,8 +11027,8 @@ static int getClosedBelt(int x, int y)
 
   for (i = MV_BIT_LEFT; i <= MV_BIT_RIGHT; i++)
   {
-    int xx = x + xy[i][0];
-    int yy = y + xy[i][1];
+    int xx = x + xy[i].x;
+    int yy = y + xy[i].y;
     int dir = MV_DIR_FROM_BIT(i);
     int dir_opposite = MV_DIR_OPPOSITE(dir);
 
@@ -10785,13 +11043,7 @@ static int getClosedBelt(int x, int y)
 
 static int getClosedPool(int x, int y)
 {
-  static int xy[4][2] =
-  {
-    { -1, 0 },
-    { +1, 0 },
-    { 0, -1 },
-    { 0, +1 }
-  };
+  struct XY *xy = xy_directions;
   int element_old = IntelliDrawBuffer[x][y];
   int direction_old = getOpenDirectionFromPool(element_old);
   int direction_new = MV_NONE;
@@ -10799,8 +11051,8 @@ static int getClosedPool(int x, int y)
 
   for (i = 0; i < NUM_DIRECTIONS; i++)
   {
-    int xx = x + xy[i][0];
-    int yy = y + xy[i][1];
+    int xx = x + xy[i].x;
+    int yy = y + xy[i].y;
     int dir = MV_DIR_FROM_BIT(i);
     int dir_opposite = MV_DIR_OPPOSITE(dir);
 
@@ -10816,13 +11068,7 @@ static int getClosedPool(int x, int y)
 
 static int getClosedPillar(int x, int y)
 {
-  static int xy[4][2] =
-  {
-    { -1, 0 },
-    { +1, 0 },
-    { 0, -1 },
-    { 0, +1 }
-  };
+  struct XY *xy = xy_directions;
   int element_old = IntelliDrawBuffer[x][y];
   int direction_old = getOpenDirectionFromPillar(element_old);
   int direction_new = MV_NONE;
@@ -10830,8 +11076,8 @@ static int getClosedPillar(int x, int y)
 
   for (i = MV_BIT_UP; i <= MV_BIT_DOWN; i++)
   {
-    int xx = x + xy[i][0];
-    int yy = y + xy[i][1];
+    int xx = x + xy[i].x;
+    int yy = y + xy[i].y;
     int dir = MV_DIR_FROM_BIT(i);
     int dir_opposite = MV_DIR_OPPOSITE(dir);
 
@@ -10846,13 +11092,7 @@ static int getClosedPillar(int x, int y)
 
 static int getClosedSteel2(int x, int y)
 {
-  static int xy[4][2] =
-  {
-    { -1, 0 },
-    { +1, 0 },
-    { 0, -1 },
-    { 0, +1 }
-  };
+  struct XY *xy = xy_directions;
   int element_old = IntelliDrawBuffer[x][y];
   int direction_old = getOpenDirectionFromSteel2(element_old);
   int direction_new = MV_NONE;
@@ -10860,8 +11100,8 @@ static int getClosedSteel2(int x, int y)
 
   for (i = 0; i < NUM_DIRECTIONS; i++)
   {
-    int xx = x + xy[i][0];
-    int yy = y + xy[i][1];
+    int xx = x + xy[i].x;
+    int yy = y + xy[i].y;
     int dir = MV_DIR_FROM_BIT(i);
     int dir_opposite = MV_DIR_OPPOSITE(dir);
 
@@ -10876,13 +11116,7 @@ static int getClosedSteel2(int x, int y)
 
 static int getClosedChip(int x, int y)
 {
-  static int xy[4][2] =
-  {
-    { -1, 0 },
-    { +1, 0 },
-    { 0, -1 },
-    { 0, +1 }
-  };
+  struct XY *xy = xy_directions;
   int element_old = IntelliDrawBuffer[x][y];
   int direction_old = getOpenDirectionFromChip(element_old);
   int direction_new = MV_NONE;
@@ -10890,8 +11124,8 @@ static int getClosedChip(int x, int y)
 
   for (i = 0; i < NUM_DIRECTIONS; i++)
   {
-    int xx = x + xy[i][0];
-    int yy = y + xy[i][1];
+    int xx = x + xy[i].x;
+    int yy = y + xy[i].y;
     int dir = MV_DIR_FROM_BIT(i);
     int dir_opposite = MV_DIR_OPPOSITE(dir);
 
@@ -10971,16 +11205,10 @@ static void MergeAndCloseNeighbourElements(int x1, int y1, int *element1,
   SetElementSimple(x2, y2, *element2, change_level);
 }
 
-static void SetElementIntelliDraw(int x, int y, int new_element,
+static void SetElementIntelliDraw(int x, int y, int dx, int dy, int new_element,
                                  boolean change_level, int button)
 {
-  static int xy[4][2] =
-  {
-    { -1, 0 },
-    { +1, 0 },
-    { 0, -1 },
-    { 0, +1 }
-  };
+  struct XY *xy = xy_directions;
   static int last_x = -1;
   static int last_y = -1;
 
@@ -11006,8 +11234,8 @@ static void SetElementIntelliDraw(int x, int y, int new_element,
 
     for (i = 0; i < NUM_DIRECTIONS; i++)
     {
-      int xx = x + xy[i][0];
-      int yy = y + xy[i][1];
+      int xx = x + xy[i].x;
+      int yy = y + xy[i].y;
 
       if (last_x == xx && last_y == yy && IN_LEV_FIELD(last_x, last_y) &&
          IS_TUBE(IntelliDrawBuffer[last_x][last_y]))
@@ -11044,8 +11272,8 @@ static void SetElementIntelliDraw(int x, int y, int new_element,
 
     for (i = MV_BIT_LEFT; i <= MV_BIT_RIGHT; i++)
     {
-      int xx = x + xy[i][0];
-      int yy = y + xy[i][1];
+      int xx = x + xy[i].x;
+      int yy = y + xy[i].y;
 
       if (last_x == xx && last_y == yy && IN_LEV_FIELD(last_x, last_y) &&
          IS_BELT(IntelliDrawBuffer[last_x][last_y]))
@@ -11082,8 +11310,8 @@ static void SetElementIntelliDraw(int x, int y, int new_element,
 
     for (i = 0; i < NUM_DIRECTIONS; i++)
     {
-      int xx = x + xy[i][0];
-      int yy = y + xy[i][1];
+      int xx = x + xy[i].x;
+      int yy = y + xy[i].y;
 
       if (last_x == xx && last_y == yy && IN_LEV_FIELD(last_x, last_y) &&
          IS_ACID_POOL_OR_ACID(IntelliDrawBuffer[last_x][last_y]))
@@ -11125,8 +11353,8 @@ static void SetElementIntelliDraw(int x, int y, int new_element,
 
     for (i = MV_BIT_UP; i <= MV_BIT_DOWN; i++)
     {
-      int xx = x + xy[i][0];
-      int yy = y + xy[i][1];
+      int xx = x + xy[i].x;
+      int yy = y + xy[i].y;
 
       if (last_x == xx && last_y == yy && IN_LEV_FIELD(last_x, last_y) &&
          IS_EMC_PILLAR(IntelliDrawBuffer[last_x][last_y]))
@@ -11162,8 +11390,8 @@ static void SetElementIntelliDraw(int x, int y, int new_element,
 
     for (i = 0; i < NUM_DIRECTIONS; i++)
     {
-      int xx = x + xy[i][0];
-      int yy = y + xy[i][1];
+      int xx = x + xy[i].x;
+      int yy = y + xy[i].y;
 
       if (last_x == xx && last_y == yy && IN_LEV_FIELD(last_x, last_y) &&
          IS_DC_STEELWALL_2(IntelliDrawBuffer[last_x][last_y]))
@@ -11197,8 +11425,8 @@ static void SetElementIntelliDraw(int x, int y, int new_element,
 
     for (i = 0; i < NUM_DIRECTIONS; i++)
     {
-      int xx = x + xy[i][0];
-      int yy = y + xy[i][1];
+      int xx = x + xy[i].x;
+      int yy = y + xy[i].y;
 
       if (last_x == xx && last_y == yy && IN_LEV_FIELD(last_x, last_y) &&
          IS_SP_CHIP(IntelliDrawBuffer[last_x][last_y]))
@@ -11258,8 +11486,8 @@ static void SetElementIntelliDraw(int x, int y, int new_element,
 
     for (i = 0; i < NUM_DIRECTIONS; i++)
     {
-      int xx = x + xy[i][0];
-      int yy = y + xy[i][1];
+      int xx = x + xy[i].x;
+      int yy = y + xy[i].y;
 
       if (last_x == xx && last_y == yy && IN_LEV_FIELD(last_x, last_y) &&
          IS_IN_GROUP_EL(IntelliDrawBuffer[last_x][last_y], new_element))
@@ -11749,7 +11977,10 @@ static void SetElementIntelliDraw(int x, int y, int new_element,
     }
   }
 
-  SetElementSimple(x, y, new_element, change_level);
+  if (IS_MM_WALL_EDITOR(new_element))
+    SetElementSimpleExt(x, y, dx, dy, new_element, change_level);
+  else
+    SetElementSimple(x, y, new_element, change_level);
 
   last_x = x;
   last_y = y;
@@ -11763,7 +11994,7 @@ static void ResetIntelliDraw(void)
     for (y = 0; y < lev_fieldy; y++)
       IntelliDrawBuffer[x][y] = Tile[x][y];
 
-  SetElementIntelliDraw(-1, -1, EL_UNDEFINED, FALSE, -1);
+  SetElementIntelliDraw(-1, -1, -1, -1, EL_UNDEFINED, FALSE, -1);
 }
 
 static boolean draw_mode_hires = FALSE;
@@ -11778,6 +12009,23 @@ static boolean isHiresDrawElement(int element)
   return (IS_MM_WALL_EDITOR(element) || element == EL_EMPTY);
 }
 
+static int numHiresTiles(int element)
+{
+  if (!IS_MM_WALL(element))
+    return 1;
+
+  int bits = MM_WALL_BITS(element);
+  int num_bits = 0;
+
+  while (bits)
+  {
+    bits &= bits - 1;
+    num_bits++;
+  }
+
+  return num_bits;
+}
+
 static void SetDrawModeHiRes(int element)
 {
   draw_mode_hires =
@@ -11805,8 +12053,8 @@ static void SetElementExt(int x, int y, int dx, int dy, int element,
 {
   if (element < 0)
     SetElementSimple(x, y, Tile[x][y], change_level);
-  else if (GetKeyModState() & KMOD_Shift && !IS_MM_WALL_EDITOR(element))
-    SetElementIntelliDraw(x, y, element, change_level, button);
+  else if (GetKeyModState() & KMOD_Shift)
+    SetElementIntelliDraw(x, y, dx, dy, element, change_level, button);
   else
     SetElementSimpleExt(x, y, dx, dy, element, change_level);
 }
@@ -12008,7 +12256,7 @@ static void DrawCircle(int from_x, int from_y, int to_x, int to_y,
   DrawArcExt(from_x, from_y, to_x2, to_y2, element, change_level);
   DrawArcExt(from_x, from_y, mirror_to_x2, to_y2, element, change_level);
   DrawArcExt(from_x, from_y, to_x2, mirror_to_y2, element, change_level);
-  DrawArcExt(from_x, from_y, mirror_to_x2, mirror_to_y2, element,change_level);
+  DrawArcExt(from_x, from_y, mirror_to_x2, mirror_to_y2, element, change_level);
 }
 #endif
 
@@ -12311,7 +12559,7 @@ static void CopyBrushExt(int from_x, int from_y, int to_x, int to_y,
        {
          int element = Tile[x][y];
 
-         if (!IS_EM_ELEMENT(element) && !ELEM_IS_PLAYER(element))
+         if (!IS_EM_ELEMENT(element) && !IS_PLAYER_ELEMENT(element))
            use_em_engine = FALSE;
 
          if (!IS_SP_ELEMENT(element))
@@ -12544,7 +12792,7 @@ static int DrawLevelText(int sx, int sy, char letter, int mode)
   static int start_sx;
   static int last_sx, last_sy;
   static boolean typing = FALSE;
-  int letter_element = EL_CHAR_ASCII0 + letter;
+  int letter_element;
   int lx = 0, ly = 0;
 
   // map lower case letters to upper case and convert special characters
@@ -12847,8 +13095,10 @@ static void CopyLevelTemplateToUserLevelSet(char *levelset_subdir)
 static void HandleDrawingAreas(struct GadgetInfo *gi)
 {
   static boolean started_inside_drawing_area = FALSE;
-  static int last_sx = -1, last_sy = -1;
-  static int last_sx2 = -1, last_sy2 = -1;
+  static int last_sx = -1;
+  static int last_sy = -1;
+  static int last_sx2 = -1;
+  static int last_sy2 = -1;
   int id = gi->custom_id;
   int type_id = gi->custom_type_id;
   boolean button_press_event;
@@ -12868,8 +13118,6 @@ static void HandleDrawingAreas(struct GadgetInfo *gi)
   int dx = sx2 % 2;
   int dy = sy2 % 2;
   int lx = 0, ly = 0;
-  int min_lx = 0, min_ly = 0;
-  int max_lx = lev_fieldx - 1, max_ly = lev_fieldy - 1;
   int x, y;
 
   button_press_event = (gi->event.type == GD_EVENT_PRESSED);
@@ -12881,6 +13129,9 @@ static void HandleDrawingAreas(struct GadgetInfo *gi)
 
   if (draw_level)
   {
+    int min_lx = 0, min_ly = 0;
+    int max_lx = lev_fieldx - 1, max_ly = lev_fieldy - 1;
+
     // get positions inside level field
     lx = sx + level_xpos;
     ly = sy + level_ypos;
@@ -12909,14 +13160,7 @@ static void HandleDrawingAreas(struct GadgetInfo *gi)
     sy2 = sy * 2 + dy;
   }
 
-  if (button_release_event)
-  {
-    last_sx = -1;
-    last_sy = -1;
-    last_sx2 = -1;
-    last_sy2 = -1;
-  }
-  else if (!button_press_event)
+  if (!button_press_event && !button_release_event)
   {
     int old_element = (IN_LEV_FIELD(lx, ly) ? Tile[lx][ly] : EL_UNDEFINED);
     boolean hires_drawing = (level.game_engine_type == GAME_ENGINE_TYPE_MM &&
@@ -12943,9 +13187,6 @@ static void HandleDrawingAreas(struct GadgetInfo *gi)
   if (!IS_VALID_BUTTON(button))
     return;
 
-  if (!button && !button_release_event)
-    return;
-
   // handle info callback for each invocation of action callback
   gi->callback_info(gi);
 
@@ -12981,10 +13222,9 @@ static void HandleDrawingAreas(struct GadgetInfo *gi)
          if (edit_mode == ED_MODE_DRAWING && draw_with_brush &&
              !inside_drawing_area)
            DeleteBrushFromCursor();
-       }
 
-       if (!button || button_release_event)
          break;
+       }
 
        if (draw_with_brush)
        {
@@ -12994,7 +13234,7 @@ static void HandleDrawingAreas(struct GadgetInfo *gi)
        {
          SetDrawModeHiRes(new_element);
 
-         if (ELEM_IS_PLAYER(new_element))
+         if (IS_PLAYER_ELEMENT(new_element) || IS_MM_MCDUFFIN(new_element))
          {
            // remove player at old position
            for (y = 0; y < lev_fieldy; y++)
@@ -13003,7 +13243,8 @@ static void HandleDrawingAreas(struct GadgetInfo *gi)
              {
                int old_element = Tile[x][y];
 
-               if (ELEM_IS_PLAYER(old_element))
+               if (IS_PLAYER_ELEMENT(old_element) &&
+                   IS_PLAYER_ELEMENT(new_element))
                {
                  int replaced_with_element =
                    (old_element == EL_SOKOBAN_FIELD_PLAYER &&
@@ -13023,6 +13264,12 @@ static void HandleDrawingAreas(struct GadgetInfo *gi)
 
                  SetElement(x, y, replaced_with_element);
                }
+               else if (IS_MM_MCDUFFIN(old_element) &&
+                        IS_MM_MCDUFFIN(new_element))
+               {
+                 // remove McDuffin at old position
+                 SetElement(x, y, EL_EMPTY);
+               }
              }
            }
          }
@@ -13069,22 +13316,19 @@ static void HandleDrawingAreas(struct GadgetInfo *gi)
        if (button_release_event)
          CopyLevelToUndoBuffer(UNDO_IMMEDIATE);
 
-       if (button)
-       {
-         SetDrawModeHiRes(new_element);
+       SetDrawModeHiRes(new_element);
 
-         if (getDrawModeHiRes())
-         {
-           sx = sx2;
-           sy = sy2;
-         }
+       if (getDrawModeHiRes())
+       {
+         sx = sx2;
+         sy = sy2;
+       }
 
-         if (!button_press_event)
-           DrawLine(last_sx, last_sy, sx, sy, new_element, TRUE);
+       if (!button_press_event)
+         DrawLine(last_sx, last_sy, sx, sy, new_element, TRUE);
 
-         last_sx = sx;
-         last_sy = sy;
-       }
+       last_sx = sx;
+       last_sy = sy;
       }
       break;
 
@@ -13267,11 +13511,11 @@ static void HandleCounterButtons(struct GadgetInfo *gi)
       break;
 
     case ED_COUNTER_ID_ANDROID_CONTENT:
-      DrawAndroidElementArea(properties_element);
+      DrawAndroidElementArea();
       break;
 
     case ED_COUNTER_ID_GROUP_CONTENT:
-      DrawGroupElementArea(properties_element);
+      DrawGroupElementArea();
       CopyGroupElementPropertiesToGame(properties_element);
       break;
 
@@ -13279,6 +13523,10 @@ static void HandleCounterButtons(struct GadgetInfo *gi)
       DrawPlayerInitialInventoryArea(properties_element);
       break;
 
+    case ED_COUNTER_ID_MM_BALL_CONTENT:
+      DrawMMBallContentArea();
+      break;
+
     case ED_COUNTER_ID_ENVELOPE_XSIZE:
     case ED_COUNTER_ID_ENVELOPE_YSIZE:
       DrawEnvelopeTextArea(-1);
@@ -13634,9 +13882,11 @@ static void HandleCheckbuttons(struct GadgetInfo *gi)
     boolean template_related_changes_found = FALSE;
     int i;
 
-    // check if any custom or group elements have been changed
+    // check if any custom, group or empty elements have been changed
     for (i = 0; i < NUM_FILE_ELEMENTS; i++)
-      if ((IS_CUSTOM_ELEMENT(i) || IS_GROUP_ELEMENT(i)) &&
+      if ((IS_CUSTOM_ELEMENT(i) ||
+          IS_GROUP_ELEMENT(i) ||
+          IS_EMPTY_ELEMENT(i)) &&
          element_info[i].modified_settings)
        template_related_changes_found = TRUE;
 
@@ -14171,7 +14421,8 @@ static void HandleControlButtons(struct GadgetInfo *gi)
          id <= GADGET_ID_ELEMENTLIST_LAST)
       {
        int element_position = id - GADGET_ID_ELEMENTLIST_FIRST;
-       int new_element = editor_elements[element_position + element_shift];
+
+       new_element = editor_elements[element_position + element_shift];
 
        if (IS_EDITOR_CASCADE(new_element))
        {
@@ -14411,11 +14662,12 @@ static void HandleLevelEditorIdle_Properties(void)
   int element_border = graphic_info[IMG_EDITOR_ELEMENT_BORDER].border_size;
   int x = editor.settings.element_graphic.x + element_border;
   int y = editor.settings.element_graphic.y + element_border;
-  static unsigned int action_delay = 0;
-  unsigned int action_delay_value = GameFrameDelay;
+  static DelayCounter action_delay = { 0 };
   int i;
 
-  if (!DelayReached(&action_delay, action_delay_value))
+  action_delay.value = GameFrameDelay;
+
+  if (!DelayReached(&action_delay))
     return;
 
   for (i = 0; i < ED_NUM_SELECTBOX; i++)
@@ -14464,7 +14716,6 @@ static void ClearEditorGadgetInfoText(void)
 void PrintEditorGadgetInfoText(struct GadgetInfo *gi)
 {
   char infotext[MAX_OUTPUT_LINESIZE + 1];
-  char shortcut[MAX_OUTPUT_LINESIZE + 1];
   int max_infotext_len = getMaxInfoTextLength();
 
   if (gi == NULL || strlen(gi->info_text) == 0)
@@ -14479,6 +14730,8 @@ void PrintEditorGadgetInfoText(struct GadgetInfo *gi)
 
     if (key)
     {
+      char shortcut[MAX_OUTPUT_LINESIZE + 1];
+
       if (gi->custom_id == GADGET_ID_SINGLE_ITEMS)
        sprintf(shortcut, " ('.' or '%c')", key);
       else if (gi->custom_id == GADGET_ID_PICK_ELEMENT)
@@ -14522,7 +14775,6 @@ void HandleEditorGadgetInfoText(void *ptr)
 
 static void HandleDrawingAreaInfo(struct GadgetInfo *gi)
 {
-  static int start_lx, start_ly;
   int id = gi->custom_id;
   int type_id = gi->custom_type_id;
   int sx = gi->event.x;
@@ -14535,7 +14787,6 @@ static void HandleDrawingAreaInfo(struct GadgetInfo *gi)
   int actual_drawing_function = drawing_function;
   int max_infotext_len = getMaxInfoTextLength();
   char infotext[MAX_OUTPUT_LINESIZE + 1];
-  char *text;
 
   infotext[0] = '\0';          // start with empty info text
 
@@ -14572,10 +14823,14 @@ static void HandleDrawingAreaInfo(struct GadgetInfo *gi)
       sy = ly - level_ypos;
     }
 
-    if (IN_ED_FIELD(sx,sy) && IN_LEV_FIELD(lx, ly))
+    if (IN_ED_FIELD(sx, sy) && IN_LEV_FIELD(lx, ly))
     {
       if (button_status)       // if (gi->state == GD_BUTTON_PRESSED)
       {
+       static int start_lx = 0;
+       static int start_ly = 0;
+       char *text;
+
        if (gi->event.type == GD_EVENT_PRESSED)
        {
          start_lx = lx;