+ return getAnimationFrame(graphic_info[graphic].anim_frames,
+ graphic_info[graphic].anim_delay,
+ graphic_info[graphic].anim_mode,
+ graphic_info[graphic].anim_start_frame,
+ sync_frame);
+}
+
+int getGraphicAnimationFrameXY(int graphic, int lx, int ly)
+{
+ if (graphic_info[graphic].anim_mode & ANIM_TILED)
+ {
+ struct GraphicInfo *g = &graphic_info[graphic];
+ int xsize = MAX(1, g->anim_frames_per_line);
+ int ysize = MAX(1, g->anim_frames / xsize);
+ int xoffset = g->anim_start_frame % xsize;
+ int yoffset = g->anim_start_frame % ysize;
+ // may be needed if screen field is significantly larger than playfield
+ int x = (lx + xoffset + SCR_FIELDX * xsize) % xsize;
+ int y = (ly + yoffset + SCR_FIELDY * ysize) % ysize;
+ int sync_frame = y * xsize + x;
+
+ return sync_frame % g->anim_frames;
+ }
+ else if (graphic_info[graphic].anim_mode & ANIM_RANDOM_STATIC)
+ {
+ struct GraphicInfo *g = &graphic_info[graphic];
+ // may be needed if screen field is significantly larger than playfield
+ int x = (lx + SCR_FIELDX * lev_fieldx) % lev_fieldx;
+ int y = (ly + SCR_FIELDY * lev_fieldy) % lev_fieldy;
+ int sync_frame = GfxRandomStatic[x][y];
+
+ return sync_frame % g->anim_frames;
+ }
+ else
+ {
+ int sync_frame = (IN_LEV_FIELD(lx, ly) ? GfxFrame[lx][ly] : -1);
+
+ return getGraphicAnimationFrame(graphic, sync_frame);
+ }
+}
+
+void getGraphicSourceBitmap(int graphic, int tilesize, Bitmap **bitmap)
+{
+ struct GraphicInfo *g = &graphic_info[graphic];
+ int tilesize_capped = MIN(MAX(1, tilesize), TILESIZE);
+
+ if (tilesize == gfx.standard_tile_size)
+ *bitmap = g->bitmaps[IMG_BITMAP_STANDARD];
+ else if (tilesize == game.tile_size)
+ *bitmap = g->bitmaps[IMG_BITMAP_PTR_GAME];
+ else
+ *bitmap = g->bitmaps[IMG_BITMAP_1x1 - log_2(tilesize_capped)];
+}
+
+void getGraphicSourceXY(int graphic, int frame, int *x, int *y,
+ boolean get_backside)
+{
+ struct GraphicInfo *g = &graphic_info[graphic];
+ int src_x = g->src_x + (get_backside ? g->offset2_x : 0);
+ int src_y = g->src_y + (get_backside ? g->offset2_y : 0);
+
+ if (g->offset_y == 0) // frames are ordered horizontally
+ {
+ int max_width = g->anim_frames_per_line * g->width;
+ int pos = (src_y / g->height) * max_width + src_x + frame * g->offset_x;
+
+ *x = pos % max_width;
+ *y = src_y % g->height + pos / max_width * g->height;
+ }
+ else if (g->offset_x == 0) // frames are ordered vertically
+ {
+ int max_height = g->anim_frames_per_line * g->height;
+ int pos = (src_x / g->width) * max_height + src_y + frame * g->offset_y;
+
+ *x = src_x % g->width + pos / max_height * g->width;
+ *y = pos % max_height;
+ }
+ else // frames are ordered diagonally
+ {
+ *x = src_x + frame * g->offset_x;
+ *y = src_y + frame * g->offset_y;
+ }
+}
+
+void getSizedGraphicSourceExt(int graphic, int frame, int tilesize,
+ Bitmap **bitmap, int *x, int *y,
+ boolean get_backside)
+{
+ struct GraphicInfo *g = &graphic_info[graphic];
+
+ // if no graphics defined at all, use fallback graphics
+ if (g->bitmaps == NULL)
+ *g = graphic_info[IMG_CHAR_EXCLAM];
+
+ // if no in-game graphics defined, always use standard graphic size
+ if (g->bitmaps[IMG_BITMAP_PTR_GAME] == NULL)
+ tilesize = TILESIZE;
+
+ getGraphicSourceBitmap(graphic, tilesize, bitmap);
+ getGraphicSourceXY(graphic, frame, x, y, get_backside);
+
+ *x = *x * tilesize / g->tile_size;
+ *y = *y * tilesize / g->tile_size;
+}
+
+void getSizedGraphicSource(int graphic, int frame, int tilesize,
+ Bitmap **bitmap, int *x, int *y)
+{
+ getSizedGraphicSourceExt(graphic, frame, tilesize, bitmap, x, y, FALSE);
+}
+
+void getFixedGraphicSource(int graphic, int frame,
+ Bitmap **bitmap, int *x, int *y)
+{
+ getSizedGraphicSourceExt(graphic, frame, TILESIZE, bitmap, x, y, FALSE);
+}
+
+void getMiniGraphicSource(int graphic, Bitmap **bitmap, int *x, int *y)
+{
+ getSizedGraphicSource(graphic, 0, MINI_TILESIZE, bitmap, x, y);
+}
+
+void getGlobalAnimGraphicSource(int graphic, int frame,
+ Bitmap **bitmap, int *x, int *y)
+{
+ struct GraphicInfo *g = &graphic_info[graphic];
+
+ // if no graphics defined at all, use fallback graphics
+ if (g->bitmaps == NULL)
+ *g = graphic_info[IMG_CHAR_EXCLAM];
+
+ // use original size graphics, if existing, else use standard size graphics
+ if (g->bitmaps[IMG_BITMAP_PTR_ORIGINAL])
+ *bitmap = g->bitmaps[IMG_BITMAP_PTR_ORIGINAL];
+ else
+ *bitmap = g->bitmaps[IMG_BITMAP_STANDARD];
+
+ getGraphicSourceXY(graphic, frame, x, y, FALSE);
+}
+
+static void getGraphicSourceExt(int graphic, int frame, Bitmap **bitmap,
+ int *x, int *y, boolean get_backside)
+{
+ getSizedGraphicSourceExt(graphic, frame, TILESIZE_VAR, bitmap, x, y,
+ get_backside);
+}
+
+void getGraphicSource(int graphic, int frame, Bitmap **bitmap, int *x, int *y)
+{
+ getGraphicSourceExt(graphic, frame, bitmap, x, y, FALSE);
+}
+
+void DrawGraphic(int x, int y, int graphic, int frame)
+{
+#if DEBUG
+ if (!IN_SCR_FIELD(x, y))
+ {
+ Debug("draw:DrawGraphic", "x = %d, y = %d, graphic = %d", x, y, graphic);
+ Debug("draw:DrawGraphic", "This should never happen!");
+
+ return;
+ }
+#endif
+
+ DrawGraphicExt(drawto_field, FX + x * TILEX_VAR, FY + y * TILEY_VAR, graphic,
+ frame);
+
+ MarkTileDirty(x, y);
+}
+
+void DrawFixedGraphic(int x, int y, int graphic, int frame)
+{
+#if DEBUG
+ if (!IN_SCR_FIELD(x, y))
+ {
+ Debug("draw:DrawFixedGraphic", "x = %d, y = %d, graphic = %d",
+ x, y, graphic);
+ Debug("draw:DrawFixedGraphic", "This should never happen!");
+
+ return;
+ }
+#endif
+
+ DrawFixedGraphicExt(drawto_field, FX + x * TILEX, FY + y * TILEY, graphic,
+ frame);
+ MarkTileDirty(x, y);
+}
+
+void DrawGraphicExt(DrawBuffer *dst_bitmap, int x, int y, int graphic,
+ int frame)
+{
+ Bitmap *src_bitmap;
+ int src_x, src_y;
+
+ getGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
+
+ BlitBitmap(src_bitmap, dst_bitmap, src_x, src_y, TILEX_VAR, TILEY_VAR, x, y);
+}
+
+void DrawFixedGraphicExt(DrawBuffer *dst_bitmap, int x, int y, int graphic,
+ int frame)
+{
+ Bitmap *src_bitmap;
+ int src_x, src_y;
+
+ getFixedGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
+ BlitBitmap(src_bitmap, dst_bitmap, src_x, src_y, TILEX, TILEY, x, y);
+}
+
+void DrawGraphicThruMask(int x, int y, int graphic, int frame)
+{
+#if DEBUG
+ if (!IN_SCR_FIELD(x, y))
+ {
+ Debug("draw:DrawGraphicThruMask", "x = %d, y = %d, graphic = %d",
+ x, y, graphic);
+ Debug("draw:DrawGraphicThruMask", "This should never happen!");
+
+ return;
+ }
+#endif
+
+ DrawGraphicThruMaskExt(drawto_field, FX + x * TILEX_VAR, FY + y * TILEY_VAR,
+ graphic, frame);
+
+ MarkTileDirty(x, y);
+}
+
+void DrawFixedGraphicThruMask(int x, int y, int graphic, int frame)
+{
+#if DEBUG
+ if (!IN_SCR_FIELD(x, y))
+ {
+ Debug("draw:DrawFixedGraphicThruMask", "x = %d, y = %d, graphic = %d",
+ x, y, graphic);
+ Debug("draw:DrawFixedGraphicThruMask", "This should never happen!");
+
+ return;
+ }
+#endif
+
+ DrawFixedGraphicThruMaskExt(drawto_field, FX + x * TILEX, FY + y * TILEY,
+ graphic, frame);
+ MarkTileDirty(x, y);
+}
+
+void DrawGraphicThruMaskExt(DrawBuffer *d, int dst_x, int dst_y, int graphic,
+ int frame)
+{
+ Bitmap *src_bitmap;
+ int src_x, src_y;
+
+ getGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
+
+ BlitBitmapMasked(src_bitmap, d, src_x, src_y, TILEX_VAR, TILEY_VAR,
+ dst_x, dst_y);
+}
+
+void DrawFixedGraphicThruMaskExt(DrawBuffer *d, int dst_x, int dst_y,
+ int graphic, int frame)
+{
+ Bitmap *src_bitmap;
+ int src_x, src_y;
+
+ getFixedGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
+
+ BlitBitmapMasked(src_bitmap, d, src_x, src_y, TILEX, TILEY,
+ dst_x, dst_y);
+}
+
+void DrawSizedGraphic(int x, int y, int graphic, int frame, int tilesize)
+{
+ DrawSizedGraphicExt(drawto, SX + x * tilesize, SY + y * tilesize, graphic,
+ frame, tilesize);
+ MarkTileDirty(x / tilesize, y / tilesize);
+}
+
+void DrawSizedGraphicThruMask(int x, int y, int graphic, int frame,
+ int tilesize)
+{
+ DrawSizedGraphicThruMaskExt(drawto, SX + x * tilesize, SY + y * tilesize,
+ graphic, frame, tilesize);
+ MarkTileDirty(x / tilesize, y / tilesize);
+}
+
+void DrawSizedGraphicExt(DrawBuffer *d, int x, int y, int graphic, int frame,
+ int tilesize)
+{
+ Bitmap *src_bitmap;
+ int src_x, src_y;
+
+ getSizedGraphicSource(graphic, frame, tilesize, &src_bitmap, &src_x, &src_y);
+ BlitBitmap(src_bitmap, d, src_x, src_y, tilesize, tilesize, x, y);
+}
+
+void DrawSizedGraphicThruMaskExt(DrawBuffer *d, int x, int y, int graphic,
+ int frame, int tilesize)
+{
+ Bitmap *src_bitmap;
+ int src_x, src_y;
+
+ getSizedGraphicSource(graphic, frame, tilesize, &src_bitmap, &src_x, &src_y);
+ BlitBitmapMasked(src_bitmap, d, src_x, src_y, tilesize, tilesize, x, y);
+}
+
+void DrawMiniGraphic(int x, int y, int graphic)
+{
+ DrawMiniGraphicExt(drawto, SX + x * MINI_TILEX, SY + y * MINI_TILEY, graphic);
+ MarkTileDirty(x / 2, y / 2);
+}
+
+void DrawMiniGraphicExt(DrawBuffer *d, int x, int y, int graphic)
+{
+ Bitmap *src_bitmap;
+ int src_x, src_y;
+
+ getMiniGraphicSource(graphic, &src_bitmap, &src_x, &src_y);
+ BlitBitmap(src_bitmap, d, src_x, src_y, MINI_TILEX, MINI_TILEY, x, y);
+}
+
+static void DrawGraphicShiftedNormal(int x, int y, int dx, int dy,
+ int graphic, int frame,
+ int cut_mode, int mask_mode)
+{
+ Bitmap *src_bitmap;
+ int src_x, src_y;
+ int dst_x, dst_y;
+ int width = TILEX, height = TILEY;
+ int cx = 0, cy = 0;
+
+ if (dx || dy) // shifted graphic
+ {
+ if (x < BX1) // object enters playfield from the left
+ {
+ x = BX1;
+ width = dx;
+ cx = TILEX - dx;
+ dx = 0;
+ }
+ else if (x > BX2) // object enters playfield from the right
+ {
+ x = BX2;
+ width = -dx;
+ dx = TILEX + dx;
+ }
+ else if (x == BX1 && dx < 0) // object leaves playfield to the left
+ {
+ width += dx;
+ cx = -dx;
+ dx = 0;
+ }
+ else if (x == BX2 && dx > 0) // object leaves playfield to the right
+ width -= dx;
+ else if (dx) // general horizontal movement
+ MarkTileDirty(x + SIGN(dx), y);
+
+ if (y < BY1) // object enters playfield from the top
+ {
+ if (cut_mode == CUT_BELOW) // object completely above top border
+ return;
+
+ y = BY1;
+ height = dy;
+ cy = TILEY - dy;
+ dy = 0;
+ }
+ else if (y > BY2) // object enters playfield from the bottom
+ {
+ y = BY2;
+ height = -dy;
+ dy = TILEY + dy;
+ }
+ else if (y == BY1 && dy < 0) // object leaves playfield to the top
+ {
+ height += dy;
+ cy = -dy;
+ dy = 0;
+ }
+ else if (dy > 0 && cut_mode == CUT_ABOVE)
+ {
+ if (y == BY2) // object completely above bottom border
+ return;
+
+ height = dy;
+ cy = TILEY - dy;
+ dy = TILEY;
+ MarkTileDirty(x, y + 1);
+ } // object leaves playfield to the bottom
+ else if (dy > 0 && (y == BY2 || cut_mode == CUT_BELOW))
+ height -= dy;
+ else if (dy) // general vertical movement
+ MarkTileDirty(x, y + SIGN(dy));
+ }
+
+#if DEBUG
+ if (!IN_SCR_FIELD(x, y))
+ {
+ Debug("draw:DrawGraphicShiftedNormal", "x = %d, y = %d, graphic = %d",
+ x, y, graphic);
+ Debug("draw:DrawGraphicShiftedNormal", "This should never happen!");
+
+ return;
+ }
+#endif
+
+ width = width * TILESIZE_VAR / TILESIZE;
+ height = height * TILESIZE_VAR / TILESIZE;
+ cx = cx * TILESIZE_VAR / TILESIZE;
+ cy = cy * TILESIZE_VAR / TILESIZE;
+ dx = dx * TILESIZE_VAR / TILESIZE;
+ dy = dy * TILESIZE_VAR / TILESIZE;
+
+ if (width > 0 && height > 0)
+ {
+ getGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
+
+ src_x += cx;
+ src_y += cy;
+
+ dst_x = FX + x * TILEX_VAR + dx;
+ dst_y = FY + y * TILEY_VAR + dy;
+
+ if (mask_mode == USE_MASKING)
+ BlitBitmapMasked(src_bitmap, drawto_field, src_x, src_y, width, height,
+ dst_x, dst_y);
+ else
+ BlitBitmap(src_bitmap, drawto_field, src_x, src_y, width, height,
+ dst_x, dst_y);
+
+ MarkTileDirty(x, y);
+ }
+}
+
+static void DrawGraphicShiftedDouble(int x, int y, int dx, int dy,
+ int graphic, int frame,
+ int cut_mode, int mask_mode)
+{
+ Bitmap *src_bitmap;
+ int src_x, src_y;
+ int dst_x, dst_y;
+ int width = TILEX_VAR, height = TILEY_VAR;
+ int x1 = x;
+ int y1 = y;
+ int x2 = x + SIGN(dx);
+ int y2 = y + SIGN(dy);
+
+ // movement with two-tile animations must be sync'ed with movement position,
+ // not with current GfxFrame (which can be higher when using slow movement)
+ int anim_pos = (dx ? ABS(dx) : ABS(dy));
+ int anim_frames = graphic_info[graphic].anim_frames;
+
+ // (we also need anim_delay here for movement animations with less frames)
+ int anim_delay = graphic_info[graphic].anim_delay;
+ int sync_frame = anim_pos * anim_frames * anim_delay / TILESIZE;
+
+ boolean draw_start_tile = (cut_mode != CUT_ABOVE); // only for falling!
+ boolean draw_end_tile = (cut_mode != CUT_BELOW); // only for falling!
+
+ // re-calculate animation frame for two-tile movement animation
+ frame = getGraphicAnimationFrame(graphic, sync_frame);
+
+ // check if movement start graphic inside screen area and should be drawn
+ if (draw_start_tile && IN_SCR_FIELD(x1, y1))
+ {
+ getGraphicSourceExt(graphic, frame, &src_bitmap, &src_x, &src_y, TRUE);
+
+ dst_x = FX + x1 * TILEX_VAR;
+ dst_y = FY + y1 * TILEY_VAR;
+
+ if (mask_mode == USE_MASKING)
+ BlitBitmapMasked(src_bitmap, drawto_field, src_x, src_y, width, height,
+ dst_x, dst_y);
+ else
+ BlitBitmap(src_bitmap, drawto_field, src_x, src_y, width, height,
+ dst_x, dst_y);
+
+ MarkTileDirty(x1, y1);
+ }
+
+ // check if movement end graphic inside screen area and should be drawn
+ if (draw_end_tile && IN_SCR_FIELD(x2, y2))
+ {
+ getGraphicSourceExt(graphic, frame, &src_bitmap, &src_x, &src_y, FALSE);
+
+ dst_x = FX + x2 * TILEX_VAR;
+ dst_y = FY + y2 * TILEY_VAR;
+
+ if (mask_mode == USE_MASKING)
+ BlitBitmapMasked(src_bitmap, drawto_field, src_x, src_y, width, height,
+ dst_x, dst_y);
+ else
+ BlitBitmap(src_bitmap, drawto_field, src_x, src_y, width, height,
+ dst_x, dst_y);
+
+ MarkTileDirty(x2, y2);
+ }
+}
+
+static void DrawGraphicShifted(int x, int y, int dx, int dy,
+ int graphic, int frame,
+ int cut_mode, int mask_mode)
+{
+ if (graphic < 0)
+ {
+ DrawGraphic(x, y, graphic, frame);
+
+ return;
+ }
+
+ if (graphic_info[graphic].double_movement) // EM style movement images
+ DrawGraphicShiftedDouble(x, y, dx, dy, graphic, frame, cut_mode, mask_mode);
+ else
+ DrawGraphicShiftedNormal(x, y, dx, dy, graphic, frame, cut_mode, mask_mode);
+}
+
+static void DrawGraphicShiftedThruMask(int x, int y, int dx, int dy,
+ int graphic, int frame, int cut_mode)
+{
+ DrawGraphicShifted(x, y, dx, dy, graphic, frame, cut_mode, USE_MASKING);
+}
+
+void DrawScreenElementExt(int x, int y, int dx, int dy, int element,
+ int cut_mode, int mask_mode)
+{
+ int lx = LEVELX(x), ly = LEVELY(y);
+ int graphic;
+ int frame;
+
+ if (IN_LEV_FIELD(lx, ly))
+ {
+ if (element == EL_EMPTY)
+ element = GfxElementEmpty[lx][ly];
+
+ SetRandomAnimationValue(lx, ly);
+
+ graphic = el_act_dir2img(element, GfxAction[lx][ly], GfxDir[lx][ly]);
+ frame = getGraphicAnimationFrameXY(graphic, lx, ly);
+
+ // do not use double (EM style) movement graphic when not moving
+ if (graphic_info[graphic].double_movement && !dx && !dy)
+ {
+ graphic = el_act_dir2img(element, ACTION_DEFAULT, GfxDir[lx][ly]);
+ frame = getGraphicAnimationFrameXY(graphic, lx, ly);
+ }
+
+ if (game.use_masked_elements && (dx || dy))
+ mask_mode = USE_MASKING;
+ }
+ else // border element
+ {
+ graphic = el2img(element);
+ frame = getGraphicAnimationFrameXY(graphic, lx, ly);
+ }
+
+ if (element == EL_EXPANDABLE_WALL)
+ {
+ boolean left_stopped = FALSE, right_stopped = FALSE;
+
+ if (!IN_LEV_FIELD(lx - 1, ly) || IS_WALL(Tile[lx - 1][ly]))
+ left_stopped = TRUE;
+ if (!IN_LEV_FIELD(lx + 1, ly) || IS_WALL(Tile[lx + 1][ly]))
+ right_stopped = TRUE;
+
+ if (left_stopped && right_stopped)
+ graphic = IMG_WALL;
+ else if (left_stopped)
+ {
+ graphic = IMG_EXPANDABLE_WALL_GROWING_RIGHT;
+ frame = graphic_info[graphic].anim_frames - 1;
+ }
+ else if (right_stopped)
+ {
+ graphic = IMG_EXPANDABLE_WALL_GROWING_LEFT;
+ frame = graphic_info[graphic].anim_frames - 1;
+ }
+ }
+
+ if (dx || dy)
+ DrawGraphicShifted(x, y, dx, dy, graphic, frame, cut_mode, mask_mode);
+ else if (mask_mode == USE_MASKING)
+ DrawGraphicThruMask(x, y, graphic, frame);
+ else
+ DrawGraphic(x, y, graphic, frame);
+}
+
+void DrawLevelElementExt(int x, int y, int dx, int dy, int element,
+ int cut_mode, int mask_mode)
+{
+ if (IN_LEV_FIELD(x, y) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
+ DrawScreenElementExt(SCREENX(x), SCREENY(y), dx, dy, element,
+ cut_mode, mask_mode);
+}
+
+void DrawScreenElementShifted(int x, int y, int dx, int dy, int element,
+ int cut_mode)
+{
+ DrawScreenElementExt(x, y, dx, dy, element, cut_mode, NO_MASKING);
+}
+
+void DrawLevelElementShifted(int x, int y, int dx, int dy, int element,
+ int cut_mode)
+{
+ DrawLevelElementExt(x, y, dx, dy, element, cut_mode, NO_MASKING);
+}
+
+void DrawLevelElementThruMask(int x, int y, int element)
+{
+ DrawLevelElementExt(x, y, 0, 0, element, NO_CUTTING, USE_MASKING);
+}
+
+void DrawLevelFieldThruMask(int x, int y)