+ DrawMaskedBorderExt(REDRAW_ALL, draw_target);
+ }
+ else
+ {
+ int last_border_status = global.border_status;
+
+ if (draw_target == DRAW_TO_FADE_SOURCE)
+ {
+ global.border_status = gfx.fade_border_source_status;
+ gfx.masked_border_bitmap_ptr = gfx.fade_bitmap_source;
+ }
+ else if (draw_target == DRAW_TO_FADE_TARGET)
+ {
+ global.border_status = gfx.fade_border_target_status;
+ gfx.masked_border_bitmap_ptr = gfx.fade_bitmap_target;
+ }
+
+ DrawMaskedBorderExt(REDRAW_ALL, draw_target);
+
+ global.border_status = last_border_status;
+ gfx.masked_border_bitmap_ptr = backbuffer;
+ }
+}
+
+void BlitScreenToBitmap_RND(Bitmap *target_bitmap)
+{
+ int fx = getFieldbufferOffsetX_RND();
+ int fy = getFieldbufferOffsetY_RND();
+
+ BlitBitmap(drawto_field, target_bitmap, fx, fy, SXSIZE, SYSIZE, SX, SY);
+}
+
+void BlitScreenToBitmap(Bitmap *target_bitmap)
+{
+ if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+ BlitScreenToBitmap_EM(target_bitmap);
+ else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
+ BlitScreenToBitmap_SP(target_bitmap);
+ else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
+ BlitScreenToBitmap_MM(target_bitmap);
+ else if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
+ BlitScreenToBitmap_RND(target_bitmap);
+
+ redraw_mask |= REDRAW_FIELD;
+}
+
+void DrawFramesPerSecond()
+{
+ char text[100];
+ int font_nr = FONT_TEXT_2;
+ int font_width = getFontWidth(font_nr);
+ int draw_deactivation_mask = GetDrawDeactivationMask();
+ boolean draw_masked = (draw_deactivation_mask == REDRAW_NONE);
+
+ /* draw FPS with leading space (needed if field buffer deactivated) */
+ sprintf(text, " %04.1f fps", global.frames_per_second);
+
+ /* override draw deactivation mask (required for invisible warp mode) */
+ SetDrawDeactivationMask(REDRAW_NONE);
+
+ /* draw opaque FPS if field buffer deactivated, else draw masked FPS */
+ DrawTextExt(backbuffer, SX + SXSIZE - font_width * strlen(text), SY, text,
+ font_nr, (draw_masked ? BLIT_MASKED : BLIT_OPAQUE));
+
+ /* set draw deactivation mask to previous value */
+ SetDrawDeactivationMask(draw_deactivation_mask);
+
+ /* force full-screen redraw in this frame */
+ redraw_mask = REDRAW_ALL;
+}
+
+#if DEBUG_FRAME_TIME
+static void PrintFrameTimeDebugging()
+{
+ static unsigned int last_counter = 0;
+ unsigned int counter = Counter();
+ int diff_1 = counter - last_counter;
+ int diff_2 = diff_1 - GAME_FRAME_DELAY;
+ int diff_2_max = 20;
+ int diff_2_cut = MIN(ABS(diff_2), diff_2_max);
+ char diff_bar[2 * diff_2_max + 5];
+ int pos = 0;
+ int i;
+
+ diff_bar[pos++] = (diff_2 < -diff_2_max ? '<' : ' ');
+
+ for (i = 0; i < diff_2_max; i++)
+ diff_bar[pos++] = (diff_2 >= 0 ? ' ' :
+ i >= diff_2_max - diff_2_cut ? '-' : ' ');
+
+ diff_bar[pos++] = '|';
+
+ for (i = 0; i < diff_2_max; i++)
+ diff_bar[pos++] = (diff_2 <= 0 ? ' ' : i < diff_2_cut ? '+' : ' ');
+
+ diff_bar[pos++] = (diff_2 > diff_2_max ? '>' : ' ');
+
+ diff_bar[pos++] = '\0';
+
+ Error(ERR_INFO, "%06d [%02d] [%c%02d] %s",
+ counter,
+ diff_1,
+ (diff_2 < 0 ? '-' : diff_2 > 0 ? '+' : ' '), ABS(diff_2),
+ diff_bar);
+
+ last_counter = counter;
+}
+#endif
+
+static int unifiedRedrawMask(int mask)
+{
+ if (mask & REDRAW_ALL)
+ return REDRAW_ALL;
+
+ if (mask & REDRAW_FIELD && mask & REDRAW_DOORS)
+ return REDRAW_ALL;
+
+ return mask;
+}
+
+static boolean equalRedrawMasks(int mask_1, int mask_2)
+{
+ return unifiedRedrawMask(mask_1) == unifiedRedrawMask(mask_2);
+}
+
+void BackToFront()
+{
+ static int last_redraw_mask = REDRAW_NONE;
+
+ // force screen redraw in every frame to continue drawing global animations
+ // (but always use the last redraw mask to prevent unwanted side effects)
+ if (redraw_mask == REDRAW_NONE)
+ redraw_mask = last_redraw_mask;
+
+ last_redraw_mask = redraw_mask;
+
+#if 1
+ // masked border now drawn immediately when blitting backbuffer to window
+#else
+ // draw masked border to all viewports, if defined
+ DrawMaskedBorder(redraw_mask);
+#endif
+
+ // draw frames per second (only if debug mode is enabled)
+ if (redraw_mask & REDRAW_FPS)
+ DrawFramesPerSecond();
+
+ // remove playfield redraw before potentially merging with doors redraw
+ if (DrawingDeactivated(REAL_SX, REAL_SY, FULL_SXSIZE, FULL_SYSIZE))
+ redraw_mask &= ~REDRAW_FIELD;
+
+ // redraw complete window if both playfield and (some) doors need redraw
+ if (redraw_mask & REDRAW_FIELD && redraw_mask & REDRAW_DOORS)
+ redraw_mask = REDRAW_ALL;
+
+ /* although redrawing the whole window would be fine for normal gameplay,
+ being able to only redraw the playfield is required for deactivating
+ certain drawing areas (mainly playfield) to work, which is needed for
+ warp-forward to be fast enough (by skipping redraw of most frames) */
+
+ if (redraw_mask & REDRAW_ALL)
+ {
+ BlitBitmap(backbuffer, window, 0, 0, WIN_XSIZE, WIN_YSIZE, 0, 0);
+ }
+ else if (redraw_mask & REDRAW_FIELD)
+ {
+ BlitBitmap(backbuffer, window,
+ REAL_SX, REAL_SY, FULL_SXSIZE, FULL_SYSIZE, REAL_SX, REAL_SY);
+ }
+ else if (redraw_mask & REDRAW_DOORS)
+ {
+ // merge door areas to prevent calling screen redraw more than once
+ int x1 = WIN_XSIZE;
+ int y1 = WIN_YSIZE;
+ int x2 = 0;
+ int y2 = 0;
+
+ if (redraw_mask & REDRAW_DOOR_1)
+ {
+ x1 = MIN(x1, DX);
+ y1 = MIN(y1, DY);
+ x2 = MAX(x2, DX + DXSIZE);
+ y2 = MAX(y2, DY + DYSIZE);
+ }
+
+ if (redraw_mask & REDRAW_DOOR_2)
+ {
+ x1 = MIN(x1, VX);
+ y1 = MIN(y1, VY);
+ x2 = MAX(x2, VX + VXSIZE);
+ y2 = MAX(y2, VY + VYSIZE);
+ }
+
+ if (redraw_mask & REDRAW_DOOR_3)
+ {
+ x1 = MIN(x1, EX);
+ y1 = MIN(y1, EY);
+ x2 = MAX(x2, EX + EXSIZE);
+ y2 = MAX(y2, EY + EYSIZE);
+ }
+
+ // make sure that at least one pixel is blitted, and inside the screen
+ // (else nothing is blitted, causing the animations not to be updated)
+ x1 = MIN(MAX(0, x1), WIN_XSIZE - 1);
+ y1 = MIN(MAX(0, y1), WIN_YSIZE - 1);
+ x2 = MIN(MAX(1, x2), WIN_XSIZE);
+ y2 = MIN(MAX(1, y2), WIN_YSIZE);
+
+ BlitBitmap(backbuffer, window, x1, y1, x2 - x1, y2 - y1, x1, y1);
+ }
+
+ redraw_mask = REDRAW_NONE;
+
+#if DEBUG_FRAME_TIME
+ PrintFrameTimeDebugging();
+#endif
+}
+
+void BackToFront_WithFrameDelay(unsigned int frame_delay_value)
+{
+ unsigned int frame_delay_value_old = GetVideoFrameDelay();
+
+ SetVideoFrameDelay(frame_delay_value);
+
+ BackToFront();
+
+ SetVideoFrameDelay(frame_delay_value_old);
+}
+
+static int fade_type_skip = FADE_TYPE_NONE;
+
+static void FadeExt(int fade_mask, int fade_mode, int fade_type)
+{
+ void (*draw_border_function)(void) = NULL;
+ int x, y, width, height;
+ int fade_delay, post_delay;
+
+ if (fade_type == FADE_TYPE_FADE_OUT)
+ {
+ if (fade_type_skip != FADE_TYPE_NONE)
+ {
+ /* skip all fade operations until specified fade operation */
+ if (fade_type & fade_type_skip)
+ fade_type_skip = FADE_TYPE_NONE;
+
+ return;
+ }
+
+ if (fading.fade_mode & FADE_TYPE_TRANSFORM)
+ return;
+ }
+
+ redraw_mask |= fade_mask;
+
+ if (fade_type == FADE_TYPE_SKIP)
+ {
+ fade_type_skip = fade_mode;
+
+ return;
+ }
+
+ fade_delay = fading.fade_delay;
+ post_delay = (fade_mode == FADE_MODE_FADE_OUT ? fading.post_delay : 0);
+
+ if (fade_type_skip != FADE_TYPE_NONE)
+ {
+ /* skip all fade operations until specified fade operation */
+ if (fade_type & fade_type_skip)
+ fade_type_skip = FADE_TYPE_NONE;
+
+ fade_delay = 0;
+ }
+
+ if (global.autoplay_leveldir)
+ {
+ return;
+ }
+
+ if (fade_mask == REDRAW_FIELD)
+ {
+ x = FADE_SX;
+ y = FADE_SY;
+ width = FADE_SXSIZE;
+ height = FADE_SYSIZE;
+
+ if (border.draw_masked_when_fading)
+ draw_border_function = DrawMaskedBorder_FIELD; /* update when fading */
+ else
+ DrawMaskedBorder_FIELD(); /* draw once */
+ }
+ else /* REDRAW_ALL */
+ {
+ x = 0;
+ y = 0;
+ width = WIN_XSIZE;
+ height = WIN_YSIZE;
+ }
+
+ if (!setup.fade_screens ||
+ fade_delay == 0 ||
+ fading.fade_mode == FADE_MODE_NONE)
+ {
+ if (fade_mode == FADE_MODE_FADE_OUT)
+ return;
+
+ BlitBitmap(backbuffer, window, x, y, width, height, x, y);
+
+ redraw_mask &= ~fade_mask;
+
+ return;
+ }
+
+ FadeRectangle(x, y, width, height, fade_mode, fade_delay, post_delay,
+ draw_border_function);
+
+ redraw_mask &= ~fade_mask;
+}
+
+static void SetScreenStates_BeforeFadingIn()
+{
+ // temporarily set screen mode for animations to screen after fading in
+ global.anim_status = global.anim_status_next;
+
+ // store backbuffer with all animations that will be started after fading in
+ if (fade_type_skip != FADE_MODE_SKIP_FADE_IN)
+ PrepareFadeBitmap(DRAW_TO_FADE_TARGET);
+
+ // set screen mode for animations back to fading
+ global.anim_status = GAME_MODE_PSEUDO_FADING;
+}
+
+static void SetScreenStates_AfterFadingIn()
+{
+ // store new source screen (to use correct masked border for fading)
+ gfx.fade_border_source_status = global.border_status;
+
+ global.anim_status = global.anim_status_next;
+}
+
+static void SetScreenStates_BeforeFadingOut()
+{
+ // store new target screen (to use correct masked border for fading)
+ gfx.fade_border_target_status = game_status;
+
+ // set screen mode for animations to fading
+ global.anim_status = GAME_MODE_PSEUDO_FADING;
+
+ // store backbuffer with all animations that will be stopped for fading out
+ if (fade_type_skip != FADE_MODE_SKIP_FADE_OUT)
+ PrepareFadeBitmap(DRAW_TO_FADE_SOURCE);
+}
+
+static void SetScreenStates_AfterFadingOut()
+{
+ global.border_status = game_status;
+}
+
+void FadeIn(int fade_mask)
+{
+ SetScreenStates_BeforeFadingIn();
+
+#if 1
+ DrawMaskedBorder(REDRAW_ALL);
+#endif
+
+ if (fading.fade_mode & FADE_TYPE_TRANSFORM)
+ FadeExt(fade_mask, fading.fade_mode, FADE_TYPE_FADE_IN);
+ else
+ FadeExt(fade_mask, FADE_MODE_FADE_IN, FADE_TYPE_FADE_IN);
+
+ FADE_SX = REAL_SX;
+ FADE_SY = REAL_SY;
+ FADE_SXSIZE = FULL_SXSIZE;
+ FADE_SYSIZE = FULL_SYSIZE;
+
+ if (game_status == GAME_MODE_PLAYING &&
+ strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
+ SetOverlayActive(TRUE);
+
+ SetScreenStates_AfterFadingIn();
+
+ // force update of global animation status in case of rapid screen changes
+ redraw_mask = REDRAW_ALL;
+ BackToFront();
+}
+
+void FadeOut(int fade_mask)
+{
+ // update screen if areas covered by "fade_mask" and "redraw_mask" differ
+ if (!equalRedrawMasks(fade_mask, redraw_mask))
+ BackToFront();
+
+ SetScreenStates_BeforeFadingOut();
+
+ SetOverlayActive(FALSE);
+
+#if 0
+ DrawMaskedBorder(REDRAW_ALL);
+#endif
+
+ if (fading.fade_mode & FADE_TYPE_TRANSFORM)
+ FadeExt(fade_mask, fading.fade_mode, FADE_TYPE_FADE_OUT);
+ else
+ FadeExt(fade_mask, FADE_MODE_FADE_OUT, FADE_TYPE_FADE_OUT);
+
+ SetScreenStates_AfterFadingOut();
+}
+
+static void FadeSetLeaveNext(struct TitleFadingInfo fading_leave, boolean set)
+{
+ static struct TitleFadingInfo fading_leave_stored;
+
+ if (set)
+ fading_leave_stored = fading_leave;
+ else
+ fading = fading_leave_stored;
+}
+
+void FadeSetEnterMenu()
+{
+ fading = menu.enter_menu;
+
+ FadeSetLeaveNext(fading, TRUE); /* (keep same fade mode) */
+}
+
+void FadeSetLeaveMenu()
+{
+ fading = menu.leave_menu;
+
+ FadeSetLeaveNext(fading, TRUE); /* (keep same fade mode) */
+}
+
+void FadeSetEnterScreen()
+{
+ fading = menu.enter_screen[game_status];
+
+ FadeSetLeaveNext(menu.leave_screen[game_status], TRUE); /* store */
+}
+
+void FadeSetNextScreen()
+{
+ fading = menu.next_screen[game_status];
+
+ // (do not overwrite fade mode set by FadeSetEnterScreen)
+ // FadeSetLeaveNext(fading, TRUE); /* (keep same fade mode) */
+}
+
+void FadeSetLeaveScreen()
+{
+ FadeSetLeaveNext(menu.leave_screen[game_status], FALSE); /* recall */
+}
+
+void FadeSetFromType(int type)
+{
+ if (type & TYPE_ENTER_SCREEN)
+ FadeSetEnterScreen();
+ else if (type & TYPE_ENTER)
+ FadeSetEnterMenu();
+ else if (type & TYPE_LEAVE)
+ FadeSetLeaveMenu();
+}
+
+void FadeSetDisabled()
+{
+ static struct TitleFadingInfo fading_none = { FADE_MODE_NONE, -1, -1, -1 };
+
+ fading = fading_none;
+}
+
+void FadeSkipNextFadeIn()
+{
+ FadeExt(0, FADE_MODE_SKIP_FADE_IN, FADE_TYPE_SKIP);
+}
+
+void FadeSkipNextFadeOut()
+{
+ FadeExt(0, FADE_MODE_SKIP_FADE_OUT, FADE_TYPE_SKIP);
+}
+
+Bitmap *getBitmapFromGraphicOrDefault(int graphic, int default_graphic)
+{
+ boolean redefined = getImageListEntryFromImageID(graphic)->redefined;
+
+ return (graphic == IMG_UNDEFINED ? NULL :
+ graphic_info[graphic].bitmap != NULL || redefined ?
+ graphic_info[graphic].bitmap :
+ graphic_info[default_graphic].bitmap);
+}
+
+Bitmap *getBackgroundBitmap(int graphic)
+{
+ return getBitmapFromGraphicOrDefault(graphic, IMG_BACKGROUND);
+}
+
+Bitmap *getGlobalBorderBitmap(int graphic)
+{
+ return getBitmapFromGraphicOrDefault(graphic, IMG_GLOBAL_BORDER);
+}
+
+Bitmap *getGlobalBorderBitmapFromStatus(int status)
+{
+ int graphic =
+ (status == GAME_MODE_MAIN ||
+ status == GAME_MODE_PSEUDO_TYPENAME ? IMG_GLOBAL_BORDER_MAIN :
+ status == GAME_MODE_SCORES ? IMG_GLOBAL_BORDER_SCORES :
+ status == GAME_MODE_EDITOR ? IMG_GLOBAL_BORDER_EDITOR :
+ status == GAME_MODE_PLAYING ? IMG_GLOBAL_BORDER_PLAYING :
+ IMG_GLOBAL_BORDER);
+
+ return getGlobalBorderBitmap(graphic);
+}
+
+void SetWindowBackgroundImageIfDefined(int graphic)
+{
+ if (graphic_info[graphic].bitmap)
+ SetWindowBackgroundBitmap(graphic_info[graphic].bitmap);
+}
+
+void SetMainBackgroundImageIfDefined(int graphic)
+{
+ if (graphic_info[graphic].bitmap)
+ SetMainBackgroundBitmap(graphic_info[graphic].bitmap);
+}
+
+void SetDoorBackgroundImageIfDefined(int graphic)
+{
+ if (graphic_info[graphic].bitmap)
+ SetDoorBackgroundBitmap(graphic_info[graphic].bitmap);
+}
+
+void SetWindowBackgroundImage(int graphic)
+{
+ SetWindowBackgroundBitmap(getBackgroundBitmap(graphic));
+}
+
+void SetMainBackgroundImage(int graphic)
+{
+ SetMainBackgroundBitmap(getBackgroundBitmap(graphic));
+}
+
+void SetDoorBackgroundImage(int graphic)
+{
+ SetDoorBackgroundBitmap(getBackgroundBitmap(graphic));
+}
+
+void SetPanelBackground()
+{
+ struct GraphicInfo *gfx = &graphic_info[IMG_BACKGROUND_PANEL];
+
+ BlitBitmapTiled(gfx->bitmap, bitmap_db_panel, gfx->src_x, gfx->src_y,
+ gfx->width, gfx->height, 0, 0, DXSIZE, DYSIZE);
+
+ SetDoorBackgroundBitmap(bitmap_db_panel);
+}
+
+void DrawBackground(int x, int y, int width, int height)
+{
+ /* "drawto" might still point to playfield buffer here (hall of fame) */
+ ClearRectangleOnBackground(backbuffer, x, y, width, height);
+
+ if (IN_GFX_FIELD_FULL(x, y))
+ redraw_mask |= REDRAW_FIELD;
+ else if (IN_GFX_DOOR_1(x, y))
+ redraw_mask |= REDRAW_DOOR_1;
+ else if (IN_GFX_DOOR_2(x, y))
+ redraw_mask |= REDRAW_DOOR_2;
+ else if (IN_GFX_DOOR_3(x, y))
+ redraw_mask |= REDRAW_DOOR_3;
+}
+
+void DrawBackgroundForFont(int x, int y, int width, int height, int font_nr)
+{
+ struct FontBitmapInfo *font = getFontBitmapInfo(font_nr);
+
+ if (font->bitmap == NULL)
+ return;
+
+ DrawBackground(x, y, width, height);
+}
+
+void DrawBackgroundForGraphic(int x, int y, int width, int height, int graphic)
+{
+ struct GraphicInfo *g = &graphic_info[graphic];
+
+ if (g->bitmap == NULL)
+ return;
+
+ DrawBackground(x, y, width, height);
+}
+
+static int game_status_last = -1;
+static Bitmap *global_border_bitmap_last = NULL;
+static Bitmap *global_border_bitmap = NULL;
+static int real_sx_last = -1, real_sy_last = -1;
+static int full_sxsize_last = -1, full_sysize_last = -1;
+static int dx_last = -1, dy_last = -1;
+static int dxsize_last = -1, dysize_last = -1;
+static int vx_last = -1, vy_last = -1;
+static int vxsize_last = -1, vysize_last = -1;
+
+boolean CheckIfGlobalBorderHasChanged()
+{
+ // if game status has not changed, global border has not changed either
+ if (game_status == game_status_last)
+ return FALSE;
+
+ // determine and store new global border bitmap for current game status
+ global_border_bitmap = getGlobalBorderBitmapFromStatus(game_status);
+
+ return (global_border_bitmap_last != global_border_bitmap);
+}
+
+boolean CheckIfGlobalBorderRedrawIsNeeded()
+{
+ // if game status has not changed, nothing has to be redrawn
+ if (game_status == game_status_last)
+ return FALSE;
+
+ // redraw if last screen was title screen
+ if (game_status_last == GAME_MODE_TITLE)
+ return TRUE;
+
+ // redraw if global screen border has changed
+ if (CheckIfGlobalBorderHasChanged())
+ return TRUE;
+
+ // redraw if position or size of playfield area has changed
+ if (real_sx_last != REAL_SX || real_sy_last != REAL_SY ||
+ full_sxsize_last != FULL_SXSIZE || full_sysize_last != FULL_SYSIZE)
+ return TRUE;
+
+ // redraw if position or size of door area has changed
+ if (dx_last != DX || dy_last != DY ||
+ dxsize_last != DXSIZE || dysize_last != DYSIZE)
+ return TRUE;
+
+ // redraw if position or size of tape area has changed
+ if (vx_last != VX || vy_last != VY ||
+ vxsize_last != VXSIZE || vysize_last != VYSIZE)
+ return TRUE;
+
+ return FALSE;
+}
+
+void RedrawGlobalBorderFromBitmap(Bitmap *bitmap)
+{
+ if (bitmap)
+ BlitBitmap(bitmap, backbuffer, 0, 0, WIN_XSIZE, WIN_YSIZE, 0, 0);
+ else
+ ClearRectangle(backbuffer, 0, 0, WIN_XSIZE, WIN_YSIZE);
+}
+
+void RedrawGlobalBorder()
+{
+ Bitmap *bitmap = getGlobalBorderBitmapFromStatus(game_status);
+
+ RedrawGlobalBorderFromBitmap(bitmap);
+
+ redraw_mask = REDRAW_ALL;
+}
+
+static void RedrawGlobalBorderIfNeeded()
+{
+ if (game_status == game_status_last)
+ return;
+
+ // copy current draw buffer to later copy back areas that have not changed
+ if (game_status_last != GAME_MODE_TITLE)
+ BlitBitmap(backbuffer, bitmap_db_store_1, 0, 0, WIN_XSIZE, WIN_YSIZE, 0, 0);
+
+ if (CheckIfGlobalBorderRedrawIsNeeded())
+ {
+ // redraw global screen border (or clear, if defined to be empty)
+ RedrawGlobalBorderFromBitmap(global_border_bitmap);
+
+ // copy previous playfield and door areas, if they are defined on both
+ // previous and current screen and if they still have the same size
+
+ if (real_sx_last != -1 && real_sy_last != -1 &&
+ REAL_SX != -1 && REAL_SY != -1 &&
+ full_sxsize_last == FULL_SXSIZE && full_sysize_last == FULL_SYSIZE)
+ BlitBitmap(bitmap_db_store_1, backbuffer,
+ real_sx_last, real_sy_last, FULL_SXSIZE, FULL_SYSIZE,
+ REAL_SX, REAL_SY);
+
+ if (dx_last != -1 && dy_last != -1 &&
+ DX != -1 && DY != -1 &&
+ dxsize_last == DXSIZE && dysize_last == DYSIZE)
+ BlitBitmap(bitmap_db_store_1, backbuffer,
+ dx_last, dy_last, DXSIZE, DYSIZE, DX, DY);
+
+ if (vx_last != -1 && vy_last != -1 &&
+ VX != -1 && VY != -1 &&
+ vxsize_last == VXSIZE && vysize_last == VYSIZE)
+ BlitBitmap(bitmap_db_store_1, backbuffer,
+ vx_last, vy_last, VXSIZE, VYSIZE, VX, VY);
+
+ redraw_mask = REDRAW_ALL;
+ }
+
+ game_status_last = game_status;
+
+ global_border_bitmap_last = global_border_bitmap;
+
+ real_sx_last = REAL_SX;
+ real_sy_last = REAL_SY;
+ full_sxsize_last = FULL_SXSIZE;
+ full_sysize_last = FULL_SYSIZE;
+ dx_last = DX;
+ dy_last = DY;
+ dxsize_last = DXSIZE;
+ dysize_last = DYSIZE;
+ vx_last = VX;
+ vy_last = VY;
+ vxsize_last = VXSIZE;
+ vysize_last = VYSIZE;
+}
+
+void ClearField()
+{
+ RedrawGlobalBorderIfNeeded();
+
+ /* !!! "drawto" might still point to playfield buffer here (see above) !!! */
+ /* (when entering hall of fame after playing) */
+ DrawBackground(REAL_SX, REAL_SY, FULL_SXSIZE, FULL_SYSIZE);
+
+ /* !!! maybe this should be done before clearing the background !!! */
+ if (game_status == GAME_MODE_PLAYING)
+ {
+ ClearRectangle(fieldbuffer, 0, 0, FXSIZE, FYSIZE);
+ SetDrawtoField(DRAW_TO_FIELDBUFFER);
+ }
+ else
+ {
+ SetDrawtoField(DRAW_TO_BACKBUFFER);
+ }
+}
+
+void MarkTileDirty(int x, int y)
+{
+ redraw_mask |= REDRAW_FIELD;
+}
+
+void SetBorderElement()
+{
+ int x, y;
+
+ BorderElement = EL_EMPTY;
+
+ /* the MM game engine does not use a visible border element */
+ if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
+ return;
+
+ for (y = 0; y < lev_fieldy && BorderElement == EL_EMPTY; y++)
+ {
+ for (x = 0; x < lev_fieldx; x++)
+ {
+ if (!IS_INDESTRUCTIBLE(Feld[x][y]))
+ BorderElement = EL_STEELWALL;
+
+ if (y != 0 && y != lev_fieldy - 1 && x != lev_fieldx - 1)
+ x = lev_fieldx - 2;
+ }
+ }
+}
+
+void FloodFillLevelExt(int from_x, int from_y, int fill_element,
+ int max_array_fieldx, int max_array_fieldy,
+ short field[max_array_fieldx][max_array_fieldy],
+ int max_fieldx, int max_fieldy)
+{
+ int i,x,y;
+ int old_element;
+ static int check[4][2] = { { -1, 0 }, { 0, -1 }, { 1, 0 }, { 0, 1 } };
+ static int safety = 0;
+
+ /* check if starting field still has the desired content */
+ if (field[from_x][from_y] == fill_element)
+ return;
+
+ safety++;
+
+ if (safety > max_fieldx * max_fieldy)
+ Error(ERR_EXIT, "Something went wrong in 'FloodFill()'. Please debug.");
+
+ old_element = field[from_x][from_y];
+ field[from_x][from_y] = fill_element;
+
+ for (i = 0; i < 4; i++)
+ {
+ x = from_x + check[i][0];
+ y = from_y + check[i][1];
+
+ if (IN_FIELD(x, y, max_fieldx, max_fieldy) && field[x][y] == old_element)
+ FloodFillLevelExt(x, y, fill_element, max_array_fieldx, max_array_fieldy,
+ field, max_fieldx, max_fieldy);
+ }
+
+ safety--;
+}
+
+void FloodFillLevel(int from_x, int from_y, int fill_element,
+ short field[MAX_LEV_FIELDX][MAX_LEV_FIELDY],
+ int max_fieldx, int max_fieldy)
+{
+ FloodFillLevelExt(from_x, from_y, fill_element,
+ MAX_LEV_FIELDX, MAX_LEV_FIELDY, field,
+ max_fieldx, max_fieldy);
+}
+
+void SetRandomAnimationValue(int x, int y)
+{
+ gfx.anim_random_frame = GfxRandom[x][y];
+}
+
+int getGraphicAnimationFrame(int graphic, int sync_frame)
+{
+ /* animation synchronized with global frame counter, not move position */
+ if (graphic_info[graphic].anim_global_sync || sync_frame < 0)
+ sync_frame = FrameCounter;
+
+ 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);
+}
+
+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_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 in-game graphics defined, always use standard graphic size
+ if (g->bitmaps[IMG_BITMAP_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);
+}
+
+inline 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))
+ {
+ printf("DrawGraphic(): x = %d, y = %d, graphic = %d\n", x, y, graphic);
+ printf("DrawGraphic(): This should never happen!\n");
+ 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))
+ {
+ printf("DrawGraphic(): x = %d, y = %d, graphic = %d\n", x, y, graphic);
+ printf("DrawGraphic(): This should never happen!\n");
+ 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))
+ {
+ printf("DrawGraphicThruMask(): x = %d,y = %d, graphic = %d\n",x,y,graphic);
+ printf("DrawGraphicThruMask(): This should never happen!\n");
+ 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))
+ {
+ printf("DrawGraphicThruMask(): x = %d,y = %d, graphic = %d\n",x,y,graphic);
+ printf("DrawGraphicThruMask(): This should never happen!\n");
+ 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);
+}
+
+inline 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))
+ {
+ printf("DrawGraphicShifted(): x = %d, y = %d, graphic = %d\n",x,y,graphic);
+ printf("DrawGraphicShifted(): This should never happen!\n");
+ 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);
+ }
+}
+
+inline 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);
+}
+
+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))
+ {
+ SetRandomAnimationValue(lx, ly);
+
+ graphic = el_act_dir2img(element, GfxAction[lx][ly], GfxDir[lx][ly]);
+ frame = getGraphicAnimationFrame(graphic, GfxFrame[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 = getGraphicAnimationFrame(graphic, GfxFrame[lx][ly]);
+ }
+ }
+ else /* border element */
+ {
+ graphic = el2img(element);
+ frame = getGraphicAnimationFrame(graphic, -1);
+ }
+
+ if (element == EL_EXPANDABLE_WALL)
+ {
+ boolean left_stopped = FALSE, right_stopped = FALSE;
+
+ if (!IN_LEV_FIELD(lx - 1, ly) || IS_WALL(Feld[lx - 1][ly]))
+ left_stopped = TRUE;
+ if (!IN_LEV_FIELD(lx + 1, ly) || IS_WALL(Feld[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)
+{
+ DrawLevelElementExt(x, y, 0, 0, Feld[x][y], NO_CUTTING, USE_MASKING);
+}
+
+/* !!! implementation of quicksand is totally broken !!! */
+#define IS_CRUMBLED_TILE(x, y, e) \
+ (GFX_CRUMBLED(e) && (!IN_LEV_FIELD(x, y) || \
+ !IS_MOVING(x, y) || \
+ (e) == EL_QUICKSAND_EMPTYING || \
+ (e) == EL_QUICKSAND_FAST_EMPTYING))
+
+static void DrawLevelFieldCrumbledInnerCorners(int x, int y, int dx, int dy,
+ int graphic)
+{
+ Bitmap *src_bitmap;
+ int src_x, src_y;
+ int width, height, cx, cy;
+ int sx = SCREENX(x), sy = SCREENY(y);
+ int crumbled_border_size = graphic_info[graphic].border_size;
+ int crumbled_tile_size = graphic_info[graphic].tile_size;
+ int crumbled_border_size_var =
+ crumbled_border_size * TILESIZE_VAR / crumbled_tile_size;
+ int i;
+
+ getGraphicSource(graphic, 0, &src_bitmap, &src_x, &src_y);
+
+ for (i = 1; i < 4; i++)
+ {
+ int dxx = (i & 1 ? dx : 0);
+ int dyy = (i & 2 ? dy : 0);
+ int xx = x + dxx;
+ int yy = y + dyy;
+ int element = (IN_LEV_FIELD(xx, yy) ? TILE_GFX_ELEMENT(xx, yy) :
+ BorderElement);
+
+ /* check if neighbour field is of same crumble type */
+ boolean same = (IS_CRUMBLED_TILE(xx, yy, element) &&
+ graphic_info[graphic].class ==
+ graphic_info[el_act2crm(element, ACTION_DEFAULT)].class);
+
+ /* return if check prevents inner corner */
+ if (same == (dxx == dx && dyy == dy))
+ return;
+ }
+
+ /* if we reach this point, we have an inner corner */
+
+ getGraphicSource(graphic, 1, &src_bitmap, &src_x, &src_y);
+
+ width = crumbled_border_size_var;
+ height = crumbled_border_size_var;
+ cx = (dx > 0 ? TILESIZE_VAR - width : 0);
+ cy = (dy > 0 ? TILESIZE_VAR - height : 0);
+
+ BlitBitmap(src_bitmap, drawto_field, src_x + cx, src_y + cy,
+ width, height, FX + sx * TILEX_VAR + cx, FY + sy * TILEY_VAR + cy);
+}
+
+static void DrawLevelFieldCrumbledBorders(int x, int y, int graphic, int frame,
+ int dir)
+{
+ Bitmap *src_bitmap;
+ int src_x, src_y;
+ int width, height, bx, by, cx, cy;
+ int sx = SCREENX(x), sy = SCREENY(y);
+ int crumbled_border_size = graphic_info[graphic].border_size;
+ int crumbled_tile_size = graphic_info[graphic].tile_size;
+ int crumbled_border_size_var =
+ crumbled_border_size * TILESIZE_VAR / crumbled_tile_size;
+ int crumbled_border_pos_var = TILESIZE_VAR - crumbled_border_size_var;
+ int i;
+
+ getGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
+
+ /* draw simple, sloppy, non-corner-accurate crumbled border */
+
+ width = (dir == 1 || dir == 2 ? crumbled_border_size_var : TILESIZE_VAR);
+ height = (dir == 0 || dir == 3 ? crumbled_border_size_var : TILESIZE_VAR);
+ cx = (dir == 2 ? crumbled_border_pos_var : 0);
+ cy = (dir == 3 ? crumbled_border_pos_var : 0);
+
+ BlitBitmap(src_bitmap, drawto_field, src_x + cx, src_y + cy, width, height,
+ FX + sx * TILEX_VAR + cx,
+ FY + sy * TILEY_VAR + cy);
+
+ /* (remaining middle border part must be at least as big as corner part) */
+ if (!(graphic_info[graphic].style & STYLE_ACCURATE_BORDERS) ||
+ crumbled_border_size_var >= TILESIZE_VAR / 3)
+ return;
+
+ /* correct corners of crumbled border, if needed */
+
+ for (i = -1; i <= 1; i += 2)
+ {
+ int xx = x + (dir == 0 || dir == 3 ? i : 0);
+ int yy = y + (dir == 1 || dir == 2 ? i : 0);
+ int element = (IN_LEV_FIELD(xx, yy) ? TILE_GFX_ELEMENT(xx, yy) :
+ BorderElement);
+
+ /* check if neighbour field is of same crumble type */
+ if (IS_CRUMBLED_TILE(xx, yy, element) &&
+ graphic_info[graphic].class ==
+ graphic_info[el_act2crm(element, ACTION_DEFAULT)].class)
+ {
+ /* no crumbled corner, but continued crumbled border */
+
+ int c1 = (dir == 2 || dir == 3 ? crumbled_border_pos_var : 0);
+ int c2 = (i == 1 ? crumbled_border_pos_var : 0);
+ int b1 = (i == 1 ? crumbled_border_size_var :
+ TILESIZE_VAR - 2 * crumbled_border_size_var);
+
+ width = crumbled_border_size_var;
+ height = crumbled_border_size_var;
+
+ if (dir == 1 || dir == 2)
+ {
+ cx = c1;
+ cy = c2;
+ bx = cx;
+ by = b1;
+ }
+ else
+ {
+ cx = c2;
+ cy = c1;
+ bx = b1;
+ by = cy;
+ }
+
+ BlitBitmap(src_bitmap, drawto_field, src_x + bx, src_y + by,
+ width, height,
+ FX + sx * TILEX_VAR + cx,
+ FY + sy * TILEY_VAR + cy);
+ }
+ }
+}
+
+static void DrawLevelFieldCrumbledExt(int x, int y, int graphic, int frame)
+{
+ int sx = SCREENX(x), sy = SCREENY(y);
+ int element;
+ int i;
+ static int xy[4][2] =
+ {
+ { 0, -1 },
+ { -1, 0 },
+ { +1, 0 },
+ { 0, +1 }
+ };
+
+ if (!IN_LEV_FIELD(x, y))
+ return;
+
+ element = TILE_GFX_ELEMENT(x, y);
+
+ if (IS_CRUMBLED_TILE(x, y, element)) /* crumble field itself */
+ {
+ if (!IN_SCR_FIELD(sx, sy))
+ return;
+
+ /* crumble field borders towards direct neighbour fields */
+ for (i = 0; i < 4; i++)
+ {
+ int xx = x + xy[i][0];
+ int yy = y + xy[i][1];
+
+ element = (IN_LEV_FIELD(xx, yy) ? TILE_GFX_ELEMENT(xx, yy) :
+ BorderElement);
+
+ /* check if neighbour field is of same crumble type */
+ if (IS_CRUMBLED_TILE(xx, yy, element) &&
+ graphic_info[graphic].class ==
+ graphic_info[el_act2crm(element, ACTION_DEFAULT)].class)
+ continue;
+
+ DrawLevelFieldCrumbledBorders(x, y, graphic, frame, i);
+ }
+
+ /* crumble inner field corners towards corner neighbour fields */
+ if ((graphic_info[graphic].style & STYLE_INNER_CORNERS) &&
+ graphic_info[graphic].anim_frames == 2)
+ {
+ for (i = 0; i < 4; i++)
+ {
+ int dx = (i & 1 ? +1 : -1);
+ int dy = (i & 2 ? +1 : -1);
+
+ DrawLevelFieldCrumbledInnerCorners(x, y, dx, dy, graphic);
+ }
+ }
+
+ MarkTileDirty(sx, sy);
+ }
+ else /* center field is not crumbled -- crumble neighbour fields */
+ {
+ /* crumble field borders of direct neighbour fields */
+ for (i = 0; i < 4; i++)
+ {
+ int xx = x + xy[i][0];
+ int yy = y + xy[i][1];
+ int sxx = sx + xy[i][0];
+ int syy = sy + xy[i][1];
+
+ if (!IN_LEV_FIELD(xx, yy) ||
+ !IN_SCR_FIELD(sxx, syy))
+ continue;
+
+ if (Feld[xx][yy] == EL_ELEMENT_SNAPPING)
+ continue;
+
+ element = TILE_GFX_ELEMENT(xx, yy);
+
+ if (!IS_CRUMBLED_TILE(xx, yy, element))
+ continue;
+
+ graphic = el_act2crm(element, ACTION_DEFAULT);
+
+ DrawLevelFieldCrumbledBorders(xx, yy, graphic, 0, 3 - i);
+
+ MarkTileDirty(sxx, syy);
+ }
+
+ /* crumble inner field corners of corner neighbour fields */
+ for (i = 0; i < 4; i++)
+ {
+ int dx = (i & 1 ? +1 : -1);
+ int dy = (i & 2 ? +1 : -1);
+ int xx = x + dx;
+ int yy = y + dy;
+ int sxx = sx + dx;
+ int syy = sy + dy;
+
+ if (!IN_LEV_FIELD(xx, yy) ||
+ !IN_SCR_FIELD(sxx, syy))
+ continue;
+
+ if (Feld[xx][yy] == EL_ELEMENT_SNAPPING)
+ continue;
+
+ element = TILE_GFX_ELEMENT(xx, yy);
+
+ if (!IS_CRUMBLED_TILE(xx, yy, element))
+ continue;
+
+ graphic = el_act2crm(element, ACTION_DEFAULT);
+
+ if ((graphic_info[graphic].style & STYLE_INNER_CORNERS) &&
+ graphic_info[graphic].anim_frames == 2)
+ DrawLevelFieldCrumbledInnerCorners(xx, yy, -dx, -dy, graphic);
+
+ MarkTileDirty(sxx, syy);
+ }
+ }
+}
+
+void DrawLevelFieldCrumbled(int x, int y)
+{
+ int graphic;
+
+ if (!IN_LEV_FIELD(x, y))
+ return;
+
+ if (Feld[x][y] == EL_ELEMENT_SNAPPING &&
+ GfxElement[x][y] != EL_UNDEFINED &&
+ GFX_CRUMBLED(GfxElement[x][y]))
+ {
+ DrawLevelFieldCrumbledDigging(x, y, GfxDir[x][y], GfxFrame[x][y]);
+
+ return;
+ }
+
+ graphic = el_act2crm(TILE_GFX_ELEMENT(x, y), ACTION_DEFAULT);
+
+ DrawLevelFieldCrumbledExt(x, y, graphic, 0);
+}
+
+void DrawLevelFieldCrumbledDigging(int x, int y, int direction,
+ int step_frame)
+{
+ int graphic1 = el_act_dir2img(GfxElement[x][y], ACTION_DIGGING, direction);
+ int graphic2 = el_act_dir2crm(GfxElement[x][y], ACTION_DIGGING, direction);
+ int frame1 = getGraphicAnimationFrame(graphic1, step_frame);
+ int frame2 = getGraphicAnimationFrame(graphic2, step_frame);
+ int sx = SCREENX(x), sy = SCREENY(y);
+
+ DrawGraphic(sx, sy, graphic1, frame1);
+ DrawLevelFieldCrumbledExt(x, y, graphic2, frame2);
+}
+
+void DrawLevelFieldCrumbledNeighbours(int x, int y)
+{
+ int sx = SCREENX(x), sy = SCREENY(y);
+ static int xy[4][2] =
+ {
+ { 0, -1 },
+ { -1, 0 },
+ { +1, 0 },
+ { 0, +1 }
+ };
+ int i;
+
+ /* crumble direct neighbour fields (required for field borders) */
+ for (i = 0; i < 4; i++)
+ {
+ int xx = x + xy[i][0];
+ int yy = y + xy[i][1];
+ int sxx = sx + xy[i][0];
+ int syy = sy + xy[i][1];
+
+ if (!IN_LEV_FIELD(xx, yy) ||
+ !IN_SCR_FIELD(sxx, syy) ||
+ !GFX_CRUMBLED(Feld[xx][yy]) ||
+ IS_MOVING(xx, yy))
+ continue;
+
+ DrawLevelField(xx, yy);
+ }
+
+ /* crumble corner neighbour fields (required for inner field corners) */
+ for (i = 0; i < 4; i++)
+ {
+ int dx = (i & 1 ? +1 : -1);
+ int dy = (i & 2 ? +1 : -1);
+ int xx = x + dx;
+ int yy = y + dy;
+ int sxx = sx + dx;
+ int syy = sy + dy;
+
+ if (!IN_LEV_FIELD(xx, yy) ||
+ !IN_SCR_FIELD(sxx, syy) ||
+ !GFX_CRUMBLED(Feld[xx][yy]) ||
+ IS_MOVING(xx, yy))
+ continue;
+
+ int element = TILE_GFX_ELEMENT(xx, yy);
+ int graphic = el_act2crm(element, ACTION_DEFAULT);
+
+ if ((graphic_info[graphic].style & STYLE_INNER_CORNERS) &&
+ graphic_info[graphic].anim_frames == 2)
+ DrawLevelField(xx, yy);
+ }
+}
+
+static int getBorderElement(int x, int y)
+{
+ int border[7][2] =
+ {
+ { EL_STEELWALL_TOPLEFT, EL_INVISIBLE_STEELWALL_TOPLEFT },
+ { EL_STEELWALL_TOPRIGHT, EL_INVISIBLE_STEELWALL_TOPRIGHT },
+ { EL_STEELWALL_BOTTOMLEFT, EL_INVISIBLE_STEELWALL_BOTTOMLEFT },
+ { EL_STEELWALL_BOTTOMRIGHT, EL_INVISIBLE_STEELWALL_BOTTOMRIGHT },
+ { EL_STEELWALL_VERTICAL, EL_INVISIBLE_STEELWALL_VERTICAL },
+ { EL_STEELWALL_HORIZONTAL, EL_INVISIBLE_STEELWALL_HORIZONTAL },
+ { EL_STEELWALL, EL_INVISIBLE_STEELWALL }
+ };
+ int steel_type = (BorderElement == EL_STEELWALL ? 0 : 1);
+ int steel_position = (x == -1 && y == -1 ? 0 :
+ x == lev_fieldx && y == -1 ? 1 :
+ x == -1 && y == lev_fieldy ? 2 :
+ x == lev_fieldx && y == lev_fieldy ? 3 :
+ x == -1 || x == lev_fieldx ? 4 :
+ y == -1 || y == lev_fieldy ? 5 : 6);
+
+ return border[steel_position][steel_type];
+}
+
+void DrawScreenElement(int x, int y, int element)
+{
+ DrawScreenElementExt(x, y, 0, 0, element, NO_CUTTING, NO_MASKING);
+ DrawLevelFieldCrumbled(LEVELX(x), LEVELY(y));
+}
+
+void DrawLevelElement(int x, int y, int element)
+{
+ if (IN_LEV_FIELD(x, y) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
+ DrawScreenElement(SCREENX(x), SCREENY(y), element);
+}
+
+void DrawScreenField(int x, int y)
+{
+ int lx = LEVELX(x), ly = LEVELY(y);
+ int element, content;
+
+ if (!IN_LEV_FIELD(lx, ly))
+ {
+ if (lx < -1 || lx > lev_fieldx || ly < -1 || ly > lev_fieldy)
+ element = EL_EMPTY;
+ else
+ element = getBorderElement(lx, ly);
+
+ DrawScreenElement(x, y, element);
+
+ return;
+ }
+
+ element = Feld[lx][ly];
+ content = Store[lx][ly];
+
+ if (IS_MOVING(lx, ly))
+ {
+ int horiz_move = (MovDir[lx][ly] == MV_LEFT || MovDir[lx][ly] == MV_RIGHT);
+ boolean cut_mode = NO_CUTTING;
+
+ if (element == EL_QUICKSAND_EMPTYING ||
+ element == EL_QUICKSAND_FAST_EMPTYING ||
+ element == EL_MAGIC_WALL_EMPTYING ||
+ element == EL_BD_MAGIC_WALL_EMPTYING ||
+ element == EL_DC_MAGIC_WALL_EMPTYING ||
+ element == EL_AMOEBA_DROPPING)
+ cut_mode = CUT_ABOVE;
+ else if (element == EL_QUICKSAND_FILLING ||
+ element == EL_QUICKSAND_FAST_FILLING ||
+ element == EL_MAGIC_WALL_FILLING ||
+ element == EL_BD_MAGIC_WALL_FILLING ||
+ element == EL_DC_MAGIC_WALL_FILLING)
+ cut_mode = CUT_BELOW;
+
+ if (cut_mode == CUT_ABOVE)
+ DrawScreenElement(x, y, element);
+ else
+ DrawScreenElement(x, y, EL_EMPTY);
+
+ if (horiz_move)
+ DrawScreenElementShifted(x, y, MovPos[lx][ly], 0, element, NO_CUTTING);
+ else if (cut_mode == NO_CUTTING)
+ DrawScreenElementShifted(x, y, 0, MovPos[lx][ly], element, cut_mode);
+ else
+ {
+ DrawScreenElementShifted(x, y, 0, MovPos[lx][ly], content, cut_mode);
+
+ if (cut_mode == CUT_BELOW &&
+ IN_LEV_FIELD(lx, ly + 1) && IN_SCR_FIELD(x, y + 1))
+ DrawLevelElement(lx, ly + 1, element);
+ }
+
+ if (content == EL_ACID)
+ {
+ int dir = MovDir[lx][ly];
+ int newlx = lx + (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
+ int newly = ly + (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
+
+ DrawLevelElementThruMask(newlx, newly, EL_ACID);
+
+ // prevent target field from being drawn again (but without masking)
+ // (this would happen if target field is scanned after moving element)
+ Stop[newlx][newly] = TRUE;
+ }
+ }
+ else if (IS_BLOCKED(lx, ly))
+ {
+ int oldx, oldy;
+ int sx, sy;
+ int horiz_move;
+ boolean cut_mode = NO_CUTTING;
+ int element_old, content_old;
+
+ Blocked2Moving(lx, ly, &oldx, &oldy);
+ sx = SCREENX(oldx);
+ sy = SCREENY(oldy);
+ horiz_move = (MovDir[oldx][oldy] == MV_LEFT ||
+ MovDir[oldx][oldy] == MV_RIGHT);
+
+ element_old = Feld[oldx][oldy];
+ content_old = Store[oldx][oldy];
+
+ if (element_old == EL_QUICKSAND_EMPTYING ||
+ element_old == EL_QUICKSAND_FAST_EMPTYING ||
+ element_old == EL_MAGIC_WALL_EMPTYING ||
+ element_old == EL_BD_MAGIC_WALL_EMPTYING ||
+ element_old == EL_DC_MAGIC_WALL_EMPTYING ||
+ element_old == EL_AMOEBA_DROPPING)
+ cut_mode = CUT_ABOVE;
+
+ DrawScreenElement(x, y, EL_EMPTY);
+
+ if (horiz_move)
+ DrawScreenElementShifted(sx, sy, MovPos[oldx][oldy], 0, element_old,
+ NO_CUTTING);
+ else if (cut_mode == NO_CUTTING)
+ DrawScreenElementShifted(sx, sy, 0, MovPos[oldx][oldy], element_old,
+ cut_mode);
+ else
+ DrawScreenElementShifted(sx, sy, 0, MovPos[oldx][oldy], content_old,
+ cut_mode);
+ }
+ else if (IS_DRAWABLE(element))
+ DrawScreenElement(x, y, element);
+ else
+ DrawScreenElement(x, y, EL_EMPTY);
+}
+
+void DrawLevelField(int x, int y)
+{
+ if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
+ DrawScreenField(SCREENX(x), SCREENY(y));
+ else if (IS_MOVING(x, y))
+ {
+ int newx,newy;
+
+ Moving2Blocked(x, y, &newx, &newy);
+ if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
+ DrawScreenField(SCREENX(newx), SCREENY(newy));
+ }
+ else if (IS_BLOCKED(x, y))
+ {
+ int oldx, oldy;
+
+ Blocked2Moving(x, y, &oldx, &oldy);
+ if (IN_SCR_FIELD(SCREENX(oldx), SCREENY(oldy)))
+ DrawScreenField(SCREENX(oldx), SCREENY(oldy));
+ }
+}
+
+static void DrawSizedWallExt_MM(int dst_x, int dst_y, int element, int tilesize,
+ int (*el2img_function)(int), boolean masked)
+{
+ int element_base = map_mm_wall_element(element);
+ int element_bits = (IS_DF_WALL(element) ?
+ element - EL_DF_WALL_START :
+ element - EL_MM_WALL_START) & 0x000f;
+ int graphic = el2img_function(element_base);
+ int tilesize_draw = tilesize / 2;
+ Bitmap *src_bitmap;
+ int src_x, src_y;
+ int i;
+
+ getSizedGraphicSource(graphic, 0, tilesize_draw, &src_bitmap, &src_x, &src_y);
+
+ for (i = 0; i < 4; i++)
+ {
+ int dst_draw_x = dst_x + (i % 2) * tilesize_draw;
+ int dst_draw_y = dst_y + (i / 2) * tilesize_draw;
+
+ if (element_bits & (1 << i))
+ {
+ if (masked)
+ BlitBitmapMasked(src_bitmap, drawto, src_x, src_y,
+ tilesize_draw, tilesize_draw, dst_draw_x, dst_draw_y);
+ else
+ BlitBitmap(src_bitmap, drawto, src_x, src_y,
+ tilesize_draw, tilesize_draw, dst_draw_x, dst_draw_y);
+ }
+ else
+ {
+ if (!masked)
+ ClearRectangle(drawto, dst_draw_x, dst_draw_y,
+ tilesize_draw, tilesize_draw);
+ }
+ }
+}
+
+void DrawSizedWall_MM(int dst_x, int dst_y, int element, int tilesize,
+ int (*el2img_function)(int))
+{
+ DrawSizedWallExt_MM(dst_x, dst_y, element, tilesize, el2img_function, FALSE);
+}
+
+void DrawSizedElementExt(int x, int y, int element, int tilesize,
+ boolean masked)
+{
+ if (IS_MM_WALL(element))
+ {
+ DrawSizedWallExt_MM(SX + x * tilesize, SY + y * tilesize,
+ element, tilesize, el2edimg, masked);
+ }
+ else
+ {
+ int graphic = el2edimg(element);
+
+ if (masked)
+ DrawSizedGraphicThruMask(x, y, graphic, 0, tilesize);
+ else
+ DrawSizedGraphic(x, y, graphic, 0, tilesize);
+ }
+}
+
+void DrawSizedElement(int x, int y, int element, int tilesize)
+{
+ DrawSizedElementExt(x, y, element, tilesize, FALSE);
+}
+
+void DrawSizedElementThruMask(int x, int y, int element, int tilesize)
+{
+ DrawSizedElementExt(x, y, element, tilesize, TRUE);
+}
+
+void DrawMiniElement(int x, int y, int element)
+{
+ int graphic;
+
+ graphic = el2edimg(element);
+ DrawMiniGraphic(x, y, graphic);
+}
+
+void DrawSizedElementOrWall(int sx, int sy, int scroll_x, int scroll_y,
+ int tilesize)
+{
+ int x = sx + scroll_x, y = sy + scroll_y;
+
+ if (x < -1 || x > lev_fieldx || y < -1 || y > lev_fieldy)
+ DrawSizedElement(sx, sy, EL_EMPTY, tilesize);
+ else if (x > -1 && x < lev_fieldx && y > -1 && y < lev_fieldy)
+ DrawSizedElement(sx, sy, Feld[x][y], tilesize);
+ else
+ DrawSizedGraphic(sx, sy, el2edimg(getBorderElement(x, y)), 0, tilesize);
+}
+
+void DrawMiniElementOrWall(int sx, int sy, int scroll_x, int scroll_y)
+{
+ int x = sx + scroll_x, y = sy + scroll_y;
+
+ if (x < -1 || x > lev_fieldx || y < -1 || y > lev_fieldy)
+ DrawMiniElement(sx, sy, EL_EMPTY);
+ else if (x > -1 && x < lev_fieldx && y > -1 && y < lev_fieldy)
+ DrawMiniElement(sx, sy, Feld[x][y]);
+ else
+ DrawMiniGraphic(sx, sy, el2edimg(getBorderElement(x, y)));
+}
+
+void DrawEnvelopeBackgroundTiles(int graphic, int startx, int starty,
+ int x, int y, int xsize, int ysize,
+ int tile_width, int tile_height)
+{
+ Bitmap *src_bitmap;
+ int src_x, src_y;
+ int dst_x = startx + x * tile_width;
+ int dst_y = starty + y * tile_height;
+ int width = graphic_info[graphic].width;
+ int height = graphic_info[graphic].height;
+ int inner_width_raw = MAX(width - 2 * tile_width, tile_width);
+ int inner_height_raw = MAX(height - 2 * tile_height, tile_height);
+ int inner_width = inner_width_raw - (inner_width_raw % tile_width);
+ int inner_height = inner_height_raw - (inner_height_raw % tile_height);
+ int inner_sx = (width >= 3 * tile_width ? tile_width : 0);
+ int inner_sy = (height >= 3 * tile_height ? tile_height : 0);
+ boolean draw_masked = graphic_info[graphic].draw_masked;
+
+ getFixedGraphicSource(graphic, 0, &src_bitmap, &src_x, &src_y);
+
+ if (src_bitmap == NULL || width < tile_width || height < tile_height)
+ {
+ ClearRectangle(drawto, dst_x, dst_y, tile_width, tile_height);
+ return;
+ }
+
+ src_x += (x == 0 ? 0 : x == xsize - 1 ? width - tile_width :
+ inner_sx + (x - 1) * tile_width % inner_width);
+ src_y += (y == 0 ? 0 : y == ysize - 1 ? height - tile_height :
+ inner_sy + (y - 1) * tile_height % inner_height);
+
+ if (draw_masked)
+ BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, tile_width, tile_height,
+ dst_x, dst_y);
+ else
+ BlitBitmap(src_bitmap, drawto, src_x, src_y, tile_width, tile_height,
+ dst_x, dst_y);
+}
+
+void DrawEnvelopeBackground(int graphic, int startx, int starty,
+ int x, int y, int xsize, int ysize, int font_nr)
+{
+ int font_width = getFontWidth(font_nr);
+ int font_height = getFontHeight(font_nr);
+
+ DrawEnvelopeBackgroundTiles(graphic, startx, starty, x, y, xsize, ysize,
+ font_width, font_height);
+}
+
+void AnimateEnvelope(int envelope_nr, int anim_mode, int action)
+{
+ int graphic = IMG_BACKGROUND_ENVELOPE_1 + envelope_nr;
+ Bitmap *src_bitmap = graphic_info[graphic].bitmap;
+ int mask_mode = (src_bitmap != NULL ? BLIT_MASKED : BLIT_ON_BACKGROUND);
+ boolean ffwd_delay = (tape.playing && tape.fast_forward);
+ boolean no_delay = (tape.warp_forward);
+ unsigned int anim_delay = 0;
+ int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
+ int anim_delay_value = MAX(1, (no_delay ? 0 : frame_delay_value) / 2);
+ int font_nr = FONT_ENVELOPE_1 + envelope_nr;
+ int font_width = getFontWidth(font_nr);
+ int font_height = getFontHeight(font_nr);
+ int max_xsize = level.envelope[envelope_nr].xsize;
+ int max_ysize = level.envelope[envelope_nr].ysize;
+ int xstart = (anim_mode & ANIM_VERTICAL ? max_xsize : 0);
+ int ystart = (anim_mode & ANIM_HORIZONTAL ? max_ysize : 0);
+ int xend = max_xsize;
+ int yend = (anim_mode != ANIM_DEFAULT ? max_ysize : 0);
+ int xstep = (xstart < xend ? 1 : 0);
+ int ystep = (ystart < yend || xstep == 0 ? 1 : 0);
+ int start = 0;
+ int end = MAX(xend - xstart, yend - ystart);
+ int i;
+
+ for (i = start; i <= end; i++)
+ {
+ int last_frame = end; // last frame of this "for" loop
+ int x = xstart + i * xstep;
+ int y = ystart + i * ystep;
+ int xsize = (action == ACTION_CLOSING ? xend - (x - xstart) : x) + 2;
+ int ysize = (action == ACTION_CLOSING ? yend - (y - ystart) : y) + 2;
+ int sx = SX + (SXSIZE - xsize * font_width) / 2;
+ int sy = SY + (SYSIZE - ysize * font_height) / 2;
+ int xx, yy;
+
+ SetDrawtoField(DRAW_TO_FIELDBUFFER);
+
+ BlitScreenToBitmap(backbuffer);
+
+ SetDrawtoField(DRAW_TO_BACKBUFFER);
+
+ for (yy = 0; yy < ysize; yy++)
+ for (xx = 0; xx < xsize; xx++)
+ DrawEnvelopeBackground(graphic, sx, sy, xx, yy, xsize, ysize, font_nr);
+
+ DrawTextBuffer(sx + font_width, sy + font_height,
+ level.envelope[envelope_nr].text, font_nr, max_xsize,
+ xsize - 2, ysize - 2, 0, mask_mode,
+ level.envelope[envelope_nr].autowrap,
+ level.envelope[envelope_nr].centered, FALSE);
+
+ redraw_mask |= REDRAW_FIELD;
+ BackToFront();
+
+ SkipUntilDelayReached(&anim_delay, anim_delay_value, &i, last_frame);
+ }
+}
+
+void ShowEnvelope(int envelope_nr)
+{
+ int element = EL_ENVELOPE_1 + envelope_nr;
+ int graphic = IMG_BACKGROUND_ENVELOPE_1 + envelope_nr;
+ int sound_opening = element_info[element].sound[ACTION_OPENING];
+ int sound_closing = element_info[element].sound[ACTION_CLOSING];
+ boolean ffwd_delay = (tape.playing && tape.fast_forward);
+ boolean no_delay = (tape.warp_forward);
+ int normal_delay_value = ONE_SECOND_DELAY / (ffwd_delay ? 2 : 1);
+ int wait_delay_value = (no_delay ? 0 : normal_delay_value);
+ int anim_mode = graphic_info[graphic].anim_mode;
+ int main_anim_mode = (anim_mode == ANIM_NONE ? ANIM_VERTICAL|ANIM_HORIZONTAL:
+ anim_mode == ANIM_DEFAULT ? ANIM_VERTICAL : anim_mode);
+
+ game.envelope_active = TRUE; /* needed for RedrawPlayfield() events */
+
+ PlayMenuSoundStereo(sound_opening, SOUND_MIDDLE);
+
+ if (anim_mode == ANIM_DEFAULT)
+ AnimateEnvelope(envelope_nr, ANIM_DEFAULT, ACTION_OPENING);
+
+ AnimateEnvelope(envelope_nr, main_anim_mode, ACTION_OPENING);
+
+ if (tape.playing)
+ Delay(wait_delay_value);
+ else
+ WaitForEventToContinue();
+
+ PlayMenuSoundStereo(sound_closing, SOUND_MIDDLE);
+
+ if (anim_mode != ANIM_NONE)
+ AnimateEnvelope(envelope_nr, main_anim_mode, ACTION_CLOSING);
+
+ if (anim_mode == ANIM_DEFAULT)
+ AnimateEnvelope(envelope_nr, ANIM_DEFAULT, ACTION_CLOSING);
+
+ game.envelope_active = FALSE;
+
+ SetDrawtoField(DRAW_TO_FIELDBUFFER);
+
+ redraw_mask |= REDRAW_FIELD;
+ BackToFront();
+}
+
+static void setRequestBasePosition(int *x, int *y)
+{
+ int sx_base, sy_base;
+
+ if (request.x != -1)
+ sx_base = request.x;
+ else if (request.align == ALIGN_LEFT)
+ sx_base = SX;
+ else if (request.align == ALIGN_RIGHT)
+ sx_base = SX + SXSIZE;
+ else
+ sx_base = SX + SXSIZE / 2;
+
+ if (request.y != -1)
+ sy_base = request.y;
+ else if (request.valign == VALIGN_TOP)
+ sy_base = SY;
+ else if (request.valign == VALIGN_BOTTOM)
+ sy_base = SY + SYSIZE;
+ else
+ sy_base = SY + SYSIZE / 2;
+
+ *x = sx_base;
+ *y = sy_base;
+}
+
+static void setRequestPositionExt(int *x, int *y, int width, int height,
+ boolean add_border_size)
+{
+ int border_size = request.border_size;
+ int sx_base, sy_base;
+ int sx, sy;
+
+ setRequestBasePosition(&sx_base, &sy_base);
+
+ if (request.align == ALIGN_LEFT)
+ sx = sx_base;
+ else if (request.align == ALIGN_RIGHT)
+ sx = sx_base - width;
+ else
+ sx = sx_base - width / 2;
+
+ if (request.valign == VALIGN_TOP)
+ sy = sy_base;
+ else if (request.valign == VALIGN_BOTTOM)
+ sy = sy_base - height;
+ else
+ sy = sy_base - height / 2;
+
+ sx = MAX(0, MIN(sx, WIN_XSIZE - width));
+ sy = MAX(0, MIN(sy, WIN_YSIZE - height));
+
+ if (add_border_size)
+ {
+ sx += border_size;
+ sy += border_size;
+ }
+
+ *x = sx;
+ *y = sy;
+}
+
+static void setRequestPosition(int *x, int *y, boolean add_border_size)
+{
+ setRequestPositionExt(x, y, request.width, request.height, add_border_size);
+}
+
+void DrawEnvelopeRequest(char *text)
+{
+ char *text_final = text;
+ char *text_door_style = NULL;
+ int graphic = IMG_BACKGROUND_REQUEST;
+ Bitmap *src_bitmap = graphic_info[graphic].bitmap;
+ int mask_mode = (src_bitmap != NULL ? BLIT_MASKED : BLIT_ON_BACKGROUND);
+ int font_nr = FONT_REQUEST;
+ int font_width = getFontWidth(font_nr);
+ int font_height = getFontHeight(font_nr);
+ int border_size = request.border_size;
+ int line_spacing = request.line_spacing;
+ int line_height = font_height + line_spacing;
+ int max_text_width = request.width - 2 * border_size;
+ int max_text_height = request.height - 2 * border_size;
+ int line_length = max_text_width / font_width;
+ int max_lines = max_text_height / line_height;
+ int text_width = line_length * font_width;
+ int width = request.width;
+ int height = request.height;
+ int tile_size = MAX(request.step_offset, 1);
+ int x_steps = width / tile_size;
+ int y_steps = height / tile_size;
+ int sx_offset = border_size;
+ int sy_offset = border_size;
+ int sx, sy;
+ int i, x, y;
+
+ if (request.centered)
+ sx_offset = (request.width - text_width) / 2;
+
+ if (request.wrap_single_words && !request.autowrap)
+ {
+ char *src_text_ptr, *dst_text_ptr;
+
+ text_door_style = checked_malloc(2 * strlen(text) + 1);
+
+ src_text_ptr = text;
+ dst_text_ptr = text_door_style;
+
+ while (*src_text_ptr)
+ {
+ if (*src_text_ptr == ' ' ||
+ *src_text_ptr == '?' ||
+ *src_text_ptr == '!')
+ *dst_text_ptr++ = '\n';
+
+ if (*src_text_ptr != ' ')
+ *dst_text_ptr++ = *src_text_ptr;
+
+ src_text_ptr++;
+ }
+
+ *dst_text_ptr = '\0';
+
+ text_final = text_door_style;
+ }
+
+ setRequestPosition(&sx, &sy, FALSE);
+
+ ClearRectangle(backbuffer, 0, 0, WIN_XSIZE, WIN_YSIZE);
+
+ for (y = 0; y < y_steps; y++)
+ for (x = 0; x < x_steps; x++)
+ DrawEnvelopeBackgroundTiles(graphic, sx, sy,
+ x, y, x_steps, y_steps,
+ tile_size, tile_size);
+
+ /* force DOOR font inside door area */
+ SetFontStatus(GAME_MODE_PSEUDO_DOOR);
+
+ DrawTextBuffer(sx + sx_offset, sy + sy_offset, text_final, font_nr,
+ line_length, -1, max_lines, line_spacing, mask_mode,
+ request.autowrap, request.centered, FALSE);
+
+ ResetFontStatus();
+
+ for (i = 0; i < NUM_TOOL_BUTTONS; i++)
+ RedrawGadget(tool_gadget[i]);
+
+ // store readily prepared envelope request for later use when animating
+ BlitBitmap(backbuffer, bitmap_db_store_2, 0, 0, WIN_XSIZE, WIN_YSIZE, 0, 0);
+
+ if (text_door_style)
+ free(text_door_style);
+}
+
+void AnimateEnvelopeRequest(int anim_mode, int action)
+{
+ int graphic = IMG_BACKGROUND_REQUEST;
+ boolean draw_masked = graphic_info[graphic].draw_masked;
+ int delay_value_normal = request.step_delay;
+ int delay_value_fast = delay_value_normal / 2;
+ boolean ffwd_delay = (tape.playing && tape.fast_forward);
+ boolean no_delay = (tape.warp_forward);
+ int delay_value = (ffwd_delay ? delay_value_fast : delay_value_normal);
+ int anim_delay_value = MAX(1, (no_delay ? 0 : delay_value + 500 * 0) / 2);
+ unsigned int anim_delay = 0;
+
+ int tile_size = MAX(request.step_offset, 1);
+ int max_xsize = request.width / tile_size;
+ int max_ysize = request.height / tile_size;
+ int max_xsize_inner = max_xsize - 2;
+ int max_ysize_inner = max_ysize - 2;
+
+ int xstart = (anim_mode & ANIM_VERTICAL ? max_xsize_inner : 0);
+ int ystart = (anim_mode & ANIM_HORIZONTAL ? max_ysize_inner : 0);
+ int xend = max_xsize_inner;
+ int yend = (anim_mode != ANIM_DEFAULT ? max_ysize_inner : 0);
+ int xstep = (xstart < xend ? 1 : 0);
+ int ystep = (ystart < yend || xstep == 0 ? 1 : 0);
+ int start = 0;
+ int end = MAX(xend - xstart, yend - ystart);
+ int i;
+
+ if (setup.quick_doors)
+ {
+ xstart = xend;
+ ystart = yend;
+ end = 0;
+ }
+
+ for (i = start; i <= end; i++)
+ {
+ int last_frame = end; // last frame of this "for" loop
+ int x = xstart + i * xstep;
+ int y = ystart + i * ystep;
+ int xsize = (action == ACTION_CLOSING ? xend - (x - xstart) : x) + 2;
+ int ysize = (action == ACTION_CLOSING ? yend - (y - ystart) : y) + 2;
+ int xsize_size_left = (xsize - 1) * tile_size;
+ int ysize_size_top = (ysize - 1) * tile_size;
+ int max_xsize_pos = (max_xsize - 1) * tile_size;
+ int max_ysize_pos = (max_ysize - 1) * tile_size;
+ int width = xsize * tile_size;
+ int height = ysize * tile_size;
+ int src_x, src_y;
+ int dst_x, dst_y;
+ int xx, yy;
+
+ setRequestPosition(&src_x, &src_y, FALSE);
+ setRequestPositionExt(&dst_x, &dst_y, width, height, FALSE);
+
+ BlitBitmap(bitmap_db_store_1, backbuffer, 0, 0, WIN_XSIZE, WIN_YSIZE, 0, 0);
+
+ for (yy = 0; yy < 2; yy++)
+ {
+ for (xx = 0; xx < 2; xx++)
+ {
+ int src_xx = src_x + xx * max_xsize_pos;
+ int src_yy = src_y + yy * max_ysize_pos;
+ int dst_xx = dst_x + xx * xsize_size_left;
+ int dst_yy = dst_y + yy * ysize_size_top;
+ int xx_size = (xx ? tile_size : xsize_size_left);
+ int yy_size = (yy ? tile_size : ysize_size_top);
+
+ if (draw_masked)
+ BlitBitmapMasked(bitmap_db_store_2, backbuffer,
+ src_xx, src_yy, xx_size, yy_size, dst_xx, dst_yy);
+ else
+ BlitBitmap(bitmap_db_store_2, backbuffer,
+ src_xx, src_yy, xx_size, yy_size, dst_xx, dst_yy);
+ }
+ }
+
+ redraw_mask |= REDRAW_FIELD;
+
+ BackToFront();
+
+ SkipUntilDelayReached(&anim_delay, anim_delay_value, &i, last_frame);
+ }
+}
+
+void ShowEnvelopeRequest(char *text, unsigned int req_state, int action)
+{
+ int graphic = IMG_BACKGROUND_REQUEST;
+ int sound_opening = SND_REQUEST_OPENING;
+ int sound_closing = SND_REQUEST_CLOSING;
+ int anim_mode_1 = request.anim_mode; /* (higher priority) */
+ int anim_mode_2 = graphic_info[graphic].anim_mode; /* (lower priority) */
+ int anim_mode = (anim_mode_1 != ANIM_DEFAULT ? anim_mode_1 : anim_mode_2);
+ int main_anim_mode = (anim_mode == ANIM_NONE ? ANIM_VERTICAL|ANIM_HORIZONTAL:
+ anim_mode == ANIM_DEFAULT ? ANIM_VERTICAL : anim_mode);
+
+ if (game_status == GAME_MODE_PLAYING)
+ BlitScreenToBitmap(backbuffer);
+
+ SetDrawtoField(DRAW_TO_BACKBUFFER);
+
+ // SetDrawBackgroundMask(REDRAW_NONE);
+
+ if (action == ACTION_OPENING)
+ {
+ BlitBitmap(backbuffer, bitmap_db_store_1, 0, 0, WIN_XSIZE, WIN_YSIZE, 0, 0);
+
+ if (req_state & REQ_ASK)
+ {
+ MapGadget(tool_gadget[TOOL_CTRL_ID_YES]);
+ MapGadget(tool_gadget[TOOL_CTRL_ID_NO]);
+ }
+ else if (req_state & REQ_CONFIRM)
+ {
+ MapGadget(tool_gadget[TOOL_CTRL_ID_CONFIRM]);
+ }
+ else if (req_state & REQ_PLAYER)
+ {
+ MapGadget(tool_gadget[TOOL_CTRL_ID_PLAYER_1]);
+ MapGadget(tool_gadget[TOOL_CTRL_ID_PLAYER_2]);
+ MapGadget(tool_gadget[TOOL_CTRL_ID_PLAYER_3]);
+ MapGadget(tool_gadget[TOOL_CTRL_ID_PLAYER_4]);
+ }
+
+ DrawEnvelopeRequest(text);
+ }
+
+ game.envelope_active = TRUE; /* needed for RedrawPlayfield() events */
+
+ if (action == ACTION_OPENING)
+ {
+ PlayMenuSoundStereo(sound_opening, SOUND_MIDDLE);
+
+ if (anim_mode == ANIM_DEFAULT)
+ AnimateEnvelopeRequest(ANIM_DEFAULT, ACTION_OPENING);
+
+ AnimateEnvelopeRequest(main_anim_mode, ACTION_OPENING);
+ }
+ else
+ {
+ PlayMenuSoundStereo(sound_closing, SOUND_MIDDLE);
+
+ if (anim_mode != ANIM_NONE)
+ AnimateEnvelopeRequest(main_anim_mode, ACTION_CLOSING);
+
+ if (anim_mode == ANIM_DEFAULT)
+ AnimateEnvelopeRequest(ANIM_DEFAULT, ACTION_CLOSING);
+ }
+
+ game.envelope_active = FALSE;
+
+ if (action == ACTION_CLOSING)
+ BlitBitmap(bitmap_db_store_1, backbuffer, 0, 0, WIN_XSIZE, WIN_YSIZE, 0, 0);
+
+ // SetDrawBackgroundMask(last_draw_background_mask);
+
+ redraw_mask |= REDRAW_FIELD;
+
+ BackToFront();
+
+ if (action == ACTION_CLOSING &&
+ game_status == GAME_MODE_PLAYING &&
+ level.game_engine_type == GAME_ENGINE_TYPE_RND)
+ SetDrawtoField(DRAW_TO_FIELDBUFFER);
+}
+
+void DrawPreviewElement(int dst_x, int dst_y, int element, int tilesize)
+{
+ if (IS_MM_WALL(element))
+ {
+ DrawSizedWall_MM(dst_x, dst_y, element, tilesize, el2preimg);
+ }
+ else
+ {
+ Bitmap *src_bitmap;
+ int src_x, src_y;
+ int graphic = el2preimg(element);
+
+ getSizedGraphicSource(graphic, 0, tilesize, &src_bitmap, &src_x, &src_y);
+ BlitBitmap(src_bitmap, drawto, src_x, src_y, tilesize, tilesize,
+ dst_x, dst_y);
+ }
+}
+
+void DrawLevel(int draw_background_mask)
+{
+ int x,y;
+
+ SetMainBackgroundImage(IMG_BACKGROUND_PLAYING);
+ SetDrawBackgroundMask(draw_background_mask);
+
+ ClearField();
+
+ for (x = BX1; x <= BX2; x++)
+ for (y = BY1; y <= BY2; y++)
+ DrawScreenField(x, y);
+
+ redraw_mask |= REDRAW_FIELD;
+}
+
+void DrawSizedLevel(int size_x, int size_y, int scroll_x, int scroll_y,
+ int tilesize)
+{
+ int x,y;
+
+ for (x = 0; x < size_x; x++)
+ for (y = 0; y < size_y; y++)
+ DrawSizedElementOrWall(x, y, scroll_x, scroll_y, tilesize);
+
+ redraw_mask |= REDRAW_FIELD;
+}
+
+void DrawMiniLevel(int size_x, int size_y, int scroll_x, int scroll_y)
+{
+ int x,y;
+
+ for (x = 0; x < size_x; x++)
+ for (y = 0; y < size_y; y++)
+ DrawMiniElementOrWall(x, y, scroll_x, scroll_y);
+
+ redraw_mask |= REDRAW_FIELD;
+}
+
+static void DrawPreviewLevelPlayfield(int from_x, int from_y)
+{
+ boolean show_level_border = (BorderElement != EL_EMPTY);
+ int level_xsize = lev_fieldx + (show_level_border ? 2 : 0);
+ int level_ysize = lev_fieldy + (show_level_border ? 2 : 0);
+ int tile_size = preview.tile_size;
+ int preview_width = preview.xsize * tile_size;
+ int preview_height = preview.ysize * tile_size;
+ int real_preview_xsize = MIN(level_xsize, preview.xsize);
+ int real_preview_ysize = MIN(level_ysize, preview.ysize);
+ int real_preview_width = real_preview_xsize * tile_size;
+ int real_preview_height = real_preview_ysize * tile_size;
+ int dst_x = SX + ALIGNED_XPOS(preview.x, preview_width, preview.align);
+ int dst_y = SY + ALIGNED_YPOS(preview.y, preview_height, preview.valign);
+ int x, y;
+
+ if (!IN_GFX_FIELD_FULL(dst_x, dst_y + preview_height - 1))
+ return;
+
+ DrawBackground(dst_x, dst_y, preview_width, preview_height);
+
+ dst_x += (preview_width - real_preview_width) / 2;
+ dst_y += (preview_height - real_preview_height) / 2;
+
+ for (x = 0; x < real_preview_xsize; x++)
+ {
+ for (y = 0; y < real_preview_ysize; y++)
+ {
+ int lx = from_x + x + (show_level_border ? -1 : 0);
+ int ly = from_y + y + (show_level_border ? -1 : 0);
+ int element = (IN_LEV_FIELD(lx, ly) ? level.field[lx][ly] :
+ getBorderElement(lx, ly));
+
+ DrawPreviewElement(dst_x + x * tile_size, dst_y + y * tile_size,
+ element, tile_size);
+ }
+ }
+
+ redraw_mask |= REDRAW_FIELD;
+}
+
+#define MICROLABEL_EMPTY 0
+#define MICROLABEL_LEVEL_NAME 1
+#define MICROLABEL_LEVEL_AUTHOR_HEAD 2
+#define MICROLABEL_LEVEL_AUTHOR 3
+#define MICROLABEL_IMPORTED_FROM_HEAD 4
+#define MICROLABEL_IMPORTED_FROM 5
+#define MICROLABEL_IMPORTED_BY_HEAD 6
+#define MICROLABEL_IMPORTED_BY 7
+
+static int getMaxTextLength(struct TextPosInfo *pos, int font_nr)
+{
+ int max_text_width = SXSIZE;
+ int font_width = getFontWidth(font_nr);
+
+ if (pos->align == ALIGN_CENTER)
+ max_text_width = (pos->x < SXSIZE / 2 ? pos->x * 2 : (SXSIZE - pos->x) * 2);
+ else if (pos->align == ALIGN_RIGHT)
+ max_text_width = pos->x;
+ else
+ max_text_width = SXSIZE - pos->x;
+
+ return max_text_width / font_width;
+}
+
+static void DrawPreviewLevelLabelExt(int mode, struct TextPosInfo *pos)
+{
+ char label_text[MAX_OUTPUT_LINESIZE + 1];
+ int max_len_label_text;
+ int font_nr = pos->font;
+ int i;
+
+ if (!IN_GFX_FIELD_FULL(pos->x, pos->y + getFontHeight(pos->font)))
+ return;
+
+ if (mode == MICROLABEL_LEVEL_AUTHOR_HEAD ||
+ mode == MICROLABEL_IMPORTED_FROM_HEAD ||
+ mode == MICROLABEL_IMPORTED_BY_HEAD)
+ font_nr = pos->font_alt;
+
+ max_len_label_text = getMaxTextLength(pos, font_nr);
+
+ if (pos->size != -1)
+ max_len_label_text = pos->size;
+
+ for (i = 0; i < max_len_label_text; i++)
+ label_text[i] = ' ';
+ label_text[max_len_label_text] = '\0';
+
+ if (strlen(label_text) > 0)
+ DrawTextSAligned(pos->x, pos->y, label_text, font_nr, pos->align);
+
+ strncpy(label_text,
+ (mode == MICROLABEL_LEVEL_NAME ? level.name :
+ mode == MICROLABEL_LEVEL_AUTHOR_HEAD ? "created by" :
+ mode == MICROLABEL_LEVEL_AUTHOR ? level.author :
+ mode == MICROLABEL_IMPORTED_FROM_HEAD ? "imported from" :
+ mode == MICROLABEL_IMPORTED_FROM ? leveldir_current->imported_from :
+ mode == MICROLABEL_IMPORTED_BY_HEAD ? "imported by" :
+ mode == MICROLABEL_IMPORTED_BY ? leveldir_current->imported_by :""),
+ max_len_label_text);
+ label_text[max_len_label_text] = '\0';
+
+ if (strlen(label_text) > 0)
+ DrawTextSAligned(pos->x, pos->y, label_text, font_nr, pos->align);
+
+ redraw_mask |= REDRAW_FIELD;
+}
+
+static void DrawPreviewLevelLabel(int mode)
+{
+ DrawPreviewLevelLabelExt(mode, &menu.main.text.level_info_2);
+}
+
+static void DrawPreviewLevelInfo(int mode)
+{
+ if (mode == MICROLABEL_LEVEL_NAME)
+ DrawPreviewLevelLabelExt(mode, &menu.main.text.level_name);
+ else if (mode == MICROLABEL_LEVEL_AUTHOR)
+ DrawPreviewLevelLabelExt(mode, &menu.main.text.level_author);
+}
+
+static void DrawPreviewLevelExt(boolean restart)
+{
+ static unsigned int scroll_delay = 0;
+ static unsigned int label_delay = 0;
+ static int from_x, from_y, scroll_direction;
+ static int label_state, label_counter;
+ unsigned int scroll_delay_value = preview.step_delay;
+ boolean show_level_border = (BorderElement != EL_EMPTY);
+ int level_xsize = lev_fieldx + (show_level_border ? 2 : 0);
+ int level_ysize = lev_fieldy + (show_level_border ? 2 : 0);
+
+ if (restart)
+ {
+ from_x = 0;
+ from_y = 0;
+
+ if (preview.anim_mode == ANIM_CENTERED)
+ {
+ if (level_xsize > preview.xsize)
+ from_x = (level_xsize - preview.xsize) / 2;
+ if (level_ysize > preview.ysize)
+ from_y = (level_ysize - preview.ysize) / 2;
+ }
+
+ from_x += preview.xoffset;
+ from_y += preview.yoffset;
+
+ scroll_direction = MV_RIGHT;
+ label_state = 1;
+ label_counter = 0;
+
+ DrawPreviewLevelPlayfield(from_x, from_y);
+ DrawPreviewLevelLabel(label_state);
+
+ DrawPreviewLevelInfo(MICROLABEL_LEVEL_NAME);
+ DrawPreviewLevelInfo(MICROLABEL_LEVEL_AUTHOR);
+
+ /* initialize delay counters */
+ DelayReached(&scroll_delay, 0);
+ DelayReached(&label_delay, 0);
+
+ if (leveldir_current->name)
+ {
+ struct TextPosInfo *pos = &menu.main.text.level_info_1;
+ char label_text[MAX_OUTPUT_LINESIZE + 1];
+ int font_nr = pos->font;
+ int max_len_label_text = getMaxTextLength(pos, font_nr);
+
+ if (pos->size != -1)
+ max_len_label_text = pos->size;
+
+ strncpy(label_text, leveldir_current->name, max_len_label_text);
+ label_text[max_len_label_text] = '\0';
+
+ if (IN_GFX_FIELD_FULL(pos->x, pos->y + getFontHeight(pos->font)))
+ DrawTextSAligned(pos->x, pos->y, label_text, font_nr, pos->align);
+ }
+
+ return;
+ }
+
+ /* scroll preview level, if needed */
+ if (preview.anim_mode != ANIM_NONE &&
+ (level_xsize > preview.xsize || level_ysize > preview.ysize) &&
+ DelayReached(&scroll_delay, scroll_delay_value))
+ {
+ switch (scroll_direction)
+ {
+ case MV_LEFT:
+ if (from_x > 0)
+ {
+ from_x -= preview.step_offset;
+ from_x = (from_x < 0 ? 0 : from_x);
+ }
+ else
+ scroll_direction = MV_UP;
+ break;
+
+ case MV_RIGHT:
+ if (from_x < level_xsize - preview.xsize)
+ {
+ from_x += preview.step_offset;
+ from_x = (from_x > level_xsize - preview.xsize ?
+ level_xsize - preview.xsize : from_x);
+ }
+ else
+ scroll_direction = MV_DOWN;
+ break;
+
+ case MV_UP:
+ if (from_y > 0)
+ {
+ from_y -= preview.step_offset;
+ from_y = (from_y < 0 ? 0 : from_y);
+ }
+ else
+ scroll_direction = MV_RIGHT;
+ break;
+
+ case MV_DOWN:
+ if (from_y < level_ysize - preview.ysize)
+ {
+ from_y += preview.step_offset;
+ from_y = (from_y > level_ysize - preview.ysize ?
+ level_ysize - preview.ysize : from_y);
+ }
+ else
+ scroll_direction = MV_LEFT;
+ break;
+
+ default:
+ break;
+ }
+
+ DrawPreviewLevelPlayfield(from_x, from_y);
+ }
+
+ /* !!! THIS ALL SUCKS -- SHOULD BE CLEANLY REWRITTEN !!! */
+ /* redraw micro level label, if needed */
+ if (!strEqual(level.name, NAMELESS_LEVEL_NAME) &&
+ !strEqual(level.author, ANONYMOUS_NAME) &&
+ !strEqual(level.author, leveldir_current->name) &&
+ DelayReached(&label_delay, MICROLEVEL_LABEL_DELAY))
+ {
+ int max_label_counter = 23;
+
+ if (leveldir_current->imported_from != NULL &&
+ strlen(leveldir_current->imported_from) > 0)
+ max_label_counter += 14;
+ if (leveldir_current->imported_by != NULL &&
+ strlen(leveldir_current->imported_by) > 0)
+ max_label_counter += 14;
+
+ label_counter = (label_counter + 1) % max_label_counter;
+ label_state = (label_counter >= 0 && label_counter <= 7 ?
+ MICROLABEL_LEVEL_NAME :
+ label_counter >= 9 && label_counter <= 12 ?
+ MICROLABEL_LEVEL_AUTHOR_HEAD :
+ label_counter >= 14 && label_counter <= 21 ?
+ MICROLABEL_LEVEL_AUTHOR :
+ label_counter >= 23 && label_counter <= 26 ?
+ MICROLABEL_IMPORTED_FROM_HEAD :
+ label_counter >= 28 && label_counter <= 35 ?
+ MICROLABEL_IMPORTED_FROM :
+ label_counter >= 37 && label_counter <= 40 ?
+ MICROLABEL_IMPORTED_BY_HEAD :
+ label_counter >= 42 && label_counter <= 49 ?
+ MICROLABEL_IMPORTED_BY : MICROLABEL_EMPTY);
+
+ if (leveldir_current->imported_from == NULL &&
+ (label_state == MICROLABEL_IMPORTED_FROM_HEAD ||
+ label_state == MICROLABEL_IMPORTED_FROM))
+ label_state = (label_state == MICROLABEL_IMPORTED_FROM_HEAD ?
+ MICROLABEL_IMPORTED_BY_HEAD : MICROLABEL_IMPORTED_BY);
+
+ DrawPreviewLevelLabel(label_state);
+ }
+}
+
+void DrawPreviewLevelInitial()
+{
+ DrawPreviewLevelExt(TRUE);
+}
+
+void DrawPreviewLevelAnimation()
+{
+ DrawPreviewLevelExt(FALSE);
+}
+
+inline static void DrawGraphicAnimationExt(DrawBuffer *dst_bitmap, int x, int y,
+ int graphic, int sync_frame,
+ int mask_mode)
+{
+ int frame = getGraphicAnimationFrame(graphic, sync_frame);
+
+ if (mask_mode == USE_MASKING)
+ DrawGraphicThruMaskExt(dst_bitmap, x, y, graphic, frame);
+ else
+ DrawGraphicExt(dst_bitmap, x, y, graphic, frame);
+}
+
+void DrawFixedGraphicAnimationExt(DrawBuffer *dst_bitmap, int x, int y,
+ int graphic, int sync_frame, int mask_mode)
+{
+ int frame = getGraphicAnimationFrame(graphic, sync_frame);
+
+ if (mask_mode == USE_MASKING)
+ DrawFixedGraphicThruMaskExt(dst_bitmap, x, y, graphic, frame);
+ else
+ DrawFixedGraphicExt(dst_bitmap, x, y, graphic, frame);
+}
+
+inline static void DrawGraphicAnimation(int x, int y, int graphic)
+{
+ int lx = LEVELX(x), ly = LEVELY(y);
+
+ if (!IN_SCR_FIELD(x, y))
+ return;
+
+ DrawGraphicAnimationExt(drawto_field, FX + x * TILEX_VAR, FY + y * TILEY_VAR,
+ graphic, GfxFrame[lx][ly], NO_MASKING);
+
+ MarkTileDirty(x, y);
+}
+
+void DrawFixedGraphicAnimation(int x, int y, int graphic)
+{
+ int lx = LEVELX(x), ly = LEVELY(y);
+
+ if (!IN_SCR_FIELD(x, y))
+ return;
+
+ DrawGraphicAnimationExt(drawto_field, FX + x * TILEX, FY + y * TILEY,
+ graphic, GfxFrame[lx][ly], NO_MASKING);
+ MarkTileDirty(x, y);
+}
+
+void DrawLevelGraphicAnimation(int x, int y, int graphic)
+{
+ DrawGraphicAnimation(SCREENX(x), SCREENY(y), graphic);
+}
+
+void DrawLevelElementAnimation(int x, int y, int element)
+{
+ int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
+
+ DrawGraphicAnimation(SCREENX(x), SCREENY(y), graphic);
+}
+
+void DrawLevelGraphicAnimationIfNeeded(int x, int y, int graphic)
+{
+ int sx = SCREENX(x), sy = SCREENY(y);
+
+ if (!IN_LEV_FIELD(x, y) || !IN_SCR_FIELD(sx, sy))
+ return;
+
+ if (!IS_NEW_FRAME(GfxFrame[x][y], graphic))
+ return;
+
+ DrawGraphicAnimation(sx, sy, graphic);
+
+#if 1
+ if (GFX_CRUMBLED(TILE_GFX_ELEMENT(x, y)))
+ DrawLevelFieldCrumbled(x, y);
+#else
+ if (GFX_CRUMBLED(Feld[x][y]))
+ DrawLevelFieldCrumbled(x, y);
+#endif
+}
+
+void DrawLevelElementAnimationIfNeeded(int x, int y, int element)
+{
+ int sx = SCREENX(x), sy = SCREENY(y);
+ int graphic;
+
+ if (!IN_LEV_FIELD(x, y) || !IN_SCR_FIELD(sx, sy))
+ return;
+
+ graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
+
+ if (!IS_NEW_FRAME(GfxFrame[x][y], graphic))
+ return;
+
+ DrawGraphicAnimation(sx, sy, graphic);
+
+ if (GFX_CRUMBLED(element))
+ DrawLevelFieldCrumbled(x, y);
+}
+
+static int getPlayerGraphic(struct PlayerInfo *player, int move_dir)
+{
+ if (player->use_murphy)
+ {
+ /* this works only because currently only one player can be "murphy" ... */
+ static int last_horizontal_dir = MV_LEFT;
+ int graphic = el_act_dir2img(EL_SP_MURPHY, player->GfxAction, move_dir);
+
+ if (move_dir == MV_LEFT || move_dir == MV_RIGHT)
+ last_horizontal_dir = move_dir;
+
+ if (graphic == IMG_SP_MURPHY) /* undefined => use special graphic */
+ {
+ int direction = (player->is_snapping ? move_dir : last_horizontal_dir);
+
+ graphic = el_act_dir2img(EL_SP_MURPHY, player->GfxAction, direction);
+ }
+
+ return graphic;
+ }
+ else
+ return el_act_dir2img(player->artwork_element, player->GfxAction,move_dir);
+}
+
+static boolean equalGraphics(int graphic1, int graphic2)
+{
+ struct GraphicInfo *g1 = &graphic_info[graphic1];
+ struct GraphicInfo *g2 = &graphic_info[graphic2];
+
+ return (g1->bitmap == g2->bitmap &&
+ g1->src_x == g2->src_x &&
+ g1->src_y == g2->src_y &&
+ g1->anim_frames == g2->anim_frames &&
+ g1->anim_delay == g2->anim_delay &&
+ g1->anim_mode == g2->anim_mode);
+}
+
+void DrawAllPlayers()
+{
+ int i;
+
+ for (i = 0; i < MAX_PLAYERS; i++)
+ if (stored_player[i].active)
+ DrawPlayer(&stored_player[i]);
+}
+
+void DrawPlayerField(int x, int y)
+{
+ if (!IS_PLAYER(x, y))
+ return;
+
+ DrawPlayer(PLAYERINFO(x, y));
+}
+
+#define DRAW_PLAYER_OVER_PUSHED_ELEMENT 1
+
+void DrawPlayer(struct PlayerInfo *player)
+{
+ int jx = player->jx;
+ int jy = player->jy;
+ int move_dir = player->MovDir;
+ int dx = (move_dir == MV_LEFT ? -1 : move_dir == MV_RIGHT ? +1 : 0);
+ int dy = (move_dir == MV_UP ? -1 : move_dir == MV_DOWN ? +1 : 0);
+ int last_jx = (player->is_moving ? jx - dx : jx);
+ int last_jy = (player->is_moving ? jy - dy : jy);
+ int next_jx = jx + dx;
+ int next_jy = jy + dy;
+ boolean player_is_moving = (player->MovPos ? TRUE : FALSE);
+ boolean player_is_opaque = FALSE;
+ int sx = SCREENX(jx), sy = SCREENY(jy);
+ int sxx = 0, syy = 0;
+ int element = Feld[jx][jy], last_element = Feld[last_jx][last_jy];
+ int graphic;
+ int action = ACTION_DEFAULT;
+ int last_player_graphic = getPlayerGraphic(player, move_dir);
+ int last_player_frame = player->Frame;
+ int frame = 0;
+
+ /* GfxElement[][] is set to the element the player is digging or collecting;
+ remove also for off-screen player if the player is not moving anymore */
+ if (IN_LEV_FIELD(jx, jy) && !player_is_moving)
+ GfxElement[jx][jy] = EL_UNDEFINED;
+
+ if (!player->active || !IN_SCR_FIELD(SCREENX(last_jx), SCREENY(last_jy)))
+ return;
+
+#if DEBUG
+ if (!IN_LEV_FIELD(jx, jy))
+ {
+ printf("DrawPlayerField(): x = %d, y = %d\n",jx,jy);
+ printf("DrawPlayerField(): sx = %d, sy = %d\n",sx,sy);
+ printf("DrawPlayerField(): This should never happen!\n");
+ return;
+ }
+#endif
+
+ if (element == EL_EXPLOSION)
+ return;
+
+ action = (player->is_pushing ? ACTION_PUSHING :
+ player->is_digging ? ACTION_DIGGING :
+ player->is_collecting ? ACTION_COLLECTING :
+ player->is_moving ? ACTION_MOVING :
+ player->is_snapping ? ACTION_SNAPPING :
+ player->is_dropping ? ACTION_DROPPING :
+ player->is_waiting ? player->action_waiting : ACTION_DEFAULT);
+
+ if (player->is_waiting)
+ move_dir = player->dir_waiting;
+
+ InitPlayerGfxAnimation(player, action, move_dir);
+
+ /* ----------------------------------------------------------------------- */
+ /* draw things in the field the player is leaving, if needed */
+ /* ----------------------------------------------------------------------- */
+
+ if (player->is_moving)
+ {
+ if (Back[last_jx][last_jy] && IS_DRAWABLE(last_element))
+ {
+ DrawLevelElement(last_jx, last_jy, Back[last_jx][last_jy]);
+
+ if (last_element == EL_DYNAMITE_ACTIVE ||
+ last_element == EL_EM_DYNAMITE_ACTIVE ||
+ last_element == EL_SP_DISK_RED_ACTIVE)
+ DrawDynamite(last_jx, last_jy);
+ else
+ DrawLevelFieldThruMask(last_jx, last_jy);
+ }
+ else if (last_element == EL_DYNAMITE_ACTIVE ||
+ last_element == EL_EM_DYNAMITE_ACTIVE ||
+ last_element == EL_SP_DISK_RED_ACTIVE)
+ DrawDynamite(last_jx, last_jy);
+#if 0
+ /* !!! this is not enough to prevent flickering of players which are
+ moving next to each others without a free tile between them -- this
+ can only be solved by drawing all players layer by layer (first the
+ background, then the foreground etc.) !!! => TODO */
+ else if (!IS_PLAYER(last_jx, last_jy))
+ DrawLevelField(last_jx, last_jy);
+#else
+ else
+ DrawLevelField(last_jx, last_jy);
+#endif
+
+ if (player->is_pushing && IN_SCR_FIELD(SCREENX(next_jx), SCREENY(next_jy)))
+ DrawLevelElement(next_jx, next_jy, EL_EMPTY);
+ }
+
+ if (!IN_SCR_FIELD(sx, sy))
+ return;
+
+ /* ----------------------------------------------------------------------- */
+ /* draw things behind the player, if needed */
+ /* ----------------------------------------------------------------------- */
+
+ if (Back[jx][jy])
+ DrawLevelElement(jx, jy, Back[jx][jy]);
+ else if (IS_ACTIVE_BOMB(element))
+ DrawLevelElement(jx, jy, EL_EMPTY);
+ else
+ {
+ if (player_is_moving && GfxElement[jx][jy] != EL_UNDEFINED)
+ {
+ int old_element = GfxElement[jx][jy];
+ int old_graphic = el_act_dir2img(old_element, action, move_dir);
+ int frame = getGraphicAnimationFrame(old_graphic, player->StepFrame);
+
+ if (GFX_CRUMBLED(old_element))
+ DrawLevelFieldCrumbledDigging(jx, jy, move_dir, player->StepFrame);
+ else
+ DrawGraphic(sx, sy, old_graphic, frame);
+
+ if (graphic_info[old_graphic].anim_mode & ANIM_OPAQUE_PLAYER)
+ player_is_opaque = TRUE;
+ }
+ else
+ {
+ GfxElement[jx][jy] = EL_UNDEFINED;
+
+ /* make sure that pushed elements are drawn with correct frame rate */
+ graphic = el_act_dir2img(element, ACTION_PUSHING, move_dir);
+
+ if (player->is_pushing && player->is_moving && !IS_ANIM_MODE_CE(graphic))
+ GfxFrame[jx][jy] = player->StepFrame;
+
+ DrawLevelField(jx, jy);
+ }
+ }
+
+#if !DRAW_PLAYER_OVER_PUSHED_ELEMENT
+ /* ----------------------------------------------------------------------- */
+ /* draw player himself */
+ /* ----------------------------------------------------------------------- */
+
+ graphic = getPlayerGraphic(player, move_dir);
+
+ /* in the case of changed player action or direction, prevent the current
+ animation frame from being restarted for identical animations */
+ if (player->Frame == 0 && equalGraphics(graphic, last_player_graphic))
+ player->Frame = last_player_frame;
+
+ frame = getGraphicAnimationFrame(graphic, player->Frame);
+
+ if (player->GfxPos)
+ {
+ if (move_dir == MV_LEFT || move_dir == MV_RIGHT)
+ sxx = player->GfxPos;
+ else
+ syy = player->GfxPos;
+ }
+
+ if (player_is_opaque)
+ DrawGraphicShifted(sx, sy, sxx, syy, graphic, frame,NO_CUTTING,NO_MASKING);
+ else
+ DrawGraphicShiftedThruMask(sx, sy, sxx, syy, graphic, frame, NO_CUTTING);
+
+ if (SHIELD_ON(player))
+ {
+ int graphic = (player->shield_deadly_time_left ? IMG_SHIELD_DEADLY_ACTIVE :
+ IMG_SHIELD_NORMAL_ACTIVE);
+ int frame = getGraphicAnimationFrame(graphic, -1);
+
+ DrawGraphicShiftedThruMask(sx, sy, sxx, syy, graphic, frame, NO_CUTTING);
+ }
+#endif
+
+#if DRAW_PLAYER_OVER_PUSHED_ELEMENT
+ if (player->GfxPos)
+ {
+ if (move_dir == MV_LEFT || move_dir == MV_RIGHT)
+ sxx = player->GfxPos;
+ else
+ syy = player->GfxPos;
+ }
+#endif
+
+ /* ----------------------------------------------------------------------- */
+ /* draw things the player is pushing, if needed */
+ /* ----------------------------------------------------------------------- */
+
+ if (player->is_pushing && player->is_moving)
+ {
+ int px = SCREENX(jx), py = SCREENY(jy);
+ int pxx = (TILEX - ABS(sxx)) * dx;
+ int pyy = (TILEY - ABS(syy)) * dy;
+ int gfx_frame = GfxFrame[jx][jy];
+
+ int graphic;
+ int sync_frame;
+ int frame;
+
+ if (!IS_MOVING(jx, jy)) /* push movement already finished */
+ {
+ element = Feld[next_jx][next_jy];
+ gfx_frame = GfxFrame[next_jx][next_jy];
+ }
+
+ graphic = el_act_dir2img(element, ACTION_PUSHING, move_dir);
+
+ sync_frame = (IS_ANIM_MODE_CE(graphic) ? gfx_frame : player->StepFrame);
+ frame = getGraphicAnimationFrame(graphic, sync_frame);
+
+ /* draw background element under pushed element (like the Sokoban field) */
+ if (game.use_masked_pushing && IS_MOVING(jx, jy))
+ {
+ /* this allows transparent pushing animation over non-black background */
+
+ if (Back[jx][jy])
+ DrawLevelElement(jx, jy, Back[jx][jy]);
+ else
+ DrawLevelElement(jx, jy, EL_EMPTY);
+
+ if (Back[next_jx][next_jy])
+ DrawLevelElement(next_jx, next_jy, Back[next_jx][next_jy]);
+ else
+ DrawLevelElement(next_jx, next_jy, EL_EMPTY);
+ }
+ else if (Back[next_jx][next_jy])
+ DrawLevelElement(next_jx, next_jy, Back[next_jx][next_jy]);
+
+#if 1
+ /* do not draw (EM style) pushing animation when pushing is finished */
+ /* (two-tile animations usually do not contain start and end frame) */
+ if (graphic_info[graphic].double_movement && !IS_MOVING(jx, jy))
+ DrawLevelElement(next_jx, next_jy, Feld[next_jx][next_jy]);
+ else
+ DrawGraphicShiftedThruMask(px, py, pxx, pyy, graphic, frame, NO_CUTTING);
+#else
+ /* masked drawing is needed for EMC style (double) movement graphics */
+ /* !!! (ONLY WHEN DRAWING PUSHED ELEMENT OVER THE PLAYER) !!! */
+ DrawGraphicShiftedThruMask(px, py, pxx, pyy, graphic, frame, NO_CUTTING);
+#endif
+ }
+
+#if DRAW_PLAYER_OVER_PUSHED_ELEMENT
+ /* ----------------------------------------------------------------------- */
+ /* draw player himself */
+ /* ----------------------------------------------------------------------- */
+
+ graphic = getPlayerGraphic(player, move_dir);
+
+ /* in the case of changed player action or direction, prevent the current
+ animation frame from being restarted for identical animations */
+ if (player->Frame == 0 && equalGraphics(graphic, last_player_graphic))
+ player->Frame = last_player_frame;
+
+ frame = getGraphicAnimationFrame(graphic, player->Frame);
+
+ if (player->GfxPos)
+ {
+ if (move_dir == MV_LEFT || move_dir == MV_RIGHT)
+ sxx = player->GfxPos;
+ else
+ syy = player->GfxPos;
+ }
+
+ if (player_is_opaque)
+ DrawGraphicShifted(sx, sy, sxx, syy, graphic, frame,NO_CUTTING,NO_MASKING);
+ else
+ DrawGraphicShiftedThruMask(sx, sy, sxx, syy, graphic, frame, NO_CUTTING);
+
+ if (SHIELD_ON(player))
+ {
+ int graphic = (player->shield_deadly_time_left ? IMG_SHIELD_DEADLY_ACTIVE :
+ IMG_SHIELD_NORMAL_ACTIVE);
+ int frame = getGraphicAnimationFrame(graphic, -1);
+
+ DrawGraphicShiftedThruMask(sx, sy, sxx, syy, graphic, frame, NO_CUTTING);
+ }
+#endif
+
+ /* ----------------------------------------------------------------------- */
+ /* draw things in front of player (active dynamite or dynabombs) */
+ /* ----------------------------------------------------------------------- */
+
+ if (IS_ACTIVE_BOMB(element))
+ {
+ graphic = el2img(element);
+ frame = getGraphicAnimationFrame(graphic, GfxFrame[jx][jy]);
+
+ if (game.emulation == EMU_SUPAPLEX)
+ DrawGraphic(sx, sy, IMG_SP_DISK_RED, frame);
+ else
+ DrawGraphicThruMask(sx, sy, graphic, frame);
+ }
+
+ if (player_is_moving && last_element == EL_EXPLOSION)
+ {
+ int element = (GfxElement[last_jx][last_jy] != EL_UNDEFINED ?
+ GfxElement[last_jx][last_jy] : EL_EMPTY);
+ int graphic = el_act2img(element, ACTION_EXPLODING);
+ int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
+ int phase = ExplodePhase[last_jx][last_jy] - 1;
+ int frame = getGraphicAnimationFrame(graphic, phase - delay);
+
+ if (phase >= delay)
+ DrawGraphicThruMask(SCREENX(last_jx), SCREENY(last_jy), graphic, frame);
+ }
+
+ /* ----------------------------------------------------------------------- */
+ /* draw elements the player is just walking/passing through/under */
+ /* ----------------------------------------------------------------------- */
+
+ if (player_is_moving)
+ {
+ /* handle the field the player is leaving ... */
+ if (IS_ACCESSIBLE_INSIDE(last_element))
+ DrawLevelField(last_jx, last_jy);
+ else if (IS_ACCESSIBLE_UNDER(last_element))
+ DrawLevelFieldThruMask(last_jx, last_jy);
+ }
+
+ /* do not redraw accessible elements if the player is just pushing them */
+ if (!player_is_moving || !player->is_pushing)
+ {
+ /* ... and the field the player is entering */
+ if (IS_ACCESSIBLE_INSIDE(element))
+ DrawLevelField(jx, jy);
+ else if (IS_ACCESSIBLE_UNDER(element))
+ DrawLevelFieldThruMask(jx, jy);
+ }
+
+ MarkTileDirty(sx, sy);
+}
+
+/* ------------------------------------------------------------------------- */
+
+void WaitForEventToContinue()
+{
+ boolean still_wait = TRUE;
+
+ if (program.headless)
+ return;
+
+ /* simulate releasing mouse button over last gadget, if still pressed */
+ if (button_status)
+ HandleGadgets(-1, -1, 0);
+
+ button_status = MB_RELEASED;
+
+ ClearEventQueue();
+
+ while (still_wait)
+ {
+ Event event;
+
+ if (NextValidEvent(&event))
+ {
+ switch (event.type)
+ {
+ case EVENT_BUTTONPRESS:
+ case EVENT_KEYPRESS:
+#if defined(TARGET_SDL2)
+ case SDL_CONTROLLERBUTTONDOWN:
+#endif
+ case SDL_JOYBUTTONDOWN:
+ still_wait = FALSE;
+ break;
+
+ case EVENT_KEYRELEASE:
+ ClearPlayerAction();
+ break;
+
+ default:
+ HandleOtherEvents(&event);
+ break;
+ }
+ }
+ else if (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED)
+ {
+ still_wait = FALSE;
+ }
+
+ BackToFront();
+ }
+}
+
+#define MAX_REQUEST_LINES 13
+#define MAX_REQUEST_LINE_FONT1_LEN 7
+#define MAX_REQUEST_LINE_FONT2_LEN 10
+
+static int RequestHandleEvents(unsigned int req_state)
+{
+ boolean level_solved = (game_status == GAME_MODE_PLAYING &&
+ local_player->LevelSolved_GameEnd);
+ int width = request.width;
+ int height = request.height;
+ int sx, sy;
+ int result;
+
+ setRequestPosition(&sx, &sy, FALSE);
+
+ button_status = MB_RELEASED;
+
+ request_gadget_id = -1;
+ result = -1;
+
+ while (result < 0)
+ {
+ if (level_solved)
+ {
+ /* the MM game engine does not use a special (scrollable) field buffer */
+ if (level.game_engine_type != GAME_ENGINE_TYPE_MM)
+ SetDrawtoField(DRAW_TO_FIELDBUFFER);
+
+ HandleGameActions();
+
+ SetDrawtoField(DRAW_TO_BACKBUFFER);
+
+ if (global.use_envelope_request)
+ {
+ /* copy current state of request area to middle of playfield area */
+ BlitBitmap(bitmap_db_store_2, drawto, sx, sy, width, height, sx, sy);
+ }
+ }
+
+ if (PendingEvent())
+ {
+ Event event;
+
+ while (NextValidEvent(&event))
+ {
+ switch (event.type)
+ {
+ case EVENT_BUTTONPRESS:
+ case EVENT_BUTTONRELEASE:
+ case EVENT_MOTIONNOTIFY:
+ {
+ int mx, my;
+
+ if (event.type == EVENT_MOTIONNOTIFY)
+ {
+ if (!button_status)
+ continue;
+
+ motion_status = TRUE;
+ mx = ((MotionEvent *) &event)->x;
+ my = ((MotionEvent *) &event)->y;
+ }
+ else
+ {
+ motion_status = FALSE;
+ mx = ((ButtonEvent *) &event)->x;
+ my = ((ButtonEvent *) &event)->y;
+ if (event.type == EVENT_BUTTONPRESS)
+ button_status = ((ButtonEvent *) &event)->button;
+ else
+ button_status = MB_RELEASED;
+ }
+
+ /* this sets 'request_gadget_id' */
+ HandleGadgets(mx, my, button_status);
+
+ switch (request_gadget_id)
+ {
+ case TOOL_CTRL_ID_YES:
+ result = TRUE;
+ break;
+ case TOOL_CTRL_ID_NO:
+ result = FALSE;
+ break;
+ case TOOL_CTRL_ID_CONFIRM:
+ result = TRUE | FALSE;
+ break;
+
+ case TOOL_CTRL_ID_PLAYER_1:
+ result = 1;
+ break;
+ case TOOL_CTRL_ID_PLAYER_2:
+ result = 2;
+ break;
+ case TOOL_CTRL_ID_PLAYER_3:
+ result = 3;
+ break;
+ case TOOL_CTRL_ID_PLAYER_4:
+ result = 4;
+ break;
+
+ default:
+ break;
+ }
+
+ break;
+ }
+
+#if defined(TARGET_SDL2)
+ case SDL_WINDOWEVENT:
+ HandleWindowEvent((WindowEvent *) &event);
+ break;
+
+ case SDL_APP_WILLENTERBACKGROUND:
+ case SDL_APP_DIDENTERBACKGROUND:
+ case SDL_APP_WILLENTERFOREGROUND:
+ case SDL_APP_DIDENTERFOREGROUND:
+ HandlePauseResumeEvent((PauseResumeEvent *) &event);
+ break;
+#endif
+
+ case EVENT_KEYPRESS:
+ {
+ Key key = GetEventKey((KeyEvent *)&event, TRUE);
+
+ switch (key)
+ {
+ case KSYM_space:
+ if (req_state & REQ_CONFIRM)
+ result = 1;
+ break;
+
+ case KSYM_Return:
+#if defined(TARGET_SDL2)
+ case KSYM_Select:
+ case KSYM_Menu:
+#if defined(KSYM_Rewind)
+ case KSYM_Rewind: /* for Amazon Fire TV remote */
+#endif
+#endif
+ result = 1;
+ break;
+
+ case KSYM_Escape:
+#if defined(TARGET_SDL2)
+ case KSYM_Back:
+#if defined(KSYM_FastForward)
+ case KSYM_FastForward: /* for Amazon Fire TV remote */
+#endif
+#endif
+ result = 0;
+ break;
+
+ default:
+ HandleKeysDebug(key);
+ break;
+ }
+
+ if (req_state & REQ_PLAYER)
+ result = 0;
+
+ break;
+ }
+
+ case EVENT_KEYRELEASE:
+ ClearPlayerAction();
+ break;
+
+#if defined(TARGET_SDL2)
+ case SDL_CONTROLLERBUTTONDOWN:
+ switch (event.cbutton.button)
+ {
+ case SDL_CONTROLLER_BUTTON_A:
+ case SDL_CONTROLLER_BUTTON_X:
+ case SDL_CONTROLLER_BUTTON_LEFTSHOULDER:
+ result = 1;
+ break;
+
+ case SDL_CONTROLLER_BUTTON_B:
+ case SDL_CONTROLLER_BUTTON_Y:
+ case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER:
+ case SDL_CONTROLLER_BUTTON_BACK:
+ result = 0;
+ break;
+ }
+
+ if (req_state & REQ_PLAYER)
+ result = 0;
+
+ break;
+
+ case SDL_CONTROLLERBUTTONUP:
+ HandleJoystickEvent(&event);
+ ClearPlayerAction();
+ break;
+#endif
+
+ default:
+ HandleOtherEvents(&event);
+ break;
+ }
+ }
+ }
+ else if (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED)
+ {
+ int joy = AnyJoystick();
+
+ if (joy & JOY_BUTTON_1)
+ result = 1;
+ else if (joy & JOY_BUTTON_2)
+ result = 0;
+ }
+
+ if (level_solved)
+ {
+ if (global.use_envelope_request)
+ {
+ /* copy back current state of pressed buttons inside request area */
+ BlitBitmap(drawto, bitmap_db_store_2, sx, sy, width, height, sx, sy);
+ }
+ }
+
+ BackToFront();
+ }
+
+ return result;
+}
+
+static boolean RequestDoor(char *text, unsigned int req_state)
+{
+ unsigned int old_door_state;
+ int max_request_line_len = MAX_REQUEST_LINE_FONT1_LEN;
+ int font_nr = FONT_TEXT_2;
+ char *text_ptr;
+ int result;
+ int ty;
+
+ if (maxWordLengthInString(text) > MAX_REQUEST_LINE_FONT1_LEN)
+ {
+ max_request_line_len = MAX_REQUEST_LINE_FONT2_LEN;
+ font_nr = FONT_TEXT_1;
+ }
+
+ if (game_status == GAME_MODE_PLAYING)
+ BlitScreenToBitmap(backbuffer);
+
+ /* disable deactivated drawing when quick-loading level tape recording */
+ if (tape.playing && tape.deactivate_display)
+ TapeDeactivateDisplayOff(TRUE);
+
+ SetMouseCursor(CURSOR_DEFAULT);
+
+#if defined(NETWORK_AVALIABLE)
+ /* pause network game while waiting for request to answer */
+ if (options.network &&
+ game_status == GAME_MODE_PLAYING &&
+ req_state & REQUEST_WAIT_FOR_INPUT)
+ SendToServer_PausePlaying();
+#endif
+
+ old_door_state = GetDoorState();
+
+ /* simulate releasing mouse button over last gadget, if still pressed */
+ if (button_status)
+ HandleGadgets(-1, -1, 0);
+
+ UnmapAllGadgets();
+
+ /* draw released gadget before proceeding */
+ // BackToFront();
+
+ if (old_door_state & DOOR_OPEN_1)
+ {
+ CloseDoor(DOOR_CLOSE_1);
+
+ /* save old door content */
+ BlitBitmap(bitmap_db_door_1, bitmap_db_door_1,
+ 0 * DXSIZE, 0, DXSIZE, DYSIZE, 1 * DXSIZE, 0);
+ }
+
+ SetDoorBackgroundImage(IMG_BACKGROUND_DOOR);
+ SetDrawBackgroundMask(REDRAW_FIELD | REDRAW_DOOR_1);
+
+ /* clear door drawing field */
+ DrawBackground(DX, DY, DXSIZE, DYSIZE);
+
+ /* force DOOR font inside door area */
+ SetFontStatus(GAME_MODE_PSEUDO_DOOR);
+
+ /* write text for request */
+ for (text_ptr = text, ty = 0; ty < MAX_REQUEST_LINES; ty++)
+ {
+ char text_line[max_request_line_len + 1];
+ int tx, tl, tc = 0;
+
+ if (!*text_ptr)