+ // 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,
+ EL_SIGN_RADIOACTIVITY,
+ EL_SIGN_STOP,
+ EL_SIGN_WHEELCHAIR,
+ EL_SIGN_PARKING,
+ EL_SIGN_NO_ENTRY,
+ EL_SIGN_UNUSED_1,
+ EL_SIGN_GIVE_WAY,
+ EL_SIGN_ENTRY_FORBIDDEN,
+ EL_SIGN_EMERGENCY_EXIT,
+ EL_SIGN_YIN_YANG,
+ EL_SIGN_UNUSED_2,
+ EL_SIGN_SPERMS,
+ EL_SIGN_BULLET,
+ EL_SIGN_HEART,
+ EL_SIGN_CROSS,
+ EL_SIGN_FRANKIE,
+ EL_STEEL_EXIT_CLOSED,
+ EL_STEEL_EXIT_OPEN,
+ EL_STEEL_EXIT_OPENING,
+ EL_STEEL_EXIT_CLOSING,
+ EL_EM_STEEL_EXIT_CLOSED,
+ EL_EM_STEEL_EXIT_OPEN,
+ EL_EM_STEEL_EXIT_OPENING,
+ EL_EM_STEEL_EXIT_CLOSING,
+ EL_DC_STEELWALL_1_LEFT,
+ EL_DC_STEELWALL_1_RIGHT,
+ EL_DC_STEELWALL_1_TOP,
+ EL_DC_STEELWALL_1_BOTTOM,
+ EL_DC_STEELWALL_1_HORIZONTAL,
+ EL_DC_STEELWALL_1_VERTICAL,
+ EL_DC_STEELWALL_1_TOPLEFT,
+ EL_DC_STEELWALL_1_TOPRIGHT,
+ EL_DC_STEELWALL_1_BOTTOMLEFT,
+ EL_DC_STEELWALL_1_BOTTOMRIGHT,
+ EL_DC_STEELWALL_1_TOPLEFT_2,
+ EL_DC_STEELWALL_1_TOPRIGHT_2,
+ EL_DC_STEELWALL_1_BOTTOMLEFT_2,
+ EL_DC_STEELWALL_1_BOTTOMRIGHT_2,
+ EL_DC_STEELWALL_2_LEFT,
+ EL_DC_STEELWALL_2_RIGHT,
+ EL_DC_STEELWALL_2_TOP,
+ EL_DC_STEELWALL_2_BOTTOM,
+ EL_DC_STEELWALL_2_HORIZONTAL,
+ EL_DC_STEELWALL_2_VERTICAL,
+ EL_DC_STEELWALL_2_MIDDLE,
+ EL_DC_STEELWALL_2_SINGLE,
+ EL_STEELWALL_SLIPPERY,
+ EL_EMC_STEELWALL_1,
+ EL_EMC_STEELWALL_2,
+ EL_EMC_STEELWALL_3,
+ EL_EMC_STEELWALL_4,
+ EL_CRYSTAL,
+ EL_GATE_1,
+ EL_GATE_2,
+ EL_GATE_3,
+ EL_GATE_4,
+ EL_GATE_1_GRAY,
+ EL_GATE_2_GRAY,
+ EL_GATE_3_GRAY,
+ EL_GATE_4_GRAY,
+ EL_GATE_1_GRAY_ACTIVE,
+ EL_GATE_2_GRAY_ACTIVE,
+ EL_GATE_3_GRAY_ACTIVE,
+ EL_GATE_4_GRAY_ACTIVE,
+ EL_EM_GATE_1,
+ EL_EM_GATE_2,
+ EL_EM_GATE_3,
+ EL_EM_GATE_4,
+ EL_EM_GATE_1_GRAY,
+ EL_EM_GATE_2_GRAY,
+ EL_EM_GATE_3_GRAY,
+ EL_EM_GATE_4_GRAY,
+ EL_EM_GATE_1_GRAY_ACTIVE,
+ EL_EM_GATE_2_GRAY_ACTIVE,
+ EL_EM_GATE_3_GRAY_ACTIVE,
+ EL_EM_GATE_4_GRAY_ACTIVE,
+ EL_EMC_GATE_5,
+ EL_EMC_GATE_6,
+ EL_EMC_GATE_7,
+ EL_EMC_GATE_8,
+ EL_EMC_GATE_5_GRAY,
+ EL_EMC_GATE_6_GRAY,
+ EL_EMC_GATE_7_GRAY,
+ EL_EMC_GATE_8_GRAY,
+ EL_EMC_GATE_5_GRAY_ACTIVE,
+ EL_EMC_GATE_6_GRAY_ACTIVE,
+ EL_EMC_GATE_7_GRAY_ACTIVE,
+ EL_EMC_GATE_8_GRAY_ACTIVE,
+ EL_DC_GATE_WHITE,
+ EL_DC_GATE_WHITE_GRAY,
+ EL_DC_GATE_WHITE_GRAY_ACTIVE,
+ EL_DC_GATE_FAKE_GRAY,
+ EL_SWITCHGATE_OPEN,
+ EL_SWITCHGATE_OPENING,
+ EL_SWITCHGATE_CLOSED,
+ EL_SWITCHGATE_CLOSING,
+ EL_DC_SWITCHGATE_SWITCH_UP,
+ EL_DC_SWITCHGATE_SWITCH_DOWN,
+ EL_TIMEGATE_OPEN,
+ EL_TIMEGATE_OPENING,
+ EL_TIMEGATE_CLOSED,
+ EL_TIMEGATE_CLOSING,
+ EL_DC_TIMEGATE_SWITCH,
+ EL_DC_TIMEGATE_SWITCH_ACTIVE,
+ EL_TUBE_ANY,
+ EL_TUBE_VERTICAL,
+ EL_TUBE_HORIZONTAL,
+ EL_TUBE_VERTICAL_LEFT,
+ EL_TUBE_VERTICAL_RIGHT,
+ EL_TUBE_HORIZONTAL_UP,
+ EL_TUBE_HORIZONTAL_DOWN,
+ EL_TUBE_LEFT_UP,
+ EL_TUBE_LEFT_DOWN,
+ EL_TUBE_RIGHT_UP,
+ EL_TUBE_RIGHT_DOWN,
+ EL_EXPANDABLE_STEELWALL_HORIZONTAL,
+ EL_EXPANDABLE_STEELWALL_VERTICAL,
+ EL_EXPANDABLE_STEELWALL_ANY,
+
+ -1
+ };
+
+ static int ep_slippery[] =
+ {
+ EL_WALL_SLIPPERY,
+ EL_BD_WALL,
+ EL_ROCK,
+ EL_BD_ROCK,
+ EL_EMERALD,
+ EL_BD_DIAMOND,
+ EL_EMERALD_YELLOW,
+ EL_EMERALD_RED,
+ EL_EMERALD_PURPLE,
+ EL_DIAMOND,
+ EL_BOMB,
+ EL_NUT,
+ EL_ROBOT_WHEEL_ACTIVE,
+ EL_ROBOT_WHEEL,
+ EL_TIME_ORB_FULL,
+ EL_TIME_ORB_EMPTY,
+ EL_LAMP_ACTIVE,
+ EL_LAMP,
+ EL_ACID_POOL_TOPLEFT,
+ EL_ACID_POOL_TOPRIGHT,
+ EL_SATELLITE,
+ EL_SP_ZONK,
+ EL_SP_INFOTRON,
+ EL_SP_CHIP_SINGLE,
+ EL_SP_CHIP_LEFT,
+ EL_SP_CHIP_RIGHT,
+ EL_SP_CHIP_TOP,
+ EL_SP_CHIP_BOTTOM,
+ EL_SPEED_PILL,
+ EL_STEELWALL_SLIPPERY,
+ EL_PEARL,
+ EL_CRYSTAL,
+ EL_EMC_WALL_SLIPPERY_1,
+ EL_EMC_WALL_SLIPPERY_2,
+ EL_EMC_WALL_SLIPPERY_3,
+ EL_EMC_WALL_SLIPPERY_4,
+ EL_EMC_MAGIC_BALL,
+ EL_EMC_MAGIC_BALL_ACTIVE,
+
+ -1
+ };
+
+ static int ep_can_change[] =
+ {
+ -1
+ };
+
+ static int ep_can_move[] =
+ {
+ // same elements as in 'pb_can_move_into_acid'
+ EL_BUG,
+ EL_SPACESHIP,
+ EL_BD_BUTTERFLY,
+ EL_BD_FIREFLY,
+ EL_YAMYAM,
+ EL_DARK_YAMYAM,
+ EL_ROBOT,
+ EL_PACMAN,
+ EL_MOLE,
+ EL_PENGUIN,
+ EL_PIG,
+ EL_DRAGON,
+ EL_SATELLITE,
+ EL_SP_SNIKSNAK,
+ EL_SP_ELECTRON,
+ EL_BALLOON,
+ EL_SPRING,
+ EL_EMC_ANDROID,
+
+ -1
+ };
+
+ static int ep_can_fall[] =
+ {
+ EL_ROCK,
+ EL_BD_ROCK,
+ EL_EMERALD,
+ EL_BD_DIAMOND,
+ EL_EMERALD_YELLOW,
+ EL_EMERALD_RED,
+ EL_EMERALD_PURPLE,
+ EL_DIAMOND,
+ EL_BOMB,
+ EL_NUT,
+ EL_AMOEBA_DROP,
+ EL_AMOEBA_DROPPING,
+ EL_QUICKSAND_FULL,
+ EL_QUICKSAND_FAST_FULL,
+ EL_MAGIC_WALL_FULL,
+ EL_BD_MAGIC_WALL_FULL,
+ EL_DC_MAGIC_WALL_FULL,
+ EL_TIME_ORB_FULL,
+ EL_TIME_ORB_EMPTY,
+ EL_SP_ZONK,
+ EL_SP_INFOTRON,
+ EL_SP_DISK_ORANGE,
+ EL_PEARL,
+ EL_CRYSTAL,
+ EL_SPRING,
+ EL_DX_SUPABOMB,
+
+ -1
+ };
+
+ static int ep_can_smash_player[] =
+ {
+ EL_ROCK,
+ EL_BD_ROCK,
+ EL_EMERALD,
+ EL_BD_DIAMOND,
+ EL_EMERALD_YELLOW,
+ EL_EMERALD_RED,
+ EL_EMERALD_PURPLE,
+ EL_DIAMOND,
+ EL_BOMB,
+ EL_NUT,
+ EL_AMOEBA_DROP,
+ EL_TIME_ORB_FULL,
+ EL_TIME_ORB_EMPTY,
+ EL_SP_ZONK,
+ EL_SP_INFOTRON,
+ EL_SP_DISK_ORANGE,
+ EL_PEARL,
+ EL_CRYSTAL,
+ EL_SPRING,
+ EL_DX_SUPABOMB,
+
+ -1
+ };
+
+ static int ep_can_smash_enemies[] =
+ {
+ EL_ROCK,
+ EL_BD_ROCK,
+ EL_SP_ZONK,
+
+ -1
+ };
+
+ static int ep_can_smash_everything[] =
+ {
+ EL_ROCK,
+ EL_BD_ROCK,
+ EL_SP_ZONK,
+
+ -1
+ };
+
+ static int ep_explodes_by_fire[] =
+ {
+ // same elements as in 'ep_explodes_impact'
+ EL_BOMB,
+ EL_SP_DISK_ORANGE,
+ EL_DX_SUPABOMB,
+
+ // same elements as in 'ep_explodes_smashed'
+ EL_SATELLITE,
+ EL_PIG,
+ EL_DRAGON,
+ EL_MOLE,
+
+ // new elements
+ EL_DYNAMITE,
+ EL_DYNAMITE_ACTIVE,
+ EL_EM_DYNAMITE,
+ EL_EM_DYNAMITE_ACTIVE,
+ EL_DYNABOMB_PLAYER_1_ACTIVE,
+ EL_DYNABOMB_PLAYER_2_ACTIVE,
+ EL_DYNABOMB_PLAYER_3_ACTIVE,
+ EL_DYNABOMB_PLAYER_4_ACTIVE,
+ EL_DYNABOMB_INCREASE_NUMBER,
+ EL_DYNABOMB_INCREASE_SIZE,
+ EL_DYNABOMB_INCREASE_POWER,
+ EL_SP_DISK_RED_ACTIVE,
+ EL_BUG,
+ EL_PENGUIN,
+ EL_SP_DISK_RED,
+ EL_SP_DISK_YELLOW,
+ EL_SP_SNIKSNAK,
+ EL_SP_ELECTRON,
+#if 0
+ EL_BLACK_ORB,
+#endif
+
+ -1
+ };
+
+ static int ep_explodes_smashed[] =
+ {
+ // same elements as in 'ep_explodes_impact'
+ EL_BOMB,
+ EL_SP_DISK_ORANGE,
+ EL_DX_SUPABOMB,
+
+ // new elements
+ EL_SATELLITE,
+ EL_PIG,
+ EL_DRAGON,
+ EL_MOLE,
+
+ -1
+ };
+
+ static int ep_explodes_impact[] =
+ {
+ EL_BOMB,
+ EL_SP_DISK_ORANGE,
+ EL_DX_SUPABOMB,
+
+ -1
+ };
+
+ static int ep_walkable_over[] =
+ {
+ 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,
+ EL_SP_EMPTY_SPACE,
+ EL_SOKOBAN_FIELD_EMPTY,
+ EL_EXIT_OPEN,
+ EL_EM_EXIT_OPEN,
+ EL_EM_EXIT_OPENING,
+ EL_SP_EXIT_OPEN,
+ EL_SP_EXIT_OPENING,
+ EL_STEEL_EXIT_OPEN,
+ EL_EM_STEEL_EXIT_OPEN,
+ EL_EM_STEEL_EXIT_OPENING,
+ EL_GATE_1,
+ EL_GATE_2,
+ EL_GATE_3,
+ EL_GATE_4,
+ EL_GATE_1_GRAY,
+ EL_GATE_2_GRAY,
+ EL_GATE_3_GRAY,
+ EL_GATE_4_GRAY,
+ EL_GATE_1_GRAY_ACTIVE,
+ EL_GATE_2_GRAY_ACTIVE,
+ EL_GATE_3_GRAY_ACTIVE,
+ EL_GATE_4_GRAY_ACTIVE,
+ EL_PENGUIN,
+ EL_PIG,
+ EL_DRAGON,
+
+ -1
+ };
+
+ static int ep_walkable_inside[] =
+ {
+ EL_TUBE_ANY,
+ EL_TUBE_VERTICAL,
+ EL_TUBE_HORIZONTAL,
+ EL_TUBE_VERTICAL_LEFT,
+ EL_TUBE_VERTICAL_RIGHT,
+ EL_TUBE_HORIZONTAL_UP,
+ EL_TUBE_HORIZONTAL_DOWN,
+ EL_TUBE_LEFT_UP,
+ EL_TUBE_LEFT_DOWN,
+ EL_TUBE_RIGHT_UP,
+ EL_TUBE_RIGHT_DOWN,
+
+ -1
+ };
+
+ static int ep_walkable_under[] =
+ {
+ -1
+ };
+
+ static int ep_passable_over[] =
+ {
+ EL_EM_GATE_1,
+ EL_EM_GATE_2,
+ EL_EM_GATE_3,
+ EL_EM_GATE_4,
+ EL_EM_GATE_1_GRAY,
+ EL_EM_GATE_2_GRAY,
+ EL_EM_GATE_3_GRAY,
+ EL_EM_GATE_4_GRAY,
+ EL_EM_GATE_1_GRAY_ACTIVE,
+ EL_EM_GATE_2_GRAY_ACTIVE,
+ EL_EM_GATE_3_GRAY_ACTIVE,
+ EL_EM_GATE_4_GRAY_ACTIVE,
+ EL_EMC_GATE_5,
+ EL_EMC_GATE_6,
+ EL_EMC_GATE_7,
+ EL_EMC_GATE_8,
+ EL_EMC_GATE_5_GRAY,
+ EL_EMC_GATE_6_GRAY,
+ EL_EMC_GATE_7_GRAY,
+ EL_EMC_GATE_8_GRAY,
+ EL_EMC_GATE_5_GRAY_ACTIVE,
+ EL_EMC_GATE_6_GRAY_ACTIVE,
+ EL_EMC_GATE_7_GRAY_ACTIVE,
+ EL_EMC_GATE_8_GRAY_ACTIVE,
+ EL_DC_GATE_WHITE,
+ EL_DC_GATE_WHITE_GRAY,
+ EL_DC_GATE_WHITE_GRAY_ACTIVE,
+ EL_SWITCHGATE_OPEN,
+ EL_TIMEGATE_OPEN,
+
+ -1
+ };
+
+ static int ep_passable_inside[] =
+ {
+ EL_SP_PORT_LEFT,
+ EL_SP_PORT_RIGHT,
+ EL_SP_PORT_UP,
+ EL_SP_PORT_DOWN,
+ EL_SP_PORT_HORIZONTAL,
+ EL_SP_PORT_VERTICAL,
+ EL_SP_PORT_ANY,
+ EL_SP_GRAVITY_PORT_LEFT,
+ EL_SP_GRAVITY_PORT_RIGHT,
+ EL_SP_GRAVITY_PORT_UP,
+ EL_SP_GRAVITY_PORT_DOWN,
+ EL_SP_GRAVITY_ON_PORT_LEFT,
+ EL_SP_GRAVITY_ON_PORT_RIGHT,
+ EL_SP_GRAVITY_ON_PORT_UP,
+ EL_SP_GRAVITY_ON_PORT_DOWN,
+ EL_SP_GRAVITY_OFF_PORT_LEFT,
+ EL_SP_GRAVITY_OFF_PORT_RIGHT,
+ EL_SP_GRAVITY_OFF_PORT_UP,
+ EL_SP_GRAVITY_OFF_PORT_DOWN,
+
+ -1
+ };
+
+ static int ep_passable_under[] =
+ {
+ -1
+ };
+
+ static int ep_droppable[] =
+ {
+ -1
+ };
+
+ static int ep_explodes_1x1_old[] =
+ {
+ -1
+ };
+
+ static int ep_pushable[] =
+ {
+ EL_ROCK,
+ EL_BOMB,
+ EL_DX_SUPABOMB,
+ EL_NUT,
+ EL_TIME_ORB_EMPTY,
+ EL_SP_ZONK,
+ EL_SP_DISK_ORANGE,
+ EL_SPRING,
+ EL_BD_ROCK,
+ EL_SOKOBAN_OBJECT,
+ EL_SOKOBAN_FIELD_FULL,
+ EL_SATELLITE,
+ EL_SP_DISK_YELLOW,
+ EL_BALLOON,
+ EL_EMC_ANDROID,
+
+ -1
+ };
+
+ static int ep_explodes_cross_old[] =
+ {
+ -1
+ };
+
+ static int ep_protected[] =
+ {
+ // same elements as in 'ep_walkable_inside'
+ EL_TUBE_ANY,
+ EL_TUBE_VERTICAL,
+ EL_TUBE_HORIZONTAL,
+ EL_TUBE_VERTICAL_LEFT,
+ EL_TUBE_VERTICAL_RIGHT,
+ EL_TUBE_HORIZONTAL_UP,
+ EL_TUBE_HORIZONTAL_DOWN,
+ EL_TUBE_LEFT_UP,
+ EL_TUBE_LEFT_DOWN,
+ EL_TUBE_RIGHT_UP,
+ EL_TUBE_RIGHT_DOWN,
+
+ // same elements as in 'ep_passable_over'
+ EL_EM_GATE_1,
+ EL_EM_GATE_2,
+ EL_EM_GATE_3,
+ EL_EM_GATE_4,
+ EL_EM_GATE_1_GRAY,
+ EL_EM_GATE_2_GRAY,
+ EL_EM_GATE_3_GRAY,
+ EL_EM_GATE_4_GRAY,
+ EL_EM_GATE_1_GRAY_ACTIVE,
+ EL_EM_GATE_2_GRAY_ACTIVE,
+ EL_EM_GATE_3_GRAY_ACTIVE,
+ EL_EM_GATE_4_GRAY_ACTIVE,
+ EL_EMC_GATE_5,
+ EL_EMC_GATE_6,
+ EL_EMC_GATE_7,
+ EL_EMC_GATE_8,
+ EL_EMC_GATE_5_GRAY,
+ EL_EMC_GATE_6_GRAY,
+ EL_EMC_GATE_7_GRAY,
+ EL_EMC_GATE_8_GRAY,
+ EL_EMC_GATE_5_GRAY_ACTIVE,
+ EL_EMC_GATE_6_GRAY_ACTIVE,
+ EL_EMC_GATE_7_GRAY_ACTIVE,
+ EL_EMC_GATE_8_GRAY_ACTIVE,
+ EL_DC_GATE_WHITE,
+ EL_DC_GATE_WHITE_GRAY,
+ EL_DC_GATE_WHITE_GRAY_ACTIVE,
+ EL_SWITCHGATE_OPEN,
+ EL_TIMEGATE_OPEN,
+
+ // same elements as in 'ep_passable_inside'
+ EL_SP_PORT_LEFT,
+ EL_SP_PORT_RIGHT,
+ EL_SP_PORT_UP,
+ EL_SP_PORT_DOWN,
+ EL_SP_PORT_HORIZONTAL,
+ EL_SP_PORT_VERTICAL,
+ EL_SP_PORT_ANY,
+ EL_SP_GRAVITY_PORT_LEFT,
+ EL_SP_GRAVITY_PORT_RIGHT,
+ EL_SP_GRAVITY_PORT_UP,
+ EL_SP_GRAVITY_PORT_DOWN,
+ EL_SP_GRAVITY_ON_PORT_LEFT,
+ EL_SP_GRAVITY_ON_PORT_RIGHT,
+ EL_SP_GRAVITY_ON_PORT_UP,
+ EL_SP_GRAVITY_ON_PORT_DOWN,
+ EL_SP_GRAVITY_OFF_PORT_LEFT,
+ EL_SP_GRAVITY_OFF_PORT_RIGHT,
+ EL_SP_GRAVITY_OFF_PORT_UP,
+ EL_SP_GRAVITY_OFF_PORT_DOWN,
+
+ -1
+ };
+
+ static int ep_throwable[] =
+ {
+ -1
+ };
+
+ static int ep_can_explode[] =
+ {
+ // same elements as in 'ep_explodes_impact'
+ EL_BOMB,
+ EL_SP_DISK_ORANGE,
+ EL_DX_SUPABOMB,
+
+ // same elements as in 'ep_explodes_smashed'
+ EL_SATELLITE,
+ EL_PIG,
+ EL_DRAGON,
+ EL_MOLE,
+
+ // elements that can explode by explosion or by dragonfire
+ EL_DYNAMITE,
+ EL_DYNAMITE_ACTIVE,
+ EL_EM_DYNAMITE,
+ EL_EM_DYNAMITE_ACTIVE,
+ EL_DYNABOMB_PLAYER_1_ACTIVE,
+ EL_DYNABOMB_PLAYER_2_ACTIVE,
+ EL_DYNABOMB_PLAYER_3_ACTIVE,
+ EL_DYNABOMB_PLAYER_4_ACTIVE,
+ EL_DYNABOMB_INCREASE_NUMBER,
+ EL_DYNABOMB_INCREASE_SIZE,
+ EL_DYNABOMB_INCREASE_POWER,
+ EL_SP_DISK_RED_ACTIVE,
+ EL_BUG,
+ EL_PENGUIN,
+ EL_SP_DISK_RED,
+ EL_SP_DISK_YELLOW,
+ EL_SP_SNIKSNAK,
+ EL_SP_ELECTRON,
+
+ // elements that can explode only by explosion
+ EL_BLACK_ORB,
+
+ -1
+ };
+
+ static int ep_gravity_reachable[] =
+ {
+ EL_SAND,
+ EL_SP_BASE,
+ EL_TRAP,
+ EL_INVISIBLE_SAND,
+ EL_INVISIBLE_SAND_ACTIVE,
+ EL_SP_PORT_LEFT,
+ EL_SP_PORT_RIGHT,
+ EL_SP_PORT_UP,
+ EL_SP_PORT_DOWN,
+ EL_SP_PORT_HORIZONTAL,
+ EL_SP_PORT_VERTICAL,
+ EL_SP_PORT_ANY,
+ EL_SP_GRAVITY_PORT_LEFT,
+ EL_SP_GRAVITY_PORT_RIGHT,
+ EL_SP_GRAVITY_PORT_UP,
+ EL_SP_GRAVITY_PORT_DOWN,
+ EL_SP_GRAVITY_ON_PORT_LEFT,
+ EL_SP_GRAVITY_ON_PORT_RIGHT,
+ EL_SP_GRAVITY_ON_PORT_UP,
+ EL_SP_GRAVITY_ON_PORT_DOWN,
+ EL_SP_GRAVITY_OFF_PORT_LEFT,
+ EL_SP_GRAVITY_OFF_PORT_RIGHT,
+ EL_SP_GRAVITY_OFF_PORT_UP,
+ EL_SP_GRAVITY_OFF_PORT_DOWN,
+ EL_EMC_GRASS,
+
+ -1
+ };
+
+ static int ep_empty_space[] =
+ {
+ 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,
+
+ -1
+ };
+
+ static int ep_player[] =
+ {
+ EL_PLAYER_1,
+ EL_PLAYER_2,
+ EL_PLAYER_3,
+ EL_PLAYER_4,
+ EL_SP_MURPHY,
+ EL_SOKOBAN_FIELD_PLAYER,
+ EL_TRIGGER_PLAYER,
+
+ -1
+ };
+
+ static int ep_can_pass_magic_wall[] =
+ {
+ EL_ROCK,
+ EL_BD_ROCK,
+ EL_EMERALD,
+ EL_BD_DIAMOND,
+ EL_EMERALD_YELLOW,
+ EL_EMERALD_RED,
+ EL_EMERALD_PURPLE,
+ EL_DIAMOND,
+
+ -1
+ };
+
+ static int ep_can_pass_dc_magic_wall[] =
+ {
+ EL_ROCK,
+ EL_BD_ROCK,
+ EL_EMERALD,
+ EL_BD_DIAMOND,
+ EL_EMERALD_YELLOW,
+ EL_EMERALD_RED,
+ EL_EMERALD_PURPLE,
+ EL_DIAMOND,
+ EL_PEARL,
+ EL_CRYSTAL,
+
+ -1
+ };
+
+ static int ep_switchable[] =
+ {
+ EL_ROBOT_WHEEL,
+ EL_SP_TERMINAL,
+ 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_SWITCHGATE_SWITCH_UP,
+ EL_SWITCHGATE_SWITCH_DOWN,
+ EL_DC_SWITCHGATE_SWITCH_UP,
+ EL_DC_SWITCHGATE_SWITCH_DOWN,
+ EL_LIGHT_SWITCH,
+ EL_LIGHT_SWITCH_ACTIVE,
+ EL_TIMEGATE_SWITCH,
+ EL_DC_TIMEGATE_SWITCH,
+ EL_BALLOON_SWITCH_LEFT,
+ EL_BALLOON_SWITCH_RIGHT,
+ EL_BALLOON_SWITCH_UP,
+ EL_BALLOON_SWITCH_DOWN,
+ EL_BALLOON_SWITCH_ANY,
+ EL_BALLOON_SWITCH_NONE,
+ EL_LAMP,
+ EL_TIME_ORB_FULL,
+ EL_EMC_MAGIC_BALL_SWITCH,
+ EL_EMC_MAGIC_BALL_SWITCH_ACTIVE,
+
+ -1
+ };
+
+ static int ep_bd_element[] =
+ {
+ EL_EMPTY,
+ EL_SAND,
+ EL_WALL_SLIPPERY,
+ EL_BD_WALL,
+ EL_ROCK,
+ EL_BD_ROCK,
+ EL_BD_DIAMOND,
+ EL_BD_MAGIC_WALL,
+ EL_EXIT_CLOSED,
+ EL_EXIT_OPEN,
+ EL_STEELWALL,
+ EL_PLAYER_1,
+ EL_PLAYER_2,
+ EL_PLAYER_3,
+ EL_PLAYER_4,
+ EL_BD_FIREFLY,
+ EL_BD_FIREFLY_1,
+ EL_BD_FIREFLY_2,
+ EL_BD_FIREFLY_3,
+ EL_BD_FIREFLY_4,
+ EL_BD_BUTTERFLY,
+ EL_BD_BUTTERFLY_1,
+ EL_BD_BUTTERFLY_2,
+ EL_BD_BUTTERFLY_3,
+ EL_BD_BUTTERFLY_4,
+ EL_BD_AMOEBA,
+ EL_CHAR_QUESTION,
+ EL_UNKNOWN,
+
+ -1
+ };
+
+ static int ep_sp_element[] =
+ {
+ // should always be valid
+ EL_EMPTY,
+
+ // standard classic Supaplex elements
+ EL_SP_EMPTY,
+ EL_SP_ZONK,
+ EL_SP_BASE,
+ EL_SP_MURPHY,
+ EL_SP_INFOTRON,
+ EL_SP_CHIP_SINGLE,
+ EL_SP_HARDWARE_GRAY,
+ EL_SP_EXIT_CLOSED,
+ EL_SP_EXIT_OPEN,
+ EL_SP_DISK_ORANGE,
+ EL_SP_PORT_RIGHT,
+ EL_SP_PORT_DOWN,
+ EL_SP_PORT_LEFT,
+ EL_SP_PORT_UP,
+ EL_SP_GRAVITY_PORT_RIGHT,
+ EL_SP_GRAVITY_PORT_DOWN,
+ EL_SP_GRAVITY_PORT_LEFT,
+ EL_SP_GRAVITY_PORT_UP,
+ EL_SP_SNIKSNAK,
+ EL_SP_DISK_YELLOW,
+ EL_SP_TERMINAL,
+ EL_SP_DISK_RED,
+ EL_SP_PORT_VERTICAL,
+ EL_SP_PORT_HORIZONTAL,
+ EL_SP_PORT_ANY,
+ EL_SP_ELECTRON,
+ EL_SP_BUGGY_BASE,
+ EL_SP_CHIP_LEFT,
+ EL_SP_CHIP_RIGHT,
+ EL_SP_HARDWARE_BASE_1,
+ EL_SP_HARDWARE_GREEN,
+ EL_SP_HARDWARE_BLUE,
+ EL_SP_HARDWARE_RED,
+ EL_SP_HARDWARE_YELLOW,
+ 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_SP_CHIP_TOP,
+ EL_SP_CHIP_BOTTOM,
+
+ // additional elements that appeared in newer Supaplex levels
+ EL_INVISIBLE_WALL,
+
+ // additional gravity port elements (not switching, but setting gravity)
+ EL_SP_GRAVITY_ON_PORT_LEFT,
+ EL_SP_GRAVITY_ON_PORT_RIGHT,
+ EL_SP_GRAVITY_ON_PORT_UP,
+ EL_SP_GRAVITY_ON_PORT_DOWN,
+ EL_SP_GRAVITY_OFF_PORT_LEFT,
+ EL_SP_GRAVITY_OFF_PORT_RIGHT,
+ EL_SP_GRAVITY_OFF_PORT_UP,
+ EL_SP_GRAVITY_OFF_PORT_DOWN,
+
+ // more than one Murphy in a level results in an inactive clone
+ EL_SP_MURPHY_CLONE,
+
+ // runtime Supaplex elements
+ EL_SP_DISK_RED_ACTIVE,
+ EL_SP_TERMINAL_ACTIVE,
+ EL_SP_BUGGY_BASE_ACTIVATING,
+ EL_SP_BUGGY_BASE_ACTIVE,
+ EL_SP_EXIT_OPENING,
+ EL_SP_EXIT_CLOSING,
+
+ -1