+ int element = property_mapping[i].base_index;
+ int action = property_mapping[i].ext1_index;
+ int direction = property_mapping[i].ext2_index;
+ int special = property_mapping[i].ext3_index;
+ int graphic = property_mapping[i].artwork_index;
+ boolean crumbled = FALSE;
+
+ if (special == GFX_SPECIAL_ARG_CRUMBLED)
+ {
+ special = -1;
+ crumbled = TRUE;
+ }
+
+ if (graphic_info[graphic].bitmap == NULL)
+ continue;
+
+ if (element >= MAX_NUM_ELEMENTS || special != -1)
+ continue;
+
+ if (action < 0)
+ action = ACTION_DEFAULT;
+
+ if (crumbled)
+ {
+ if (direction < 0)
+ for (dir = 0; dir < NUM_DIRECTIONS_FULL; dir++)
+ element_info[element].direction_crumbled[action][dir] = -1;
+
+ if (direction > -1)
+ element_info[element].direction_crumbled[action][direction] = graphic;
+ else
+ element_info[element].crumbled[action] = graphic;
+ }
+ else
+ {
+ if (direction < 0)
+ for (dir = 0; dir < NUM_DIRECTIONS_FULL; dir++)
+ element_info[element].direction_graphic[action][dir] = -1;
+
+ if (direction > -1)
+ element_info[element].direction_graphic[action][direction] = graphic;
+ else
+ element_info[element].graphic[action] = graphic;
+ }
+ }
+
+ // now copy all graphics that are defined to be cloned from other graphics
+ for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+ {
+ int graphic = element_info[i].graphic[ACTION_DEFAULT];
+ int crumbled_like, diggable_like;
+
+ if (graphic == -1)
+ continue;
+
+ crumbled_like = graphic_info[graphic].crumbled_like;
+ diggable_like = graphic_info[graphic].diggable_like;
+
+ if (crumbled_like != -1 && element_info[i].crumbled[ACTION_DEFAULT] == -1)
+ {
+ for (act = 0; act < NUM_ACTIONS; act++)
+ element_info[i].crumbled[act] =
+ element_info[crumbled_like].crumbled[act];
+ for (act = 0; act < NUM_ACTIONS; act++)
+ for (dir = 0; dir < NUM_DIRECTIONS_FULL; dir++)
+ element_info[i].direction_crumbled[act][dir] =
+ element_info[crumbled_like].direction_crumbled[act][dir];
+ }
+
+ if (diggable_like != -1 && element_info[i].graphic[ACTION_DIGGING] == -1)
+ {
+ element_info[i].graphic[ACTION_DIGGING] =
+ element_info[diggable_like].graphic[ACTION_DIGGING];
+ for (dir = 0; dir < NUM_DIRECTIONS_FULL; dir++)
+ element_info[i].direction_graphic[ACTION_DIGGING][dir] =
+ element_info[diggable_like].direction_graphic[ACTION_DIGGING][dir];
+ }
+ }
+
+ // set hardcoded definitions for some runtime elements without graphic
+ element_info[EL_AMOEBA_TO_DIAMOND].graphic[ACTION_DEFAULT] = IMG_AMOEBA_DEAD;
+
+ // set hardcoded definitions for some internal elements without graphic
+ for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+ {
+ if (IS_EDITOR_CASCADE_INACTIVE(i))
+ element_info[i].graphic[ACTION_DEFAULT] = IMG_EDITOR_CASCADE_LIST;
+ else if (IS_EDITOR_CASCADE_ACTIVE(i))
+ element_info[i].graphic[ACTION_DEFAULT] = IMG_EDITOR_CASCADE_LIST_ACTIVE;
+ }
+
+ // now set all undefined/invalid graphics to -1 to set to default after it
+ for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+ {
+ for (act = 0; act < NUM_ACTIONS; act++)
+ {
+ int graphic;
+
+ graphic = element_info[i].graphic[act];
+ if (graphic > 0 && graphic_info[graphic].bitmap == NULL)
+ element_info[i].graphic[act] = -1;
+
+ graphic = element_info[i].crumbled[act];
+ if (graphic > 0 && graphic_info[graphic].bitmap == NULL)
+ element_info[i].crumbled[act] = -1;
+
+ for (dir = 0; dir < NUM_DIRECTIONS_FULL; dir++)
+ {
+ graphic = element_info[i].direction_graphic[act][dir];
+ if (graphic > 0 && graphic_info[graphic].bitmap == NULL)
+ element_info[i].direction_graphic[act][dir] = -1;
+
+ graphic = element_info[i].direction_crumbled[act][dir];
+ if (graphic > 0 && graphic_info[graphic].bitmap == NULL)
+ element_info[i].direction_crumbled[act][dir] = -1;
+ }
+ }
+ }
+
+ UPDATE_BUSY_STATE();
+
+ // adjust graphics with 2nd tile for movement according to direction
+ // (do this before correcting '-1' values to minimize calculations)
+ for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+ {
+ for (act = 0; act < NUM_ACTIONS; act++)
+ {
+ for (dir = 0; dir < NUM_DIRECTIONS_FULL; dir++)
+ {
+ int graphic = element_info[i].direction_graphic[act][dir];
+ int move_dir = (act == ACTION_FALLING ? MV_BIT_DOWN : dir);
+
+ if (act == ACTION_FALLING) // special case
+ graphic = element_info[i].graphic[act];
+
+ if (graphic != -1 &&
+ graphic_info[graphic].double_movement &&
+ graphic_info[graphic].swap_double_tiles != 0)
+ {
+ struct GraphicInfo *g = &graphic_info[graphic];
+ int src_x_front = g->src_x;
+ int src_y_front = g->src_y;
+ int src_x_back = g->src_x + g->offset2_x;
+ int src_y_back = g->src_y + g->offset2_y;
+ boolean frames_are_ordered_diagonally = (g->offset_x != 0 &&
+ g->offset_y != 0);
+ boolean front_is_left_or_upper = (src_x_front < src_x_back ||
+ src_y_front < src_y_back);
+ boolean swap_movement_tiles_always = (g->swap_double_tiles == 1);
+ boolean swap_movement_tiles_autodetected =
+ (!frames_are_ordered_diagonally &&
+ ((move_dir == MV_BIT_LEFT && !front_is_left_or_upper) ||
+ (move_dir == MV_BIT_UP && !front_is_left_or_upper) ||
+ (move_dir == MV_BIT_RIGHT && front_is_left_or_upper) ||
+ (move_dir == MV_BIT_DOWN && front_is_left_or_upper)));
+
+ // swap frontside and backside graphic tile coordinates, if needed
+ if (swap_movement_tiles_always || swap_movement_tiles_autodetected)
+ {
+ // get current (wrong) backside tile coordinates
+ getGraphicSourceXY(graphic, 0, &src_x_back, &src_y_back, TRUE);
+
+ // set frontside tile coordinates to backside tile coordinates
+ g->src_x = src_x_back;
+ g->src_y = src_y_back;
+
+ // invert tile offset to point to new backside tile coordinates
+ g->offset2_x *= -1;
+ g->offset2_y *= -1;
+
+ // do not swap front and backside tiles again after correction
+ g->swap_double_tiles = 0;
+ }
+ }
+ }
+ }
+ }
+
+ UPDATE_BUSY_STATE();
+
+ // now set all '-1' values to element specific default values
+ for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+ {
+ int default_graphic = element_info[i].graphic[ACTION_DEFAULT];
+ int default_crumbled = element_info[i].crumbled[ACTION_DEFAULT];
+ int default_direction_graphic[NUM_DIRECTIONS_FULL];
+ int default_direction_crumbled[NUM_DIRECTIONS_FULL];
+
+ if (default_graphic == -1)
+ default_graphic = IMG_UNKNOWN;
+
+ if (default_crumbled == -1)
+ default_crumbled = default_graphic;
+
+ for (dir = 0; dir < NUM_DIRECTIONS_FULL; dir++)
+ {
+ default_direction_graphic[dir] =
+ element_info[i].direction_graphic[ACTION_DEFAULT][dir];
+ default_direction_crumbled[dir] =
+ element_info[i].direction_crumbled[ACTION_DEFAULT][dir];
+
+ if (default_direction_graphic[dir] == -1)
+ default_direction_graphic[dir] = default_graphic;
+
+ if (default_direction_crumbled[dir] == -1)
+ default_direction_crumbled[dir] = default_direction_graphic[dir];
+ }
+
+ for (act = 0; act < NUM_ACTIONS; act++)
+ {
+ boolean act_remove = ((IS_DIGGABLE(i) && act == ACTION_DIGGING) ||
+ (IS_SNAPPABLE(i) && act == ACTION_SNAPPING) ||
+ (IS_COLLECTIBLE(i) && act == ACTION_COLLECTING));
+ boolean act_turning = (act == ACTION_TURNING_FROM_LEFT ||
+ act == ACTION_TURNING_FROM_RIGHT ||
+ act == ACTION_TURNING_FROM_UP ||
+ act == ACTION_TURNING_FROM_DOWN);
+
+ // generic default action graphic (defined by "[default]" directive)
+ int default_action_graphic = element_info[EL_DEFAULT].graphic[act];
+ int default_action_crumbled = element_info[EL_DEFAULT].crumbled[act];
+ int default_remove_graphic = IMG_EMPTY;
+
+ if (act_remove && default_action_graphic != -1)
+ default_remove_graphic = default_action_graphic;
+
+ // look for special default action graphic (classic game specific)
+ if (IS_BD_ELEMENT(i) && element_info[EL_BD_DEFAULT].graphic[act] != -1)
+ default_action_graphic = element_info[EL_BD_DEFAULT].graphic[act];
+ if (IS_SP_ELEMENT(i) && element_info[EL_SP_DEFAULT].graphic[act] != -1)
+ default_action_graphic = element_info[EL_SP_DEFAULT].graphic[act];
+ if (IS_SB_ELEMENT(i) && element_info[EL_SB_DEFAULT].graphic[act] != -1)
+ default_action_graphic = element_info[EL_SB_DEFAULT].graphic[act];
+ if (IS_MM_ELEMENT(i) && element_info[EL_MM_DEFAULT].graphic[act] != -1)
+ default_action_graphic = element_info[EL_MM_DEFAULT].graphic[act];
+
+ if (IS_BD_ELEMENT(i) && element_info[EL_BD_DEFAULT].crumbled[act] != -1)
+ default_action_crumbled = element_info[EL_BD_DEFAULT].crumbled[act];
+ if (IS_SP_ELEMENT(i) && element_info[EL_SP_DEFAULT].crumbled[act] != -1)
+ default_action_crumbled = element_info[EL_SP_DEFAULT].crumbled[act];
+ if (IS_SB_ELEMENT(i) && element_info[EL_SB_DEFAULT].crumbled[act] != -1)
+ default_action_crumbled = element_info[EL_SB_DEFAULT].crumbled[act];
+ if (IS_MM_ELEMENT(i) && element_info[EL_MM_DEFAULT].crumbled[act] != -1)
+ default_action_crumbled = element_info[EL_MM_DEFAULT].crumbled[act];
+
+ // !!! needed because EL_EMPTY_SPACE treated as IS_SP_ELEMENT !!!
+ // !!! make this better !!!
+ if (i == EL_EMPTY_SPACE)
+ {
+ default_action_graphic = element_info[EL_DEFAULT].graphic[act];
+ default_action_crumbled = element_info[EL_DEFAULT].crumbled[act];
+ }
+
+ if (default_action_graphic == -1)
+ default_action_graphic = default_graphic;
+
+ if (default_action_crumbled == -1)
+ default_action_crumbled = default_action_graphic;
+
+ for (dir = 0; dir < NUM_DIRECTIONS_FULL; dir++)
+ {
+ // use action graphic as the default direction graphic, if undefined
+ int default_action_direction_graphic = element_info[i].graphic[act];
+ int default_action_direction_crumbled = element_info[i].crumbled[act];
+
+ // no graphic for current action -- use default direction graphic
+ if (default_action_direction_graphic == -1)
+ default_action_direction_graphic =
+ (act_remove ? default_remove_graphic :
+ act_turning ?
+ element_info[i].direction_graphic[ACTION_TURNING][dir] :
+ default_action_graphic != default_graphic ?
+ default_action_graphic :
+ default_direction_graphic[dir]);
+
+ if (element_info[i].direction_graphic[act][dir] == -1)
+ element_info[i].direction_graphic[act][dir] =
+ default_action_direction_graphic;
+
+ if (default_action_direction_crumbled == -1)
+ default_action_direction_crumbled =
+ element_info[i].direction_graphic[act][dir];
+
+ if (element_info[i].direction_crumbled[act][dir] == -1)
+ element_info[i].direction_crumbled[act][dir] =
+ default_action_direction_crumbled;
+ }
+
+ // no graphic for this specific action -- use default action graphic
+ if (element_info[i].graphic[act] == -1)
+ element_info[i].graphic[act] =
+ (act_remove ? default_remove_graphic :
+ act_turning ? element_info[i].graphic[ACTION_TURNING] :
+ default_action_graphic);
+
+ if (element_info[i].crumbled[act] == -1)
+ element_info[i].crumbled[act] = element_info[i].graphic[act];
+ }
+ }
+
+ UPDATE_BUSY_STATE();
+}
+
+static void InitElementSpecialGraphicInfo(void)
+{
+ struct PropertyMapping *property_mapping = getImageListPropertyMapping();
+ int num_property_mappings = getImageListPropertyMappingSize();
+ int i, j;
+
+ // always start with reliable default values
+ for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+ for (j = 0; j < NUM_SPECIAL_GFX_ARGS; j++)
+ element_info[i].special_graphic[j] =
+ element_info[i].graphic[ACTION_DEFAULT];
+
+ // initialize special element/graphic mapping from static configuration
+ for (i = 0; element_to_special_graphic[i].element > -1; i++)
+ {
+ int element = element_to_special_graphic[i].element;
+ int special = element_to_special_graphic[i].special;
+ int graphic = element_to_special_graphic[i].graphic;
+ int base_graphic = el2baseimg(element);
+ boolean base_redefined =
+ getImageListEntryFromImageID(base_graphic)->redefined;
+ boolean special_redefined =
+ getImageListEntryFromImageID(graphic)->redefined;
+
+ /* if the base graphic ("emerald", for example) has been redefined,
+ but not the special graphic ("emerald.EDITOR", for example), do not
+ use an existing (in this case considered obsolete) special graphic
+ anymore, but use the automatically created (down-scaled) graphic */
+ if (base_redefined && !special_redefined)
+ continue;
+
+ element_info[element].special_graphic[special] = graphic;
+ }
+
+ // initialize special element/graphic mapping from dynamic configuration
+ for (i = 0; i < num_property_mappings; i++)
+ {
+ int element = property_mapping[i].base_index;
+ int action = property_mapping[i].ext1_index;
+ int direction = property_mapping[i].ext2_index;
+ int special = property_mapping[i].ext3_index;
+ int graphic = property_mapping[i].artwork_index;
+
+ // for action ".active", replace element with active element, if exists
+ if (action == ACTION_ACTIVE && element != ELEMENT_ACTIVE(element))
+ {
+ element = ELEMENT_ACTIVE(element);
+ action = -1;
+ }
+
+ if (element >= MAX_NUM_ELEMENTS)
+ continue;
+
+ // do not change special graphic if action or direction was specified
+ if (action != -1 || direction != -1)
+ continue;
+
+ if (IS_SPECIAL_GFX_ARG(special))
+ element_info[element].special_graphic[special] = graphic;
+ }
+
+ // now set all undefined/invalid graphics to default
+ for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+ for (j = 0; j < NUM_SPECIAL_GFX_ARGS; j++)
+ if (graphic_info[element_info[i].special_graphic[j]].bitmap == NULL)
+ element_info[i].special_graphic[j] =
+ element_info[i].graphic[ACTION_DEFAULT];
+}
+
+static int get_graphic_parameter_value(char *value_raw, char *suffix, int type)
+{
+ if (type != TYPE_ELEMENT && type != TYPE_GRAPHIC)
+ return get_parameter_value(value_raw, suffix, type);
+
+ if (strEqual(value_raw, ARG_UNDEFINED))
+ return ARG_UNDEFINED_VALUE;
+
+ if (type == TYPE_ELEMENT)
+ {
+ char *value = getHashEntry(element_token_hash, value_raw);
+
+ if (value == NULL)
+ {
+ Warn("---");
+ Warn("error found in config file:");
+ Warn("- config file: '%s'", getImageConfigFilename());
+ Warn("error: invalid element token '%s'", value_raw);
+ Warn("custom graphic rejected for this element/action");
+ Warn("fallback done to undefined element for this graphic");
+ Warn("---");
+ }
+
+ return (value != NULL ? atoi(value) : EL_UNDEFINED);
+ }
+ else if (type == TYPE_GRAPHIC)
+ {
+ char *value = getHashEntry(graphic_token_hash, value_raw);
+ int fallback_graphic = IMG_CHAR_EXCLAM;
+
+ if (value == NULL)
+ {
+ Warn("---");
+ Warn("error found in config file:");
+ Warn("- config file: '%s'", getImageConfigFilename());
+ Warn("error: invalid graphic token '%s'", value_raw);
+ Warn("custom graphic rejected for this element/action");
+ Warn("fallback done to 'char_exclam' for this graphic");
+ Warn("---");
+ }
+
+ return (value != NULL ? atoi(value) : fallback_graphic);
+ }
+
+ return -1;
+}
+
+static int get_scaled_graphic_width(Bitmap *src_bitmap, int graphic)
+{
+ int original_width = getOriginalImageWidthFromImageID(graphic);
+ int scale_up_factor = graphic_info[graphic].scale_up_factor;
+
+ // only happens when loaded outside artwork system (like "global.busy")
+ if (graphic_info == image_initial && src_bitmap)
+ original_width = src_bitmap->width;
+
+ return original_width * scale_up_factor;
+}
+
+static int get_scaled_graphic_height(Bitmap *src_bitmap, int graphic)
+{
+ int original_height = getOriginalImageHeightFromImageID(graphic);
+ int scale_up_factor = graphic_info[graphic].scale_up_factor;
+
+ // only happens when loaded outside artwork system (like "global.busy")
+ if (graphic_info == image_initial && src_bitmap)
+ original_height = src_bitmap->height;
+
+ return original_height * scale_up_factor;
+}
+
+static void set_graphic_parameters_ext(int graphic, int *parameter,
+ Bitmap **src_bitmaps)
+{
+ struct GraphicInfo *g = &graphic_info[graphic];
+ Bitmap *src_bitmap = (src_bitmaps ? src_bitmaps[IMG_BITMAP_STANDARD] : NULL);
+ int anim_frames_per_row = 1, anim_frames_per_col = 1;
+ int anim_frames_per_line = 1;
+
+ // always start with reliable default values
+ g->src_image_width = 0;
+ g->src_image_height = 0;
+ g->src_x = 0;
+ g->src_y = 0;
+ g->width = TILEX; // default for element graphics
+ g->height = TILEY; // default for element graphics
+ g->offset_x = 0; // one or both of these values ...
+ g->offset_y = 0; // ... will be corrected later
+ g->offset2_x = 0; // one or both of these values ...
+ g->offset2_y = 0; // ... will be corrected later
+ g->swap_double_tiles = -1; // auto-detect tile swapping
+ g->crumbled_like = -1; // do not use clone element
+ g->diggable_like = -1; // do not use clone element
+ g->border_size = TILEX / 8; // "CRUMBLED" border size
+ g->scale_up_factor = 1; // default: no scaling up
+ g->tile_size = TILESIZE; // default: standard tile size
+ g->clone_from = -1; // do not use clone graphic
+ g->init_delay_fixed = 0;
+ g->init_delay_random = 0;
+ g->init_delay_action = -1;
+ g->anim_delay_fixed = 0;
+ g->anim_delay_random = 0;
+ g->anim_delay_action = -1;
+ g->post_delay_fixed = 0;
+ g->post_delay_random = 0;
+ g->post_delay_action = -1;
+ g->init_event = ANIM_EVENT_UNDEFINED;
+ g->anim_event = ANIM_EVENT_UNDEFINED;
+ g->init_event_action = -1;
+ g->anim_event_action = -1;
+ g->draw_masked = FALSE;
+ g->draw_order = 0;
+ g->fade_mode = FADE_MODE_DEFAULT;
+ g->fade_delay = -1;
+ g->post_delay = -1;
+ g->auto_delay = -1;
+ g->auto_delay_unit = AUTO_DELAY_UNIT_DEFAULT;
+ g->align = ALIGN_CENTER; // default for title screens
+ g->valign = VALIGN_MIDDLE; // default for title screens
+ g->sort_priority = 0; // default for title screens
+ g->class = 0;
+ g->style = STYLE_DEFAULT;
+ g->alpha = -1;
+
+ g->bitmaps = src_bitmaps;
+ g->bitmap = src_bitmap;
+
+ // optional zoom factor for scaling up the image to a larger size
+ if (parameter[GFX_ARG_SCALE_UP_FACTOR] != ARG_UNDEFINED_VALUE)
+ g->scale_up_factor = parameter[GFX_ARG_SCALE_UP_FACTOR];
+ if (g->scale_up_factor < 1)
+ g->scale_up_factor = 1; // no scaling
+
+ // optional tile size for using non-standard image size
+ if (parameter[GFX_ARG_TILE_SIZE] != ARG_UNDEFINED_VALUE)
+ {
+ g->tile_size = parameter[GFX_ARG_TILE_SIZE];
+
+#if 0
+ // CHECK: should tile sizes less than standard tile size be allowed?
+ if (g->tile_size < TILESIZE)
+ g->tile_size = TILESIZE; // standard tile size
+#endif
+
+ // when setting tile size, also set width and height accordingly
+ g->width = g->tile_size;
+ g->height = g->tile_size;
+ }
+
+ if (g->use_image_size)
+ {
+ // set new default bitmap size (with scaling, but without small images)
+ g->width = get_scaled_graphic_width(src_bitmap, graphic);
+ g->height = get_scaled_graphic_height(src_bitmap, graphic);
+ }
+
+ // optional width and height of each animation frame
+ if (parameter[GFX_ARG_WIDTH] != ARG_UNDEFINED_VALUE)
+ g->width = parameter[GFX_ARG_WIDTH];
+ if (parameter[GFX_ARG_HEIGHT] != ARG_UNDEFINED_VALUE)
+ g->height = parameter[GFX_ARG_HEIGHT];
+
+ // optional x and y tile position of animation frame sequence
+ if (parameter[GFX_ARG_XPOS] != ARG_UNDEFINED_VALUE)
+ g->src_x = parameter[GFX_ARG_XPOS] * g->width;
+ if (parameter[GFX_ARG_YPOS] != ARG_UNDEFINED_VALUE)
+ g->src_y = parameter[GFX_ARG_YPOS] * g->height;
+
+ // optional x and y pixel position of animation frame sequence
+ if (parameter[GFX_ARG_X] != ARG_UNDEFINED_VALUE)
+ g->src_x = parameter[GFX_ARG_X];
+ if (parameter[GFX_ARG_Y] != ARG_UNDEFINED_VALUE)
+ g->src_y = parameter[GFX_ARG_Y];
+
+ if (src_bitmap)
+ {
+ if (g->width <= 0)
+ {
+ Warn("---");
+ Warn("invalid value %d for '%s.width' (fallback done to %d)",
+ g->width, getTokenFromImageID(graphic), TILEX);
+ Warn("---");
+
+ g->width = TILEX; // will be checked to be inside bitmap later
+ }
+
+ if (g->height <= 0)
+ {
+ Warn("---");
+ Warn("invalid value %d for '%s.height' (fallback done to %d)",
+ g->height, getTokenFromImageID(graphic), TILEY);
+ Warn("---");
+
+ g->height = TILEY; // will be checked to be inside bitmap later
+ }
+ }
+
+ if (src_bitmap)
+ {
+ // get final bitmap size (with scaling, but without small images)
+ int src_image_width = get_scaled_graphic_width(src_bitmap, graphic);
+ int src_image_height = get_scaled_graphic_height(src_bitmap, graphic);
+
+ if (parameter[GFX_ARG_TILE_SIZE] != ARG_UNDEFINED_VALUE)
+ {
+ anim_frames_per_row = MAX(1, src_image_width / g->tile_size);
+ anim_frames_per_col = MAX(1, src_image_height / g->tile_size);
+ }
+ else
+ {
+ anim_frames_per_row = MAX(1, src_image_width / g->width);
+ anim_frames_per_col = MAX(1, src_image_height / g->height);
+ }
+
+ g->src_image_width = src_image_width;
+ g->src_image_height = src_image_height;
+ }
+
+ // correct x or y offset dependent of vertical or horizontal frame order
+ if (parameter[GFX_ARG_VERTICAL]) // frames are ordered vertically
+ {
+ g->offset_y = (parameter[GFX_ARG_OFFSET] != ARG_UNDEFINED_VALUE ?
+ parameter[GFX_ARG_OFFSET] : g->height);
+ anim_frames_per_line = anim_frames_per_col;
+ }
+ else // frames are ordered horizontally
+ {
+ g->offset_x = (parameter[GFX_ARG_OFFSET] != ARG_UNDEFINED_VALUE ?
+ parameter[GFX_ARG_OFFSET] : g->width);
+ anim_frames_per_line = anim_frames_per_row;
+ }
+
+ // optionally, the x and y offset of frames can be specified directly
+ if (parameter[GFX_ARG_XOFFSET] != ARG_UNDEFINED_VALUE)
+ g->offset_x = parameter[GFX_ARG_XOFFSET];
+ if (parameter[GFX_ARG_YOFFSET] != ARG_UNDEFINED_VALUE)
+ g->offset_y = parameter[GFX_ARG_YOFFSET];
+
+ // optionally, moving animations may have separate start and end graphics
+ g->double_movement = parameter[GFX_ARG_2ND_MOVEMENT_TILE];
+
+ if (parameter[GFX_ARG_2ND_VERTICAL] == ARG_UNDEFINED_VALUE)
+ parameter[GFX_ARG_2ND_VERTICAL] = !parameter[GFX_ARG_VERTICAL];
+
+ // correct x or y offset2 dependent of vertical or horizontal frame order
+ if (parameter[GFX_ARG_2ND_VERTICAL]) // frames are ordered vertically
+ g->offset2_y = (parameter[GFX_ARG_2ND_OFFSET] != ARG_UNDEFINED_VALUE ?
+ parameter[GFX_ARG_2ND_OFFSET] : g->height);
+ else // frames are ordered horizontally
+ g->offset2_x = (parameter[GFX_ARG_2ND_OFFSET] != ARG_UNDEFINED_VALUE ?
+ parameter[GFX_ARG_2ND_OFFSET] : g->width);
+
+ // optionally, the x and y offset of 2nd graphic can be specified directly
+ if (parameter[GFX_ARG_2ND_XOFFSET] != ARG_UNDEFINED_VALUE)
+ g->offset2_x = parameter[GFX_ARG_2ND_XOFFSET];
+ if (parameter[GFX_ARG_2ND_YOFFSET] != ARG_UNDEFINED_VALUE)
+ g->offset2_y = parameter[GFX_ARG_2ND_YOFFSET];
+
+ // optionally, the second movement tile can be specified as start tile
+ if (parameter[GFX_ARG_2ND_SWAP_TILES] != ARG_UNDEFINED_VALUE)
+ g->swap_double_tiles= parameter[GFX_ARG_2ND_SWAP_TILES];
+
+ // automatically determine correct number of frames, if not defined
+ if (parameter[GFX_ARG_FRAMES] != ARG_UNDEFINED_VALUE)
+ g->anim_frames = parameter[GFX_ARG_FRAMES];
+ else if (parameter[GFX_ARG_XPOS] == 0 && !parameter[GFX_ARG_VERTICAL])
+ g->anim_frames = anim_frames_per_row;
+ else if (parameter[GFX_ARG_YPOS] == 0 && parameter[GFX_ARG_VERTICAL])
+ g->anim_frames = anim_frames_per_col;
+ else
+ g->anim_frames = 1;
+
+ if (g->anim_frames < 1) // frames must be at least 1
+ g->anim_frames = 1;
+
+ g->anim_frames_per_line =
+ (parameter[GFX_ARG_FRAMES_PER_LINE] != ARG_UNDEFINED_VALUE ?
+ parameter[GFX_ARG_FRAMES_PER_LINE] : anim_frames_per_line);
+
+ g->anim_delay = parameter[GFX_ARG_DELAY];
+ if (g->anim_delay < 1) // delay must be at least 1
+ g->anim_delay = 1;
+
+ g->anim_mode = parameter[GFX_ARG_ANIM_MODE];
+
+ // automatically determine correct start frame, if not defined
+ if (parameter[GFX_ARG_START_FRAME] == ARG_UNDEFINED_VALUE)
+ g->anim_start_frame = 0;
+ else if (g->anim_mode & ANIM_REVERSE)
+ g->anim_start_frame = g->anim_frames - parameter[GFX_ARG_START_FRAME] - 1;
+ else
+ g->anim_start_frame = parameter[GFX_ARG_START_FRAME];
+
+ // animation synchronized with global frame counter, not move position
+ g->anim_global_sync = parameter[GFX_ARG_GLOBAL_SYNC];
+
+ // animation synchronized with global anim frame counter, not move position
+ g->anim_global_anim_sync = parameter[GFX_ARG_GLOBAL_ANIM_SYNC];
+
+ // optional element for cloning crumble graphics
+ if (parameter[GFX_ARG_CRUMBLED_LIKE] != ARG_UNDEFINED_VALUE)
+ g->crumbled_like = parameter[GFX_ARG_CRUMBLED_LIKE];
+
+ // optional element for cloning digging graphics
+ if (parameter[GFX_ARG_DIGGABLE_LIKE] != ARG_UNDEFINED_VALUE)
+ g->diggable_like = parameter[GFX_ARG_DIGGABLE_LIKE];
+
+ // optional border size for "crumbling" diggable graphics
+ if (parameter[GFX_ARG_BORDER_SIZE] != ARG_UNDEFINED_VALUE)
+ g->border_size = parameter[GFX_ARG_BORDER_SIZE];
+
+ // used for global animations and player "boring" and "sleeping" actions
+ if (parameter[GFX_ARG_INIT_DELAY_FIXED] != ARG_UNDEFINED_VALUE)
+ g->init_delay_fixed = parameter[GFX_ARG_INIT_DELAY_FIXED];
+ if (parameter[GFX_ARG_INIT_DELAY_RANDOM] != ARG_UNDEFINED_VALUE)
+ g->init_delay_random = parameter[GFX_ARG_INIT_DELAY_RANDOM];
+ if (parameter[GFX_ARG_ANIM_DELAY_FIXED] != ARG_UNDEFINED_VALUE)
+ g->anim_delay_fixed = parameter[GFX_ARG_ANIM_DELAY_FIXED];
+ if (parameter[GFX_ARG_ANIM_DELAY_RANDOM] != ARG_UNDEFINED_VALUE)
+ g->anim_delay_random = parameter[GFX_ARG_ANIM_DELAY_RANDOM];
+ if (parameter[GFX_ARG_POST_DELAY_FIXED] != ARG_UNDEFINED_VALUE)
+ g->post_delay_fixed = parameter[GFX_ARG_POST_DELAY_FIXED];
+ if (parameter[GFX_ARG_POST_DELAY_RANDOM] != ARG_UNDEFINED_VALUE)
+ g->post_delay_random = parameter[GFX_ARG_POST_DELAY_RANDOM];
+
+ // used for global animations
+ if (parameter[GFX_ARG_INIT_EVENT] != ARG_UNDEFINED_VALUE)
+ g->init_event = parameter[GFX_ARG_INIT_EVENT];
+ if (parameter[GFX_ARG_ANIM_EVENT] != ARG_UNDEFINED_VALUE)
+ g->anim_event = parameter[GFX_ARG_ANIM_EVENT];
+ if (parameter[GFX_ARG_INIT_EVENT_ACTION] != ARG_UNDEFINED_VALUE)
+ g->init_event_action = parameter[GFX_ARG_INIT_EVENT_ACTION];
+ if (parameter[GFX_ARG_ANIM_EVENT_ACTION] != ARG_UNDEFINED_VALUE)
+ g->anim_event_action = parameter[GFX_ARG_ANIM_EVENT_ACTION];
+ if (parameter[GFX_ARG_INIT_DELAY_ACTION] != ARG_UNDEFINED_VALUE)
+ g->init_delay_action = parameter[GFX_ARG_INIT_DELAY_ACTION];
+ if (parameter[GFX_ARG_ANIM_DELAY_ACTION] != ARG_UNDEFINED_VALUE)
+ g->anim_delay_action = parameter[GFX_ARG_ANIM_DELAY_ACTION];
+ if (parameter[GFX_ARG_POST_DELAY_ACTION] != ARG_UNDEFINED_VALUE)
+ g->post_delay_action = parameter[GFX_ARG_POST_DELAY_ACTION];
+
+ // used for toon animations and global animations
+ g->step_offset = parameter[GFX_ARG_STEP_OFFSET];
+ g->step_xoffset = parameter[GFX_ARG_STEP_XOFFSET];
+ g->step_yoffset = parameter[GFX_ARG_STEP_YOFFSET];
+ g->step_delay = parameter[GFX_ARG_STEP_DELAY];
+ g->direction = parameter[GFX_ARG_DIRECTION];
+ g->position = parameter[GFX_ARG_POSITION];
+ g->x = parameter[GFX_ARG_X]; // (may be uninitialized,
+ g->y = parameter[GFX_ARG_Y]; // unlike src_x and src_y)
+
+ if (g->step_delay < 1) // delay must be at least 1
+ g->step_delay = 1;
+
+ // this is only used for drawing font characters
+ g->draw_xoffset = parameter[GFX_ARG_DRAW_XOFFSET];
+ g->draw_yoffset = parameter[GFX_ARG_DRAW_YOFFSET];
+
+ // use a different default value for global animations and toons
+ if ((graphic >= IMG_GFX_GLOBAL_ANIM_1 && graphic <= IMG_GFX_GLOBAL_ANIM_32) ||
+ (graphic >= IMG_TOON_1 && graphic <= IMG_TOON_20))
+ g->draw_masked = TRUE;
+
+ // this is used for drawing envelopes, global animations and toons
+ if (parameter[GFX_ARG_DRAW_MASKED] != ARG_UNDEFINED_VALUE)
+ g->draw_masked = parameter[GFX_ARG_DRAW_MASKED];
+
+ // used for toon animations and global animations
+ if (parameter[GFX_ARG_DRAW_ORDER] != ARG_UNDEFINED_VALUE)
+ g->draw_order = parameter[GFX_ARG_DRAW_ORDER];
+
+ // optional graphic for cloning all graphics settings
+ if (parameter[GFX_ARG_CLONE_FROM] != ARG_UNDEFINED_VALUE)
+ g->clone_from = parameter[GFX_ARG_CLONE_FROM];
+
+ // optional settings for drawing title screens and title messages
+ if (parameter[GFX_ARG_FADE_MODE] != ARG_UNDEFINED_VALUE)
+ g->fade_mode = parameter[GFX_ARG_FADE_MODE];
+ if (parameter[GFX_ARG_FADE_DELAY] != ARG_UNDEFINED_VALUE)
+ g->fade_delay = parameter[GFX_ARG_FADE_DELAY];
+ if (parameter[GFX_ARG_POST_DELAY] != ARG_UNDEFINED_VALUE)
+ g->post_delay = parameter[GFX_ARG_POST_DELAY];
+ if (parameter[GFX_ARG_AUTO_DELAY] != ARG_UNDEFINED_VALUE)
+ g->auto_delay = parameter[GFX_ARG_AUTO_DELAY];
+ if (parameter[GFX_ARG_AUTO_DELAY_UNIT] != ARG_UNDEFINED_VALUE)
+ g->auto_delay_unit = parameter[GFX_ARG_AUTO_DELAY_UNIT];
+ if (parameter[GFX_ARG_ALIGN] != ARG_UNDEFINED_VALUE)
+ g->align = parameter[GFX_ARG_ALIGN];
+ if (parameter[GFX_ARG_VALIGN] != ARG_UNDEFINED_VALUE)
+ g->valign = parameter[GFX_ARG_VALIGN];
+ if (parameter[GFX_ARG_SORT_PRIORITY] != ARG_UNDEFINED_VALUE)
+ g->sort_priority = parameter[GFX_ARG_SORT_PRIORITY];
+
+ if (parameter[GFX_ARG_CLASS] != ARG_UNDEFINED_VALUE)
+ g->class = parameter[GFX_ARG_CLASS];
+ if (parameter[GFX_ARG_STYLE] != ARG_UNDEFINED_VALUE)
+ g->style = parameter[GFX_ARG_STYLE];
+ if (parameter[GFX_ARG_ALPHA] != ARG_UNDEFINED_VALUE)
+ g->alpha = parameter[GFX_ARG_ALPHA];
+
+ // this is only used for drawing menu buttons and text
+ g->active_xoffset = parameter[GFX_ARG_ACTIVE_XOFFSET];
+ g->active_yoffset = parameter[GFX_ARG_ACTIVE_YOFFSET];
+ g->pressed_xoffset = parameter[GFX_ARG_PRESSED_XOFFSET];
+ g->pressed_yoffset = parameter[GFX_ARG_PRESSED_YOFFSET];
+
+ // this is only used for drawing stacked global animations
+ g->stacked_xfactor = parameter[GFX_ARG_STACKED_XFACTOR];
+ g->stacked_yfactor = parameter[GFX_ARG_STACKED_YFACTOR];
+ g->stacked_xoffset = parameter[GFX_ARG_STACKED_XOFFSET];
+ g->stacked_yoffset = parameter[GFX_ARG_STACKED_YOFFSET];
+}
+
+static void set_graphic_parameters(int graphic)
+{
+ struct FileInfo *image = getImageListEntryFromImageID(graphic);
+ char **parameter_raw = image->parameter;
+ Bitmap **src_bitmaps = getBitmapsFromImageID(graphic);
+ int parameter[NUM_GFX_ARGS];
+ int i;
+
+ // if fallback to default artwork is done, also use the default parameters
+ if (image->fallback_to_default)
+ parameter_raw = image->default_parameter;
+
+ // get integer values from string parameters
+ for (i = 0; i < NUM_GFX_ARGS; i++)
+ parameter[i] = get_graphic_parameter_value(parameter_raw[i],
+ image_config_suffix[i].token,
+ image_config_suffix[i].type);
+
+ set_graphic_parameters_ext(graphic, parameter, src_bitmaps);
+
+ UPDATE_BUSY_STATE();
+}
+
+static void set_cloned_graphic_parameters(int graphic)
+{
+ int fallback_graphic = IMG_CHAR_EXCLAM;
+ int max_num_images = getImageListSize();
+ int clone_graphic = graphic_info[graphic].clone_from;
+ int num_references_followed = 1;
+
+ while (graphic_info[clone_graphic].clone_from != -1 &&
+ num_references_followed < max_num_images)
+ {
+ clone_graphic = graphic_info[clone_graphic].clone_from;
+
+ num_references_followed++;
+ }
+
+ if (num_references_followed >= max_num_images)
+ {
+ Warn("---");
+ Warn("error found in config file:");
+ Warn("- config file: '%s'", getImageConfigFilename());
+ Warn("- config token: '%s'", getTokenFromImageID(graphic));
+ Warn("error: loop discovered when resolving cloned graphics");
+ Warn("custom graphic rejected for this element/action");
+
+ if (graphic == fallback_graphic)
+ Fail("no fallback graphic available");
+
+ Warn("fallback done to 'char_exclam' for this graphic");
+ Warn("---");
+
+ graphic_info[graphic] = graphic_info[fallback_graphic];
+ }
+ else
+ {
+ graphic_info[graphic] = graphic_info[clone_graphic];
+ graphic_info[graphic].clone_from = clone_graphic;
+ }
+}
+
+static void InitGraphicInfo(void)
+{
+ int fallback_graphic = IMG_CHAR_EXCLAM;
+ int num_images = getImageListSize();
+ int i;
+
+ // use image size as default values for width and height for these images
+ static int full_size_graphics[] =
+ {
+ IMG_GLOBAL_BORDER,
+ IMG_GLOBAL_BORDER_MAIN,
+ IMG_GLOBAL_BORDER_SCORES,
+ IMG_GLOBAL_BORDER_EDITOR,
+ IMG_GLOBAL_BORDER_PLAYING,
+ IMG_GLOBAL_DOOR,
+
+ IMG_BACKGROUND_ENVELOPE_1,
+ IMG_BACKGROUND_ENVELOPE_2,
+ IMG_BACKGROUND_ENVELOPE_3,
+ IMG_BACKGROUND_ENVELOPE_4,
+ IMG_BACKGROUND_REQUEST,
+
+ IMG_BACKGROUND,
+ IMG_BACKGROUND_LOADING_INITIAL,
+ IMG_BACKGROUND_LOADING,
+ IMG_BACKGROUND_TITLE_INITIAL,
+ IMG_BACKGROUND_TITLE,
+ IMG_BACKGROUND_MAIN,
+ IMG_BACKGROUND_NAMES,
+ IMG_BACKGROUND_LEVELS,
+ IMG_BACKGROUND_LEVELNR,
+ IMG_BACKGROUND_SCORES,
+ IMG_BACKGROUND_SCOREINFO,
+ IMG_BACKGROUND_EDITOR,
+ IMG_BACKGROUND_INFO,
+ IMG_BACKGROUND_INFO_ELEMENTS,
+ IMG_BACKGROUND_INFO_MUSIC,
+ IMG_BACKGROUND_INFO_CREDITS,
+ IMG_BACKGROUND_INFO_PROGRAM,
+ IMG_BACKGROUND_INFO_VERSION,
+ IMG_BACKGROUND_INFO_LEVELSET,
+ IMG_BACKGROUND_SETUP,
+ IMG_BACKGROUND_PLAYING,
+ IMG_BACKGROUND_DOOR,
+ IMG_BACKGROUND_TAPE,
+ IMG_BACKGROUND_PANEL,
+ IMG_BACKGROUND_PALETTE,
+ IMG_BACKGROUND_TOOLBOX,
+
+ IMG_TITLESCREEN_INITIAL_1,
+ IMG_TITLESCREEN_INITIAL_2,
+ IMG_TITLESCREEN_INITIAL_3,
+ IMG_TITLESCREEN_INITIAL_4,
+ IMG_TITLESCREEN_INITIAL_5,
+ IMG_TITLESCREEN_1,
+ IMG_TITLESCREEN_2,
+ IMG_TITLESCREEN_3,
+ IMG_TITLESCREEN_4,
+ IMG_TITLESCREEN_5,
+
+ IMG_BACKGROUND_TITLEMESSAGE_INITIAL_1,
+ IMG_BACKGROUND_TITLEMESSAGE_INITIAL_2,
+ IMG_BACKGROUND_TITLEMESSAGE_INITIAL_3,
+ IMG_BACKGROUND_TITLEMESSAGE_INITIAL_4,
+ IMG_BACKGROUND_TITLEMESSAGE_INITIAL_5,
+ IMG_BACKGROUND_TITLEMESSAGE_1,
+ IMG_BACKGROUND_TITLEMESSAGE_2,
+ IMG_BACKGROUND_TITLEMESSAGE_3,
+ IMG_BACKGROUND_TITLEMESSAGE_4,
+ IMG_BACKGROUND_TITLEMESSAGE_5,
+
+ -1
+ };
+
+ FreeGlobalAnimEventInfo();
+
+ checked_free(graphic_info);
+
+ graphic_info = checked_calloc(num_images * sizeof(struct GraphicInfo));
+
+ // initialize "use_image_size" flag with default value
+ for (i = 0; i < num_images; i++)
+ graphic_info[i].use_image_size = FALSE;
+
+ // initialize "use_image_size" flag from static configuration above
+ for (i = 0; full_size_graphics[i] != -1; i++)
+ graphic_info[full_size_graphics[i]].use_image_size = TRUE;
+
+ // first set all graphic paramaters ...
+ for (i = 0; i < num_images; i++)
+ set_graphic_parameters(i);
+
+ // ... then copy these parameters for cloned graphics
+ for (i = 0; i < num_images; i++)
+ if (graphic_info[i].clone_from != -1)
+ set_cloned_graphic_parameters(i);
+
+ for (i = 0; i < num_images; i++)
+ {
+ Bitmap *src_bitmap = graphic_info[i].bitmap;
+ int src_x, src_y;
+ int width, height;
+ int last_frame;
+ int src_bitmap_width, src_bitmap_height;
+
+ // now check if no animation frames are outside of the loaded image
+
+ if (graphic_info[i].bitmap == NULL)
+ continue; // skip check for optional images that are undefined
+
+ // get image size (this can differ from the standard element tile size!)
+ width = graphic_info[i].width;
+ height = graphic_info[i].height;
+
+ // get final bitmap size (with scaling, but without small images)
+ src_bitmap_width = graphic_info[i].src_image_width;
+ src_bitmap_height = graphic_info[i].src_image_height;
+
+ // check if first animation frame is inside specified bitmap
+
+ // do not use getGraphicSourceXY() here to get position of first frame;
+ // this avoids calculating wrong start position for out-of-bounds frame
+ src_x = graphic_info[i].src_x;
+ src_y = graphic_info[i].src_y;
+
+ if (program.headless)
+ continue;
+
+ if (src_x < 0 || src_y < 0 ||
+ src_x + width > src_bitmap_width ||
+ src_y + height > src_bitmap_height)
+ {
+ Warn("---");
+ Warn("error found in config file:");
+ Warn("- config file: '%s'", getImageConfigFilename());
+ Warn("- config token: '%s'", getTokenFromImageID(i));
+ Warn("- image file: '%s'", src_bitmap->source_filename);
+ Warn("- frame size: %d, %d", width, height);
+ Warn("error: first animation frame out of bounds (%d, %d) [%d, %d]",
+ src_x, src_y, src_bitmap_width, src_bitmap_height);
+ Warn("custom graphic rejected for this element/action");
+
+ if (i == fallback_graphic)
+ Fail("no fallback graphic available");
+
+ Warn("fallback done to 'char_exclam' for this graphic");
+ Warn("---");
+
+ graphic_info[i] = graphic_info[fallback_graphic];
+
+ // if first frame out of bounds, do not check last frame anymore
+ continue;
+ }
+
+ // check if last animation frame is inside specified bitmap
+
+ last_frame = graphic_info[i].anim_frames - 1;
+ getGraphicSourceXY(i, last_frame, &src_x, &src_y, FALSE);
+
+ if (src_x < 0 || src_y < 0 ||
+ src_x + width > src_bitmap_width ||
+ src_y + height > src_bitmap_height)
+ {
+ Warn("---");
+ Warn("error found in config file:");
+ Warn("- config file: '%s'", getImageConfigFilename());
+ Warn("- config token: '%s'", getTokenFromImageID(i));
+ Warn("- image file: '%s'", src_bitmap->source_filename);
+ Warn("- frame size: %d, %d", width, height);
+ Warn("error: last animation frame (%d) out of bounds (%d, %d) [%d, %d]",
+ last_frame, src_x, src_y, src_bitmap_width, src_bitmap_height);
+ Warn("custom graphic rejected for this element/action");
+
+ if (i == fallback_graphic)
+ Fail("no fallback graphic available");
+
+ Warn("fallback done to 'char_exclam' for this graphic");
+ Warn("---");
+
+ graphic_info[i] = graphic_info[fallback_graphic];
+ }
+ }
+}
+
+static void InitGraphicCompatibilityInfo(void)
+{
+ struct FileInfo *fi_global_door =
+ getImageListEntryFromImageID(IMG_GLOBAL_DOOR);
+ int num_images = getImageListSize();
+ int i;
+
+ /* the following compatibility handling is needed for the following case:
+ versions up to 3.3.0.0 used one large bitmap "global.door" for various
+ graphics mainly used for door and panel graphics, like editor, tape and
+ in-game buttons with hard-coded bitmap positions and button sizes; as
+ these graphics now have individual definitions, redefining "global.door"
+ to change all these graphics at once like before does not work anymore
+ (because all those individual definitions still have their default values);
+ to solve this, remap all those individual definitions that are not
+ redefined to the new bitmap of "global.door" if it was redefined */
+
+ // special compatibility handling if image "global.door" was redefined
+ if (fi_global_door->redefined)
+ {
+ for (i = 0; i < num_images; i++)
+ {
+ struct FileInfo *fi = getImageListEntryFromImageID(i);
+
+ // process only those images that still use the default settings
+ if (!fi->redefined)
+ {
+ // process all images which default to same image as "global.door"
+ if (strEqual(fi->default_filename, fi_global_door->default_filename))
+ {
+ // skip all images that are cloned from images that default to same
+ // image as "global.door", but that are redefined to something else
+ if (graphic_info[i].clone_from != -1)
+ {
+ int cloned_graphic = graphic_info[i].clone_from;
+
+ if (getImageListEntryFromImageID(cloned_graphic)->redefined)
+ continue;
+ }
+
+#if 0
+ Debug("init:InitGraphicCompatibilityInfo",
+ "special treatment needed for token '%s'", fi->token);
+#endif
+
+ graphic_info[i].bitmaps = graphic_info[IMG_GLOBAL_DOOR].bitmaps;
+ graphic_info[i].bitmap = graphic_info[IMG_GLOBAL_DOOR].bitmap;
+ }
+ }
+ }
+ }
+
+ // special compatibility handling for "Snake Bite" graphics set
+ if (strPrefix(leveldir_current->identifier, "snake_bite"))
+ {
+ Bitmap *bitmap = graphic_info[IMG_BACKGROUND_SCORES].bitmap;
+
+ BlitBitmap(bitmap, bitmap, 18, 66, 32, 480, 50, 66);
+ BlitBitmap(bitmap, bitmap, 466, 66, 32, 480, 434, 66);
+
+ ClearRectangle(bitmap, 2, 66, 32, 480);
+ ClearRectangle(bitmap, 514, 66, 32, 480);
+ }
+
+ // special compatibility handling for "Jue" graphics sets (2007 and 2019)
+ boolean supports_score_info = (menu.draw_xoffset[GAME_MODE_SCOREINFO] != 0);
+ if (strPrefix(artwork.gfx_current_identifier, "jue") && !supports_score_info)
+ {
+ int font_title[] =
+ {
+ FONT_TITLE_1,
+ FONT_TITLE_2,
+
+ -1
+ };
+ int font_text[] =
+ {
+ FONT_TEXT_1,
+ FONT_TEXT_2,
+ FONT_TEXT_3,
+ FONT_TEXT_4,
+
+ -1
+ };
+ int mode_old = GAME_MODE_SCORES;
+ int mode_new = GAME_MODE_SCOREINFO;
+ int i, j;
+
+ // adjust title screens on score info page
+ for (i = 0; font_title[i] != -1; i++)
+ {
+ struct FontInfo *fi = &font_info[font_title[i]];
+
+ fi->special_graphic[mode_new] = fi->special_graphic[mode_old];
+ fi->special_bitmap_id[mode_new] = fi->special_bitmap_id[mode_old];
+ }
+
+ // adjust vertical text and button positions on scores page
+ for (i = 0; font_text[i] != -1; i++)
+ {
+ for (j = 0; j < 2; j++)
+ {
+ boolean jue0 = strEqual(artwork.gfx_current_identifier, "jue0");
+ int font_nr = (j == 0 ? font_text[i] : FONT_ACTIVE(font_text[i]));
+ int font_bitmap_id = font_info[font_nr].special_bitmap_id[mode_old];
+ int font_yoffset = (jue0 ? 10 : 5);
+
+ gfx.font_bitmap_info[font_bitmap_id].draw_yoffset = font_yoffset;
+ }
+ }
+
+ // adjust page offsets on score info page
+ menu.draw_xoffset[mode_new] = menu.draw_xoffset[mode_old];
+ menu.draw_yoffset[mode_new] = menu.draw_yoffset[mode_old];
+ }
+
+ InitGraphicCompatibilityInfo_Doors();
+}
+
+static void InitElementSoundInfo(void)
+{
+ struct PropertyMapping *property_mapping = getSoundListPropertyMapping();
+ int num_property_mappings = getSoundListPropertyMappingSize();
+ int i, j, act;
+
+ // set values to -1 to identify later as "uninitialized" values
+ for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+ for (act = 0; act < NUM_ACTIONS; act++)
+ element_info[i].sound[act] = -1;
+
+ // initialize element/sound mapping from static configuration
+ for (i = 0; element_to_sound[i].element > -1; i++)
+ {
+ int element = element_to_sound[i].element;
+ int action = element_to_sound[i].action;
+ int sound = element_to_sound[i].sound;
+ boolean is_class = element_to_sound[i].is_class;
+
+ if (action < 0)
+ action = ACTION_DEFAULT;
+
+ if (!is_class)
+ element_info[element].sound[action] = sound;
+ else
+ for (j = 0; j < MAX_NUM_ELEMENTS; j++)
+ if (strEqual(element_info[j].class_name,
+ element_info[element].class_name))
+ element_info[j].sound[action] = sound;
+ }
+
+ // initialize element class/sound mapping from dynamic configuration
+ for (i = 0; i < num_property_mappings; i++)
+ {
+ int element_class = property_mapping[i].base_index - MAX_NUM_ELEMENTS;
+ int action = property_mapping[i].ext1_index;
+ int sound = property_mapping[i].artwork_index;
+
+ if (element_class < 0 || element_class >= MAX_NUM_ELEMENTS)
+ continue;
+
+ if (action < 0)
+ action = ACTION_DEFAULT;
+
+ for (j = 0; j < MAX_NUM_ELEMENTS; j++)
+ if (strEqual(element_info[j].class_name,
+ element_info[element_class].class_name))
+ element_info[j].sound[action] = sound;
+ }
+
+ // initialize element/sound mapping from dynamic configuration
+ for (i = 0; i < num_property_mappings; i++)
+ {
+ int element = property_mapping[i].base_index;
+ int action = property_mapping[i].ext1_index;
+ int sound = property_mapping[i].artwork_index;
+
+ if (element >= MAX_NUM_ELEMENTS)
+ continue;
+
+ if (action < 0)
+ action = ACTION_DEFAULT;
+
+ element_info[element].sound[action] = sound;
+ }
+
+ // now set all '-1' values to element specific default values
+ for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+ {
+ for (act = 0; act < NUM_ACTIONS; act++)
+ {
+ // generic default action sound (defined by "[default]" directive)
+ int default_action_sound = element_info[EL_DEFAULT].sound[act];
+
+ // look for special default action sound (classic game specific)
+ if (IS_BD_ELEMENT(i) && element_info[EL_BD_DEFAULT].sound[act] != -1)
+ default_action_sound = element_info[EL_BD_DEFAULT].sound[act];
+ if (IS_SP_ELEMENT(i) && element_info[EL_SP_DEFAULT].sound[act] != -1)
+ default_action_sound = element_info[EL_SP_DEFAULT].sound[act];
+ if (IS_SB_ELEMENT(i) && element_info[EL_SB_DEFAULT].sound[act] != -1)
+ default_action_sound = element_info[EL_SB_DEFAULT].sound[act];
+ if (IS_MM_ELEMENT(i) && element_info[EL_MM_DEFAULT].sound[act] != -1)
+ default_action_sound = element_info[EL_MM_DEFAULT].sound[act];
+
+ // !!! needed because EL_EMPTY_SPACE treated as IS_SP_ELEMENT !!!
+ // !!! make this better !!!
+ if (i == EL_EMPTY_SPACE)
+ default_action_sound = element_info[EL_DEFAULT].sound[act];
+
+ // no sound for this specific action -- use default action sound
+ if (element_info[i].sound[act] == -1)
+ element_info[i].sound[act] = default_action_sound;
+ }
+ }
+
+ // copy sound settings to some elements that are only stored in level file
+ // in native R'n'D levels, but are used by game engine in native EM levels
+ for (i = 0; copy_properties[i][0] != -1; i++)
+ for (j = 1; j <= 4; j++)
+ for (act = 0; act < NUM_ACTIONS; act++)
+ element_info[copy_properties[i][j]].sound[act] =
+ element_info[copy_properties[i][0]].sound[act];
+}
+
+static void InitGameModeSoundInfo(void)
+{
+ int i;
+
+ // set values to -1 to identify later as "uninitialized" values
+ for (i = 0; i < NUM_SPECIAL_GFX_ARGS; i++)
+ menu.sound[i] = -1;
+
+ // initialize gamemode/sound mapping from static configuration
+ for (i = 0; gamemode_to_sound[i].sound > -1; i++)
+ {
+ int gamemode = gamemode_to_sound[i].gamemode;
+ int sound = gamemode_to_sound[i].sound;
+
+ if (gamemode < 0)
+ gamemode = GAME_MODE_DEFAULT;
+
+ menu.sound[gamemode] = sound;
+ }
+
+ // now set all '-1' values to levelset specific default values
+ for (i = 0; i < NUM_SPECIAL_GFX_ARGS; i++)
+ if (menu.sound[i] == -1)
+ menu.sound[i] = menu.sound[GAME_MODE_DEFAULT];
+}
+
+static void set_sound_parameters(int sound, char **parameter_raw)
+{
+ int parameter[NUM_SND_ARGS];
+ int i;
+
+ // get integer values from string parameters
+ for (i = 0; i < NUM_SND_ARGS; i++)
+ parameter[i] =
+ get_parameter_value(parameter_raw[i],
+ sound_config_suffix[i].token,
+ sound_config_suffix[i].type);
+
+ // explicit loop mode setting in configuration overrides default value
+ if (parameter[SND_ARG_MODE_LOOP] != ARG_UNDEFINED_VALUE)
+ sound_info[sound].loop = parameter[SND_ARG_MODE_LOOP];
+
+ // sound volume to change the original volume when loading the sound file
+ sound_info[sound].volume = parameter[SND_ARG_VOLUME];
+
+ // sound priority to give certain sounds a higher or lower priority
+ sound_info[sound].priority = parameter[SND_ARG_PRIORITY];
+}
+
+static void InitSoundInfo(void)
+{
+ int *sound_effect_properties;
+ int num_sounds = getSoundListSize();
+ int i, j;
+
+ checked_free(sound_info);
+
+ sound_effect_properties = checked_calloc(num_sounds * sizeof(int));
+ sound_info = checked_calloc(num_sounds * sizeof(struct SoundInfo));
+
+ // initialize sound effect for all elements to "no sound"
+ for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+ for (j = 0; j < NUM_ACTIONS; j++)
+ element_info[i].sound[j] = SND_UNDEFINED;
+
+ for (i = 0; i < num_sounds; i++)
+ {
+ struct FileInfo *sound = getSoundListEntry(i);
+ int len_effect_text = strlen(sound->token);
+
+ sound_effect_properties[i] = ACTION_OTHER;
+ sound_info[i].loop = FALSE; // default: play sound only once
+
+ // determine all loop sounds and identify certain sound classes
+
+ for (j = 0; element_action_info[j].suffix; j++)
+ {
+ int len_action_text = strlen(element_action_info[j].suffix);
+
+ if (len_action_text < len_effect_text &&
+ strEqual(&sound->token[len_effect_text - len_action_text],
+ element_action_info[j].suffix))
+ {
+ sound_effect_properties[i] = element_action_info[j].value;
+ sound_info[i].loop = element_action_info[j].is_loop_sound;
+
+ break;
+ }
+ }
+
+ // associate elements and some selected sound actions
+
+ for (j = 0; j < MAX_NUM_ELEMENTS; j++)
+ {
+ if (element_info[j].class_name)
+ {
+ int len_class_text = strlen(element_info[j].class_name);
+
+ if (len_class_text + 1 < len_effect_text &&
+ strncmp(sound->token,
+ element_info[j].class_name, len_class_text) == 0 &&
+ sound->token[len_class_text] == '.')
+ {
+ int sound_action_value = sound_effect_properties[i];
+
+ element_info[j].sound[sound_action_value] = i;
+ }
+ }
+ }
+
+ set_sound_parameters(i, sound->parameter);
+
+#if 0
+ Debug("init:InitSoundInfo", "loop mode: %d ['%s']",
+ sound_info[i].loop, sound->token);
+#endif
+ }
+
+ free(sound_effect_properties);
+}
+
+static void InitGameModeMusicInfo(void)
+{
+ struct PropertyMapping *property_mapping = getMusicListPropertyMapping();
+ int num_property_mappings = getMusicListPropertyMappingSize();
+ int default_levelset_music = -1;
+ int i;
+
+ // set values to -1 to identify later as "uninitialized" values
+ for (i = 0; i < MAX_LEVELS; i++)
+ levelset.music[i] = -1;
+ for (i = 0; i < NUM_SPECIAL_GFX_ARGS; i++)
+ menu.music[i] = -1;
+
+ // initialize gamemode/music mapping from static configuration
+ for (i = 0; gamemode_to_music[i].music > -1; i++)
+ {
+ int gamemode = gamemode_to_music[i].gamemode;
+ int music = gamemode_to_music[i].music;
+
+ if (gamemode < 0)
+ gamemode = GAME_MODE_DEFAULT;
+
+ menu.music[gamemode] = music;
+ }
+
+ // initialize gamemode/music mapping from dynamic configuration
+ for (i = 0; i < num_property_mappings; i++)
+ {
+ int prefix = property_mapping[i].base_index;
+ int gamemode = property_mapping[i].ext2_index;
+ int level = property_mapping[i].ext3_index;
+ int music = property_mapping[i].artwork_index;
+
+ if (prefix < 0 || prefix >= NUM_MUSIC_PREFIXES)
+ continue;
+
+ if (gamemode < 0)
+ gamemode = GAME_MODE_DEFAULT;
+
+ // level specific music only allowed for in-game music
+ if (level != -1 && gamemode == GAME_MODE_DEFAULT)
+ gamemode = GAME_MODE_PLAYING;
+
+ if (level == -1)
+ {
+ level = 0;
+ default_levelset_music = music;
+ }
+
+ if (gamemode == GAME_MODE_PLAYING || gamemode == GAME_MODE_DEFAULT)
+ levelset.music[level] = music;
+ if (gamemode != GAME_MODE_PLAYING)
+ menu.music[gamemode] = music;
+ }
+
+ // now set all '-1' values to menu specific default values
+ // (undefined values of "levelset.music[]" might stay at "-1" to
+ // allow dynamic selection of music files from music directory!)
+ for (i = 0; i < MAX_LEVELS; i++)
+ if (levelset.music[i] == -1)
+ levelset.music[i] = default_levelset_music;
+ for (i = 0; i < NUM_SPECIAL_GFX_ARGS; i++)
+ if (menu.music[i] == -1)
+ menu.music[i] = menu.music[GAME_MODE_DEFAULT];
+}
+
+static void set_music_parameters(int music, char **parameter_raw)
+{
+ int parameter[NUM_MUS_ARGS];
+ int i;
+
+ // get integer values from string parameters
+ for (i = 0; i < NUM_MUS_ARGS; i++)
+ parameter[i] =
+ get_parameter_value(parameter_raw[i],
+ music_config_suffix[i].token,
+ music_config_suffix[i].type);
+
+ // explicit loop mode setting in configuration overrides default value
+ if (parameter[MUS_ARG_MODE_LOOP] != ARG_UNDEFINED_VALUE)
+ music_info[music].loop = parameter[MUS_ARG_MODE_LOOP];
+}
+
+static void InitMusicInfo(void)
+{
+ int num_music = getMusicListSize();
+ int i, j;
+
+ checked_free(music_info);
+
+ music_info = checked_calloc(num_music * sizeof(struct MusicInfo));
+
+ for (i = 0; i < num_music; i++)
+ {
+ struct FileInfo *music = getMusicListEntry(i);
+ int len_music_text = strlen(music->token);
+
+ music_info[i].loop = TRUE; // default: play music in loop mode
+
+ // determine all loop music
+
+ for (j = 0; music_prefix_info[j].prefix; j++)
+ {
+ int len_prefix_text = strlen(music_prefix_info[j].prefix);
+
+ if (len_prefix_text < len_music_text &&
+ strncmp(music->token,
+ music_prefix_info[j].prefix, len_prefix_text) == 0)
+ {
+ music_info[i].loop = music_prefix_info[j].is_loop_music;
+
+ break;
+ }
+ }
+
+ set_music_parameters(i, music->parameter);
+ }
+}
+
+
+static void InitGameInfoFromArtworkInfo(void)
+{
+ // special case: store initial value of custom artwork setting
+ game.use_masked_elements_initial = game.use_masked_elements;
+}
+
+static void ReinitializeGraphics(void)
+{
+ print_timestamp_init("ReinitializeGraphics");
+
+ InitGfxTileSizeInfo(game.tile_size, TILESIZE);
+
+ InitGraphicInfo(); // graphic properties mapping
+ print_timestamp_time("InitGraphicInfo");
+ InitElementGraphicInfo(); // element game graphic mapping
+ print_timestamp_time("InitElementGraphicInfo");
+ InitElementSpecialGraphicInfo(); // element special graphic mapping
+ print_timestamp_time("InitElementSpecialGraphicInfo");
+
+ InitElementSmallImages(); // scale elements to all needed sizes
+ print_timestamp_time("InitElementSmallImages");
+ InitScaledImages(); // scale all other images, if needed
+ print_timestamp_time("InitScaledImages");
+ InitBitmapPointers(); // set standard size bitmap pointers
+ print_timestamp_time("InitBitmapPointers");
+ InitFontGraphicInfo(); // initialize text drawing functions
+ print_timestamp_time("InitFontGraphicInfo");
+ InitGlobalAnimGraphicInfo(); // initialize global animation config
+ print_timestamp_time("InitGlobalAnimGraphicInfo");
+
+ InitImageTextures(); // create textures for certain images
+ print_timestamp_time("InitImageTextures");
+
+ InitGraphicInfo_EM(); // graphic mapping for EM engine
+ print_timestamp_time("InitGraphicInfo_EM");
+
+ InitGraphicCompatibilityInfo();
+ print_timestamp_time("InitGraphicCompatibilityInfo");
+
+ InitGadgets();
+ print_timestamp_time("InitGadgets");
+ InitDoors();
+ print_timestamp_time("InitDoors");
+
+ InitGameInfoFromArtworkInfo();
+
+ print_timestamp_done("ReinitializeGraphics");
+}
+
+static void ReinitializeSounds(void)
+{
+ InitSoundInfo(); // sound properties mapping
+ InitElementSoundInfo(); // element game sound mapping
+ InitGameModeSoundInfo(); // game mode sound mapping
+ InitGlobalAnimSoundInfo(); // global animation sound settings
+
+ InitPlayLevelSound(); // internal game sound settings
+}
+
+static void ReinitializeMusic(void)
+{
+ InitMusicInfo(); // music properties mapping
+ InitGameModeMusicInfo(); // game mode music mapping
+ InitGlobalAnimMusicInfo(); // global animation music settings
+}
+
+static int get_special_property_bit(int element, int property_bit_nr)
+{
+ struct PropertyBitInfo
+ {
+ int element;
+ int bit_nr;
+ };
+
+ static struct PropertyBitInfo pb_can_move_into_acid[] =
+ {
+ // the player may be able fall into acid when gravity is activated
+ { EL_PLAYER_1, 0 },
+ { EL_PLAYER_2, 0 },
+ { EL_PLAYER_3, 0 },
+ { EL_PLAYER_4, 0 },
+ { EL_SP_MURPHY, 0 },
+ { EL_SOKOBAN_FIELD_PLAYER, 0 },
+
+ // all elements that can move may be able to also move into acid
+ { EL_BUG, 1 },
+ { EL_BUG_LEFT, 1 },
+ { EL_BUG_RIGHT, 1 },
+ { EL_BUG_UP, 1 },
+ { EL_BUG_DOWN, 1 },
+ { EL_SPACESHIP, 2 },
+ { EL_SPACESHIP_LEFT, 2 },
+ { EL_SPACESHIP_RIGHT, 2 },
+ { EL_SPACESHIP_UP, 2 },
+ { EL_SPACESHIP_DOWN, 2 },
+ { EL_BD_BUTTERFLY, 3 },
+ { EL_BD_BUTTERFLY_LEFT, 3 },
+ { EL_BD_BUTTERFLY_RIGHT, 3 },
+ { EL_BD_BUTTERFLY_UP, 3 },
+ { EL_BD_BUTTERFLY_DOWN, 3 },
+ { EL_BD_FIREFLY, 4 },
+ { EL_BD_FIREFLY_LEFT, 4 },
+ { EL_BD_FIREFLY_RIGHT, 4 },
+ { EL_BD_FIREFLY_UP, 4 },
+ { EL_BD_FIREFLY_DOWN, 4 },
+ { EL_YAMYAM, 5 },
+ { EL_YAMYAM_LEFT, 5 },
+ { EL_YAMYAM_RIGHT, 5 },
+ { EL_YAMYAM_UP, 5 },
+ { EL_YAMYAM_DOWN, 5 },
+ { EL_DARK_YAMYAM, 6 },
+ { EL_ROBOT, 7 },
+ { EL_PACMAN, 8 },
+ { EL_PACMAN_LEFT, 8 },
+ { EL_PACMAN_RIGHT, 8 },
+ { EL_PACMAN_UP, 8 },
+ { EL_PACMAN_DOWN, 8 },
+ { EL_MOLE, 9 },
+ { EL_MOLE_LEFT, 9 },
+ { EL_MOLE_RIGHT, 9 },
+ { EL_MOLE_UP, 9 },
+ { EL_MOLE_DOWN, 9 },
+ { EL_PENGUIN, 10 },
+ { EL_PIG, 11 },
+ { EL_DRAGON, 12 },
+ { EL_SATELLITE, 13 },
+ { EL_SP_SNIKSNAK, 14 },
+ { EL_SP_ELECTRON, 15 },
+ { EL_BALLOON, 16 },
+ { EL_SPRING, 17 },
+ { EL_SPRING_LEFT, 17 },
+ { EL_SPRING_RIGHT, 17 },
+ { EL_EMC_ANDROID, 18 },
+
+ { -1, -1 },
+ };
+
+ static struct PropertyBitInfo pb_dont_collide_with[] =
+ {
+ { EL_SP_SNIKSNAK, 0 },
+ { EL_SP_ELECTRON, 1 },
+
+ { -1, -1 },
+ };
+
+ static struct
+ {
+ int bit_nr;
+ struct PropertyBitInfo *pb_info;
+ } pb_definition[] =
+ {
+ { EP_CAN_MOVE_INTO_ACID, pb_can_move_into_acid },
+ { EP_DONT_COLLIDE_WITH, pb_dont_collide_with },
+
+ { -1, NULL },
+ };
+
+ struct PropertyBitInfo *pb_info = NULL;
+ int i;
+
+ for (i = 0; pb_definition[i].bit_nr != -1; i++)
+ if (pb_definition[i].bit_nr == property_bit_nr)
+ pb_info = pb_definition[i].pb_info;
+
+ if (pb_info == NULL)
+ return -1;
+
+ for (i = 0; pb_info[i].element != -1; i++)
+ if (pb_info[i].element == element)
+ return pb_info[i].bit_nr;
+
+ return -1;
+}
+
+void setBitfieldProperty(int *bitfield, int property_bit_nr, int element,
+ boolean property_value)
+{
+ int bit_nr = get_special_property_bit(element, property_bit_nr);
+
+ if (bit_nr > -1)
+ {
+ if (property_value)
+ *bitfield |= (1 << bit_nr);
+ else
+ *bitfield &= ~(1 << bit_nr);
+ }
+}
+
+boolean getBitfieldProperty(int *bitfield, int property_bit_nr, int element)
+{
+ int bit_nr = get_special_property_bit(element, property_bit_nr);
+
+ if (bit_nr > -1)
+ return ((*bitfield & (1 << bit_nr)) != 0);
+
+ return FALSE;
+}
+
+static void ResolveGroupElementExt(int group_element, int recursion_depth)
+{
+ static int group_nr;
+ static struct ElementGroupInfo *group;
+ struct ElementGroupInfo *actual_group = element_info[group_element].group;
+ int i;
+
+ if (actual_group == NULL) // not yet initialized
+ return;
+
+ if (recursion_depth > NUM_GROUP_ELEMENTS) // recursion too deep
+ {
+ Warn("recursion too deep when resolving group element %d",
+ group_element - EL_GROUP_START + 1);
+
+ // replace element which caused too deep recursion by question mark
+ group->element_resolved[group->num_elements_resolved++] = EL_UNKNOWN;
+
+ return;
+ }
+
+ if (recursion_depth == 0) // initialization
+ {
+ group = actual_group;
+ group_nr = GROUP_NR(group_element);
+
+ group->num_elements_resolved = 0;
+ group->choice_pos = 0;
+
+ for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+ element_info[i].in_group[group_nr] = FALSE;
+ }
+
+ for (i = 0; i < actual_group->num_elements; i++)
+ {
+ int element = actual_group->element[i];
+
+ if (group->num_elements_resolved == NUM_FILE_ELEMENTS)
+ break;
+
+ if (IS_GROUP_ELEMENT(element))
+ ResolveGroupElementExt(element, recursion_depth + 1);
+ else
+ {
+ group->element_resolved[group->num_elements_resolved++] = element;
+ element_info[element].in_group[group_nr] = TRUE;
+ }
+ }
+}
+
+void ResolveGroupElement(int group_element)
+{
+ ResolveGroupElementExt(group_element, 0);
+}
+
+void InitElementPropertiesStatic(void)
+{
+ static boolean clipboard_elements_initialized = FALSE;
+
+ static int ep_diggable[] =
+ {
+ EL_SAND,
+ EL_SP_BASE,
+ EL_SP_BUGGY_BASE,
+ EL_SP_BUGGY_BASE_ACTIVATING,
+ EL_TRAP,
+ EL_INVISIBLE_SAND,
+ EL_INVISIBLE_SAND_ACTIVE,
+ EL_EMC_GRASS,
+
+ // !!! currently not diggable, but handled by 'ep_dont_run_into' !!!
+ // (if amoeba can grow into anything diggable, maybe keep these out)
+#if 0
+ EL_LANDMINE,
+ EL_DC_LANDMINE,
+ EL_TRAP_ACTIVE,
+ EL_SP_BUGGY_BASE_ACTIVE,
+ EL_EMC_PLANT,
+#endif
+
+ -1
+ };
+
+ static int ep_collectible_only[] =
+ {
+ EL_BD_DIAMOND,
+ EL_EMERALD,
+ EL_DIAMOND,
+ EL_EMERALD_YELLOW,
+ EL_EMERALD_RED,
+ EL_EMERALD_PURPLE,
+ EL_KEY_1,
+ EL_KEY_2,
+ EL_KEY_3,
+ EL_KEY_4,
+ EL_EM_KEY_1,
+ EL_EM_KEY_2,
+ EL_EM_KEY_3,
+ EL_EM_KEY_4,
+ EL_EMC_KEY_5,
+ EL_EMC_KEY_6,
+ EL_EMC_KEY_7,
+ EL_EMC_KEY_8,
+ EL_DYNAMITE,
+ EL_EM_DYNAMITE,
+ EL_DYNABOMB_INCREASE_NUMBER,
+ EL_DYNABOMB_INCREASE_SIZE,
+ EL_DYNABOMB_INCREASE_POWER,
+ EL_SP_INFOTRON,
+ EL_SP_DISK_RED,
+ EL_PEARL,
+ EL_CRYSTAL,
+ EL_DC_KEY_WHITE,
+ EL_SHIELD_NORMAL,
+ EL_SHIELD_DEADLY,
+ EL_EXTRA_TIME,
+ EL_ENVELOPE_1,
+ EL_ENVELOPE_2,
+ EL_ENVELOPE_3,
+ EL_ENVELOPE_4,
+ EL_SPEED_PILL,
+ EL_EMC_LENSES,
+ EL_EMC_MAGNIFIER,
+
+#if 0
+ // !!! handle separately !!!
+ EL_DC_LANDMINE, // deadly when running into, but can be snapped
+#endif
+
+ -1
+ };
+
+ static int ep_dont_run_into[] =
+ {
+ // same elements as in 'ep_dont_touch'
+ EL_BUG,
+ EL_SPACESHIP,
+ EL_BD_BUTTERFLY,
+ EL_BD_FIREFLY,
+
+ // same elements as in 'ep_dont_collide_with'
+ EL_YAMYAM,
+ EL_DARK_YAMYAM,
+ EL_ROBOT,
+ EL_PACMAN,
+ EL_SP_SNIKSNAK,
+ EL_SP_ELECTRON,
+
+ // new elements
+ EL_AMOEBA_DROP,
+ EL_ACID,
+
+ // !!! maybe this should better be handled by 'ep_diggable' !!!
+#if 1
+ EL_LANDMINE,
+ EL_DC_LANDMINE,
+ EL_TRAP_ACTIVE,
+ EL_SP_BUGGY_BASE_ACTIVE,
+ EL_EMC_PLANT,
+#endif
+
+ -1
+ };
+
+ static int ep_dont_collide_with[] =
+ {
+ // same elements as in 'ep_dont_touch'
+ EL_BUG,
+ EL_SPACESHIP,
+ EL_BD_BUTTERFLY,
+ EL_BD_FIREFLY,
+
+ // new elements
+ EL_YAMYAM,
+ EL_DARK_YAMYAM,
+ EL_ROBOT,
+ EL_PACMAN,
+ EL_SP_SNIKSNAK,
+ EL_SP_ELECTRON,
+
+ -1
+ };
+
+ static int ep_dont_touch[] =
+ {
+ EL_BUG,
+ EL_SPACESHIP,
+ EL_BD_BUTTERFLY,
+ EL_BD_FIREFLY,
+
+ -1
+ };
+
+ static int ep_indestructible[] =
+ {
+ EL_STEELWALL,
+ EL_ACID,
+ EL_ACID_POOL_TOPLEFT,
+ EL_ACID_POOL_TOPRIGHT,
+ EL_ACID_POOL_BOTTOMLEFT,
+ EL_ACID_POOL_BOTTOM,
+ EL_ACID_POOL_BOTTOMRIGHT,
+ EL_SP_HARDWARE_GRAY,
+ EL_SP_HARDWARE_GREEN,
+ EL_SP_HARDWARE_BLUE,
+ EL_SP_HARDWARE_RED,
+ EL_SP_HARDWARE_YELLOW,
+ EL_SP_HARDWARE_BASE_1,
+ EL_SP_HARDWARE_BASE_2,
+ EL_SP_HARDWARE_BASE_3,
+ EL_SP_HARDWARE_BASE_4,
+ EL_SP_HARDWARE_BASE_5,
+ EL_SP_HARDWARE_BASE_6,
+ EL_INVISIBLE_STEELWALL,
+ EL_INVISIBLE_STEELWALL_ACTIVE,
+ EL_CONVEYOR_BELT_1_SWITCH_LEFT,
+ EL_CONVEYOR_BELT_1_SWITCH_MIDDLE,
+ EL_CONVEYOR_BELT_1_SWITCH_RIGHT,
+ EL_CONVEYOR_BELT_2_SWITCH_LEFT,
+ EL_CONVEYOR_BELT_2_SWITCH_MIDDLE,
+ EL_CONVEYOR_BELT_2_SWITCH_RIGHT,
+ EL_CONVEYOR_BELT_3_SWITCH_LEFT,
+ EL_CONVEYOR_BELT_3_SWITCH_MIDDLE,
+ EL_CONVEYOR_BELT_3_SWITCH_RIGHT,
+ EL_CONVEYOR_BELT_4_SWITCH_LEFT,
+ EL_CONVEYOR_BELT_4_SWITCH_MIDDLE,
+ EL_CONVEYOR_BELT_4_SWITCH_RIGHT,
+ EL_LIGHT_SWITCH,
+ EL_LIGHT_SWITCH_ACTIVE,
+ EL_SIGN_EXCLAMATION,