9b573f66c2c2bbebb0880b9d882808ce1135d4e0
[rocksndiamonds.git] / src / tools.c
1 // ============================================================================
2 // Rocks'n'Diamonds - McDuffin Strikes Back!
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
5 //                  Holger Schemel
6 //                  info@artsoft.org
7 //                  http://www.artsoft.org/
8 // ----------------------------------------------------------------------------
9 // tools.c
10 // ============================================================================
11
12 #include <math.h>
13
14 #include "libgame/libgame.h"
15
16 #include "tools.h"
17 #include "init.h"
18 #include "game.h"
19 #include "events.h"
20 #include "anim.h"
21 #include "network.h"
22 #include "tape.h"
23 #include "screens.h"
24
25
26 /* select level set with EMC X11 graphics before activating EM GFX debugging */
27 #define DEBUG_EM_GFX            FALSE
28 #define DEBUG_FRAME_TIME        FALSE
29
30 /* tool button identifiers */
31 #define TOOL_CTRL_ID_YES        0
32 #define TOOL_CTRL_ID_NO         1
33 #define TOOL_CTRL_ID_CONFIRM    2
34 #define TOOL_CTRL_ID_PLAYER_1   3
35 #define TOOL_CTRL_ID_PLAYER_2   4
36 #define TOOL_CTRL_ID_PLAYER_3   5
37 #define TOOL_CTRL_ID_PLAYER_4   6
38
39 #define NUM_TOOL_BUTTONS        7
40
41 /* constants for number of doors and door parts */
42 #define NUM_DOORS               2
43 #define NUM_PANELS              NUM_DOORS
44 // #define NUM_PANELS           0
45 #define MAX_PARTS_PER_DOOR      8
46 #define MAX_DOOR_PARTS          (NUM_DOORS * MAX_PARTS_PER_DOOR + NUM_PANELS)
47 #define DOOR_PART_IS_PANEL(i)   ((i) >= NUM_DOORS * MAX_PARTS_PER_DOOR)
48
49
50 struct DoorPartOrderInfo
51 {
52   int nr;
53   int sort_priority;
54 };
55
56 static struct DoorPartOrderInfo door_part_order[MAX_DOOR_PARTS];
57
58 struct DoorPartControlInfo
59 {
60   int door_token;
61   int graphic;
62   struct DoorPartPosInfo *pos;
63 };
64
65 static struct DoorPartControlInfo door_part_controls[] =
66 {
67   {
68     DOOR_1,
69     IMG_GFX_DOOR_1_PART_1,
70     &door_1.part_1
71   },
72   {
73     DOOR_1,
74     IMG_GFX_DOOR_1_PART_2,
75     &door_1.part_2
76   },
77   {
78     DOOR_1,
79     IMG_GFX_DOOR_1_PART_3,
80     &door_1.part_3
81   },
82   {
83     DOOR_1,
84     IMG_GFX_DOOR_1_PART_4,
85     &door_1.part_4
86   },
87   {
88     DOOR_1,
89     IMG_GFX_DOOR_1_PART_5,
90     &door_1.part_5
91   },
92   {
93     DOOR_1,
94     IMG_GFX_DOOR_1_PART_6,
95     &door_1.part_6
96   },
97   {
98     DOOR_1,
99     IMG_GFX_DOOR_1_PART_7,
100     &door_1.part_7
101   },
102   {
103     DOOR_1,
104     IMG_GFX_DOOR_1_PART_8,
105     &door_1.part_8
106   },
107
108   {
109     DOOR_2,
110     IMG_GFX_DOOR_2_PART_1,
111     &door_2.part_1
112   },
113   {
114     DOOR_2,
115     IMG_GFX_DOOR_2_PART_2,
116     &door_2.part_2
117   },
118   {
119     DOOR_2,
120     IMG_GFX_DOOR_2_PART_3,
121     &door_2.part_3
122   },
123   {
124     DOOR_2,
125     IMG_GFX_DOOR_2_PART_4,
126     &door_2.part_4
127   },
128   {
129     DOOR_2,
130     IMG_GFX_DOOR_2_PART_5,
131     &door_2.part_5
132   },
133   {
134     DOOR_2,
135     IMG_GFX_DOOR_2_PART_6,
136     &door_2.part_6
137   },
138   {
139     DOOR_2,
140     IMG_GFX_DOOR_2_PART_7,
141     &door_2.part_7
142   },
143   {
144     DOOR_2,
145     IMG_GFX_DOOR_2_PART_8,
146     &door_2.part_8
147   },
148
149   {
150     DOOR_1,
151     IMG_BACKGROUND_PANEL,
152     &door_1.panel
153   },
154   {
155     DOOR_2,
156     IMG_BACKGROUND_TAPE,
157     &door_2.panel
158   },
159
160   {
161     -1,
162     -1,
163     NULL
164   }
165 };
166
167
168 /* forward declaration for internal use */
169 static void UnmapToolButtons();
170 static void HandleToolButtons(struct GadgetInfo *);
171 static int el_act_dir2crm(int, int, int);
172 static int el_act2crm(int, int);
173
174 static struct GadgetInfo *tool_gadget[NUM_TOOL_BUTTONS];
175 static int request_gadget_id = -1;
176
177 static char *print_if_not_empty(int element)
178 {
179   static char *s = NULL;
180   char *token_name = element_info[element].token_name;
181
182   if (s != NULL)
183     free(s);
184
185   s = checked_malloc(strlen(token_name) + 10 + 1);
186
187   if (element != EL_EMPTY)
188     sprintf(s, "%d\t['%s']", element, token_name);
189   else
190     sprintf(s, "%d", element);
191
192   return s;
193 }
194
195 int correctLevelPosX_EM(int lx)
196 {
197   lx -= 1;
198   lx -= (BorderElement != EL_EMPTY ? 1 : 0);
199
200   return lx;
201 }
202
203 int correctLevelPosY_EM(int ly)
204 {
205   ly -= 1;
206   ly -= (BorderElement != EL_EMPTY ? 1 : 0);
207
208   return ly;
209 }
210
211 static int getFieldbufferOffsetX_RND()
212 {
213   int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
214   int dx = (ScreenMovDir & (MV_LEFT | MV_RIGHT) ? ScreenGfxPos : 0);
215   int dx_var = dx * TILESIZE_VAR / TILESIZE;
216   int fx = FX;
217
218   if (EVEN(SCR_FIELDX))
219   {
220     int ffx = (scroll_x - SBX_Left)  * TILEX_VAR + dx_var;
221
222     if (ffx < SBX_Right * TILEX_VAR + TILEX_VAR / 2 + TILEX_VAR)
223       fx += dx_var - MIN(ffx, TILEX_VAR / 2) + TILEX_VAR;
224     else
225       fx += (dx_var > 0 ? TILEX_VAR : 0);
226   }
227   else
228   {
229     fx += dx_var;
230   }
231
232   if (full_lev_fieldx <= SCR_FIELDX)
233   {
234     if (EVEN(SCR_FIELDX))
235       fx = 2 * TILEX_VAR - (ODD(lev_fieldx)  ? TILEX_VAR / 2 : 0);
236     else
237       fx = 2 * TILEX_VAR - (EVEN(lev_fieldx) ? TILEX_VAR / 2 : 0);
238   }
239
240   return fx;
241 }
242
243 static int getFieldbufferOffsetY_RND()
244 {
245   int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
246   int dy = (ScreenMovDir & (MV_UP | MV_DOWN)    ? ScreenGfxPos : 0);
247   int dy_var = dy * TILESIZE_VAR / TILESIZE;
248   int fy = FY;
249
250   if (EVEN(SCR_FIELDY))
251   {
252     int ffy = (scroll_y - SBY_Upper) * TILEY_VAR + dy_var;
253
254     if (ffy < SBY_Lower * TILEY_VAR + TILEY_VAR / 2 + TILEY_VAR)
255       fy += dy_var - MIN(ffy, TILEY_VAR / 2) + TILEY_VAR;
256     else
257       fy += (dy_var > 0 ? TILEY_VAR : 0);
258   }
259   else
260   {
261     fy += dy_var;
262   }
263
264   if (full_lev_fieldy <= SCR_FIELDY)
265   {
266     if (EVEN(SCR_FIELDY))
267       fy = 2 * TILEY_VAR - (ODD(lev_fieldy)  ? TILEY_VAR / 2 : 0);
268     else
269       fy = 2 * TILEY_VAR - (EVEN(lev_fieldy) ? TILEY_VAR / 2 : 0);
270   }
271
272   return fy;
273 }
274
275 static int getLevelFromScreenX_RND(int sx)
276 {
277   int fx = getFieldbufferOffsetX_RND();
278   int dx = fx - FX;
279   int px = sx - SX;
280   int lx = LEVELX((px + dx) / TILESIZE_VAR);
281
282   return lx;
283 }
284
285 static int getLevelFromScreenY_RND(int sy)
286 {
287   int fy = getFieldbufferOffsetY_RND();
288   int dy = fy - FY;
289   int py = sy - SY;
290   int ly = LEVELY((py + dy) / TILESIZE_VAR);
291
292   return ly;
293 }
294
295 static int getLevelFromScreenX_EM(int sx)
296 {
297   int level_xsize = level.native_em_level->lev->width;
298   int full_xsize = level_xsize * TILESIZE_VAR;
299
300   sx -= (full_xsize < SXSIZE ? (SXSIZE - full_xsize) / 2 : 0);
301
302   int fx = getFieldbufferOffsetX_EM();
303   int dx = fx;
304   int px = sx - SX;
305   int lx = LEVELX((px + dx) / TILESIZE_VAR);
306
307   lx = correctLevelPosX_EM(lx);
308
309   return lx;
310 }
311
312 static int getLevelFromScreenY_EM(int sy)
313 {
314   int level_ysize = level.native_em_level->lev->height;
315   int full_ysize = level_ysize * TILESIZE_VAR;
316
317   sy -= (full_ysize < SYSIZE ? (SYSIZE - full_ysize) / 2 : 0);
318
319   int fy = getFieldbufferOffsetY_EM();
320   int dy = fy;
321   int py = sy - SY;
322   int ly = LEVELY((py + dy) / TILESIZE_VAR);
323
324   ly = correctLevelPosY_EM(ly);
325
326   return ly;
327 }
328
329 static int getLevelFromScreenX_SP(int sx)
330 {
331   int menBorder = setup.sp_show_border_elements;
332   int level_xsize = level.native_sp_level->width;
333   int full_xsize = (level_xsize - (menBorder ? 0 : 1)) * TILESIZE_VAR;
334
335   sx += (full_xsize < SXSIZE ? (SXSIZE - full_xsize) / 2 : 0);
336
337   int fx = getFieldbufferOffsetX_SP();
338   int dx = fx - FX;
339   int px = sx - SX;
340   int lx = LEVELX((px + dx) / TILESIZE_VAR);
341
342   return lx;
343 }
344
345 static int getLevelFromScreenY_SP(int sy)
346 {
347   int menBorder = setup.sp_show_border_elements;
348   int level_ysize = level.native_sp_level->height;
349   int full_ysize = (level_ysize - (menBorder ? 0 : 1)) * TILESIZE_VAR;
350
351   sy += (full_ysize < SYSIZE ? (SYSIZE - full_ysize) / 2 : 0);
352
353   int fy = getFieldbufferOffsetY_SP();
354   int dy = fy - FY;
355   int py = sy - SY;
356   int ly = LEVELY((py + dy) / TILESIZE_VAR);
357
358   return ly;
359 }
360
361 int getLevelFromScreenX(int x)
362 {
363   if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
364     return getLevelFromScreenX_EM(x);
365   if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
366     return getLevelFromScreenX_SP(x);
367   else
368     return getLevelFromScreenX_RND(x);
369 }
370
371 int getLevelFromScreenY(int y)
372 {
373   if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
374     return getLevelFromScreenY_EM(y);
375   if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
376     return getLevelFromScreenY_SP(y);
377   else
378     return getLevelFromScreenY_RND(y);
379 }
380
381 void DumpTile(int x, int y)
382 {
383   int sx = SCREENX(x);
384   int sy = SCREENY(y);
385
386   printf_line("-", 79);
387   printf("Field Info: SCREEN(%d, %d), LEVEL(%d, %d)\n", sx, sy, x, y);
388   printf_line("-", 79);
389
390   if (!IN_LEV_FIELD(x, y))
391   {
392     printf("(not in level field)\n");
393     printf("\n");
394
395     return;
396   }
397
398   printf("  Feld:        %d\t['%s']\n", Feld[x][y],
399          element_info[Feld[x][y]].token_name);
400   printf("  Back:        %s\n", print_if_not_empty(Back[x][y]));
401   printf("  Store:       %s\n", print_if_not_empty(Store[x][y]));
402   printf("  Store2:      %s\n", print_if_not_empty(Store2[x][y]));
403   printf("  StorePlayer: %s\n", print_if_not_empty(StorePlayer[x][y]));
404   printf("  MovPos:      %d\n", MovPos[x][y]);
405   printf("  MovDir:      %d\n", MovDir[x][y]);
406   printf("  MovDelay:    %d\n", MovDelay[x][y]);
407   printf("  ChangeDelay: %d\n", ChangeDelay[x][y]);
408   printf("  CustomValue: %d\n", CustomValue[x][y]);
409   printf("  GfxElement:  %d\n", GfxElement[x][y]);
410   printf("  GfxAction:   %d\n", GfxAction[x][y]);
411   printf("  GfxFrame:    %d [%d]\n", GfxFrame[x][y], FrameCounter);
412   printf("  Player x/y:  %d, %d\n", local_player->jx, local_player->jy);
413   printf("\n");
414 }
415
416 void DumpTileFromScreen(int sx, int sy)
417 {
418   int lx = getLevelFromScreenX(sx);
419   int ly = getLevelFromScreenY(sy);
420
421   DumpTile(lx, ly);
422 }
423
424 void SetDrawtoField(int mode)
425 {
426   if (mode == DRAW_TO_FIELDBUFFER)
427   {
428     FX = 2 * TILEX_VAR;
429     FY = 2 * TILEY_VAR;
430     BX1 = -2;
431     BY1 = -2;
432     BX2 = SCR_FIELDX + 1;
433     BY2 = SCR_FIELDY + 1;
434
435     drawto_field = fieldbuffer;
436   }
437   else  /* DRAW_TO_BACKBUFFER */
438   {
439     FX = SX;
440     FY = SY;
441     BX1 = 0;
442     BY1 = 0;
443     BX2 = SCR_FIELDX - 1;
444     BY2 = SCR_FIELDY - 1;
445
446     drawto_field = backbuffer;
447   }
448 }
449
450 static void RedrawPlayfield_RND()
451 {
452   if (game.envelope_active)
453     return;
454
455   DrawLevel(REDRAW_ALL);
456   DrawAllPlayers();
457 }
458
459 void RedrawPlayfield()
460 {
461   if (game_status != GAME_MODE_PLAYING)
462     return;
463
464   if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
465     RedrawPlayfield_EM(TRUE);
466   else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
467     RedrawPlayfield_SP(TRUE);
468   else if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
469     RedrawPlayfield_RND();
470
471   BlitScreenToBitmap(backbuffer);
472
473   BlitBitmap(drawto, window, gfx.sx, gfx.sy, gfx.sxsize, gfx.sysize,
474              gfx.sx, gfx.sy);
475 }
476
477 static void DrawMaskedBorderExt_Rect(int x, int y, int width, int height,
478                                      int draw_target)
479 {
480   Bitmap *src_bitmap = getGlobalBorderBitmapFromStatus(global.border_status);
481   Bitmap *dst_bitmap = gfx.masked_border_bitmap_ptr;
482
483   if (x == -1 && y == -1)
484     return;
485
486   if (draw_target == DRAW_TO_SCREEN)
487     BlitToScreenMasked(src_bitmap, x, y, width, height, x, y);
488   else
489     BlitBitmapMasked(src_bitmap, dst_bitmap, x, y, width, height, x, y);
490 }
491
492 static void DrawMaskedBorderExt_FIELD(int draw_target)
493 {
494   if (global.border_status >= GAME_MODE_MAIN &&
495       global.border_status <= GAME_MODE_PLAYING &&
496       border.draw_masked[global.border_status])
497     DrawMaskedBorderExt_Rect(REAL_SX, REAL_SY, FULL_SXSIZE, FULL_SYSIZE,
498                              draw_target);
499 }
500
501 static void DrawMaskedBorderExt_DOOR_1(int draw_target)
502 {
503   // when drawing to backbuffer, never draw border over open doors
504   if (draw_target == DRAW_TO_BACKBUFFER &&
505       (GetDoorState() & DOOR_OPEN_1))
506     return;
507
508   if (border.draw_masked[GFX_SPECIAL_ARG_DOOR] &&
509       (global.border_status != GAME_MODE_EDITOR ||
510        border.draw_masked[GFX_SPECIAL_ARG_EDITOR]))
511     DrawMaskedBorderExt_Rect(DX, DY, DXSIZE, DYSIZE, draw_target);
512 }
513
514 static void DrawMaskedBorderExt_DOOR_2(int draw_target)
515 {
516   // when drawing to backbuffer, never draw border over open doors
517   if (draw_target == DRAW_TO_BACKBUFFER &&
518       (GetDoorState() & DOOR_OPEN_2))
519     return;
520
521   if (border.draw_masked[GFX_SPECIAL_ARG_DOOR] &&
522       global.border_status != GAME_MODE_EDITOR)
523     DrawMaskedBorderExt_Rect(VX, VY, VXSIZE, VYSIZE, draw_target);
524 }
525
526 static void DrawMaskedBorderExt_DOOR_3(int draw_target)
527 {
528   /* currently not available */
529 }
530
531 static void DrawMaskedBorderExt_ALL(int draw_target)
532 {
533   DrawMaskedBorderExt_FIELD(draw_target);
534   DrawMaskedBorderExt_DOOR_1(draw_target);
535   DrawMaskedBorderExt_DOOR_2(draw_target);
536   DrawMaskedBorderExt_DOOR_3(draw_target);
537 }
538
539 static void DrawMaskedBorderExt(int redraw_mask, int draw_target)
540 {
541   /* never draw masked screen borders on borderless screens */
542   if (global.border_status == GAME_MODE_LOADING ||
543       global.border_status == GAME_MODE_TITLE)
544     return;
545
546   if (redraw_mask & REDRAW_ALL)
547     DrawMaskedBorderExt_ALL(draw_target);
548   else
549   {
550     if (redraw_mask & REDRAW_FIELD)
551       DrawMaskedBorderExt_FIELD(draw_target);
552     if (redraw_mask & REDRAW_DOOR_1)
553       DrawMaskedBorderExt_DOOR_1(draw_target);
554     if (redraw_mask & REDRAW_DOOR_2)
555       DrawMaskedBorderExt_DOOR_2(draw_target);
556     if (redraw_mask & REDRAW_DOOR_3)
557       DrawMaskedBorderExt_DOOR_3(draw_target);
558   }
559 }
560
561 void DrawMaskedBorder_FIELD()
562 {
563   DrawMaskedBorderExt_FIELD(DRAW_TO_BACKBUFFER);
564 }
565
566 void DrawMaskedBorder(int redraw_mask)
567 {
568   DrawMaskedBorderExt(redraw_mask, DRAW_TO_BACKBUFFER);
569 }
570
571 void DrawMaskedBorderToTarget(int draw_target)
572 {
573   if (draw_target == DRAW_TO_BACKBUFFER ||
574       draw_target == DRAW_TO_SCREEN)
575   {
576     DrawMaskedBorderExt(REDRAW_ALL, draw_target);
577   }
578   else
579   {
580     int last_border_status = global.border_status;
581
582     if (draw_target == DRAW_TO_FADE_SOURCE)
583     {
584       global.border_status = gfx.fade_border_source_status;
585       gfx.masked_border_bitmap_ptr = gfx.fade_bitmap_source;
586     }
587     else if (draw_target == DRAW_TO_FADE_TARGET)
588     {
589       global.border_status = gfx.fade_border_target_status;
590       gfx.masked_border_bitmap_ptr = gfx.fade_bitmap_target;
591     }
592
593     DrawMaskedBorderExt(REDRAW_ALL, draw_target);
594
595     global.border_status = last_border_status;
596     gfx.masked_border_bitmap_ptr = backbuffer;
597   }
598 }
599
600 void BlitScreenToBitmap_RND(Bitmap *target_bitmap)
601 {
602   int fx = getFieldbufferOffsetX_RND();
603   int fy = getFieldbufferOffsetY_RND();
604
605   BlitBitmap(drawto_field, target_bitmap, fx, fy, SXSIZE, SYSIZE, SX, SY);
606 }
607
608 void BlitScreenToBitmap(Bitmap *target_bitmap)
609 {
610   if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
611     BlitScreenToBitmap_EM(target_bitmap);
612   else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
613     BlitScreenToBitmap_SP(target_bitmap);
614   else if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
615     BlitScreenToBitmap_RND(target_bitmap);
616
617   redraw_mask |= REDRAW_FIELD;
618 }
619
620 void DrawFramesPerSecond()
621 {
622   char text[100];
623   int font_nr = FONT_TEXT_2;
624   int font_width = getFontWidth(font_nr);
625
626   sprintf(text, "%04.1f fps", global.frames_per_second);
627
628   DrawTextExt(backbuffer, WIN_XSIZE - font_width * strlen(text), 0, text,
629               font_nr, BLIT_OPAQUE);
630 }
631
632 #if DEBUG_FRAME_TIME
633 static void PrintFrameTimeDebugging()
634 {
635   static unsigned int last_counter = 0;
636   unsigned int counter = Counter();
637   int diff_1 = counter - last_counter;
638   int diff_2 = diff_1 - GAME_FRAME_DELAY;
639   int diff_2_max = 20;
640   int diff_2_cut = MIN(ABS(diff_2), diff_2_max);
641   char diff_bar[2 * diff_2_max + 5];
642   int pos = 0;
643   int i;
644
645   diff_bar[pos++] = (diff_2 < -diff_2_max ? '<' : ' ');
646
647   for (i = 0; i < diff_2_max; i++)
648     diff_bar[pos++] = (diff_2 >= 0 ? ' ' :
649                        i >= diff_2_max - diff_2_cut ? '-' : ' ');
650
651   diff_bar[pos++] = '|';
652
653   for (i = 0; i < diff_2_max; i++)
654     diff_bar[pos++] = (diff_2 <= 0 ? ' ' : i < diff_2_cut ? '+' : ' ');
655
656   diff_bar[pos++] = (diff_2 > diff_2_max ? '>' : ' ');
657
658   diff_bar[pos++] = '\0';
659
660   Error(ERR_INFO, "%06d [%02d] [%c%02d] %s",
661         counter,
662         diff_1,
663         (diff_2 < 0 ? '-' : diff_2 > 0 ? '+' : ' '), ABS(diff_2),
664         diff_bar);
665
666   last_counter = counter;
667 }
668 #endif
669
670 static int unifiedRedrawMask(int mask)
671 {
672   if (mask & REDRAW_ALL)
673     return REDRAW_ALL;
674
675   if (mask & REDRAW_FIELD && mask & REDRAW_DOORS)
676     return REDRAW_ALL;
677
678   return mask;
679 }
680
681 static boolean equalRedrawMasks(int mask_1, int mask_2)
682 {
683   return unifiedRedrawMask(mask_1) == unifiedRedrawMask(mask_2);
684 }
685
686 void BackToFront()
687 {
688   static int last_redraw_mask = REDRAW_NONE;
689
690   // force screen redraw in every frame to continue drawing global animations
691   // (but always use the last redraw mask to prevent unwanted side effects)
692   if (redraw_mask == REDRAW_NONE)
693     redraw_mask = last_redraw_mask;
694
695   last_redraw_mask = redraw_mask;
696
697 #if 1
698   // masked border now drawn immediately when blitting backbuffer to window
699 #else
700   // draw masked border to all viewports, if defined
701   DrawMaskedBorder(redraw_mask);
702 #endif
703
704   // draw frames per second (only if debug mode is enabled)
705   if (redraw_mask & REDRAW_FPS)
706     DrawFramesPerSecond();
707
708   // remove playfield redraw before potentially merging with doors redraw
709   if (DrawingDeactivated(REAL_SX, REAL_SY, FULL_SXSIZE, FULL_SYSIZE))
710     redraw_mask &= ~REDRAW_FIELD;
711
712   // redraw complete window if both playfield and (some) doors need redraw
713   if (redraw_mask & REDRAW_FIELD && redraw_mask & REDRAW_DOORS)
714     redraw_mask = REDRAW_ALL;
715
716   /* although redrawing the whole window would be fine for normal gameplay,
717      being able to only redraw the playfield is required for deactivating
718      certain drawing areas (mainly playfield) to work, which is needed for
719      warp-forward to be fast enough (by skipping redraw of most frames) */
720
721   if (redraw_mask & REDRAW_ALL)
722   {
723     BlitBitmap(backbuffer, window, 0, 0, WIN_XSIZE, WIN_YSIZE, 0, 0);
724   }
725   else if (redraw_mask & REDRAW_FIELD)
726   {
727     BlitBitmap(backbuffer, window,
728                REAL_SX, REAL_SY, FULL_SXSIZE, FULL_SYSIZE, REAL_SX, REAL_SY);
729   }
730   else if (redraw_mask & REDRAW_DOORS)
731   {
732     // merge door areas to prevent calling screen redraw more than once
733     int x1 = WIN_XSIZE;
734     int y1 = WIN_YSIZE;
735     int x2 = 0;
736     int y2 = 0;
737
738     if (redraw_mask & REDRAW_DOOR_1)
739     {
740       x1 = MIN(x1, DX);
741       y1 = MIN(y1, DY);
742       x2 = MAX(x2, DX + DXSIZE);
743       y2 = MAX(y2, DY + DYSIZE);
744     }
745
746     if (redraw_mask & REDRAW_DOOR_2)
747     {
748       x1 = MIN(x1, VX);
749       y1 = MIN(y1, VY);
750       x2 = MAX(x2, VX + VXSIZE);
751       y2 = MAX(y2, VY + VYSIZE);
752     }
753
754     if (redraw_mask & REDRAW_DOOR_3)
755     {
756       x1 = MIN(x1, EX);
757       y1 = MIN(y1, EY);
758       x2 = MAX(x2, EX + EXSIZE);
759       y2 = MAX(y2, EY + EYSIZE);
760     }
761
762     BlitBitmap(backbuffer, window, x1, y1, x2 - x1, y2 - y1, x1, y1);
763   }
764
765   redraw_mask = REDRAW_NONE;
766
767 #if DEBUG_FRAME_TIME
768   PrintFrameTimeDebugging();
769 #endif
770 }
771
772 void BackToFront_WithFrameDelay(unsigned int frame_delay_value)
773 {
774   unsigned int frame_delay_value_old = GetVideoFrameDelay();
775
776   SetVideoFrameDelay(frame_delay_value);
777
778   BackToFront();
779
780   SetVideoFrameDelay(frame_delay_value_old);
781 }
782
783 static int fade_type_skip = FADE_TYPE_NONE;
784
785 static void FadeExt(int fade_mask, int fade_mode, int fade_type)
786 {
787   void (*draw_border_function)(void) = NULL;
788   int x, y, width, height;
789   int fade_delay, post_delay;
790
791   if (fade_type == FADE_TYPE_FADE_OUT)
792   {
793     if (fade_type_skip != FADE_TYPE_NONE)
794     {
795       /* skip all fade operations until specified fade operation */
796       if (fade_type & fade_type_skip)
797         fade_type_skip = FADE_TYPE_NONE;
798
799       return;
800     }
801
802     if (fading.fade_mode & FADE_TYPE_TRANSFORM)
803       return;
804   }
805
806   redraw_mask |= fade_mask;
807
808   if (fade_type == FADE_TYPE_SKIP)
809   {
810     fade_type_skip = fade_mode;
811
812     return;
813   }
814
815   fade_delay = fading.fade_delay;
816   post_delay = (fade_mode == FADE_MODE_FADE_OUT ? fading.post_delay : 0);
817
818   if (fade_type_skip != FADE_TYPE_NONE)
819   {
820     /* skip all fade operations until specified fade operation */
821     if (fade_type & fade_type_skip)
822       fade_type_skip = FADE_TYPE_NONE;
823
824     fade_delay = 0;
825   }
826
827   if (global.autoplay_leveldir)
828   {
829     return;
830   }
831
832   if (fade_mask == REDRAW_FIELD)
833   {
834     x = FADE_SX;
835     y = FADE_SY;
836     width  = FADE_SXSIZE;
837     height = FADE_SYSIZE;
838
839     if (border.draw_masked_when_fading)
840       draw_border_function = DrawMaskedBorder_FIELD;    /* update when fading */
841     else
842       DrawMaskedBorder_FIELD();                         /* draw once */
843   }
844   else          /* REDRAW_ALL */
845   {
846     x = 0;
847     y = 0;
848     width  = WIN_XSIZE;
849     height = WIN_YSIZE;
850   }
851
852   if (!setup.fade_screens ||
853       fade_delay == 0 ||
854       fading.fade_mode == FADE_MODE_NONE)
855   {
856     if (fade_mode == FADE_MODE_FADE_OUT)
857       return;
858
859     BlitBitmap(backbuffer, window, x, y, width, height, x, y);
860
861     redraw_mask &= ~fade_mask;
862
863     return;
864   }
865
866   FadeRectangle(x, y, width, height, fade_mode, fade_delay, post_delay,
867                 draw_border_function);
868
869   redraw_mask &= ~fade_mask;
870 }
871
872 static void SetScreenStates_BeforeFadingIn()
873 {
874   // temporarily set screen mode for animations to screen after fading in
875   global.anim_status = global.anim_status_next;
876
877   // store backbuffer with all animations that will be started after fading in
878   if (fade_type_skip != FADE_MODE_SKIP_FADE_IN)
879     PrepareFadeBitmap(DRAW_TO_FADE_TARGET);
880
881   // set screen mode for animations back to fading
882   global.anim_status = GAME_MODE_PSEUDO_FADING;
883 }
884
885 static void SetScreenStates_AfterFadingIn()
886 {
887   // store new source screen (to use correct masked border for fading)
888   gfx.fade_border_source_status = global.border_status;
889
890   global.anim_status = global.anim_status_next;
891 }
892
893 static void SetScreenStates_BeforeFadingOut()
894 {
895   // store new target screen (to use correct masked border for fading)
896   gfx.fade_border_target_status = game_status;
897
898   // set screen mode for animations to fading
899   global.anim_status = GAME_MODE_PSEUDO_FADING;
900
901   // store backbuffer with all animations that will be stopped for fading out
902   if (fade_type_skip != FADE_MODE_SKIP_FADE_OUT)
903     PrepareFadeBitmap(DRAW_TO_FADE_SOURCE);
904 }
905
906 static void SetScreenStates_AfterFadingOut()
907 {
908   global.border_status = game_status;
909 }
910
911 void FadeIn(int fade_mask)
912 {
913   SetScreenStates_BeforeFadingIn();
914
915 #if 1
916   DrawMaskedBorder(REDRAW_ALL);
917 #endif
918
919   if (fading.fade_mode & FADE_TYPE_TRANSFORM)
920     FadeExt(fade_mask, fading.fade_mode, FADE_TYPE_FADE_IN);
921   else
922     FadeExt(fade_mask, FADE_MODE_FADE_IN, FADE_TYPE_FADE_IN);
923
924   FADE_SX = REAL_SX;
925   FADE_SY = REAL_SY;
926   FADE_SXSIZE = FULL_SXSIZE;
927   FADE_SYSIZE = FULL_SYSIZE;
928
929   if (game_status == GAME_MODE_PLAYING &&
930       strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
931     SetOverlayActive(TRUE);
932
933   SetScreenStates_AfterFadingIn();
934
935   // force update of global animation status in case of rapid screen changes
936   redraw_mask = REDRAW_ALL;
937   BackToFront();
938 }
939
940 void FadeOut(int fade_mask)
941 {
942   // update screen if areas covered by "fade_mask" and "redraw_mask" differ
943   if (!equalRedrawMasks(fade_mask, redraw_mask))
944     BackToFront();
945
946   SetScreenStates_BeforeFadingOut();
947
948   SetOverlayActive(FALSE);
949
950 #if 0
951   DrawMaskedBorder(REDRAW_ALL);
952 #endif
953
954   if (fading.fade_mode & FADE_TYPE_TRANSFORM)
955     FadeExt(fade_mask, fading.fade_mode, FADE_TYPE_FADE_OUT);
956   else
957     FadeExt(fade_mask, FADE_MODE_FADE_OUT, FADE_TYPE_FADE_OUT);
958
959   SetScreenStates_AfterFadingOut();
960 }
961
962 static void FadeSetLeaveNext(struct TitleFadingInfo fading_leave, boolean set)
963 {
964   static struct TitleFadingInfo fading_leave_stored;
965
966   if (set)
967     fading_leave_stored = fading_leave;
968   else
969     fading = fading_leave_stored;
970 }
971
972 void FadeSetEnterMenu()
973 {
974   fading = menu.enter_menu;
975
976   FadeSetLeaveNext(fading, TRUE);       /* (keep same fade mode) */
977 }
978
979 void FadeSetLeaveMenu()
980 {
981   fading = menu.leave_menu;
982
983   FadeSetLeaveNext(fading, TRUE);       /* (keep same fade mode) */
984 }
985
986 void FadeSetEnterScreen()
987 {
988   fading = menu.enter_screen[game_status];
989
990   FadeSetLeaveNext(menu.leave_screen[game_status], TRUE);       /* store */
991 }
992
993 void FadeSetNextScreen()
994 {
995   fading = menu.next_screen[game_status];
996
997   // (do not overwrite fade mode set by FadeSetEnterScreen)
998   // FadeSetLeaveNext(fading, TRUE);    /* (keep same fade mode) */
999 }
1000
1001 void FadeSetLeaveScreen()
1002 {
1003   FadeSetLeaveNext(menu.leave_screen[game_status], FALSE);      /* recall */
1004 }
1005
1006 void FadeSetFromType(int type)
1007 {
1008   if (type & TYPE_ENTER_SCREEN)
1009     FadeSetEnterScreen();
1010   else if (type & TYPE_ENTER)
1011     FadeSetEnterMenu();
1012   else if (type & TYPE_LEAVE)
1013     FadeSetLeaveMenu();
1014 }
1015
1016 void FadeSetDisabled()
1017 {
1018   static struct TitleFadingInfo fading_none = { FADE_MODE_NONE, -1, -1, -1 };
1019
1020   fading = fading_none;
1021 }
1022
1023 void FadeSkipNextFadeIn()
1024 {
1025   FadeExt(0, FADE_MODE_SKIP_FADE_IN, FADE_TYPE_SKIP);
1026 }
1027
1028 void FadeSkipNextFadeOut()
1029 {
1030   FadeExt(0, FADE_MODE_SKIP_FADE_OUT, FADE_TYPE_SKIP);
1031 }
1032
1033 Bitmap *getBitmapFromGraphicOrDefault(int graphic, int default_graphic)
1034 {
1035   boolean redefined = getImageListEntryFromImageID(graphic)->redefined;
1036
1037   return (graphic == IMG_UNDEFINED ? NULL :
1038           graphic_info[graphic].bitmap != NULL || redefined ?
1039           graphic_info[graphic].bitmap :
1040           graphic_info[default_graphic].bitmap);
1041 }
1042
1043 Bitmap *getBackgroundBitmap(int graphic)
1044 {
1045   return getBitmapFromGraphicOrDefault(graphic, IMG_BACKGROUND);
1046 }
1047
1048 Bitmap *getGlobalBorderBitmap(int graphic)
1049 {
1050   return getBitmapFromGraphicOrDefault(graphic, IMG_GLOBAL_BORDER);
1051 }
1052
1053 Bitmap *getGlobalBorderBitmapFromStatus(int status)
1054 {
1055   int graphic =
1056     (status == GAME_MODE_MAIN ||
1057      status == GAME_MODE_PSEUDO_TYPENAME        ? IMG_GLOBAL_BORDER_MAIN :
1058      status == GAME_MODE_SCORES                 ? IMG_GLOBAL_BORDER_SCORES :
1059      status == GAME_MODE_EDITOR                 ? IMG_GLOBAL_BORDER_EDITOR :
1060      status == GAME_MODE_PLAYING                ? IMG_GLOBAL_BORDER_PLAYING :
1061      IMG_GLOBAL_BORDER);
1062
1063   return getGlobalBorderBitmap(graphic);
1064 }
1065
1066 void SetWindowBackgroundImageIfDefined(int graphic)
1067 {
1068   if (graphic_info[graphic].bitmap)
1069     SetWindowBackgroundBitmap(graphic_info[graphic].bitmap);
1070 }
1071
1072 void SetMainBackgroundImageIfDefined(int graphic)
1073 {
1074   if (graphic_info[graphic].bitmap)
1075     SetMainBackgroundBitmap(graphic_info[graphic].bitmap);
1076 }
1077
1078 void SetDoorBackgroundImageIfDefined(int graphic)
1079 {
1080   if (graphic_info[graphic].bitmap)
1081     SetDoorBackgroundBitmap(graphic_info[graphic].bitmap);
1082 }
1083
1084 void SetWindowBackgroundImage(int graphic)
1085 {
1086   SetWindowBackgroundBitmap(getBackgroundBitmap(graphic));
1087 }
1088
1089 void SetMainBackgroundImage(int graphic)
1090 {
1091   SetMainBackgroundBitmap(getBackgroundBitmap(graphic));
1092 }
1093
1094 void SetDoorBackgroundImage(int graphic)
1095 {
1096   SetDoorBackgroundBitmap(getBackgroundBitmap(graphic));
1097 }
1098
1099 void SetPanelBackground()
1100 {
1101   struct GraphicInfo *gfx = &graphic_info[IMG_BACKGROUND_PANEL];
1102
1103   BlitBitmapTiled(gfx->bitmap, bitmap_db_panel, gfx->src_x, gfx->src_y,
1104                   gfx->width, gfx->height, 0, 0, DXSIZE, DYSIZE);
1105
1106   SetDoorBackgroundBitmap(bitmap_db_panel);
1107 }
1108
1109 void DrawBackground(int x, int y, int width, int height)
1110 {
1111   /* "drawto" might still point to playfield buffer here (hall of fame) */
1112   ClearRectangleOnBackground(backbuffer, x, y, width, height);
1113
1114   if (IN_GFX_FIELD_FULL(x, y))
1115     redraw_mask |= REDRAW_FIELD;
1116   else if (IN_GFX_DOOR_1(x, y))
1117     redraw_mask |= REDRAW_DOOR_1;
1118   else if (IN_GFX_DOOR_2(x, y))
1119     redraw_mask |= REDRAW_DOOR_2;
1120   else if (IN_GFX_DOOR_3(x, y))
1121     redraw_mask |= REDRAW_DOOR_3;
1122 }
1123
1124 void DrawBackgroundForFont(int x, int y, int width, int height, int font_nr)
1125 {
1126   struct FontBitmapInfo *font = getFontBitmapInfo(font_nr);
1127
1128   if (font->bitmap == NULL)
1129     return;
1130
1131   DrawBackground(x, y, width, height);
1132 }
1133
1134 void DrawBackgroundForGraphic(int x, int y, int width, int height, int graphic)
1135 {
1136   struct GraphicInfo *g = &graphic_info[graphic];
1137
1138   if (g->bitmap == NULL)
1139     return;
1140
1141   DrawBackground(x, y, width, height);
1142 }
1143
1144 static int game_status_last = -1;
1145 static Bitmap *global_border_bitmap_last = NULL;
1146 static Bitmap *global_border_bitmap = NULL;
1147 static int real_sx_last = -1, real_sy_last = -1;
1148 static int full_sxsize_last = -1, full_sysize_last = -1;
1149 static int dx_last = -1, dy_last = -1;
1150 static int dxsize_last = -1, dysize_last = -1;
1151 static int vx_last = -1, vy_last = -1;
1152 static int vxsize_last = -1, vysize_last = -1;
1153
1154 boolean CheckIfGlobalBorderHasChanged()
1155 {
1156   // if game status has not changed, global border has not changed either
1157   if (game_status == game_status_last)
1158     return FALSE;
1159
1160   // determine and store new global border bitmap for current game status
1161   global_border_bitmap = getGlobalBorderBitmapFromStatus(game_status);
1162
1163   return (global_border_bitmap_last != global_border_bitmap);
1164 }
1165
1166 boolean CheckIfGlobalBorderRedrawIsNeeded()
1167 {
1168   // if game status has not changed, nothing has to be redrawn
1169   if (game_status == game_status_last)
1170     return FALSE;
1171
1172   // redraw if last screen was title screen
1173   if (game_status_last == GAME_MODE_TITLE)
1174     return TRUE;
1175
1176   // redraw if global screen border has changed
1177   if (CheckIfGlobalBorderHasChanged())
1178     return TRUE;
1179
1180   // redraw if position or size of playfield area has changed
1181   if (real_sx_last != REAL_SX || real_sy_last != REAL_SY ||
1182       full_sxsize_last != FULL_SXSIZE || full_sysize_last != FULL_SYSIZE)
1183     return TRUE;
1184
1185   // redraw if position or size of door area has changed
1186   if (dx_last != DX || dy_last != DY ||
1187       dxsize_last != DXSIZE || dysize_last != DYSIZE)
1188     return TRUE;
1189
1190   // redraw if position or size of tape area has changed
1191   if (vx_last != VX || vy_last != VY ||
1192       vxsize_last != VXSIZE || vysize_last != VYSIZE)
1193     return TRUE;
1194
1195   return FALSE;
1196 }
1197
1198 void RedrawGlobalBorderFromBitmap(Bitmap *bitmap)
1199 {
1200   if (bitmap)
1201     BlitBitmap(bitmap, backbuffer, 0, 0, WIN_XSIZE, WIN_YSIZE, 0, 0);
1202   else
1203     ClearRectangle(backbuffer, 0, 0, WIN_XSIZE, WIN_YSIZE);
1204 }
1205
1206 void RedrawGlobalBorder()
1207 {
1208   Bitmap *bitmap = getGlobalBorderBitmapFromStatus(game_status);
1209
1210   RedrawGlobalBorderFromBitmap(bitmap);
1211
1212   redraw_mask = REDRAW_ALL;
1213 }
1214
1215 static void RedrawGlobalBorderIfNeeded()
1216 {
1217   if (game_status == game_status_last)
1218     return;
1219
1220   // copy current draw buffer to later copy back areas that have not changed
1221   if (game_status_last != GAME_MODE_TITLE)
1222     BlitBitmap(backbuffer, bitmap_db_store_1, 0, 0, WIN_XSIZE, WIN_YSIZE, 0, 0);
1223
1224   if (CheckIfGlobalBorderRedrawIsNeeded())
1225   {
1226     // redraw global screen border (or clear, if defined to be empty)
1227     RedrawGlobalBorderFromBitmap(global_border_bitmap);
1228
1229     // copy previous playfield and door areas, if they are defined on both
1230     // previous and current screen and if they still have the same size
1231
1232     if (real_sx_last != -1 && real_sy_last != -1 &&
1233         REAL_SX != -1 && REAL_SY != -1 &&
1234         full_sxsize_last == FULL_SXSIZE && full_sysize_last == FULL_SYSIZE)
1235       BlitBitmap(bitmap_db_store_1, backbuffer,
1236                  real_sx_last, real_sy_last, FULL_SXSIZE, FULL_SYSIZE,
1237                  REAL_SX, REAL_SY);
1238
1239     if (dx_last != -1 && dy_last != -1 &&
1240         DX != -1 && DY != -1 &&
1241         dxsize_last == DXSIZE && dysize_last == DYSIZE)
1242       BlitBitmap(bitmap_db_store_1, backbuffer,
1243                  dx_last, dy_last, DXSIZE, DYSIZE, DX, DY);
1244
1245     if (vx_last != -1 && vy_last != -1 &&
1246         VX != -1 && VY != -1 &&
1247         vxsize_last == VXSIZE && vysize_last == VYSIZE)
1248       BlitBitmap(bitmap_db_store_1, backbuffer,
1249                  vx_last, vy_last, VXSIZE, VYSIZE, VX, VY);
1250
1251     redraw_mask = REDRAW_ALL;
1252   }
1253
1254   game_status_last = game_status;
1255
1256   global_border_bitmap_last = global_border_bitmap;
1257
1258   real_sx_last = REAL_SX;
1259   real_sy_last = REAL_SY;
1260   full_sxsize_last = FULL_SXSIZE;
1261   full_sysize_last = FULL_SYSIZE;
1262   dx_last = DX;
1263   dy_last = DY;
1264   dxsize_last = DXSIZE;
1265   dysize_last = DYSIZE;
1266   vx_last = VX;
1267   vy_last = VY;
1268   vxsize_last = VXSIZE;
1269   vysize_last = VYSIZE;
1270 }
1271
1272 void ClearField()
1273 {
1274   RedrawGlobalBorderIfNeeded();
1275
1276   /* !!! "drawto" might still point to playfield buffer here (see above) !!! */
1277   /* (when entering hall of fame after playing) */
1278   DrawBackground(REAL_SX, REAL_SY, FULL_SXSIZE, FULL_SYSIZE);
1279
1280   /* !!! maybe this should be done before clearing the background !!! */
1281   if (game_status == GAME_MODE_PLAYING)
1282   {
1283     ClearRectangle(fieldbuffer, 0, 0, FXSIZE, FYSIZE);
1284     SetDrawtoField(DRAW_TO_FIELDBUFFER);
1285   }
1286   else
1287   {
1288     SetDrawtoField(DRAW_TO_BACKBUFFER);
1289   }
1290 }
1291
1292 void MarkTileDirty(int x, int y)
1293 {
1294   redraw_mask |= REDRAW_FIELD;
1295 }
1296
1297 void SetBorderElement()
1298 {
1299   int x, y;
1300
1301   BorderElement = EL_EMPTY;
1302
1303   for (y = 0; y < lev_fieldy && BorderElement == EL_EMPTY; y++)
1304   {
1305     for (x = 0; x < lev_fieldx; x++)
1306     {
1307       if (!IS_INDESTRUCTIBLE(Feld[x][y]))
1308         BorderElement = EL_STEELWALL;
1309
1310       if (y != 0 && y != lev_fieldy - 1 && x != lev_fieldx - 1)
1311         x = lev_fieldx - 2;
1312     }
1313   }
1314 }
1315
1316 void FloodFillLevel(int from_x, int from_y, int fill_element,
1317                     short field[MAX_LEV_FIELDX][MAX_LEV_FIELDY],
1318                     int max_fieldx, int max_fieldy)
1319 {
1320   int i,x,y;
1321   int old_element;
1322   static int check[4][2] = { { -1, 0 }, { 0, -1 }, { 1, 0 }, { 0, 1 } };
1323   static int safety = 0;
1324
1325   /* check if starting field still has the desired content */
1326   if (field[from_x][from_y] == fill_element)
1327     return;
1328
1329   safety++;
1330
1331   if (safety > max_fieldx * max_fieldy)
1332     Error(ERR_EXIT, "Something went wrong in 'FloodFill()'. Please debug.");
1333
1334   old_element = field[from_x][from_y];
1335   field[from_x][from_y] = fill_element;
1336
1337   for (i = 0; i < 4; i++)
1338   {
1339     x = from_x + check[i][0];
1340     y = from_y + check[i][1];
1341
1342     if (IN_FIELD(x, y, max_fieldx, max_fieldy) && field[x][y] == old_element)
1343       FloodFillLevel(x, y, fill_element, field, max_fieldx, max_fieldy);
1344   }
1345
1346   safety--;
1347 }
1348
1349 void SetRandomAnimationValue(int x, int y)
1350 {
1351   gfx.anim_random_frame = GfxRandom[x][y];
1352 }
1353
1354 int getGraphicAnimationFrame(int graphic, int sync_frame)
1355 {
1356   /* animation synchronized with global frame counter, not move position */
1357   if (graphic_info[graphic].anim_global_sync || sync_frame < 0)
1358     sync_frame = FrameCounter;
1359
1360   return getAnimationFrame(graphic_info[graphic].anim_frames,
1361                            graphic_info[graphic].anim_delay,
1362                            graphic_info[graphic].anim_mode,
1363                            graphic_info[graphic].anim_start_frame,
1364                            sync_frame);
1365 }
1366
1367 void getGraphicSourceBitmap(int graphic, int tilesize, Bitmap **bitmap)
1368 {
1369   struct GraphicInfo *g = &graphic_info[graphic];
1370   int tilesize_capped = MIN(MAX(1, tilesize), TILESIZE);
1371
1372   if (tilesize == gfx.standard_tile_size)
1373     *bitmap = g->bitmaps[IMG_BITMAP_STANDARD];
1374   else if (tilesize == game.tile_size)
1375     *bitmap = g->bitmaps[IMG_BITMAP_GAME];
1376   else
1377     *bitmap = g->bitmaps[IMG_BITMAP_1x1 - log_2(tilesize_capped)];
1378 }
1379
1380 void getGraphicSourceXY(int graphic, int frame, int *x, int *y,
1381                         boolean get_backside)
1382 {
1383   struct GraphicInfo *g = &graphic_info[graphic];
1384   int src_x = g->src_x + (get_backside ? g->offset2_x : 0);
1385   int src_y = g->src_y + (get_backside ? g->offset2_y : 0);
1386
1387   if (g->offset_y == 0)         /* frames are ordered horizontally */
1388   {
1389     int max_width = g->anim_frames_per_line * g->width;
1390     int pos = (src_y / g->height) * max_width + src_x + frame * g->offset_x;
1391
1392     *x = pos % max_width;
1393     *y = src_y % g->height + pos / max_width * g->height;
1394   }
1395   else if (g->offset_x == 0)    /* frames are ordered vertically */
1396   {
1397     int max_height = g->anim_frames_per_line * g->height;
1398     int pos = (src_x / g->width) * max_height + src_y + frame * g->offset_y;
1399
1400     *x = src_x % g->width + pos / max_height * g->width;
1401     *y = pos % max_height;
1402   }
1403   else                          /* frames are ordered diagonally */
1404   {
1405     *x = src_x + frame * g->offset_x;
1406     *y = src_y + frame * g->offset_y;
1407   }
1408 }
1409
1410 void getSizedGraphicSourceExt(int graphic, int frame, int tilesize,
1411                               Bitmap **bitmap, int *x, int *y,
1412                               boolean get_backside)
1413 {
1414   struct GraphicInfo *g = &graphic_info[graphic];
1415
1416   // if no in-game graphics defined, always use standard graphic size
1417   if (g->bitmaps[IMG_BITMAP_GAME] == NULL)
1418     tilesize = TILESIZE;
1419
1420   getGraphicSourceBitmap(graphic, tilesize, bitmap);
1421   getGraphicSourceXY(graphic, frame, x, y, get_backside);
1422
1423   *x = *x * tilesize / g->tile_size;
1424   *y = *y * tilesize / g->tile_size;
1425 }
1426
1427 void getFixedGraphicSourceExt(int graphic, int frame, Bitmap **bitmap,
1428                               int *x, int *y, boolean get_backside)
1429 {
1430   getSizedGraphicSourceExt(graphic, frame, TILESIZE, bitmap, x, y,
1431                            get_backside);
1432 }
1433
1434 void getSizedGraphicSource(int graphic, int frame, int tilesize,
1435                            Bitmap **bitmap, int *x, int *y)
1436 {
1437   getSizedGraphicSourceExt(graphic, frame, tilesize, bitmap, x, y, FALSE);
1438 }
1439
1440 void getFixedGraphicSource(int graphic, int frame,
1441                            Bitmap **bitmap, int *x, int *y)
1442 {
1443   getSizedGraphicSourceExt(graphic, frame, TILESIZE, bitmap, x, y, FALSE);
1444 }
1445
1446 void getMiniGraphicSource(int graphic, Bitmap **bitmap, int *x, int *y)
1447 {
1448   getSizedGraphicSource(graphic, 0, MINI_TILESIZE, bitmap, x, y);
1449 }
1450
1451 inline static void getGraphicSourceExt(int graphic, int frame, Bitmap **bitmap,
1452                                        int *x, int *y, boolean get_backside)
1453 {
1454   getSizedGraphicSourceExt(graphic, frame, TILESIZE_VAR, bitmap, x, y,
1455                            get_backside);
1456 }
1457
1458 void getGraphicSource(int graphic, int frame, Bitmap **bitmap, int *x, int *y)
1459 {
1460   getGraphicSourceExt(graphic, frame, bitmap, x, y, FALSE);
1461 }
1462
1463 void DrawGraphic(int x, int y, int graphic, int frame)
1464 {
1465 #if DEBUG
1466   if (!IN_SCR_FIELD(x, y))
1467   {
1468     printf("DrawGraphic(): x = %d, y = %d, graphic = %d\n", x, y, graphic);
1469     printf("DrawGraphic(): This should never happen!\n");
1470     return;
1471   }
1472 #endif
1473
1474   DrawGraphicExt(drawto_field, FX + x * TILEX_VAR, FY + y * TILEY_VAR, graphic,
1475                  frame);
1476
1477   MarkTileDirty(x, y);
1478 }
1479
1480 void DrawFixedGraphic(int x, int y, int graphic, int frame)
1481 {
1482 #if DEBUG
1483   if (!IN_SCR_FIELD(x, y))
1484   {
1485     printf("DrawGraphic(): x = %d, y = %d, graphic = %d\n", x, y, graphic);
1486     printf("DrawGraphic(): This should never happen!\n");
1487     return;
1488   }
1489 #endif
1490
1491   DrawFixedGraphicExt(drawto_field, FX + x * TILEX, FY + y * TILEY, graphic,
1492                       frame);
1493   MarkTileDirty(x, y);
1494 }
1495
1496 void DrawGraphicExt(DrawBuffer *dst_bitmap, int x, int y, int graphic,
1497                     int frame)
1498 {
1499   Bitmap *src_bitmap;
1500   int src_x, src_y;
1501
1502   getGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
1503
1504   BlitBitmap(src_bitmap, dst_bitmap, src_x, src_y, TILEX_VAR, TILEY_VAR, x, y);
1505 }
1506
1507 void DrawFixedGraphicExt(DrawBuffer *dst_bitmap, int x, int y, int graphic,
1508                          int frame)
1509 {
1510   Bitmap *src_bitmap;
1511   int src_x, src_y;
1512
1513   getFixedGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
1514   BlitBitmap(src_bitmap, dst_bitmap, src_x, src_y, TILEX, TILEY, x, y);
1515 }
1516
1517 void DrawGraphicThruMask(int x, int y, int graphic, int frame)
1518 {
1519 #if DEBUG
1520   if (!IN_SCR_FIELD(x, y))
1521   {
1522     printf("DrawGraphicThruMask(): x = %d,y = %d, graphic = %d\n",x,y,graphic);
1523     printf("DrawGraphicThruMask(): This should never happen!\n");
1524     return;
1525   }
1526 #endif
1527
1528   DrawGraphicThruMaskExt(drawto_field, FX + x * TILEX_VAR, FY + y * TILEY_VAR,
1529                          graphic, frame);
1530
1531   MarkTileDirty(x, y);
1532 }
1533
1534 void DrawFixedGraphicThruMask(int x, int y, int graphic, int frame)
1535 {
1536 #if DEBUG
1537   if (!IN_SCR_FIELD(x, y))
1538   {
1539     printf("DrawGraphicThruMask(): x = %d,y = %d, graphic = %d\n",x,y,graphic);
1540     printf("DrawGraphicThruMask(): This should never happen!\n");
1541     return;
1542   }
1543 #endif
1544
1545   DrawFixedGraphicThruMaskExt(drawto_field, FX + x * TILEX, FY + y * TILEY,
1546                               graphic, frame);
1547   MarkTileDirty(x, y);
1548 }
1549
1550 void DrawGraphicThruMaskExt(DrawBuffer *d, int dst_x, int dst_y, int graphic,
1551                             int frame)
1552 {
1553   Bitmap *src_bitmap;
1554   int src_x, src_y;
1555
1556   getGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
1557
1558   BlitBitmapMasked(src_bitmap, d, src_x, src_y, TILEX_VAR, TILEY_VAR,
1559                    dst_x, dst_y);
1560 }
1561
1562 void DrawFixedGraphicThruMaskExt(DrawBuffer *d, int dst_x, int dst_y,
1563                                  int graphic, int frame)
1564 {
1565   Bitmap *src_bitmap;
1566   int src_x, src_y;
1567
1568   getFixedGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
1569
1570   BlitBitmapMasked(src_bitmap, d, src_x, src_y, TILEX, TILEY,
1571                    dst_x, dst_y);
1572 }
1573
1574 void DrawSizedGraphic(int x, int y, int graphic, int frame, int tilesize)
1575 {
1576   DrawSizedGraphicExt(drawto, SX + x * tilesize, SY + y * tilesize, graphic,
1577                       frame, tilesize);
1578   MarkTileDirty(x / tilesize, y / tilesize);
1579 }
1580
1581 void DrawSizedGraphicExt(DrawBuffer *d, int x, int y, int graphic, int frame,
1582                          int tilesize)
1583 {
1584   Bitmap *src_bitmap;
1585   int src_x, src_y;
1586
1587   getSizedGraphicSource(graphic, frame, tilesize, &src_bitmap, &src_x, &src_y);
1588   BlitBitmap(src_bitmap, d, src_x, src_y, tilesize, tilesize, x, y);
1589 }
1590
1591 void DrawMiniGraphic(int x, int y, int graphic)
1592 {
1593   DrawMiniGraphicExt(drawto, SX + x * MINI_TILEX,SY + y * MINI_TILEY, graphic);
1594   MarkTileDirty(x / 2, y / 2);
1595 }
1596
1597 void DrawMiniGraphicExt(DrawBuffer *d, int x, int y, int graphic)
1598 {
1599   Bitmap *src_bitmap;
1600   int src_x, src_y;
1601
1602   getMiniGraphicSource(graphic, &src_bitmap, &src_x, &src_y);
1603   BlitBitmap(src_bitmap, d, src_x, src_y, MINI_TILEX, MINI_TILEY, x, y);
1604 }
1605
1606 inline static void DrawGraphicShiftedNormal(int x, int y, int dx, int dy,
1607                                             int graphic, int frame,
1608                                             int cut_mode, int mask_mode)
1609 {
1610   Bitmap *src_bitmap;
1611   int src_x, src_y;
1612   int dst_x, dst_y;
1613   int width = TILEX, height = TILEY;
1614   int cx = 0, cy = 0;
1615
1616   if (dx || dy)                 /* shifted graphic */
1617   {
1618     if (x < BX1)                /* object enters playfield from the left */
1619     {
1620       x = BX1;
1621       width = dx;
1622       cx = TILEX - dx;
1623       dx = 0;
1624     }
1625     else if (x > BX2)           /* object enters playfield from the right */
1626     {
1627       x = BX2;
1628       width = -dx;
1629       dx = TILEX + dx;
1630     }
1631     else if (x == BX1 && dx < 0) /* object leaves playfield to the left */
1632     {
1633       width += dx;
1634       cx = -dx;
1635       dx = 0;
1636     }
1637     else if (x == BX2 && dx > 0) /* object leaves playfield to the right */
1638       width -= dx;
1639     else if (dx)                /* general horizontal movement */
1640       MarkTileDirty(x + SIGN(dx), y);
1641
1642     if (y < BY1)                /* object enters playfield from the top */
1643     {
1644       if (cut_mode == CUT_BELOW) /* object completely above top border */
1645         return;
1646
1647       y = BY1;
1648       height = dy;
1649       cy = TILEY - dy;
1650       dy = 0;
1651     }
1652     else if (y > BY2)           /* object enters playfield from the bottom */
1653     {
1654       y = BY2;
1655       height = -dy;
1656       dy = TILEY + dy;
1657     }
1658     else if (y == BY1 && dy < 0) /* object leaves playfield to the top */
1659     {
1660       height += dy;
1661       cy = -dy;
1662       dy = 0;
1663     }
1664     else if (dy > 0 && cut_mode == CUT_ABOVE)
1665     {
1666       if (y == BY2)             /* object completely above bottom border */
1667         return;
1668
1669       height = dy;
1670       cy = TILEY - dy;
1671       dy = TILEY;
1672       MarkTileDirty(x, y + 1);
1673     }                           /* object leaves playfield to the bottom */
1674     else if (dy > 0 && (y == BY2 || cut_mode == CUT_BELOW))
1675       height -= dy;
1676     else if (dy)                /* general vertical movement */
1677       MarkTileDirty(x, y + SIGN(dy));
1678   }
1679
1680 #if DEBUG
1681   if (!IN_SCR_FIELD(x, y))
1682   {
1683     printf("DrawGraphicShifted(): x = %d, y = %d, graphic = %d\n",x,y,graphic);
1684     printf("DrawGraphicShifted(): This should never happen!\n");
1685     return;
1686   }
1687 #endif
1688
1689   width = width * TILESIZE_VAR / TILESIZE;
1690   height = height * TILESIZE_VAR / TILESIZE;
1691   cx = cx * TILESIZE_VAR / TILESIZE;
1692   cy = cy * TILESIZE_VAR / TILESIZE;
1693   dx = dx * TILESIZE_VAR / TILESIZE;
1694   dy = dy * TILESIZE_VAR / TILESIZE;
1695
1696   if (width > 0 && height > 0)
1697   {
1698     getGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
1699
1700     src_x += cx;
1701     src_y += cy;
1702
1703     dst_x = FX + x * TILEX_VAR + dx;
1704     dst_y = FY + y * TILEY_VAR + dy;
1705
1706     if (mask_mode == USE_MASKING)
1707       BlitBitmapMasked(src_bitmap, drawto_field, src_x, src_y, width, height,
1708                        dst_x, dst_y);
1709     else
1710       BlitBitmap(src_bitmap, drawto_field, src_x, src_y, width, height,
1711                  dst_x, dst_y);
1712
1713     MarkTileDirty(x, y);
1714   }
1715 }
1716
1717 inline static void DrawGraphicShiftedDouble(int x, int y, int dx, int dy,
1718                                             int graphic, int frame,
1719                                             int cut_mode, int mask_mode)
1720 {
1721   Bitmap *src_bitmap;
1722   int src_x, src_y;
1723   int dst_x, dst_y;
1724   int width = TILEX_VAR, height = TILEY_VAR;
1725   int x1 = x;
1726   int y1 = y;
1727   int x2 = x + SIGN(dx);
1728   int y2 = y + SIGN(dy);
1729
1730   /* movement with two-tile animations must be sync'ed with movement position,
1731      not with current GfxFrame (which can be higher when using slow movement) */
1732   int anim_pos = (dx ? ABS(dx) : ABS(dy));
1733   int anim_frames = graphic_info[graphic].anim_frames;
1734
1735   /* (we also need anim_delay here for movement animations with less frames) */
1736   int anim_delay = graphic_info[graphic].anim_delay;
1737   int sync_frame = anim_pos * anim_frames * anim_delay / TILESIZE;
1738
1739   boolean draw_start_tile = (cut_mode != CUT_ABOVE);    /* only for falling! */
1740   boolean draw_end_tile   = (cut_mode != CUT_BELOW);    /* only for falling! */
1741
1742   /* re-calculate animation frame for two-tile movement animation */
1743   frame = getGraphicAnimationFrame(graphic, sync_frame);
1744
1745   /* check if movement start graphic inside screen area and should be drawn */
1746   if (draw_start_tile && IN_SCR_FIELD(x1, y1))
1747   {
1748     getGraphicSourceExt(graphic, frame, &src_bitmap, &src_x, &src_y, TRUE);
1749
1750     dst_x = FX + x1 * TILEX_VAR;
1751     dst_y = FY + y1 * TILEY_VAR;
1752
1753     if (mask_mode == USE_MASKING)
1754       BlitBitmapMasked(src_bitmap, drawto_field, src_x, src_y, width, height,
1755                        dst_x, dst_y);
1756     else
1757       BlitBitmap(src_bitmap, drawto_field, src_x, src_y, width, height,
1758                  dst_x, dst_y);
1759
1760     MarkTileDirty(x1, y1);
1761   }
1762
1763   /* check if movement end graphic inside screen area and should be drawn */
1764   if (draw_end_tile && IN_SCR_FIELD(x2, y2))
1765   {
1766     getGraphicSourceExt(graphic, frame, &src_bitmap, &src_x, &src_y, FALSE);
1767
1768     dst_x = FX + x2 * TILEX_VAR;
1769     dst_y = FY + y2 * TILEY_VAR;
1770
1771     if (mask_mode == USE_MASKING)
1772       BlitBitmapMasked(src_bitmap, drawto_field, src_x, src_y, width, height,
1773                        dst_x, dst_y);
1774     else
1775       BlitBitmap(src_bitmap, drawto_field, src_x, src_y, width, height,
1776                  dst_x, dst_y);
1777
1778     MarkTileDirty(x2, y2);
1779   }
1780 }
1781
1782 static void DrawGraphicShifted(int x, int y, int dx, int dy,
1783                                int graphic, int frame,
1784                                int cut_mode, int mask_mode)
1785 {
1786   if (graphic < 0)
1787   {
1788     DrawGraphic(x, y, graphic, frame);
1789
1790     return;
1791   }
1792
1793   if (graphic_info[graphic].double_movement)    /* EM style movement images */
1794     DrawGraphicShiftedDouble(x, y, dx, dy, graphic, frame, cut_mode,mask_mode);
1795   else
1796     DrawGraphicShiftedNormal(x, y, dx, dy, graphic, frame, cut_mode,mask_mode);
1797 }
1798
1799 void DrawGraphicShiftedThruMask(int x, int y, int dx, int dy, int graphic,
1800                                 int frame, int cut_mode)
1801 {
1802   DrawGraphicShifted(x, y, dx, dy, graphic, frame, cut_mode, USE_MASKING);
1803 }
1804
1805 void DrawScreenElementExt(int x, int y, int dx, int dy, int element,
1806                           int cut_mode, int mask_mode)
1807 {
1808   int lx = LEVELX(x), ly = LEVELY(y);
1809   int graphic;
1810   int frame;
1811
1812   if (IN_LEV_FIELD(lx, ly))
1813   {
1814     SetRandomAnimationValue(lx, ly);
1815
1816     graphic = el_act_dir2img(element, GfxAction[lx][ly], GfxDir[lx][ly]);
1817     frame = getGraphicAnimationFrame(graphic, GfxFrame[lx][ly]);
1818
1819     /* do not use double (EM style) movement graphic when not moving */
1820     if (graphic_info[graphic].double_movement && !dx && !dy)
1821     {
1822       graphic = el_act_dir2img(element, ACTION_DEFAULT, GfxDir[lx][ly]);
1823       frame = getGraphicAnimationFrame(graphic, GfxFrame[lx][ly]);
1824     }
1825   }
1826   else  /* border element */
1827   {
1828     graphic = el2img(element);
1829     frame = getGraphicAnimationFrame(graphic, -1);
1830   }
1831
1832   if (element == EL_EXPANDABLE_WALL)
1833   {
1834     boolean left_stopped = FALSE, right_stopped = FALSE;
1835
1836     if (!IN_LEV_FIELD(lx - 1, ly) || IS_WALL(Feld[lx - 1][ly]))
1837       left_stopped = TRUE;
1838     if (!IN_LEV_FIELD(lx + 1, ly) || IS_WALL(Feld[lx + 1][ly]))
1839       right_stopped = TRUE;
1840
1841     if (left_stopped && right_stopped)
1842       graphic = IMG_WALL;
1843     else if (left_stopped)
1844     {
1845       graphic = IMG_EXPANDABLE_WALL_GROWING_RIGHT;
1846       frame = graphic_info[graphic].anim_frames - 1;
1847     }
1848     else if (right_stopped)
1849     {
1850       graphic = IMG_EXPANDABLE_WALL_GROWING_LEFT;
1851       frame = graphic_info[graphic].anim_frames - 1;
1852     }
1853   }
1854
1855   if (dx || dy)
1856     DrawGraphicShifted(x, y, dx, dy, graphic, frame, cut_mode, mask_mode);
1857   else if (mask_mode == USE_MASKING)
1858     DrawGraphicThruMask(x, y, graphic, frame);
1859   else
1860     DrawGraphic(x, y, graphic, frame);
1861 }
1862
1863 void DrawLevelElementExt(int x, int y, int dx, int dy, int element,
1864                          int cut_mode, int mask_mode)
1865 {
1866   if (IN_LEV_FIELD(x, y) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
1867     DrawScreenElementExt(SCREENX(x), SCREENY(y), dx, dy, element,
1868                          cut_mode, mask_mode);
1869 }
1870
1871 void DrawScreenElementShifted(int x, int y, int dx, int dy, int element,
1872                               int cut_mode)
1873 {
1874   DrawScreenElementExt(x, y, dx, dy, element, cut_mode, NO_MASKING);
1875 }
1876
1877 void DrawLevelElementShifted(int x, int y, int dx, int dy, int element,
1878                              int cut_mode)
1879 {
1880   DrawLevelElementExt(x, y, dx, dy, element, cut_mode, NO_MASKING);
1881 }
1882
1883 void DrawLevelElementThruMask(int x, int y, int element)
1884 {
1885   DrawLevelElementExt(x, y, 0, 0, element, NO_CUTTING, USE_MASKING);
1886 }
1887
1888 void DrawLevelFieldThruMask(int x, int y)
1889 {
1890   DrawLevelElementExt(x, y, 0, 0, Feld[x][y], NO_CUTTING, USE_MASKING);
1891 }
1892
1893 /* !!! implementation of quicksand is totally broken !!! */
1894 #define IS_CRUMBLED_TILE(x, y, e)                                       \
1895         (GFX_CRUMBLED(e) && (!IN_LEV_FIELD(x, y) ||                     \
1896                              !IS_MOVING(x, y) ||                        \
1897                              (e) == EL_QUICKSAND_EMPTYING ||            \
1898                              (e) == EL_QUICKSAND_FAST_EMPTYING))
1899
1900 static void DrawLevelFieldCrumbledInnerCorners(int x, int y, int dx, int dy,
1901                                                int graphic)
1902 {
1903   Bitmap *src_bitmap;
1904   int src_x, src_y;
1905   int width, height, cx, cy;
1906   int sx = SCREENX(x), sy = SCREENY(y);
1907   int crumbled_border_size = graphic_info[graphic].border_size;
1908   int crumbled_tile_size = graphic_info[graphic].tile_size;
1909   int crumbled_border_size_var =
1910     crumbled_border_size * TILESIZE_VAR / crumbled_tile_size;
1911   int i;
1912
1913   getGraphicSource(graphic, 0, &src_bitmap, &src_x, &src_y);
1914
1915   for (i = 1; i < 4; i++)
1916   {
1917     int dxx = (i & 1 ? dx : 0);
1918     int dyy = (i & 2 ? dy : 0);
1919     int xx = x + dxx;
1920     int yy = y + dyy;
1921     int element = (IN_LEV_FIELD(xx, yy) ? TILE_GFX_ELEMENT(xx, yy) :
1922                    BorderElement);
1923
1924     /* check if neighbour field is of same crumble type */
1925     boolean same = (IS_CRUMBLED_TILE(xx, yy, element) &&
1926                     graphic_info[graphic].class ==
1927                     graphic_info[el_act2crm(element, ACTION_DEFAULT)].class);
1928
1929     /* return if check prevents inner corner */
1930     if (same == (dxx == dx && dyy == dy))
1931       return;
1932   }
1933
1934   /* if we reach this point, we have an inner corner */
1935
1936   getGraphicSource(graphic, 1, &src_bitmap, &src_x, &src_y);
1937
1938   width  = crumbled_border_size_var;
1939   height = crumbled_border_size_var;
1940   cx = (dx > 0 ? TILESIZE_VAR - width  : 0);
1941   cy = (dy > 0 ? TILESIZE_VAR - height : 0);
1942
1943   BlitBitmap(src_bitmap, drawto_field, src_x + cx, src_y + cy,
1944              width, height, FX + sx * TILEX_VAR + cx, FY + sy * TILEY_VAR + cy);
1945 }
1946
1947 static void DrawLevelFieldCrumbledBorders(int x, int y, int graphic, int frame,
1948                                           int dir)
1949 {
1950   Bitmap *src_bitmap;
1951   int src_x, src_y;
1952   int width, height, bx, by, cx, cy;
1953   int sx = SCREENX(x), sy = SCREENY(y);
1954   int crumbled_border_size = graphic_info[graphic].border_size;
1955   int crumbled_tile_size = graphic_info[graphic].tile_size;
1956   int crumbled_border_size_var =
1957     crumbled_border_size * TILESIZE_VAR / crumbled_tile_size;
1958   int crumbled_border_pos_var = TILESIZE_VAR - crumbled_border_size_var;
1959   int i;
1960
1961   getGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
1962
1963   /* draw simple, sloppy, non-corner-accurate crumbled border */
1964
1965   width  = (dir == 1 || dir == 2 ? crumbled_border_size_var : TILESIZE_VAR);
1966   height = (dir == 0 || dir == 3 ? crumbled_border_size_var : TILESIZE_VAR);
1967   cx = (dir == 2 ? crumbled_border_pos_var : 0);
1968   cy = (dir == 3 ? crumbled_border_pos_var : 0);
1969
1970   BlitBitmap(src_bitmap, drawto_field, src_x + cx, src_y + cy, width, height,
1971              FX + sx * TILEX_VAR + cx,
1972              FY + sy * TILEY_VAR + cy);
1973
1974   /* (remaining middle border part must be at least as big as corner part) */
1975   if (!(graphic_info[graphic].style & STYLE_ACCURATE_BORDERS) ||
1976       crumbled_border_size_var >= TILESIZE_VAR / 3)
1977     return;
1978
1979   /* correct corners of crumbled border, if needed */
1980
1981   for (i = -1; i <= 1; i += 2)
1982   {
1983     int xx = x + (dir == 0 || dir == 3 ? i : 0);
1984     int yy = y + (dir == 1 || dir == 2 ? i : 0);
1985     int element = (IN_LEV_FIELD(xx, yy) ? TILE_GFX_ELEMENT(xx, yy) :
1986                    BorderElement);
1987
1988     /* check if neighbour field is of same crumble type */
1989     if (IS_CRUMBLED_TILE(xx, yy, element) &&
1990         graphic_info[graphic].class ==
1991         graphic_info[el_act2crm(element, ACTION_DEFAULT)].class)
1992     {
1993       /* no crumbled corner, but continued crumbled border */
1994
1995       int c1 = (dir == 2 || dir == 3 ? crumbled_border_pos_var : 0);
1996       int c2 = (i == 1 ? crumbled_border_pos_var : 0);
1997       int b1 = (i == 1 ? crumbled_border_size_var :
1998                 TILESIZE_VAR - 2 * crumbled_border_size_var);
1999
2000       width  = crumbled_border_size_var;
2001       height = crumbled_border_size_var;
2002
2003       if (dir == 1 || dir == 2)
2004       {
2005         cx = c1;
2006         cy = c2;
2007         bx = cx;
2008         by = b1;
2009       }
2010       else
2011       {
2012         cx = c2;
2013         cy = c1;
2014         bx = b1;
2015         by = cy;
2016       }
2017
2018       BlitBitmap(src_bitmap, drawto_field, src_x + bx, src_y + by,
2019                  width, height,
2020                  FX + sx * TILEX_VAR + cx,
2021                  FY + sy * TILEY_VAR + cy);
2022     }
2023   }
2024 }
2025
2026 static void DrawLevelFieldCrumbledExt(int x, int y, int graphic, int frame)
2027 {
2028   int sx = SCREENX(x), sy = SCREENY(y);
2029   int element;
2030   int i;
2031   static int xy[4][2] =
2032   {
2033     { 0, -1 },
2034     { -1, 0 },
2035     { +1, 0 },
2036     { 0, +1 }
2037   };
2038
2039   if (!IN_LEV_FIELD(x, y))
2040     return;
2041
2042   element = TILE_GFX_ELEMENT(x, y);
2043
2044   if (IS_CRUMBLED_TILE(x, y, element))          /* crumble field itself */
2045   {
2046     if (!IN_SCR_FIELD(sx, sy))
2047       return;
2048
2049     /* crumble field borders towards direct neighbour fields */
2050     for (i = 0; i < 4; i++)
2051     {
2052       int xx = x + xy[i][0];
2053       int yy = y + xy[i][1];
2054
2055       element = (IN_LEV_FIELD(xx, yy) ? TILE_GFX_ELEMENT(xx, yy) :
2056                  BorderElement);
2057
2058       /* check if neighbour field is of same crumble type */
2059       if (IS_CRUMBLED_TILE(xx, yy, element) &&
2060           graphic_info[graphic].class ==
2061           graphic_info[el_act2crm(element, ACTION_DEFAULT)].class)
2062         continue;
2063
2064       DrawLevelFieldCrumbledBorders(x, y, graphic, frame, i);
2065     }
2066
2067     /* crumble inner field corners towards corner neighbour fields */
2068     if ((graphic_info[graphic].style & STYLE_INNER_CORNERS) &&
2069         graphic_info[graphic].anim_frames == 2)
2070     {
2071       for (i = 0; i < 4; i++)
2072       {
2073         int dx = (i & 1 ? +1 : -1);
2074         int dy = (i & 2 ? +1 : -1);
2075
2076         DrawLevelFieldCrumbledInnerCorners(x, y, dx, dy, graphic);
2077       }
2078     }
2079
2080     MarkTileDirty(sx, sy);
2081   }
2082   else          /* center field is not crumbled -- crumble neighbour fields */
2083   {
2084     /* crumble field borders of direct neighbour fields */
2085     for (i = 0; i < 4; i++)
2086     {
2087       int xx = x + xy[i][0];
2088       int yy = y + xy[i][1];
2089       int sxx = sx + xy[i][0];
2090       int syy = sy + xy[i][1];
2091
2092       if (!IN_LEV_FIELD(xx, yy) ||
2093           !IN_SCR_FIELD(sxx, syy))
2094         continue;
2095
2096       if (Feld[xx][yy] == EL_ELEMENT_SNAPPING)
2097         continue;
2098
2099       element = TILE_GFX_ELEMENT(xx, yy);
2100
2101       if (!IS_CRUMBLED_TILE(xx, yy, element))
2102         continue;
2103
2104       graphic = el_act2crm(element, ACTION_DEFAULT);
2105
2106       DrawLevelFieldCrumbledBorders(xx, yy, graphic, 0, 3 - i);
2107
2108       MarkTileDirty(sxx, syy);
2109     }
2110
2111     /* crumble inner field corners of corner neighbour fields */
2112     for (i = 0; i < 4; i++)
2113     {
2114       int dx = (i & 1 ? +1 : -1);
2115       int dy = (i & 2 ? +1 : -1);
2116       int xx = x + dx;
2117       int yy = y + dy;
2118       int sxx = sx + dx;
2119       int syy = sy + dy;
2120
2121       if (!IN_LEV_FIELD(xx, yy) ||
2122           !IN_SCR_FIELD(sxx, syy))
2123         continue;
2124
2125       if (Feld[xx][yy] == EL_ELEMENT_SNAPPING)
2126         continue;
2127
2128       element = TILE_GFX_ELEMENT(xx, yy);
2129
2130       if (!IS_CRUMBLED_TILE(xx, yy, element))
2131         continue;
2132
2133       graphic = el_act2crm(element, ACTION_DEFAULT);
2134
2135       if ((graphic_info[graphic].style & STYLE_INNER_CORNERS) &&
2136           graphic_info[graphic].anim_frames == 2)
2137         DrawLevelFieldCrumbledInnerCorners(xx, yy, -dx, -dy, graphic);
2138
2139       MarkTileDirty(sxx, syy);
2140     }
2141   }
2142 }
2143
2144 void DrawLevelFieldCrumbled(int x, int y)
2145 {
2146   int graphic;
2147
2148   if (!IN_LEV_FIELD(x, y))
2149     return;
2150
2151   if (Feld[x][y] == EL_ELEMENT_SNAPPING &&
2152       GfxElement[x][y] != EL_UNDEFINED &&
2153       GFX_CRUMBLED(GfxElement[x][y]))
2154   {
2155     DrawLevelFieldCrumbledDigging(x, y, GfxDir[x][y], GfxFrame[x][y]);
2156
2157     return;
2158   }
2159
2160   graphic = el_act2crm(TILE_GFX_ELEMENT(x, y), ACTION_DEFAULT);
2161
2162   DrawLevelFieldCrumbledExt(x, y, graphic, 0);
2163 }
2164
2165 void DrawLevelFieldCrumbledDigging(int x, int y, int direction,
2166                                    int step_frame)
2167 {
2168   int graphic1 = el_act_dir2img(GfxElement[x][y], ACTION_DIGGING, direction);
2169   int graphic2 = el_act_dir2crm(GfxElement[x][y], ACTION_DIGGING, direction);
2170   int frame1 = getGraphicAnimationFrame(graphic1, step_frame);
2171   int frame2 = getGraphicAnimationFrame(graphic2, step_frame);
2172   int sx = SCREENX(x), sy = SCREENY(y);
2173
2174   DrawGraphic(sx, sy, graphic1, frame1);
2175   DrawLevelFieldCrumbledExt(x, y, graphic2, frame2);
2176 }
2177
2178 void DrawLevelFieldCrumbledNeighbours(int x, int y)
2179 {
2180   int sx = SCREENX(x), sy = SCREENY(y);
2181   static int xy[4][2] =
2182   {
2183     { 0, -1 },
2184     { -1, 0 },
2185     { +1, 0 },
2186     { 0, +1 }
2187   };
2188   int i;
2189
2190   /* crumble direct neighbour fields (required for field borders) */
2191   for (i = 0; i < 4; i++)
2192   {
2193     int xx = x + xy[i][0];
2194     int yy = y + xy[i][1];
2195     int sxx = sx + xy[i][0];
2196     int syy = sy + xy[i][1];
2197
2198     if (!IN_LEV_FIELD(xx, yy) ||
2199         !IN_SCR_FIELD(sxx, syy) ||
2200         !GFX_CRUMBLED(Feld[xx][yy]) ||
2201         IS_MOVING(xx, yy))
2202       continue;
2203
2204     DrawLevelField(xx, yy);
2205   }
2206
2207   /* crumble corner neighbour fields (required for inner field corners) */
2208   for (i = 0; i < 4; i++)
2209   {
2210     int dx = (i & 1 ? +1 : -1);
2211     int dy = (i & 2 ? +1 : -1);
2212     int xx = x + dx;
2213     int yy = y + dy;
2214     int sxx = sx + dx;
2215     int syy = sy + dy;
2216
2217     if (!IN_LEV_FIELD(xx, yy) ||
2218         !IN_SCR_FIELD(sxx, syy) ||
2219         !GFX_CRUMBLED(Feld[xx][yy]) ||
2220         IS_MOVING(xx, yy))
2221       continue;
2222
2223     int element = TILE_GFX_ELEMENT(xx, yy);
2224     int graphic = el_act2crm(element, ACTION_DEFAULT);
2225
2226     if ((graphic_info[graphic].style & STYLE_INNER_CORNERS) &&
2227         graphic_info[graphic].anim_frames == 2)
2228       DrawLevelField(xx, yy);
2229   }
2230 }
2231
2232 static int getBorderElement(int x, int y)
2233 {
2234   int border[7][2] =
2235   {
2236     { EL_STEELWALL_TOPLEFT,             EL_INVISIBLE_STEELWALL_TOPLEFT     },
2237     { EL_STEELWALL_TOPRIGHT,            EL_INVISIBLE_STEELWALL_TOPRIGHT    },
2238     { EL_STEELWALL_BOTTOMLEFT,          EL_INVISIBLE_STEELWALL_BOTTOMLEFT  },
2239     { EL_STEELWALL_BOTTOMRIGHT,         EL_INVISIBLE_STEELWALL_BOTTOMRIGHT },
2240     { EL_STEELWALL_VERTICAL,            EL_INVISIBLE_STEELWALL_VERTICAL    },
2241     { EL_STEELWALL_HORIZONTAL,          EL_INVISIBLE_STEELWALL_HORIZONTAL  },
2242     { EL_STEELWALL,                     EL_INVISIBLE_STEELWALL             }
2243   };
2244   int steel_type = (BorderElement == EL_STEELWALL ? 0 : 1);
2245   int steel_position = (x == -1         && y == -1              ? 0 :
2246                         x == lev_fieldx && y == -1              ? 1 :
2247                         x == -1         && y == lev_fieldy      ? 2 :
2248                         x == lev_fieldx && y == lev_fieldy      ? 3 :
2249                         x == -1         || x == lev_fieldx      ? 4 :
2250                         y == -1         || y == lev_fieldy      ? 5 : 6);
2251
2252   return border[steel_position][steel_type];
2253 }
2254
2255 void DrawScreenElement(int x, int y, int element)
2256 {
2257   DrawScreenElementExt(x, y, 0, 0, element, NO_CUTTING, NO_MASKING);
2258   DrawLevelFieldCrumbled(LEVELX(x), LEVELY(y));
2259 }
2260
2261 void DrawLevelElement(int x, int y, int element)
2262 {
2263   if (IN_LEV_FIELD(x, y) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2264     DrawScreenElement(SCREENX(x), SCREENY(y), element);
2265 }
2266
2267 void DrawScreenField(int x, int y)
2268 {
2269   int lx = LEVELX(x), ly = LEVELY(y);
2270   int element, content;
2271
2272   if (!IN_LEV_FIELD(lx, ly))
2273   {
2274     if (lx < -1 || lx > lev_fieldx || ly < -1 || ly > lev_fieldy)
2275       element = EL_EMPTY;
2276     else
2277       element = getBorderElement(lx, ly);
2278
2279     DrawScreenElement(x, y, element);
2280
2281     return;
2282   }
2283
2284   element = Feld[lx][ly];
2285   content = Store[lx][ly];
2286
2287   if (IS_MOVING(lx, ly))
2288   {
2289     int horiz_move = (MovDir[lx][ly] == MV_LEFT || MovDir[lx][ly] == MV_RIGHT);
2290     boolean cut_mode = NO_CUTTING;
2291
2292     if (element == EL_QUICKSAND_EMPTYING ||
2293         element == EL_QUICKSAND_FAST_EMPTYING ||
2294         element == EL_MAGIC_WALL_EMPTYING ||
2295         element == EL_BD_MAGIC_WALL_EMPTYING ||
2296         element == EL_DC_MAGIC_WALL_EMPTYING ||
2297         element == EL_AMOEBA_DROPPING)
2298       cut_mode = CUT_ABOVE;
2299     else if (element == EL_QUICKSAND_FILLING ||
2300              element == EL_QUICKSAND_FAST_FILLING ||
2301              element == EL_MAGIC_WALL_FILLING ||
2302              element == EL_BD_MAGIC_WALL_FILLING ||
2303              element == EL_DC_MAGIC_WALL_FILLING)
2304       cut_mode = CUT_BELOW;
2305
2306     if (cut_mode == CUT_ABOVE)
2307       DrawScreenElement(x, y, element);
2308     else
2309       DrawScreenElement(x, y, EL_EMPTY);
2310
2311     if (horiz_move)
2312       DrawScreenElementShifted(x, y, MovPos[lx][ly], 0, element, NO_CUTTING);
2313     else if (cut_mode == NO_CUTTING)
2314       DrawScreenElementShifted(x, y, 0, MovPos[lx][ly], element, cut_mode);
2315     else
2316     {
2317       DrawScreenElementShifted(x, y, 0, MovPos[lx][ly], content, cut_mode);
2318
2319       if (cut_mode == CUT_BELOW &&
2320           IN_LEV_FIELD(lx, ly + 1) && IN_SCR_FIELD(x, y + 1))
2321         DrawLevelElement(lx, ly + 1, element);
2322     }
2323
2324     if (content == EL_ACID)
2325     {
2326       int dir = MovDir[lx][ly];
2327       int newlx = lx + (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
2328       int newly = ly + (dir == MV_UP   ? -1 : dir == MV_DOWN  ? +1 : 0);
2329
2330       DrawLevelElementThruMask(newlx, newly, EL_ACID);
2331
2332       // prevent target field from being drawn again (but without masking)
2333       // (this would happen if target field is scanned after moving element)
2334       Stop[newlx][newly] = TRUE;
2335     }
2336   }
2337   else if (IS_BLOCKED(lx, ly))
2338   {
2339     int oldx, oldy;
2340     int sx, sy;
2341     int horiz_move;
2342     boolean cut_mode = NO_CUTTING;
2343     int element_old, content_old;
2344
2345     Blocked2Moving(lx, ly, &oldx, &oldy);
2346     sx = SCREENX(oldx);
2347     sy = SCREENY(oldy);
2348     horiz_move = (MovDir[oldx][oldy] == MV_LEFT ||
2349                   MovDir[oldx][oldy] == MV_RIGHT);
2350
2351     element_old = Feld[oldx][oldy];
2352     content_old = Store[oldx][oldy];
2353
2354     if (element_old == EL_QUICKSAND_EMPTYING ||
2355         element_old == EL_QUICKSAND_FAST_EMPTYING ||
2356         element_old == EL_MAGIC_WALL_EMPTYING ||
2357         element_old == EL_BD_MAGIC_WALL_EMPTYING ||
2358         element_old == EL_DC_MAGIC_WALL_EMPTYING ||
2359         element_old == EL_AMOEBA_DROPPING)
2360       cut_mode = CUT_ABOVE;
2361
2362     DrawScreenElement(x, y, EL_EMPTY);
2363
2364     if (horiz_move)
2365       DrawScreenElementShifted(sx, sy, MovPos[oldx][oldy], 0, element_old,
2366                                NO_CUTTING);
2367     else if (cut_mode == NO_CUTTING)
2368       DrawScreenElementShifted(sx, sy, 0, MovPos[oldx][oldy], element_old,
2369                                cut_mode);
2370     else
2371       DrawScreenElementShifted(sx, sy, 0, MovPos[oldx][oldy], content_old,
2372                                cut_mode);
2373   }
2374   else if (IS_DRAWABLE(element))
2375     DrawScreenElement(x, y, element);
2376   else
2377     DrawScreenElement(x, y, EL_EMPTY);
2378 }
2379
2380 void DrawLevelField(int x, int y)
2381 {
2382   if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2383     DrawScreenField(SCREENX(x), SCREENY(y));
2384   else if (IS_MOVING(x, y))
2385   {
2386     int newx,newy;
2387
2388     Moving2Blocked(x, y, &newx, &newy);
2389     if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
2390       DrawScreenField(SCREENX(newx), SCREENY(newy));
2391   }
2392   else if (IS_BLOCKED(x, y))
2393   {
2394     int oldx, oldy;
2395
2396     Blocked2Moving(x, y, &oldx, &oldy);
2397     if (IN_SCR_FIELD(SCREENX(oldx), SCREENY(oldy)))
2398       DrawScreenField(SCREENX(oldx), SCREENY(oldy));
2399   }
2400 }
2401
2402 void DrawSizedElement(int x, int y, int element, int tilesize)
2403 {
2404   int graphic;
2405
2406   graphic = el2edimg(element);
2407   DrawSizedGraphic(x, y, graphic, 0, tilesize);
2408 }
2409
2410 void DrawMiniElement(int x, int y, int element)
2411 {
2412   int graphic;
2413
2414   graphic = el2edimg(element);
2415   DrawMiniGraphic(x, y, graphic);
2416 }
2417
2418 void DrawSizedElementOrWall(int sx, int sy, int scroll_x, int scroll_y,
2419                             int tilesize)
2420 {
2421   int x = sx + scroll_x, y = sy + scroll_y;
2422
2423   if (x < -1 || x > lev_fieldx || y < -1 || y > lev_fieldy)
2424     DrawSizedElement(sx, sy, EL_EMPTY, tilesize);
2425   else if (x > -1 && x < lev_fieldx && y > -1 && y < lev_fieldy)
2426     DrawSizedElement(sx, sy, Feld[x][y], tilesize);
2427   else
2428     DrawSizedGraphic(sx, sy, el2edimg(getBorderElement(x, y)), 0, tilesize);
2429 }
2430
2431 void DrawMiniElementOrWall(int sx, int sy, int scroll_x, int scroll_y)
2432 {
2433   int x = sx + scroll_x, y = sy + scroll_y;
2434
2435   if (x < -1 || x > lev_fieldx || y < -1 || y > lev_fieldy)
2436     DrawMiniElement(sx, sy, EL_EMPTY);
2437   else if (x > -1 && x < lev_fieldx && y > -1 && y < lev_fieldy)
2438     DrawMiniElement(sx, sy, Feld[x][y]);
2439   else
2440     DrawMiniGraphic(sx, sy, el2edimg(getBorderElement(x, y)));
2441 }
2442
2443 void DrawEnvelopeBackgroundTiles(int graphic, int startx, int starty,
2444                                  int x, int y, int xsize, int ysize,
2445                                  int tile_width, int tile_height)
2446 {
2447   Bitmap *src_bitmap;
2448   int src_x, src_y;
2449   int dst_x = startx + x * tile_width;
2450   int dst_y = starty + y * tile_height;
2451   int width  = graphic_info[graphic].width;
2452   int height = graphic_info[graphic].height;
2453   int inner_width_raw  = MAX(width  - 2 * tile_width,  tile_width);
2454   int inner_height_raw = MAX(height - 2 * tile_height, tile_height);
2455   int inner_width  = inner_width_raw  - (inner_width_raw  % tile_width);
2456   int inner_height = inner_height_raw - (inner_height_raw % tile_height);
2457   int inner_sx = (width  >= 3 * tile_width  ? tile_width  : 0);
2458   int inner_sy = (height >= 3 * tile_height ? tile_height : 0);
2459   boolean draw_masked = graphic_info[graphic].draw_masked;
2460
2461   getFixedGraphicSource(graphic, 0, &src_bitmap, &src_x, &src_y);
2462
2463   if (src_bitmap == NULL || width < tile_width || height < tile_height)
2464   {
2465     ClearRectangle(drawto, dst_x, dst_y, tile_width, tile_height);
2466     return;
2467   }
2468
2469   src_x += (x == 0 ? 0 : x == xsize - 1 ? width  - tile_width  :
2470             inner_sx + (x - 1) * tile_width  % inner_width);
2471   src_y += (y == 0 ? 0 : y == ysize - 1 ? height - tile_height :
2472             inner_sy + (y - 1) * tile_height % inner_height);
2473
2474   if (draw_masked)
2475     BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, tile_width, tile_height,
2476                      dst_x, dst_y);
2477   else
2478     BlitBitmap(src_bitmap, drawto, src_x, src_y, tile_width, tile_height,
2479                dst_x, dst_y);
2480 }
2481
2482 void DrawEnvelopeBackground(int graphic, int startx, int starty,
2483                             int x, int y, int xsize, int ysize, int font_nr)
2484 {
2485   int font_width  = getFontWidth(font_nr);
2486   int font_height = getFontHeight(font_nr);
2487
2488   DrawEnvelopeBackgroundTiles(graphic, startx, starty, x, y, xsize, ysize,
2489                               font_width, font_height);
2490 }
2491
2492 void AnimateEnvelope(int envelope_nr, int anim_mode, int action)
2493 {
2494   int graphic = IMG_BACKGROUND_ENVELOPE_1 + envelope_nr;
2495   Bitmap *src_bitmap = graphic_info[graphic].bitmap;
2496   int mask_mode = (src_bitmap != NULL ? BLIT_MASKED : BLIT_ON_BACKGROUND);
2497   boolean ffwd_delay = (tape.playing && tape.fast_forward);
2498   boolean no_delay = (tape.warp_forward);
2499   unsigned int anim_delay = 0;
2500   int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
2501   int anim_delay_value = (no_delay ? 0 : frame_delay_value) / 2;
2502   int font_nr = FONT_ENVELOPE_1 + envelope_nr;
2503   int font_width = getFontWidth(font_nr);
2504   int font_height = getFontHeight(font_nr);
2505   int max_xsize = level.envelope[envelope_nr].xsize;
2506   int max_ysize = level.envelope[envelope_nr].ysize;
2507   int xstart = (anim_mode & ANIM_VERTICAL ? max_xsize : 0);
2508   int ystart = (anim_mode & ANIM_HORIZONTAL ? max_ysize : 0);
2509   int xend = max_xsize;
2510   int yend = (anim_mode != ANIM_DEFAULT ? max_ysize : 0);
2511   int xstep = (xstart < xend ? 1 : 0);
2512   int ystep = (ystart < yend || xstep == 0 ? 1 : 0);
2513   int start = 0;
2514   int end = MAX(xend - xstart, yend - ystart);
2515   int i;
2516
2517   for (i = start; i <= end; i++)
2518   {
2519     int last_frame = end;       // last frame of this "for" loop
2520     int x = xstart + i * xstep;
2521     int y = ystart + i * ystep;
2522     int xsize = (action == ACTION_CLOSING ? xend - (x - xstart) : x) + 2;
2523     int ysize = (action == ACTION_CLOSING ? yend - (y - ystart) : y) + 2;
2524     int sx = SX + (SXSIZE - xsize * font_width)  / 2;
2525     int sy = SY + (SYSIZE - ysize * font_height) / 2;
2526     int xx, yy;
2527
2528     SetDrawtoField(DRAW_TO_FIELDBUFFER);
2529
2530     BlitScreenToBitmap(backbuffer);
2531
2532     SetDrawtoField(DRAW_TO_BACKBUFFER);
2533
2534     for (yy = 0; yy < ysize; yy++)
2535       for (xx = 0; xx < xsize; xx++)
2536         DrawEnvelopeBackground(graphic, sx, sy, xx, yy, xsize, ysize, font_nr);
2537
2538     DrawTextBuffer(sx + font_width, sy + font_height,
2539                    level.envelope[envelope_nr].text, font_nr, max_xsize,
2540                    xsize - 2, ysize - 2, 0, mask_mode,
2541                    level.envelope[envelope_nr].autowrap,
2542                    level.envelope[envelope_nr].centered, FALSE);
2543
2544     redraw_mask |= REDRAW_FIELD;
2545     BackToFront();
2546
2547     SkipUntilDelayReached(&anim_delay, anim_delay_value, &i, last_frame);
2548   }
2549 }
2550
2551 void ShowEnvelope(int envelope_nr)
2552 {
2553   int element = EL_ENVELOPE_1 + envelope_nr;
2554   int graphic = IMG_BACKGROUND_ENVELOPE_1 + envelope_nr;
2555   int sound_opening = element_info[element].sound[ACTION_OPENING];
2556   int sound_closing = element_info[element].sound[ACTION_CLOSING];
2557   boolean ffwd_delay = (tape.playing && tape.fast_forward);
2558   boolean no_delay = (tape.warp_forward);
2559   int normal_delay_value = ONE_SECOND_DELAY / (ffwd_delay ? 2 : 1);
2560   int wait_delay_value = (no_delay ? 0 : normal_delay_value);
2561   int anim_mode = graphic_info[graphic].anim_mode;
2562   int main_anim_mode = (anim_mode == ANIM_NONE ? ANIM_VERTICAL|ANIM_HORIZONTAL:
2563                         anim_mode == ANIM_DEFAULT ? ANIM_VERTICAL : anim_mode);
2564
2565   game.envelope_active = TRUE;  /* needed for RedrawPlayfield() events */
2566
2567   PlayMenuSoundStereo(sound_opening, SOUND_MIDDLE);
2568
2569   if (anim_mode == ANIM_DEFAULT)
2570     AnimateEnvelope(envelope_nr, ANIM_DEFAULT, ACTION_OPENING);
2571
2572   AnimateEnvelope(envelope_nr, main_anim_mode, ACTION_OPENING);
2573
2574   if (tape.playing)
2575     Delay(wait_delay_value);
2576   else
2577     WaitForEventToContinue();
2578
2579   PlayMenuSoundStereo(sound_closing, SOUND_MIDDLE);
2580
2581   if (anim_mode != ANIM_NONE)
2582     AnimateEnvelope(envelope_nr, main_anim_mode, ACTION_CLOSING);
2583
2584   if (anim_mode == ANIM_DEFAULT)
2585     AnimateEnvelope(envelope_nr, ANIM_DEFAULT, ACTION_CLOSING);
2586
2587   game.envelope_active = FALSE;
2588
2589   SetDrawtoField(DRAW_TO_FIELDBUFFER);
2590
2591   redraw_mask |= REDRAW_FIELD;
2592   BackToFront();
2593 }
2594
2595 static void setRequestBasePosition(int *x, int *y)
2596 {
2597   int sx_base, sy_base;
2598
2599   if (request.x != -1)
2600     sx_base = request.x;
2601   else if (request.align == ALIGN_LEFT)
2602     sx_base = SX;
2603   else if (request.align == ALIGN_RIGHT)
2604     sx_base = SX + SXSIZE;
2605   else
2606     sx_base = SX + SXSIZE / 2;
2607
2608   if (request.y != -1)
2609     sy_base = request.y;
2610   else if (request.valign == VALIGN_TOP)
2611     sy_base = SY;
2612   else if (request.valign == VALIGN_BOTTOM)
2613     sy_base = SY + SYSIZE;
2614   else
2615     sy_base = SY + SYSIZE / 2;
2616
2617   *x = sx_base;
2618   *y = sy_base;
2619 }
2620
2621 static void setRequestPositionExt(int *x, int *y, int width, int height,
2622                                   boolean add_border_size)
2623 {
2624   int border_size = request.border_size;
2625   int sx_base, sy_base;
2626   int sx, sy;
2627
2628   setRequestBasePosition(&sx_base, &sy_base);
2629
2630   if (request.align == ALIGN_LEFT)
2631     sx = sx_base;
2632   else if (request.align == ALIGN_RIGHT)
2633     sx = sx_base - width;
2634   else
2635     sx = sx_base - width  / 2;
2636
2637   if (request.valign == VALIGN_TOP)
2638     sy = sy_base;
2639   else if (request.valign == VALIGN_BOTTOM)
2640     sy = sy_base - height;
2641   else
2642     sy = sy_base - height / 2;
2643
2644   sx = MAX(0, MIN(sx, WIN_XSIZE - width));
2645   sy = MAX(0, MIN(sy, WIN_YSIZE - height));
2646
2647   if (add_border_size)
2648   {
2649     sx += border_size;
2650     sy += border_size;
2651   }
2652
2653   *x = sx;
2654   *y = sy;
2655 }
2656
2657 static void setRequestPosition(int *x, int *y, boolean add_border_size)
2658 {
2659   setRequestPositionExt(x, y, request.width, request.height, add_border_size);
2660 }
2661
2662 void DrawEnvelopeRequest(char *text)
2663 {
2664   char *text_final = text;
2665   char *text_door_style = NULL;
2666   int graphic = IMG_BACKGROUND_REQUEST;
2667   Bitmap *src_bitmap = graphic_info[graphic].bitmap;
2668   int mask_mode = (src_bitmap != NULL ? BLIT_MASKED : BLIT_ON_BACKGROUND);
2669   int font_nr = FONT_REQUEST;
2670   int font_width = getFontWidth(font_nr);
2671   int font_height = getFontHeight(font_nr);
2672   int border_size = request.border_size;
2673   int line_spacing = request.line_spacing;
2674   int line_height = font_height + line_spacing;
2675   int max_text_width  = request.width  - 2 * border_size;
2676   int max_text_height = request.height - 2 * border_size;
2677   int line_length = max_text_width  / font_width;
2678   int max_lines   = max_text_height / line_height;
2679   int text_width = line_length * font_width;
2680   int width = request.width;
2681   int height = request.height;
2682   int tile_size = MAX(request.step_offset, 1);
2683   int x_steps = width  / tile_size;
2684   int y_steps = height / tile_size;
2685   int sx_offset = border_size;
2686   int sy_offset = border_size;
2687   int sx, sy;
2688   int i, x, y;
2689
2690   if (request.centered)
2691     sx_offset = (request.width - text_width) / 2;
2692
2693   if (request.wrap_single_words && !request.autowrap)
2694   {
2695     char *src_text_ptr, *dst_text_ptr;
2696
2697     text_door_style = checked_malloc(2 * strlen(text) + 1);
2698
2699     src_text_ptr = text;
2700     dst_text_ptr = text_door_style;
2701
2702     while (*src_text_ptr)
2703     {
2704       if (*src_text_ptr == ' ' ||
2705           *src_text_ptr == '?' ||
2706           *src_text_ptr == '!')
2707         *dst_text_ptr++ = '\n';
2708
2709       if (*src_text_ptr != ' ')
2710         *dst_text_ptr++ = *src_text_ptr;
2711
2712       src_text_ptr++;
2713     }
2714
2715     *dst_text_ptr = '\0';
2716
2717     text_final = text_door_style;
2718   }
2719
2720   setRequestPosition(&sx, &sy, FALSE);
2721
2722   ClearRectangle(backbuffer, 0, 0, WIN_XSIZE, WIN_YSIZE);
2723
2724   for (y = 0; y < y_steps; y++)
2725     for (x = 0; x < x_steps; x++)
2726       DrawEnvelopeBackgroundTiles(graphic, sx, sy,
2727                                   x, y, x_steps, y_steps,
2728                                   tile_size, tile_size);
2729
2730   /* force DOOR font inside door area */
2731   SetFontStatus(GAME_MODE_PSEUDO_DOOR);
2732
2733   DrawTextBuffer(sx + sx_offset, sy + sy_offset, text_final, font_nr,
2734                  line_length, -1, max_lines, line_spacing, mask_mode,
2735                  request.autowrap, request.centered, FALSE);
2736
2737   ResetFontStatus();
2738
2739   for (i = 0; i < NUM_TOOL_BUTTONS; i++)
2740     RedrawGadget(tool_gadget[i]);
2741
2742   // store readily prepared envelope request for later use when animating
2743   BlitBitmap(backbuffer, bitmap_db_store_2, 0, 0, WIN_XSIZE, WIN_YSIZE, 0, 0);
2744
2745   if (text_door_style)
2746     free(text_door_style);
2747 }
2748
2749 void AnimateEnvelopeRequest(int anim_mode, int action)
2750 {
2751   int graphic = IMG_BACKGROUND_REQUEST;
2752   boolean draw_masked = graphic_info[graphic].draw_masked;
2753   int delay_value_normal = request.step_delay;
2754   int delay_value_fast = delay_value_normal / 2;
2755   boolean ffwd_delay = (tape.playing && tape.fast_forward);
2756   boolean no_delay = (tape.warp_forward);
2757   int delay_value = (ffwd_delay ? delay_value_fast : delay_value_normal);
2758   int anim_delay_value = (no_delay ? 0 : delay_value + 500 * 0) / 2;
2759   unsigned int anim_delay = 0;
2760
2761   int tile_size = MAX(request.step_offset, 1);
2762   int max_xsize = request.width  / tile_size;
2763   int max_ysize = request.height / tile_size;
2764   int max_xsize_inner = max_xsize - 2;
2765   int max_ysize_inner = max_ysize - 2;
2766
2767   int xstart = (anim_mode & ANIM_VERTICAL ? max_xsize_inner : 0);
2768   int ystart = (anim_mode & ANIM_HORIZONTAL ? max_ysize_inner : 0);
2769   int xend = max_xsize_inner;
2770   int yend = (anim_mode != ANIM_DEFAULT ? max_ysize_inner : 0);
2771   int xstep = (xstart < xend ? 1 : 0);
2772   int ystep = (ystart < yend || xstep == 0 ? 1 : 0);
2773   int start = 0;
2774   int end = MAX(xend - xstart, yend - ystart);
2775   int i;
2776
2777   if (setup.quick_doors)
2778   {
2779     xstart = xend;
2780     ystart = yend;
2781     end = 0;
2782   }
2783
2784   for (i = start; i <= end; i++)
2785   {
2786     int last_frame = end;       // last frame of this "for" loop
2787     int x = xstart + i * xstep;
2788     int y = ystart + i * ystep;
2789     int xsize = (action == ACTION_CLOSING ? xend - (x - xstart) : x) + 2;
2790     int ysize = (action == ACTION_CLOSING ? yend - (y - ystart) : y) + 2;
2791     int xsize_size_left = (xsize - 1) * tile_size;
2792     int ysize_size_top  = (ysize - 1) * tile_size;
2793     int max_xsize_pos = (max_xsize - 1) * tile_size;
2794     int max_ysize_pos = (max_ysize - 1) * tile_size;
2795     int width  = xsize * tile_size;
2796     int height = ysize * tile_size;
2797     int src_x, src_y;
2798     int dst_x, dst_y;
2799     int xx, yy;
2800
2801     setRequestPosition(&src_x, &src_y, FALSE);
2802     setRequestPositionExt(&dst_x, &dst_y, width, height, FALSE);
2803
2804     BlitBitmap(bitmap_db_store_1, backbuffer, 0, 0, WIN_XSIZE, WIN_YSIZE, 0, 0);
2805
2806     for (yy = 0; yy < 2; yy++)
2807     {
2808       for (xx = 0; xx < 2; xx++)
2809       {
2810         int src_xx = src_x + xx * max_xsize_pos;
2811         int src_yy = src_y + yy * max_ysize_pos;
2812         int dst_xx = dst_x + xx * xsize_size_left;
2813         int dst_yy = dst_y + yy * ysize_size_top;
2814         int xx_size = (xx ? tile_size : xsize_size_left);
2815         int yy_size = (yy ? tile_size : ysize_size_top);
2816
2817         if (draw_masked)
2818           BlitBitmapMasked(bitmap_db_store_2, backbuffer,
2819                            src_xx, src_yy, xx_size, yy_size, dst_xx, dst_yy);
2820         else
2821           BlitBitmap(bitmap_db_store_2, backbuffer,
2822                      src_xx, src_yy, xx_size, yy_size, dst_xx, dst_yy);
2823       }
2824     }
2825
2826     redraw_mask |= REDRAW_FIELD;
2827
2828     BackToFront();
2829
2830     SkipUntilDelayReached(&anim_delay, anim_delay_value, &i, last_frame);
2831   }
2832 }
2833
2834 void ShowEnvelopeRequest(char *text, unsigned int req_state, int action)
2835 {
2836   int graphic = IMG_BACKGROUND_REQUEST;
2837   int sound_opening = SND_REQUEST_OPENING;
2838   int sound_closing = SND_REQUEST_CLOSING;
2839   int anim_mode_1 = request.anim_mode;                  /* (higher priority) */
2840   int anim_mode_2 = graphic_info[graphic].anim_mode;    /* (lower priority) */
2841   int anim_mode = (anim_mode_1 != ANIM_DEFAULT ? anim_mode_1 : anim_mode_2);
2842   int main_anim_mode = (anim_mode == ANIM_NONE ? ANIM_VERTICAL|ANIM_HORIZONTAL:
2843                         anim_mode == ANIM_DEFAULT ? ANIM_VERTICAL : anim_mode);
2844
2845   if (game_status == GAME_MODE_PLAYING)
2846     BlitScreenToBitmap(backbuffer);
2847
2848   SetDrawtoField(DRAW_TO_BACKBUFFER);
2849
2850   // SetDrawBackgroundMask(REDRAW_NONE);
2851
2852   if (action == ACTION_OPENING)
2853   {
2854     BlitBitmap(backbuffer, bitmap_db_store_1, 0, 0, WIN_XSIZE, WIN_YSIZE, 0, 0);
2855
2856     if (req_state & REQ_ASK)
2857     {
2858       MapGadget(tool_gadget[TOOL_CTRL_ID_YES]);
2859       MapGadget(tool_gadget[TOOL_CTRL_ID_NO]);
2860     }
2861     else if (req_state & REQ_CONFIRM)
2862     {
2863       MapGadget(tool_gadget[TOOL_CTRL_ID_CONFIRM]);
2864     }
2865     else if (req_state & REQ_PLAYER)
2866     {
2867       MapGadget(tool_gadget[TOOL_CTRL_ID_PLAYER_1]);
2868       MapGadget(tool_gadget[TOOL_CTRL_ID_PLAYER_2]);
2869       MapGadget(tool_gadget[TOOL_CTRL_ID_PLAYER_3]);
2870       MapGadget(tool_gadget[TOOL_CTRL_ID_PLAYER_4]);
2871     }
2872
2873     DrawEnvelopeRequest(text);
2874   }
2875
2876   game.envelope_active = TRUE;  /* needed for RedrawPlayfield() events */
2877
2878   if (action == ACTION_OPENING)
2879   {
2880     PlayMenuSoundStereo(sound_opening, SOUND_MIDDLE);
2881
2882     if (anim_mode == ANIM_DEFAULT)
2883       AnimateEnvelopeRequest(ANIM_DEFAULT, ACTION_OPENING);
2884
2885     AnimateEnvelopeRequest(main_anim_mode, ACTION_OPENING);
2886   }
2887   else
2888   {
2889     PlayMenuSoundStereo(sound_closing, SOUND_MIDDLE);
2890
2891     if (anim_mode != ANIM_NONE)
2892       AnimateEnvelopeRequest(main_anim_mode, ACTION_CLOSING);
2893
2894     if (anim_mode == ANIM_DEFAULT)
2895       AnimateEnvelopeRequest(ANIM_DEFAULT, ACTION_CLOSING);
2896   }
2897
2898   game.envelope_active = FALSE;
2899
2900   if (action == ACTION_CLOSING)
2901     BlitBitmap(bitmap_db_store_1, backbuffer, 0, 0, WIN_XSIZE, WIN_YSIZE, 0, 0);
2902
2903   // SetDrawBackgroundMask(last_draw_background_mask);
2904
2905   redraw_mask |= REDRAW_FIELD;
2906
2907   BackToFront();
2908
2909   if (action == ACTION_CLOSING &&
2910       game_status == GAME_MODE_PLAYING &&
2911       level.game_engine_type == GAME_ENGINE_TYPE_RND)
2912     SetDrawtoField(DRAW_TO_FIELDBUFFER);
2913 }
2914
2915 void DrawPreviewElement(int dst_x, int dst_y, int element, int tilesize)
2916 {
2917   Bitmap *src_bitmap;
2918   int src_x, src_y;
2919   int graphic = el2preimg(element);
2920
2921   getSizedGraphicSource(graphic, 0, tilesize, &src_bitmap, &src_x, &src_y);
2922   BlitBitmap(src_bitmap, drawto, src_x, src_y, tilesize, tilesize, dst_x,dst_y);
2923 }
2924
2925 void DrawLevel(int draw_background_mask)
2926 {
2927   int x,y;
2928
2929   SetMainBackgroundImage(IMG_BACKGROUND_PLAYING);
2930   SetDrawBackgroundMask(draw_background_mask);
2931
2932   ClearField();
2933
2934   for (x = BX1; x <= BX2; x++)
2935     for (y = BY1; y <= BY2; y++)
2936       DrawScreenField(x, y);
2937
2938   redraw_mask |= REDRAW_FIELD;
2939 }
2940
2941 void DrawSizedLevel(int size_x, int size_y, int scroll_x, int scroll_y,
2942                     int tilesize)
2943 {
2944   int x,y;
2945
2946   for (x = 0; x < size_x; x++)
2947     for (y = 0; y < size_y; y++)
2948       DrawSizedElementOrWall(x, y, scroll_x, scroll_y, tilesize);
2949
2950   redraw_mask |= REDRAW_FIELD;
2951 }
2952
2953 void DrawMiniLevel(int size_x, int size_y, int scroll_x, int scroll_y)
2954 {
2955   int x,y;
2956
2957   for (x = 0; x < size_x; x++)
2958     for (y = 0; y < size_y; y++)
2959       DrawMiniElementOrWall(x, y, scroll_x, scroll_y);
2960
2961   redraw_mask |= REDRAW_FIELD;
2962 }
2963
2964 static void DrawPreviewLevelPlayfieldExt(int from_x, int from_y)
2965 {
2966   boolean show_level_border = (BorderElement != EL_EMPTY);
2967   int level_xsize = lev_fieldx + (show_level_border ? 2 : 0);
2968   int level_ysize = lev_fieldy + (show_level_border ? 2 : 0);
2969   int tile_size = preview.tile_size;
2970   int preview_width  = preview.xsize * tile_size;
2971   int preview_height = preview.ysize * tile_size;
2972   int real_preview_xsize = MIN(level_xsize, preview.xsize);
2973   int real_preview_ysize = MIN(level_ysize, preview.ysize);
2974   int real_preview_width  = real_preview_xsize * tile_size;
2975   int real_preview_height = real_preview_ysize * tile_size;
2976   int dst_x = SX + ALIGNED_XPOS(preview.x, preview_width, preview.align);
2977   int dst_y = SY + ALIGNED_YPOS(preview.y, preview_height, preview.valign);
2978   int x, y;
2979
2980   if (!IN_GFX_FIELD_FULL(dst_x, dst_y + preview_height - 1))
2981     return;
2982
2983   DrawBackground(dst_x, dst_y, preview_width, preview_height);
2984
2985   dst_x += (preview_width  - real_preview_width)  / 2;
2986   dst_y += (preview_height - real_preview_height) / 2;
2987
2988   for (x = 0; x < real_preview_xsize; x++)
2989   {
2990     for (y = 0; y < real_preview_ysize; y++)
2991     {
2992       int lx = from_x + x + (show_level_border ? -1 : 0);
2993       int ly = from_y + y + (show_level_border ? -1 : 0);
2994       int element = (IN_LEV_FIELD(lx, ly) ? level.field[lx][ly] :
2995                      getBorderElement(lx, ly));
2996
2997       DrawPreviewElement(dst_x + x * tile_size, dst_y + y * tile_size,
2998                          element, tile_size);
2999     }
3000   }
3001
3002   redraw_mask |= REDRAW_FIELD;
3003 }
3004
3005 #define MICROLABEL_EMPTY                0
3006 #define MICROLABEL_LEVEL_NAME           1
3007 #define MICROLABEL_LEVEL_AUTHOR_HEAD    2
3008 #define MICROLABEL_LEVEL_AUTHOR         3
3009 #define MICROLABEL_IMPORTED_FROM_HEAD   4
3010 #define MICROLABEL_IMPORTED_FROM        5
3011 #define MICROLABEL_IMPORTED_BY_HEAD     6
3012 #define MICROLABEL_IMPORTED_BY          7
3013
3014 static int getMaxTextLength(struct TextPosInfo *pos, int font_nr)
3015 {
3016   int max_text_width = SXSIZE;
3017   int font_width = getFontWidth(font_nr);
3018
3019   if (pos->align == ALIGN_CENTER)
3020     max_text_width = (pos->x < SXSIZE / 2 ? pos->x * 2 : (SXSIZE - pos->x) * 2);
3021   else if (pos->align == ALIGN_RIGHT)
3022     max_text_width = pos->x;
3023   else
3024     max_text_width = SXSIZE - pos->x;
3025
3026   return max_text_width / font_width;
3027 }
3028
3029 static void DrawPreviewLevelLabelExt(int mode)
3030 {
3031   struct TextPosInfo *pos = &menu.main.text.level_info_2;
3032   char label_text[MAX_OUTPUT_LINESIZE + 1];
3033   int max_len_label_text;
3034   int font_nr = pos->font;
3035   int i;
3036
3037   if (!IN_GFX_FIELD_FULL(pos->x, pos->y + getFontHeight(pos->font)))
3038     return;
3039
3040   if (mode == MICROLABEL_LEVEL_AUTHOR_HEAD ||
3041       mode == MICROLABEL_IMPORTED_FROM_HEAD ||
3042       mode == MICROLABEL_IMPORTED_BY_HEAD)
3043     font_nr = pos->font_alt;
3044
3045   max_len_label_text = getMaxTextLength(pos, font_nr);
3046
3047   if (pos->size != -1)
3048     max_len_label_text = pos->size;
3049
3050   for (i = 0; i < max_len_label_text; i++)
3051     label_text[i] = ' ';
3052   label_text[max_len_label_text] = '\0';
3053
3054   if (strlen(label_text) > 0)
3055     DrawTextSAligned(pos->x, pos->y, label_text, font_nr, pos->align);
3056
3057   strncpy(label_text,
3058           (mode == MICROLABEL_LEVEL_NAME ? level.name :
3059            mode == MICROLABEL_LEVEL_AUTHOR_HEAD ? "created by" :
3060            mode == MICROLABEL_LEVEL_AUTHOR ? level.author :
3061            mode == MICROLABEL_IMPORTED_FROM_HEAD ? "imported from" :
3062            mode == MICROLABEL_IMPORTED_FROM ? leveldir_current->imported_from :
3063            mode == MICROLABEL_IMPORTED_BY_HEAD ? "imported by" :
3064            mode == MICROLABEL_IMPORTED_BY ? leveldir_current->imported_by :""),
3065           max_len_label_text);
3066   label_text[max_len_label_text] = '\0';
3067
3068   if (strlen(label_text) > 0)
3069     DrawTextSAligned(pos->x, pos->y, label_text, font_nr, pos->align);
3070
3071   redraw_mask |= REDRAW_FIELD;
3072 }
3073
3074 static void DrawPreviewLevelExt(boolean restart)
3075 {
3076   static unsigned int scroll_delay = 0;
3077   static unsigned int label_delay = 0;
3078   static int from_x, from_y, scroll_direction;
3079   static int label_state, label_counter;
3080   unsigned int scroll_delay_value = preview.step_delay;
3081   boolean show_level_border = (BorderElement != EL_EMPTY);
3082   int level_xsize = lev_fieldx + (show_level_border ? 2 : 0);
3083   int level_ysize = lev_fieldy + (show_level_border ? 2 : 0);
3084
3085   if (restart)
3086   {
3087     from_x = 0;
3088     from_y = 0;
3089
3090     if (preview.anim_mode == ANIM_CENTERED)
3091     {
3092       if (level_xsize > preview.xsize)
3093         from_x = (level_xsize - preview.xsize) / 2;
3094       if (level_ysize > preview.ysize)
3095         from_y = (level_ysize - preview.ysize) / 2;
3096     }
3097
3098     from_x += preview.xoffset;
3099     from_y += preview.yoffset;
3100
3101     scroll_direction = MV_RIGHT;
3102     label_state = 1;
3103     label_counter = 0;
3104
3105     DrawPreviewLevelPlayfieldExt(from_x, from_y);
3106     DrawPreviewLevelLabelExt(label_state);
3107
3108     /* initialize delay counters */
3109     DelayReached(&scroll_delay, 0);
3110     DelayReached(&label_delay, 0);
3111
3112     if (leveldir_current->name)
3113     {
3114       struct TextPosInfo *pos = &menu.main.text.level_info_1;
3115       char label_text[MAX_OUTPUT_LINESIZE + 1];
3116       int font_nr = pos->font;
3117       int max_len_label_text = getMaxTextLength(pos, font_nr);
3118
3119       if (pos->size != -1)
3120         max_len_label_text = pos->size;
3121
3122       strncpy(label_text, leveldir_current->name, max_len_label_text);
3123       label_text[max_len_label_text] = '\0';
3124
3125       if (IN_GFX_FIELD_FULL(pos->x, pos->y + getFontHeight(pos->font)))
3126         DrawTextSAligned(pos->x, pos->y, label_text, font_nr, pos->align);
3127     }
3128
3129     return;
3130   }
3131
3132   /* scroll preview level, if needed */
3133   if (preview.anim_mode != ANIM_NONE &&
3134       (level_xsize > preview.xsize || level_ysize > preview.ysize) &&
3135       DelayReached(&scroll_delay, scroll_delay_value))
3136   {
3137     switch (scroll_direction)
3138     {
3139       case MV_LEFT:
3140         if (from_x > 0)
3141         {
3142           from_x -= preview.step_offset;
3143           from_x = (from_x < 0 ? 0 : from_x);
3144         }
3145         else
3146           scroll_direction = MV_UP;
3147         break;
3148
3149       case MV_RIGHT:
3150         if (from_x < level_xsize - preview.xsize)
3151         {
3152           from_x += preview.step_offset;
3153           from_x = (from_x > level_xsize - preview.xsize ?
3154                     level_xsize - preview.xsize : from_x);
3155         }
3156         else
3157           scroll_direction = MV_DOWN;
3158         break;
3159
3160       case MV_UP:
3161         if (from_y > 0)
3162         {
3163           from_y -= preview.step_offset;
3164           from_y = (from_y < 0 ? 0 : from_y);
3165         }
3166         else
3167           scroll_direction = MV_RIGHT;
3168         break;
3169
3170       case MV_DOWN:
3171         if (from_y < level_ysize - preview.ysize)
3172         {
3173           from_y += preview.step_offset;
3174           from_y = (from_y > level_ysize - preview.ysize ?
3175                     level_ysize - preview.ysize : from_y);
3176         }
3177         else
3178           scroll_direction = MV_LEFT;
3179         break;
3180
3181       default:
3182         break;
3183     }
3184
3185     DrawPreviewLevelPlayfieldExt(from_x, from_y);
3186   }
3187
3188   /* !!! THIS ALL SUCKS -- SHOULD BE CLEANLY REWRITTEN !!! */
3189   /* redraw micro level label, if needed */
3190   if (!strEqual(level.name, NAMELESS_LEVEL_NAME) &&
3191       !strEqual(level.author, ANONYMOUS_NAME) &&
3192       !strEqual(level.author, leveldir_current->name) &&
3193       DelayReached(&label_delay, MICROLEVEL_LABEL_DELAY))
3194   {
3195     int max_label_counter = 23;
3196
3197     if (leveldir_current->imported_from != NULL &&
3198         strlen(leveldir_current->imported_from) > 0)
3199       max_label_counter += 14;
3200     if (leveldir_current->imported_by != NULL &&
3201         strlen(leveldir_current->imported_by) > 0)
3202       max_label_counter += 14;
3203
3204     label_counter = (label_counter + 1) % max_label_counter;
3205     label_state = (label_counter >= 0 && label_counter <= 7 ?
3206                    MICROLABEL_LEVEL_NAME :
3207                    label_counter >= 9 && label_counter <= 12 ?
3208                    MICROLABEL_LEVEL_AUTHOR_HEAD :
3209                    label_counter >= 14 && label_counter <= 21 ?
3210                    MICROLABEL_LEVEL_AUTHOR :
3211                    label_counter >= 23 && label_counter <= 26 ?
3212                    MICROLABEL_IMPORTED_FROM_HEAD :
3213                    label_counter >= 28 && label_counter <= 35 ?
3214                    MICROLABEL_IMPORTED_FROM :
3215                    label_counter >= 37 && label_counter <= 40 ?
3216                    MICROLABEL_IMPORTED_BY_HEAD :
3217                    label_counter >= 42 && label_counter <= 49 ?
3218                    MICROLABEL_IMPORTED_BY : MICROLABEL_EMPTY);
3219
3220     if (leveldir_current->imported_from == NULL &&
3221         (label_state == MICROLABEL_IMPORTED_FROM_HEAD ||
3222          label_state == MICROLABEL_IMPORTED_FROM))
3223       label_state = (label_state == MICROLABEL_IMPORTED_FROM_HEAD ?
3224                      MICROLABEL_IMPORTED_BY_HEAD : MICROLABEL_IMPORTED_BY);
3225
3226     DrawPreviewLevelLabelExt(label_state);
3227   }
3228 }
3229
3230 void DrawPreviewLevelInitial()
3231 {
3232   DrawPreviewLevelExt(TRUE);
3233 }
3234
3235 void DrawPreviewLevelAnimation()
3236 {
3237   DrawPreviewLevelExt(FALSE);
3238 }
3239
3240 inline static void DrawGraphicAnimationExt(DrawBuffer *dst_bitmap, int x, int y,
3241                                            int graphic, int sync_frame,
3242                                            int mask_mode)
3243 {
3244   int frame = getGraphicAnimationFrame(graphic, sync_frame);
3245
3246   if (mask_mode == USE_MASKING)
3247     DrawGraphicThruMaskExt(dst_bitmap, x, y, graphic, frame);
3248   else
3249     DrawGraphicExt(dst_bitmap, x, y, graphic, frame);
3250 }
3251
3252 void DrawFixedGraphicAnimationExt(DrawBuffer *dst_bitmap, int x, int y,
3253                                   int graphic, int sync_frame, int mask_mode)
3254 {
3255   int frame = getGraphicAnimationFrame(graphic, sync_frame);
3256
3257   if (mask_mode == USE_MASKING)
3258     DrawFixedGraphicThruMaskExt(dst_bitmap, x, y, graphic, frame);
3259   else
3260     DrawFixedGraphicExt(dst_bitmap, x, y, graphic, frame);
3261 }
3262
3263 inline static void DrawGraphicAnimation(int x, int y, int graphic)
3264 {
3265   int lx = LEVELX(x), ly = LEVELY(y);
3266
3267   if (!IN_SCR_FIELD(x, y))
3268     return;
3269
3270   DrawGraphicAnimationExt(drawto_field, FX + x * TILEX_VAR, FY + y * TILEY_VAR,
3271                           graphic, GfxFrame[lx][ly], NO_MASKING);
3272
3273   MarkTileDirty(x, y);
3274 }
3275
3276 void DrawFixedGraphicAnimation(int x, int y, int graphic)
3277 {
3278   int lx = LEVELX(x), ly = LEVELY(y);
3279
3280   if (!IN_SCR_FIELD(x, y))
3281     return;
3282
3283   DrawGraphicAnimationExt(drawto_field, FX + x * TILEX, FY + y * TILEY,
3284                           graphic, GfxFrame[lx][ly], NO_MASKING);
3285   MarkTileDirty(x, y);
3286 }
3287
3288 void DrawLevelGraphicAnimation(int x, int y, int graphic)
3289 {
3290   DrawGraphicAnimation(SCREENX(x), SCREENY(y), graphic);
3291 }
3292
3293 void DrawLevelElementAnimation(int x, int y, int element)
3294 {
3295   int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
3296
3297   DrawGraphicAnimation(SCREENX(x), SCREENY(y), graphic);
3298 }
3299
3300 void DrawLevelGraphicAnimationIfNeeded(int x, int y, int graphic)
3301 {
3302   int sx = SCREENX(x), sy = SCREENY(y);
3303
3304   if (!IN_LEV_FIELD(x, y) || !IN_SCR_FIELD(sx, sy))
3305     return;
3306
3307   if (!IS_NEW_FRAME(GfxFrame[x][y], graphic))
3308     return;
3309
3310   DrawGraphicAnimation(sx, sy, graphic);
3311
3312 #if 1
3313   if (GFX_CRUMBLED(TILE_GFX_ELEMENT(x, y)))
3314     DrawLevelFieldCrumbled(x, y);
3315 #else
3316   if (GFX_CRUMBLED(Feld[x][y]))
3317     DrawLevelFieldCrumbled(x, y);
3318 #endif
3319 }
3320
3321 void DrawLevelElementAnimationIfNeeded(int x, int y, int element)
3322 {
3323   int sx = SCREENX(x), sy = SCREENY(y);
3324   int graphic;
3325
3326   if (!IN_LEV_FIELD(x, y) || !IN_SCR_FIELD(sx, sy))
3327     return;
3328
3329   graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
3330
3331   if (!IS_NEW_FRAME(GfxFrame[x][y], graphic))
3332     return;
3333
3334   DrawGraphicAnimation(sx, sy, graphic);
3335
3336   if (GFX_CRUMBLED(element))
3337     DrawLevelFieldCrumbled(x, y);
3338 }
3339
3340 static int getPlayerGraphic(struct PlayerInfo *player, int move_dir)
3341 {
3342   if (player->use_murphy)
3343   {
3344     /* this works only because currently only one player can be "murphy" ... */
3345     static int last_horizontal_dir = MV_LEFT;
3346     int graphic = el_act_dir2img(EL_SP_MURPHY, player->GfxAction, move_dir);
3347
3348     if (move_dir == MV_LEFT || move_dir == MV_RIGHT)
3349       last_horizontal_dir = move_dir;
3350
3351     if (graphic == IMG_SP_MURPHY)       /* undefined => use special graphic */
3352     {
3353       int direction = (player->is_snapping ? move_dir : last_horizontal_dir);
3354
3355       graphic = el_act_dir2img(EL_SP_MURPHY, player->GfxAction, direction);
3356     }
3357
3358     return graphic;
3359   }
3360   else
3361     return el_act_dir2img(player->artwork_element, player->GfxAction,move_dir);
3362 }
3363
3364 static boolean equalGraphics(int graphic1, int graphic2)
3365 {
3366   struct GraphicInfo *g1 = &graphic_info[graphic1];
3367   struct GraphicInfo *g2 = &graphic_info[graphic2];
3368
3369   return (g1->bitmap      == g2->bitmap &&
3370           g1->src_x       == g2->src_x &&
3371           g1->src_y       == g2->src_y &&
3372           g1->anim_frames == g2->anim_frames &&
3373           g1->anim_delay  == g2->anim_delay &&
3374           g1->anim_mode   == g2->anim_mode);
3375 }
3376
3377 void DrawAllPlayers()
3378 {
3379   int i;
3380
3381   for (i = 0; i < MAX_PLAYERS; i++)
3382     if (stored_player[i].active)
3383       DrawPlayer(&stored_player[i]);
3384 }
3385
3386 void DrawPlayerField(int x, int y)
3387 {
3388   if (!IS_PLAYER(x, y))
3389     return;
3390
3391   DrawPlayer(PLAYERINFO(x, y));
3392 }
3393
3394 #define DRAW_PLAYER_OVER_PUSHED_ELEMENT 1
3395
3396 void DrawPlayer(struct PlayerInfo *player)
3397 {
3398   int jx = player->jx;
3399   int jy = player->jy;
3400   int move_dir = player->MovDir;
3401   int dx = (move_dir == MV_LEFT ? -1 : move_dir == MV_RIGHT ? +1 : 0);
3402   int dy = (move_dir == MV_UP   ? -1 : move_dir == MV_DOWN  ? +1 : 0);
3403   int last_jx = (player->is_moving ? jx - dx : jx);
3404   int last_jy = (player->is_moving ? jy - dy : jy);
3405   int next_jx = jx + dx;
3406   int next_jy = jy + dy;
3407   boolean player_is_moving = (player->MovPos ? TRUE : FALSE);
3408   boolean player_is_opaque = FALSE;
3409   int sx = SCREENX(jx), sy = SCREENY(jy);
3410   int sxx = 0, syy = 0;
3411   int element = Feld[jx][jy], last_element = Feld[last_jx][last_jy];
3412   int graphic;
3413   int action = ACTION_DEFAULT;
3414   int last_player_graphic = getPlayerGraphic(player, move_dir);
3415   int last_player_frame = player->Frame;
3416   int frame = 0;
3417
3418   /* GfxElement[][] is set to the element the player is digging or collecting;
3419      remove also for off-screen player if the player is not moving anymore */
3420   if (IN_LEV_FIELD(jx, jy) && !player_is_moving)
3421     GfxElement[jx][jy] = EL_UNDEFINED;
3422
3423   if (!player->active || !IN_SCR_FIELD(SCREENX(last_jx), SCREENY(last_jy)))
3424     return;
3425
3426 #if DEBUG
3427   if (!IN_LEV_FIELD(jx, jy))
3428   {
3429     printf("DrawPlayerField(): x = %d, y = %d\n",jx,jy);
3430     printf("DrawPlayerField(): sx = %d, sy = %d\n",sx,sy);
3431     printf("DrawPlayerField(): This should never happen!\n");
3432     return;
3433   }
3434 #endif
3435
3436   if (element == EL_EXPLOSION)
3437     return;
3438
3439   action = (player->is_pushing    ? ACTION_PUSHING         :
3440             player->is_digging    ? ACTION_DIGGING         :
3441             player->is_collecting ? ACTION_COLLECTING      :
3442             player->is_moving     ? ACTION_MOVING          :
3443             player->is_snapping   ? ACTION_SNAPPING        :
3444             player->is_dropping   ? ACTION_DROPPING        :
3445             player->is_waiting    ? player->action_waiting : ACTION_DEFAULT);
3446
3447   if (player->is_waiting)
3448     move_dir = player->dir_waiting;
3449
3450   InitPlayerGfxAnimation(player, action, move_dir);
3451
3452   /* ----------------------------------------------------------------------- */
3453   /* draw things in the field the player is leaving, if needed               */
3454   /* ----------------------------------------------------------------------- */
3455
3456   if (player->is_moving)
3457   {
3458     if (Back[last_jx][last_jy] && IS_DRAWABLE(last_element))
3459     {
3460       DrawLevelElement(last_jx, last_jy, Back[last_jx][last_jy]);
3461
3462       if (last_element == EL_DYNAMITE_ACTIVE ||
3463           last_element == EL_EM_DYNAMITE_ACTIVE ||
3464           last_element == EL_SP_DISK_RED_ACTIVE)
3465         DrawDynamite(last_jx, last_jy);
3466       else
3467         DrawLevelFieldThruMask(last_jx, last_jy);
3468     }
3469     else if (last_element == EL_DYNAMITE_ACTIVE ||
3470              last_element == EL_EM_DYNAMITE_ACTIVE ||
3471              last_element == EL_SP_DISK_RED_ACTIVE)
3472       DrawDynamite(last_jx, last_jy);
3473 #if 0
3474     /* !!! this is not enough to prevent flickering of players which are
3475        moving next to each others without a free tile between them -- this
3476        can only be solved by drawing all players layer by layer (first the
3477        background, then the foreground etc.) !!! => TODO */
3478     else if (!IS_PLAYER(last_jx, last_jy))
3479       DrawLevelField(last_jx, last_jy);
3480 #else
3481     else
3482       DrawLevelField(last_jx, last_jy);
3483 #endif
3484
3485     if (player->is_pushing && IN_SCR_FIELD(SCREENX(next_jx), SCREENY(next_jy)))
3486       DrawLevelElement(next_jx, next_jy, EL_EMPTY);
3487   }
3488
3489   if (!IN_SCR_FIELD(sx, sy))
3490     return;
3491
3492   /* ----------------------------------------------------------------------- */
3493   /* draw things behind the player, if needed                                */
3494   /* ----------------------------------------------------------------------- */
3495
3496   if (Back[jx][jy])
3497     DrawLevelElement(jx, jy, Back[jx][jy]);
3498   else if (IS_ACTIVE_BOMB(element))
3499     DrawLevelElement(jx, jy, EL_EMPTY);
3500   else
3501   {
3502     if (player_is_moving && GfxElement[jx][jy] != EL_UNDEFINED)
3503     {
3504       int old_element = GfxElement[jx][jy];
3505       int old_graphic = el_act_dir2img(old_element, action, move_dir);
3506       int frame = getGraphicAnimationFrame(old_graphic, player->StepFrame);
3507
3508       if (GFX_CRUMBLED(old_element))
3509         DrawLevelFieldCrumbledDigging(jx, jy, move_dir, player->StepFrame);
3510       else
3511         DrawGraphic(sx, sy, old_graphic, frame);
3512
3513       if (graphic_info[old_graphic].anim_mode & ANIM_OPAQUE_PLAYER)
3514         player_is_opaque = TRUE;
3515     }
3516     else
3517     {
3518       GfxElement[jx][jy] = EL_UNDEFINED;
3519
3520       /* make sure that pushed elements are drawn with correct frame rate */
3521       graphic = el_act_dir2img(element, ACTION_PUSHING, move_dir);
3522
3523       if (player->is_pushing && player->is_moving && !IS_ANIM_MODE_CE(graphic))
3524         GfxFrame[jx][jy] = player->StepFrame;
3525
3526       DrawLevelField(jx, jy);
3527     }
3528   }
3529
3530 #if !DRAW_PLAYER_OVER_PUSHED_ELEMENT
3531   /* ----------------------------------------------------------------------- */
3532   /* draw player himself                                                     */
3533   /* ----------------------------------------------------------------------- */
3534
3535   graphic = getPlayerGraphic(player, move_dir);
3536
3537   /* in the case of changed player action or direction, prevent the current
3538      animation frame from being restarted for identical animations */
3539   if (player->Frame == 0 && equalGraphics(graphic, last_player_graphic))
3540     player->Frame = last_player_frame;
3541
3542   frame = getGraphicAnimationFrame(graphic, player->Frame);
3543
3544   if (player->GfxPos)
3545   {
3546     if (move_dir == MV_LEFT || move_dir == MV_RIGHT)
3547       sxx = player->GfxPos;
3548     else
3549       syy = player->GfxPos;
3550   }
3551
3552   if (player_is_opaque)
3553     DrawGraphicShifted(sx, sy, sxx, syy, graphic, frame,NO_CUTTING,NO_MASKING);
3554   else
3555     DrawGraphicShiftedThruMask(sx, sy, sxx, syy, graphic, frame, NO_CUTTING);
3556
3557   if (SHIELD_ON(player))
3558   {
3559     int graphic = (player->shield_deadly_time_left ? IMG_SHIELD_DEADLY_ACTIVE :
3560                    IMG_SHIELD_NORMAL_ACTIVE);
3561     int frame = getGraphicAnimationFrame(graphic, -1);
3562
3563     DrawGraphicShiftedThruMask(sx, sy, sxx, syy, graphic, frame, NO_CUTTING);
3564   }
3565 #endif
3566
3567 #if DRAW_PLAYER_OVER_PUSHED_ELEMENT
3568   if (player->GfxPos)
3569   {
3570     if (move_dir == MV_LEFT || move_dir == MV_RIGHT)
3571       sxx = player->GfxPos;
3572     else
3573       syy = player->GfxPos;
3574   }
3575 #endif
3576
3577   /* ----------------------------------------------------------------------- */
3578   /* draw things the player is pushing, if needed                            */
3579   /* ----------------------------------------------------------------------- */
3580
3581   if (player->is_pushing && player->is_moving)
3582   {
3583     int px = SCREENX(jx), py = SCREENY(jy);
3584     int pxx = (TILEX - ABS(sxx)) * dx;
3585     int pyy = (TILEY - ABS(syy)) * dy;
3586     int gfx_frame = GfxFrame[jx][jy];
3587
3588     int graphic;
3589     int sync_frame;
3590     int frame;
3591
3592     if (!IS_MOVING(jx, jy))             /* push movement already finished */
3593     {
3594       element = Feld[next_jx][next_jy];
3595       gfx_frame = GfxFrame[next_jx][next_jy];
3596     }
3597
3598     graphic = el_act_dir2img(element, ACTION_PUSHING, move_dir);
3599
3600     sync_frame = (IS_ANIM_MODE_CE(graphic) ? gfx_frame : player->StepFrame);
3601     frame = getGraphicAnimationFrame(graphic, sync_frame);
3602
3603     /* draw background element under pushed element (like the Sokoban field) */
3604     if (game.use_masked_pushing && IS_MOVING(jx, jy))
3605     {
3606       /* this allows transparent pushing animation over non-black background */
3607
3608       if (Back[jx][jy])
3609         DrawLevelElement(jx, jy, Back[jx][jy]);
3610       else
3611         DrawLevelElement(jx, jy, EL_EMPTY);
3612
3613       if (Back[next_jx][next_jy])
3614         DrawLevelElement(next_jx, next_jy, Back[next_jx][next_jy]);
3615       else
3616         DrawLevelElement(next_jx, next_jy, EL_EMPTY);
3617     }
3618     else if (Back[next_jx][next_jy])
3619       DrawLevelElement(next_jx, next_jy, Back[next_jx][next_jy]);
3620
3621 #if 1
3622     /* do not draw (EM style) pushing animation when pushing is finished */
3623     /* (two-tile animations usually do not contain start and end frame) */
3624     if (graphic_info[graphic].double_movement && !IS_MOVING(jx, jy))
3625       DrawLevelElement(next_jx, next_jy, Feld[next_jx][next_jy]);
3626     else
3627       DrawGraphicShiftedThruMask(px, py, pxx, pyy, graphic, frame, NO_CUTTING);
3628 #else
3629     /* masked drawing is needed for EMC style (double) movement graphics */
3630     /* !!! (ONLY WHEN DRAWING PUSHED ELEMENT OVER THE PLAYER) !!! */
3631     DrawGraphicShiftedThruMask(px, py, pxx, pyy, graphic, frame, NO_CUTTING);
3632 #endif
3633   }
3634
3635 #if DRAW_PLAYER_OVER_PUSHED_ELEMENT
3636   /* ----------------------------------------------------------------------- */
3637   /* draw player himself                                                     */
3638   /* ----------------------------------------------------------------------- */
3639
3640   graphic = getPlayerGraphic(player, move_dir);
3641
3642   /* in the case of changed player action or direction, prevent the current
3643      animation frame from being restarted for identical animations */
3644   if (player->Frame == 0 && equalGraphics(graphic, last_player_graphic))
3645     player->Frame = last_player_frame;
3646
3647   frame = getGraphicAnimationFrame(graphic, player->Frame);
3648
3649   if (player->GfxPos)
3650   {
3651     if (move_dir == MV_LEFT || move_dir == MV_RIGHT)
3652       sxx = player->GfxPos;
3653     else
3654       syy = player->GfxPos;
3655   }
3656
3657   if (player_is_opaque)
3658     DrawGraphicShifted(sx, sy, sxx, syy, graphic, frame,NO_CUTTING,NO_MASKING);
3659   else
3660     DrawGraphicShiftedThruMask(sx, sy, sxx, syy, graphic, frame, NO_CUTTING);
3661
3662   if (SHIELD_ON(player))
3663   {
3664     int graphic = (player->shield_deadly_time_left ? IMG_SHIELD_DEADLY_ACTIVE :
3665                    IMG_SHIELD_NORMAL_ACTIVE);
3666     int frame = getGraphicAnimationFrame(graphic, -1);
3667
3668     DrawGraphicShiftedThruMask(sx, sy, sxx, syy, graphic, frame, NO_CUTTING);
3669   }
3670 #endif
3671
3672   /* ----------------------------------------------------------------------- */
3673   /* draw things in front of player (active dynamite or dynabombs)           */
3674   /* ----------------------------------------------------------------------- */
3675
3676   if (IS_ACTIVE_BOMB(element))
3677   {
3678     graphic = el2img(element);
3679     frame = getGraphicAnimationFrame(graphic, GfxFrame[jx][jy]);
3680
3681     if (game.emulation == EMU_SUPAPLEX)
3682       DrawGraphic(sx, sy, IMG_SP_DISK_RED, frame);
3683     else
3684       DrawGraphicThruMask(sx, sy, graphic, frame);
3685   }
3686
3687   if (player_is_moving && last_element == EL_EXPLOSION)
3688   {
3689     int element = (GfxElement[last_jx][last_jy] != EL_UNDEFINED ?
3690                    GfxElement[last_jx][last_jy] :  EL_EMPTY);
3691     int graphic = el_act2img(element, ACTION_EXPLODING);
3692     int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
3693     int phase = ExplodePhase[last_jx][last_jy] - 1;
3694     int frame = getGraphicAnimationFrame(graphic, phase - delay);
3695
3696     if (phase >= delay)
3697       DrawGraphicThruMask(SCREENX(last_jx), SCREENY(last_jy), graphic, frame);
3698   }
3699
3700   /* ----------------------------------------------------------------------- */
3701   /* draw elements the player is just walking/passing through/under          */
3702   /* ----------------------------------------------------------------------- */
3703
3704   if (player_is_moving)
3705   {
3706     /* handle the field the player is leaving ... */
3707     if (IS_ACCESSIBLE_INSIDE(last_element))
3708       DrawLevelField(last_jx, last_jy);
3709     else if (IS_ACCESSIBLE_UNDER(last_element))
3710       DrawLevelFieldThruMask(last_jx, last_jy);
3711   }
3712
3713   /* do not redraw accessible elements if the player is just pushing them */
3714   if (!player_is_moving || !player->is_pushing)
3715   {
3716     /* ... and the field the player is entering */
3717     if (IS_ACCESSIBLE_INSIDE(element))
3718       DrawLevelField(jx, jy);
3719     else if (IS_ACCESSIBLE_UNDER(element))
3720       DrawLevelFieldThruMask(jx, jy);
3721   }
3722
3723   MarkTileDirty(sx, sy);
3724 }
3725
3726 /* ------------------------------------------------------------------------- */
3727
3728 void WaitForEventToContinue()
3729 {
3730   boolean still_wait = TRUE;
3731
3732   /* simulate releasing mouse button over last gadget, if still pressed */
3733   if (button_status)
3734     HandleGadgets(-1, -1, 0);
3735
3736   button_status = MB_RELEASED;
3737
3738   ClearEventQueue();
3739
3740   while (still_wait)
3741   {
3742     if (PendingEvent())
3743     {
3744       Event event;
3745
3746       NextEvent(&event);
3747
3748       switch (event.type)
3749       {
3750         case EVENT_BUTTONPRESS:
3751         case EVENT_KEYPRESS:
3752           still_wait = FALSE;
3753           break;
3754
3755         case EVENT_KEYRELEASE:
3756           ClearPlayerAction();
3757           break;
3758
3759         default:
3760           HandleOtherEvents(&event);
3761           break;
3762       }
3763     }
3764     else if (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED)
3765     {
3766       still_wait = FALSE;
3767     }
3768
3769     BackToFront();
3770   }
3771 }
3772
3773 #define MAX_REQUEST_LINES               13
3774 #define MAX_REQUEST_LINE_FONT1_LEN      7
3775 #define MAX_REQUEST_LINE_FONT2_LEN      10
3776
3777 static int RequestHandleEvents(unsigned int req_state)
3778 {
3779   boolean level_solved = (game_status == GAME_MODE_PLAYING &&
3780                           local_player->LevelSolved_GameEnd);
3781   int width  = request.width;
3782   int height = request.height;
3783   int sx, sy;
3784   int result;
3785
3786   setRequestPosition(&sx, &sy, FALSE);
3787
3788   button_status = MB_RELEASED;
3789
3790   request_gadget_id = -1;
3791   result = -1;
3792
3793   while (result < 0)
3794   {
3795     if (level_solved)
3796     {
3797       SetDrawtoField(DRAW_TO_FIELDBUFFER);
3798
3799       HandleGameActions();
3800
3801       SetDrawtoField(DRAW_TO_BACKBUFFER);
3802
3803       if (global.use_envelope_request)
3804       {
3805         /* copy current state of request area to middle of playfield area */
3806         BlitBitmap(bitmap_db_store_2, drawto, sx, sy, width, height, sx, sy);
3807       }
3808     }
3809
3810     if (PendingEvent())
3811     {
3812       Event event;
3813
3814       while (NextValidEvent(&event))
3815       {
3816         switch (event.type)
3817         {
3818           case EVENT_BUTTONPRESS:
3819           case EVENT_BUTTONRELEASE:
3820           case EVENT_MOTIONNOTIFY:
3821           {
3822             int mx, my;
3823
3824             if (event.type == EVENT_MOTIONNOTIFY)
3825             {
3826               if (!button_status)
3827                 continue;
3828
3829               motion_status = TRUE;
3830               mx = ((MotionEvent *) &event)->x;
3831               my = ((MotionEvent *) &event)->y;
3832             }
3833             else
3834             {
3835               motion_status = FALSE;
3836               mx = ((ButtonEvent *) &event)->x;
3837               my = ((ButtonEvent *) &event)->y;
3838               if (event.type == EVENT_BUTTONPRESS)
3839                 button_status = ((ButtonEvent *) &event)->button;
3840               else
3841                 button_status = MB_RELEASED;
3842             }
3843
3844             /* this sets 'request_gadget_id' */
3845             HandleGadgets(mx, my, button_status);
3846
3847             switch (request_gadget_id)
3848             {
3849               case TOOL_CTRL_ID_YES:
3850                 result = TRUE;
3851                 break;
3852               case TOOL_CTRL_ID_NO:
3853                 result = FALSE;
3854                 break;
3855               case TOOL_CTRL_ID_CONFIRM:
3856                 result = TRUE | FALSE;
3857                 break;
3858
3859               case TOOL_CTRL_ID_PLAYER_1:
3860                 result = 1;
3861                 break;
3862               case TOOL_CTRL_ID_PLAYER_2:
3863                 result = 2;
3864                 break;
3865               case TOOL_CTRL_ID_PLAYER_3:
3866                 result = 3;
3867                 break;
3868               case TOOL_CTRL_ID_PLAYER_4:
3869                 result = 4;
3870                 break;
3871
3872               default:
3873                 break;
3874             }
3875
3876             break;
3877           }
3878
3879           case EVENT_KEYPRESS:
3880           {
3881             Key key = GetEventKey((KeyEvent *)&event, TRUE);
3882
3883             switch (key)
3884             {
3885               case KSYM_space:
3886                 if (req_state & REQ_CONFIRM)
3887                   result = 1;
3888                 break;
3889
3890               case KSYM_Return:
3891 #if defined(TARGET_SDL2)
3892               case KSYM_Menu:
3893 #endif
3894                 result = 1;
3895                 break;
3896
3897               case KSYM_Escape:
3898 #if defined(TARGET_SDL2)
3899               case KSYM_Back:
3900 #endif
3901                 result = 0;
3902                 break;
3903
3904               default:
3905                 HandleKeysDebug(key);
3906                 break;
3907             }
3908
3909             if (req_state & REQ_PLAYER)
3910               result = 0;
3911
3912             break;
3913           }
3914
3915           case EVENT_KEYRELEASE:
3916             ClearPlayerAction();
3917             break;
3918
3919           default:
3920             HandleOtherEvents(&event);
3921             break;
3922         }
3923       }
3924     }
3925     else if (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED)
3926     {
3927       int joy = AnyJoystick();
3928
3929       if (joy & JOY_BUTTON_1)
3930         result = 1;
3931       else if (joy & JOY_BUTTON_2)
3932         result = 0;
3933     }
3934
3935     if (level_solved)
3936     {
3937       if (global.use_envelope_request)
3938       {
3939         /* copy back current state of pressed buttons inside request area */
3940         BlitBitmap(drawto, bitmap_db_store_2, sx, sy, width, height, sx, sy);
3941       }
3942     }
3943
3944     BackToFront();
3945   }
3946
3947   return result;
3948 }
3949
3950 static boolean RequestDoor(char *text, unsigned int req_state)
3951 {
3952   unsigned int old_door_state;
3953   int max_request_line_len = MAX_REQUEST_LINE_FONT1_LEN;
3954   int font_nr = FONT_TEXT_2;
3955   char *text_ptr;
3956   int result;
3957   int ty;
3958
3959   if (maxWordLengthInString(text) > MAX_REQUEST_LINE_FONT1_LEN)
3960   {
3961     max_request_line_len = MAX_REQUEST_LINE_FONT2_LEN;
3962     font_nr = FONT_TEXT_1;
3963   }
3964
3965   if (game_status == GAME_MODE_PLAYING)
3966     BlitScreenToBitmap(backbuffer);
3967
3968   /* disable deactivated drawing when quick-loading level tape recording */
3969   if (tape.playing && tape.deactivate_display)
3970     TapeDeactivateDisplayOff(TRUE);
3971
3972   SetMouseCursor(CURSOR_DEFAULT);
3973
3974 #if defined(NETWORK_AVALIABLE)
3975   /* pause network game while waiting for request to answer */
3976   if (options.network &&
3977       game_status == GAME_MODE_PLAYING &&
3978       req_state & REQUEST_WAIT_FOR_INPUT)
3979     SendToServer_PausePlaying();
3980 #endif
3981
3982   old_door_state = GetDoorState();
3983
3984   /* simulate releasing mouse button over last gadget, if still pressed */
3985   if (button_status)
3986     HandleGadgets(-1, -1, 0);
3987
3988   UnmapAllGadgets();
3989
3990   /* draw released gadget before proceeding */
3991   // BackToFront();
3992
3993   if (old_door_state & DOOR_OPEN_1)
3994   {
3995     CloseDoor(DOOR_CLOSE_1);
3996
3997     /* save old door content */
3998     BlitBitmap(bitmap_db_door_1, bitmap_db_door_1,
3999                0 * DXSIZE, 0, DXSIZE, DYSIZE, 1 * DXSIZE, 0);
4000   }
4001
4002   SetDoorBackgroundImage(IMG_BACKGROUND_DOOR);
4003   SetDrawBackgroundMask(REDRAW_FIELD | REDRAW_DOOR_1);
4004
4005   /* clear door drawing field */
4006   DrawBackground(DX, DY, DXSIZE, DYSIZE);
4007
4008   /* force DOOR font inside door area */
4009   SetFontStatus(GAME_MODE_PSEUDO_DOOR);
4010
4011   /* write text for request */
4012   for (text_ptr = text, ty = 0; ty < MAX_REQUEST_LINES; ty++)
4013   {
4014     char text_line[max_request_line_len + 1];
4015     int tx, tl, tc = 0;
4016
4017     if (!*text_ptr)
4018       break;
4019
4020     for (tl = 0, tx = 0; tx < max_request_line_len; tl++, tx++)
4021     {
4022       tc = *(text_ptr + tx);
4023       // if (!tc || tc == ' ')
4024       if (!tc || tc == ' ' || tc == '?' || tc == '!')
4025         break;
4026     }
4027
4028     if ((tc == '?' || tc == '!') && tl == 0)
4029       tl = 1;
4030
4031     if (!tl)
4032     { 
4033       text_ptr++; 
4034       ty--; 
4035       continue; 
4036     }
4037
4038     strncpy(text_line, text_ptr, tl);
4039     text_line[tl] = 0;
4040
4041     DrawText(DX + (DXSIZE - tl * getFontWidth(font_nr)) / 2,
4042              DY + 8 + ty * (getFontHeight(font_nr) + 2),
4043              text_line, font_nr);
4044
4045     text_ptr += tl + (tc == ' ' ? 1 : 0);
4046     // text_ptr += tl + (tc == ' ' || tc == '?' || tc == '!' ? 1 : 0);
4047   }
4048
4049   ResetFontStatus();
4050
4051   if (req_state & REQ_ASK)
4052   {
4053     MapGadget(tool_gadget[TOOL_CTRL_ID_YES]);
4054     MapGadget(tool_gadget[TOOL_CTRL_ID_NO]);
4055   }
4056   else if (req_state & REQ_CONFIRM)
4057   {
4058     MapGadget(tool_gadget[TOOL_CTRL_ID_CONFIRM]);
4059   }
4060   else if (req_state & REQ_PLAYER)
4061   {
4062     MapGadget(tool_gadget[TOOL_CTRL_ID_PLAYER_1]);
4063     MapGadget(tool_gadget[TOOL_CTRL_ID_PLAYER_2]);
4064     MapGadget(tool_gadget[TOOL_CTRL_ID_PLAYER_3]);
4065     MapGadget(tool_gadget[TOOL_CTRL_ID_PLAYER_4]);
4066   }
4067
4068   /* copy request gadgets to door backbuffer */
4069   BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4070
4071   OpenDoor(DOOR_OPEN_1);
4072
4073   if (!(req_state & REQUEST_WAIT_FOR_INPUT))
4074   {
4075     if (game_status == GAME_MODE_PLAYING)
4076     {
4077       SetPanelBackground();
4078       SetDrawBackgroundMask(REDRAW_DOOR_1);
4079     }
4080     else
4081     {
4082       SetDrawBackgroundMask(REDRAW_FIELD);
4083     }
4084
4085     return FALSE;
4086   }
4087
4088   SetDrawBackgroundMask(REDRAW_FIELD | REDRAW_DOOR_1);
4089
4090   // ---------- handle request buttons ----------
4091   result = RequestHandleEvents(req_state);
4092
4093   UnmapToolButtons();
4094
4095   if (!(req_state & REQ_STAY_OPEN))
4096   {
4097     CloseDoor(DOOR_CLOSE_1);
4098
4099     if (((old_door_state & DOOR_OPEN_1) && !(req_state & REQ_STAY_CLOSED)) ||
4100         (req_state & REQ_REOPEN))
4101       OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
4102   }
4103
4104   RemapAllGadgets();
4105
4106   if (game_status == GAME_MODE_PLAYING)
4107   {
4108     SetPanelBackground();
4109     SetDrawBackgroundMask(REDRAW_DOOR_1);
4110   }
4111   else
4112   {
4113     SetDrawBackgroundMask(REDRAW_FIELD);
4114   }
4115
4116 #if defined(NETWORK_AVALIABLE)
4117   /* continue network game after request */
4118   if (options.network &&
4119       game_status == GAME_MODE_PLAYING &&
4120       req_state & REQUEST_WAIT_FOR_INPUT)
4121     SendToServer_ContinuePlaying();
4122 #endif
4123
4124   /* restore deactivated drawing when quick-loading level tape recording */
4125   if (tape.playing && tape.deactivate_display)
4126     TapeDeactivateDisplayOn();
4127
4128   return result;
4129 }
4130
4131 static boolean RequestEnvelope(char *text, unsigned int req_state)
4132 {
4133   int result;
4134
4135   if (game_status == GAME_MODE_PLAYING)
4136     BlitScreenToBitmap(backbuffer);
4137
4138   /* disable deactivated drawing when quick-loading level tape recording */
4139   if (tape.playing && tape.deactivate_display)
4140     TapeDeactivateDisplayOff(TRUE);
4141
4142   SetMouseCursor(CURSOR_DEFAULT);
4143
4144 #if defined(NETWORK_AVALIABLE)
4145   /* pause network game while waiting for request to answer */
4146   if (options.network &&
4147       game_status == GAME_MODE_PLAYING &&
4148       req_state & REQUEST_WAIT_FOR_INPUT)
4149     SendToServer_PausePlaying();
4150 #endif
4151
4152   /* simulate releasing mouse button over last gadget, if still pressed */
4153   if (button_status)
4154     HandleGadgets(-1, -1, 0);
4155
4156   UnmapAllGadgets();
4157
4158   // (replace with setting corresponding request background)
4159   // SetDoorBackgroundImage(IMG_BACKGROUND_DOOR);
4160   // SetDrawBackgroundMask(REDRAW_FIELD | REDRAW_DOOR_1);
4161
4162   /* clear door drawing field */
4163   // DrawBackground(DX, DY, DXSIZE, DYSIZE);
4164
4165   ShowEnvelopeRequest(text, req_state, ACTION_OPENING);
4166
4167   if (!(req_state & REQUEST_WAIT_FOR_INPUT))
4168   {
4169     if (game_status == GAME_MODE_PLAYING)
4170     {
4171       SetPanelBackground();
4172       SetDrawBackgroundMask(REDRAW_DOOR_1);
4173     }
4174     else
4175     {
4176       SetDrawBackgroundMask(REDRAW_FIELD);
4177     }
4178
4179     return FALSE;
4180   }
4181
4182   SetDrawBackgroundMask(REDRAW_FIELD | REDRAW_DOOR_1);
4183
4184   // ---------- handle request buttons ----------
4185   result = RequestHandleEvents(req_state);
4186
4187   UnmapToolButtons();
4188
4189   ShowEnvelopeRequest(text, req_state, ACTION_CLOSING);
4190
4191   RemapAllGadgets();
4192
4193   if (game_status == GAME_MODE_PLAYING)
4194   {
4195     SetPanelBackground();
4196     SetDrawBackgroundMask(REDRAW_DOOR_1);
4197   }
4198   else
4199   {
4200     SetDrawBackgroundMask(REDRAW_FIELD);
4201   }
4202
4203 #if defined(NETWORK_AVALIABLE)
4204   /* continue network game after request */
4205   if (options.network &&
4206       game_status == GAME_MODE_PLAYING &&
4207       req_state & REQUEST_WAIT_FOR_INPUT)
4208     SendToServer_ContinuePlaying();
4209 #endif
4210
4211   /* restore deactivated drawing when quick-loading level tape recording */
4212   if (tape.playing && tape.deactivate_display)
4213     TapeDeactivateDisplayOn();
4214
4215   return result;
4216 }
4217
4218 boolean Request(char *text, unsigned int req_state)
4219 {
4220   boolean overlay_active = GetOverlayActive();
4221   boolean result;
4222
4223   SetOverlayActive(FALSE);
4224
4225   if (global.use_envelope_request)
4226     result = RequestEnvelope(text, req_state);
4227   else
4228     result = RequestDoor(text, req_state);
4229
4230   SetOverlayActive(overlay_active);
4231
4232   return result;
4233 }
4234
4235 static int compareDoorPartOrderInfo(const void *object1, const void *object2)
4236 {
4237   const struct DoorPartOrderInfo *dpo1 = (struct DoorPartOrderInfo *)object1;
4238   const struct DoorPartOrderInfo *dpo2 = (struct DoorPartOrderInfo *)object2;
4239   int compare_result;
4240
4241   if (dpo1->sort_priority != dpo2->sort_priority)
4242     compare_result = dpo1->sort_priority - dpo2->sort_priority;
4243   else
4244     compare_result = dpo1->nr - dpo2->nr;
4245
4246   return compare_result;
4247 }
4248
4249 void InitGraphicCompatibilityInfo_Doors()
4250 {
4251   struct
4252   {
4253     int door_token;
4254     int part_1, part_8;
4255     struct DoorInfo *door;
4256   }
4257   doors[] =
4258   {
4259     { DOOR_1,   IMG_GFX_DOOR_1_PART_1,  IMG_GFX_DOOR_1_PART_8,  &door_1 },
4260     { DOOR_2,   IMG_GFX_DOOR_2_PART_1,  IMG_GFX_DOOR_2_PART_8,  &door_2 },
4261
4262     { -1,       -1,                     -1,                     NULL    }
4263   };
4264   struct Rect door_rect_list[] =
4265   {
4266     { DX, DY, DXSIZE, DYSIZE },
4267     { VX, VY, VXSIZE, VYSIZE }
4268   };
4269   int i, j;
4270
4271   for (i = 0; doors[i].door_token != -1; i++)
4272   {
4273     int door_token = doors[i].door_token;
4274     int door_index = DOOR_INDEX_FROM_TOKEN(door_token);
4275     int part_1 = doors[i].part_1;
4276     int part_8 = doors[i].part_8;
4277     int part_2 = part_1 + 1;
4278     int part_3 = part_1 + 2;
4279     struct DoorInfo *door = doors[i].door;
4280     struct Rect *door_rect = &door_rect_list[door_index];
4281     boolean door_gfx_redefined = FALSE;
4282
4283     /* check if any door part graphic definitions have been redefined */
4284
4285     for (j = 0; door_part_controls[j].door_token != -1; j++)
4286     {
4287       struct DoorPartControlInfo *dpc = &door_part_controls[j];
4288       struct FileInfo *fi = getImageListEntryFromImageID(dpc->graphic);
4289
4290       if (dpc->door_token == door_token && fi->redefined)
4291         door_gfx_redefined = TRUE;
4292     }
4293
4294     /* check for old-style door graphic/animation modifications */
4295
4296     if (!door_gfx_redefined)
4297     {
4298       if (door->anim_mode & ANIM_STATIC_PANEL)
4299       {
4300         door->panel.step_xoffset = 0;
4301         door->panel.step_yoffset = 0;
4302       }
4303
4304       if (door->anim_mode & (ANIM_HORIZONTAL | ANIM_VERTICAL))
4305       {
4306         struct GraphicInfo *g_part_1 = &graphic_info[part_1];
4307         struct GraphicInfo *g_part_2 = &graphic_info[part_2];
4308         int num_door_steps, num_panel_steps;
4309
4310         /* remove door part graphics other than the two default wings */
4311
4312         for (j = 0; door_part_controls[j].door_token != -1; j++)
4313         {
4314           struct DoorPartControlInfo *dpc = &door_part_controls[j];
4315           struct GraphicInfo *g = &graphic_info[dpc->graphic];
4316
4317           if (dpc->graphic >= part_3 &&
4318               dpc->graphic <= part_8)
4319             g->bitmap = NULL;
4320         }
4321
4322         /* set graphics and screen positions of the default wings */
4323
4324         g_part_1->width  = door_rect->width;
4325         g_part_1->height = door_rect->height;
4326         g_part_2->width  = door_rect->width;
4327         g_part_2->height = door_rect->height;
4328         g_part_2->src_x = door_rect->width;
4329         g_part_2->src_y = g_part_1->src_y;
4330
4331         door->part_2.x = door->part_1.x;
4332         door->part_2.y = door->part_1.y;
4333
4334         if (door->width != -1)
4335         {
4336           g_part_1->width = door->width;
4337           g_part_2->width = door->width;
4338
4339           // special treatment for graphics and screen position of right wing
4340           g_part_2->src_x += door_rect->width - door->width;
4341           door->part_2.x  += door_rect->width - door->width;
4342         }
4343
4344         if (door->height != -1)
4345         {
4346           g_part_1->height = door->height;
4347           g_part_2->height = door->height;
4348
4349           // special treatment for graphics and screen position of bottom wing
4350           g_part_2->src_y += door_rect->height - door->height;
4351           door->part_2.y  += door_rect->height - door->height;
4352         }
4353
4354         /* set animation delays for the default wings and panels */
4355
4356         door->part_1.step_delay = door->step_delay;
4357         door->part_2.step_delay = door->step_delay;
4358         door->panel.step_delay  = door->step_delay;
4359
4360         /* set animation draw order for the default wings */
4361
4362         door->part_1.sort_priority = 2; /* draw left wing over ... */
4363         door->part_2.sort_priority = 1; /*          ... right wing */
4364
4365         /* set animation draw offset for the default wings */
4366
4367         if (door->anim_mode & ANIM_HORIZONTAL)
4368         {
4369           door->part_1.step_xoffset = door->step_offset;
4370           door->part_1.step_yoffset = 0;
4371           door->part_2.step_xoffset = door->step_offset * -1;
4372           door->part_2.step_yoffset = 0;
4373
4374           num_door_steps = g_part_1->width / door->step_offset;
4375         }
4376         else    // ANIM_VERTICAL
4377         {
4378           door->part_1.step_xoffset = 0;
4379           door->part_1.step_yoffset = door->step_offset;
4380           door->part_2.step_xoffset = 0;
4381           door->part_2.step_yoffset = door->step_offset * -1;
4382
4383           num_door_steps = g_part_1->height / door->step_offset;
4384         }
4385
4386         /* set animation draw offset for the default panels */
4387
4388         if (door->step_offset > 1)
4389         {
4390           num_panel_steps = 2 * door_rect->height / door->step_offset;
4391           door->panel.start_step = num_panel_steps - num_door_steps;
4392           door->panel.start_step_closing = door->panel.start_step;
4393         }
4394         else
4395         {
4396           num_panel_steps = door_rect->height / door->step_offset;
4397           door->panel.start_step = num_panel_steps - num_door_steps / 2;
4398           door->panel.start_step_closing = door->panel.start_step;
4399           door->panel.step_delay *= 2;
4400         }
4401       }
4402     }
4403   }
4404 }
4405
4406 void InitDoors()
4407 {
4408   int i;
4409
4410   for (i = 0; door_part_controls[i].door_token != -1; i++)
4411   {
4412     struct DoorPartControlInfo *dpc = &door_part_controls[i];
4413     struct DoorPartOrderInfo *dpo = &door_part_order[i];
4414
4415     /* initialize "start_step_opening" and "start_step_closing", if needed */
4416     if (dpc->pos->start_step_opening == 0 &&
4417         dpc->pos->start_step_closing == 0)
4418     {
4419       // dpc->pos->start_step_opening = dpc->pos->start_step;
4420       dpc->pos->start_step_closing = dpc->pos->start_step;
4421     }
4422
4423     /* fill structure for door part draw order (sorted below) */
4424     dpo->nr = i;
4425     dpo->sort_priority = dpc->pos->sort_priority;
4426   }
4427
4428   /* sort door part controls according to sort_priority and graphic number */
4429   qsort(door_part_order, MAX_DOOR_PARTS,
4430         sizeof(struct DoorPartOrderInfo), compareDoorPartOrderInfo);
4431 }
4432
4433 unsigned int OpenDoor(unsigned int door_state)
4434 {
4435   if (door_state & DOOR_COPY_BACK)
4436   {
4437     if (door_state & DOOR_OPEN_1)
4438       BlitBitmap(bitmap_db_door_1, bitmap_db_door_1,
4439                  1 * DXSIZE, 0, DXSIZE, DYSIZE, 0 * DXSIZE, 0);
4440
4441     if (door_state & DOOR_OPEN_2)
4442       BlitBitmap(bitmap_db_door_2, bitmap_db_door_2,
4443                  1 * VXSIZE, 0, VXSIZE, VYSIZE, 0 * VXSIZE, 0);
4444
4445     door_state &= ~DOOR_COPY_BACK;
4446   }
4447
4448   return MoveDoor(door_state);
4449 }
4450
4451 unsigned int CloseDoor(unsigned int door_state)
4452 {
4453   unsigned int old_door_state = GetDoorState();
4454
4455   if (!(door_state & DOOR_NO_COPY_BACK))
4456   {
4457     if (old_door_state & DOOR_OPEN_1)
4458       BlitBitmap(backbuffer, bitmap_db_door_1,
4459                  DX, DY, DXSIZE, DYSIZE, 0, 0);
4460
4461     if (old_door_state & DOOR_OPEN_2)
4462       BlitBitmap(backbuffer, bitmap_db_door_2,
4463                  VX, VY, VXSIZE, VYSIZE, 0, 0);
4464
4465     door_state &= ~DOOR_NO_COPY_BACK;
4466   }
4467
4468   return MoveDoor(door_state);
4469 }
4470
4471 unsigned int GetDoorState()
4472 {
4473   return MoveDoor(DOOR_GET_STATE);
4474 }
4475
4476 unsigned int SetDoorState(unsigned int door_state)
4477 {
4478   return MoveDoor(door_state | DOOR_SET_STATE);
4479 }
4480
4481 int euclid(int a, int b)
4482 {
4483   return (b ? euclid(b, a % b) : a);
4484 }
4485
4486 unsigned int MoveDoor(unsigned int door_state)
4487 {
4488   struct Rect door_rect_list[] =
4489   {
4490     { DX, DY, DXSIZE, DYSIZE },
4491     { VX, VY, VXSIZE, VYSIZE }
4492   };
4493   static int door1 = DOOR_CLOSE_1;
4494   static int door2 = DOOR_CLOSE_2;
4495   unsigned int door_delay = 0;
4496   unsigned int door_delay_value;
4497   int i;
4498
4499   if (door_state == DOOR_GET_STATE)
4500     return (door1 | door2);
4501
4502   if (door_state & DOOR_SET_STATE)
4503   {
4504     if (door_state & DOOR_ACTION_1)
4505       door1 = door_state & DOOR_ACTION_1;
4506     if (door_state & DOOR_ACTION_2)
4507       door2 = door_state & DOOR_ACTION_2;
4508
4509     return (door1 | door2);
4510   }
4511
4512   if (!(door_state & DOOR_FORCE_REDRAW))
4513   {
4514     if (door1 == DOOR_OPEN_1 && door_state & DOOR_OPEN_1)
4515       door_state &= ~DOOR_OPEN_1;
4516     else if (door1 == DOOR_CLOSE_1 && door_state & DOOR_CLOSE_1)
4517       door_state &= ~DOOR_CLOSE_1;
4518     if (door2 == DOOR_OPEN_2 && door_state & DOOR_OPEN_2)
4519       door_state &= ~DOOR_OPEN_2;
4520     else if (door2 == DOOR_CLOSE_2 && door_state & DOOR_CLOSE_2)
4521       door_state &= ~DOOR_CLOSE_2;
4522   }
4523
4524   if (global.autoplay_leveldir)
4525   {
4526     door_state |= DOOR_NO_DELAY;
4527     door_state &= ~DOOR_CLOSE_ALL;
4528   }
4529
4530   if (game_status == GAME_MODE_EDITOR)
4531     door_state |= DOOR_NO_DELAY;
4532
4533   if (door_state & DOOR_ACTION)
4534   {
4535     boolean door_panel_drawn[NUM_DOORS];
4536     boolean panel_has_doors[NUM_DOORS];
4537     boolean door_part_skip[MAX_DOOR_PARTS];
4538     boolean door_part_done[MAX_DOOR_PARTS];
4539     boolean door_part_done_all;
4540     int num_steps[MAX_DOOR_PARTS];
4541     int max_move_delay = 0;     // delay for complete animations of all doors
4542     int max_step_delay = 0;     // delay (ms) between two animation frames
4543     int num_move_steps = 0;     // number of animation steps for all doors
4544     int max_move_delay_doors_only = 0;  // delay for doors only (no panel)
4545     int num_move_steps_doors_only = 0;  // steps for doors only (no panel)
4546     int current_move_delay = 0;
4547     int start = 0;
4548     int k;
4549
4550     for (i = 0; i < NUM_DOORS; i++)
4551       panel_has_doors[i] = FALSE;
4552
4553     for (i = 0; i < MAX_DOOR_PARTS; i++)
4554     {
4555       struct DoorPartControlInfo *dpc = &door_part_controls[i];
4556       struct GraphicInfo *g = &graphic_info[dpc->graphic];
4557       int door_token = dpc->door_token;
4558
4559       door_part_done[i] = FALSE;
4560       door_part_skip[i] = (!(door_state & door_token) ||
4561                            !g->bitmap);
4562     }
4563
4564     for (i = 0; i < MAX_DOOR_PARTS; i++)
4565     {
4566       int nr = door_part_order[i].nr;
4567       struct DoorPartControlInfo *dpc = &door_part_controls[nr];
4568       struct DoorPartPosInfo *pos = dpc->pos;
4569       struct GraphicInfo *g = &graphic_info[dpc->graphic];
4570       int door_token = dpc->door_token;
4571       int door_index = DOOR_INDEX_FROM_TOKEN(door_token);
4572       boolean is_panel = DOOR_PART_IS_PANEL(nr);
4573       int step_xoffset = ABS(pos->step_xoffset);
4574       int step_yoffset = ABS(pos->step_yoffset);
4575       int step_delay = pos->step_delay;
4576       int current_door_state = door_state & door_token;
4577       boolean door_opening = ((current_door_state & DOOR_OPEN)  != 0);
4578       boolean door_closing = ((current_door_state & DOOR_CLOSE) != 0);
4579       boolean part_opening = (is_panel ? door_closing : door_opening);
4580       int start_step = (part_opening ? pos->start_step_opening :
4581                         pos->start_step_closing);
4582       float move_xsize = (step_xoffset ? g->width  : 0);
4583       float move_ysize = (step_yoffset ? g->height : 0);
4584       int move_xsteps = (step_xoffset ? ceil(move_xsize / step_xoffset) : 0);
4585       int move_ysteps = (step_yoffset ? ceil(move_ysize / step_yoffset) : 0);
4586       int move_steps = (move_xsteps && move_ysteps ?
4587                         MIN(move_xsteps, move_ysteps) :
4588                         move_xsteps ? move_xsteps : move_ysteps) - start_step;
4589       int move_delay = move_steps * step_delay;
4590
4591       if (door_part_skip[nr])
4592         continue;
4593
4594       max_move_delay = MAX(max_move_delay, move_delay);
4595       max_step_delay = (max_step_delay == 0 ? step_delay :
4596                         euclid(max_step_delay, step_delay));
4597       num_steps[nr] = move_steps;
4598
4599       if (!is_panel)
4600       {
4601         max_move_delay_doors_only = MAX(max_move_delay_doors_only, move_delay);
4602
4603         panel_has_doors[door_index] = TRUE;
4604       }
4605     }
4606
4607     max_step_delay = MAX(1, max_step_delay);    // prevent division by zero
4608
4609     num_move_steps = max_move_delay / max_step_delay;
4610     num_move_steps_doors_only = max_move_delay_doors_only / max_step_delay;
4611
4612     door_delay_value = max_step_delay;
4613
4614     if ((door_state & DOOR_NO_DELAY) || setup.quick_doors)
4615     {
4616       start = num_move_steps - 1;
4617     }
4618     else
4619     {
4620       /* opening door sound has priority over simultaneously closing door */
4621       if (door_state & (DOOR_OPEN_1 | DOOR_OPEN_2))
4622         PlayMenuSoundStereo(SND_DOOR_OPENING, SOUND_MIDDLE);
4623       else if (door_state & (DOOR_CLOSE_1 | DOOR_CLOSE_2))
4624         PlayMenuSoundStereo(SND_DOOR_CLOSING, SOUND_MIDDLE);
4625     }
4626
4627     for (k = start; k < num_move_steps; k++)
4628     {
4629       int last_frame = num_move_steps - 1;      // last frame of this "for" loop
4630
4631       door_part_done_all = TRUE;
4632
4633       for (i = 0; i < NUM_DOORS; i++)
4634         door_panel_drawn[i] = FALSE;
4635
4636       for (i = 0; i < MAX_DOOR_PARTS; i++)
4637       {
4638         int nr = door_part_order[i].nr;
4639         struct DoorPartControlInfo *dpc = &door_part_controls[nr];
4640         struct DoorPartPosInfo *pos = dpc->pos;
4641         struct GraphicInfo *g = &graphic_info[dpc->graphic];
4642         int door_token = dpc->door_token;
4643         int door_index = DOOR_INDEX_FROM_TOKEN(door_token);
4644         boolean is_panel = DOOR_PART_IS_PANEL(nr);
4645         boolean is_panel_and_door_has_closed = FALSE;
4646         struct Rect *door_rect = &door_rect_list[door_index];
4647         Bitmap *bitmap_db_door = (door_token == DOOR_1 ? bitmap_db_door_1 :
4648                                   bitmap_db_door_2);
4649         Bitmap *bitmap = (is_panel ? bitmap_db_door : g->bitmap);
4650         int current_door_state = door_state & door_token;
4651         boolean door_opening = ((current_door_state & DOOR_OPEN)  != 0);
4652         boolean door_closing = !door_opening;
4653         boolean part_opening = (is_panel ? door_closing : door_opening);
4654         boolean part_closing = !part_opening;
4655         int start_step = (part_opening ? pos->start_step_opening :
4656                           pos->start_step_closing);
4657         int step_delay = pos->step_delay;
4658         int step_factor = step_delay / max_step_delay;
4659         int k1 = (step_factor ? k / step_factor + 1 : k);
4660         int k2 = (part_opening ? k1 + start_step : num_steps[nr] - k1);
4661         int kk = MAX(0, k2);
4662         int g_src_x = 0;
4663         int g_src_y = 0;
4664         int src_x, src_y, src_xx, src_yy;
4665         int dst_x, dst_y, dst_xx, dst_yy;
4666         int width, height;
4667
4668         if (door_part_skip[nr])
4669           continue;
4670
4671         if (!(door_state & door_token))
4672           continue;
4673
4674         if (!g->bitmap)
4675           continue;
4676
4677         if (!is_panel)
4678         {
4679           int k2_door = (door_opening ? k : num_move_steps_doors_only - k - 1);
4680           int kk_door = MAX(0, k2_door);
4681           int sync_frame = kk_door * door_delay_value;
4682           int frame = getGraphicAnimationFrame(dpc->graphic, sync_frame);
4683
4684           getFixedGraphicSource(dpc->graphic, frame, &bitmap,
4685                                 &g_src_x, &g_src_y);
4686         }
4687
4688         // draw door panel
4689
4690         if (!door_panel_drawn[door_index])
4691         {
4692           ClearRectangle(drawto, door_rect->x, door_rect->y,
4693                          door_rect->width, door_rect->height);
4694
4695           door_panel_drawn[door_index] = TRUE;
4696         }
4697
4698         // draw opening or closing door parts
4699
4700         if (pos->step_xoffset < 0)      // door part on right side
4701         {
4702           src_xx = 0;
4703           dst_xx = pos->x + ABS(kk * pos->step_xoffset);
4704           width = g->width;
4705
4706           if (dst_xx + width > door_rect->width)
4707             width = door_rect->width - dst_xx;
4708         }
4709         else                            // door part on left side
4710         {
4711           src_xx = 0;
4712           dst_xx = pos->x - kk * pos->step_xoffset;
4713
4714           if (dst_xx < 0)
4715           {
4716             src_xx = ABS(dst_xx);
4717             dst_xx = 0;
4718           }
4719
4720           width = g->width - src_xx;
4721
4722           if (width > door_rect->width)
4723             width = door_rect->width;
4724
4725           // printf("::: k == %d [%d] \n", k, start_step);
4726         }
4727
4728         if (pos->step_yoffset < 0)      // door part on bottom side
4729         {
4730           src_yy = 0;
4731           dst_yy = pos->y + ABS(kk * pos->step_yoffset);
4732           height = g->height;
4733
4734           if (dst_yy + height > door_rect->height)
4735             height = door_rect->height - dst_yy;
4736         }
4737         else                            // door part on top side
4738         {
4739           src_yy = 0;
4740           dst_yy = pos->y - kk * pos->step_yoffset;
4741
4742           if (dst_yy < 0)
4743           {
4744             src_yy = ABS(dst_yy);