fixed several bugs with shared playfield bitmap in MM game engine
[rocksndiamonds.git] / src / game_mm / mm_game.c
1 // ============================================================================
2 // Mirror Magic -- McDuffin's Revenge
3 // ----------------------------------------------------------------------------
4 // (c) 1994-2017 by Artsoft Entertainment
5 //                  Holger Schemel
6 //                  info@artsoft.org
7 //                  https://www.artsoft.org/
8 // ----------------------------------------------------------------------------
9 // mm_game.c
10 // ============================================================================
11
12 #include <math.h>
13
14 #include "main_mm.h"
15
16 #include "mm_main.h"
17 #include "mm_game.h"
18 #include "mm_tools.h"
19
20 // graphic position values for game controls
21 #define ENERGY_XSIZE            32
22 #define ENERGY_YSIZE            MAX_LASER_ENERGY
23 #define OVERLOAD_XSIZE          ENERGY_XSIZE
24 #define OVERLOAD_YSIZE          MAX_LASER_OVERLOAD
25
26 // values for Explode_MM()
27 #define EX_PHASE_START          0
28 #define EX_TYPE_NONE            0
29 #define EX_TYPE_NORMAL          (1 << 0)
30
31 // special positions in the game control window (relative to control window)
32 #define XX_LEVEL                36
33 #define YY_LEVEL                23
34 #define XX_KETTLES              29
35 #define YY_KETTLES              63
36 #define XX_SCORE                22
37 #define YY_SCORE                101
38 #define XX_ENERGY               8
39 #define YY_ENERGY               158
40 #define XX_OVERLOAD             60
41 #define YY_OVERLOAD             YY_ENERGY
42
43 // special positions in the game control window (relative to main window)
44 #define DX_LEVEL                (DX + XX_LEVEL)
45 #define DY_LEVEL                (DY + YY_LEVEL)
46 #define DX_KETTLES              (DX + XX_KETTLES)
47 #define DY_KETTLES              (DY + YY_KETTLES)
48 #define DX_SCORE                (DX + XX_SCORE)
49 #define DY_SCORE                (DY + YY_SCORE)
50 #define DX_ENERGY               (DX + XX_ENERGY)
51 #define DY_ENERGY               (DY + YY_ENERGY)
52 #define DX_OVERLOAD             (DX + XX_OVERLOAD)
53 #define DY_OVERLOAD             (DY + YY_OVERLOAD)
54
55 #define IS_LOOP_SOUND(s)        ((s) == SND_FUEL)
56 #define IS_MUSIC_SOUND(s)       ((s) == SND_TYGER || (s) == SND_VOYAGER)
57
58 // game button identifiers
59 #define GAME_CTRL_ID_LEFT       0
60 #define GAME_CTRL_ID_MIDDLE     1
61 #define GAME_CTRL_ID_RIGHT      2
62
63 #define NUM_GAME_BUTTONS        3
64
65 // values for DrawLaser()
66 #define DL_LASER_DISABLED       0
67 #define DL_LASER_ENABLED        1
68
69 // values for 'click_delay_value' in ClickElement()
70 #define CLICK_DELAY_FIRST       12      // delay (frames) after first click
71 #define CLICK_DELAY             6       // delay (frames) for pressed butten
72
73 #define AUTO_ROTATE_DELAY       CLICK_DELAY
74 #define INIT_GAME_ACTIONS_DELAY (ONE_SECOND_DELAY / GAME_FRAME_DELAY)
75 #define NUM_INIT_CYCLE_STEPS    16
76 #define PACMAN_MOVE_DELAY       12
77 #define ENERGY_DELAY            (ONE_SECOND_DELAY / GAME_FRAME_DELAY)
78 #define HEALTH_DEC_DELAY        3
79 #define HEALTH_INC_DELAY        9
80 #define HEALTH_DELAY(x)         ((x) ? HEALTH_DEC_DELAY : HEALTH_INC_DELAY)
81
82 #define BEGIN_NO_HEADLESS                       \
83   {                                             \
84     boolean last_headless = program.headless;   \
85                                                 \
86     program.headless = FALSE;                   \
87
88 #define END_NO_HEADLESS                         \
89     program.headless = last_headless;           \
90   }                                             \
91
92 // forward declaration for internal use
93 static int MovingOrBlocked2Element_MM(int, int);
94 static void Bang_MM(int, int);
95 static void RaiseScore_MM(int);
96 static void RaiseScoreElement_MM(int);
97 static void RemoveMovingField_MM(int, int);
98 static void InitMovingField_MM(int, int, int);
99 static void ContinueMoving_MM(int, int);
100 static void Moving2Blocked_MM(int, int, int *, int *);
101
102 static void AddLaserEdge(int, int);
103 static void ScanLaser(void);
104 static void DrawLaser(int, int);
105 static boolean HitElement(int, int);
106 static boolean HitOnlyAnEdge(int);
107 static boolean HitPolarizer(int, int);
108 static boolean HitBlock(int, int);
109 static boolean HitLaserSource(int, int);
110 static boolean HitLaserDestination(int, int);
111 static boolean HitReflectingWalls(int, int);
112 static boolean HitAbsorbingWalls(int, int);
113 static void RotateMirror(int, int, int);
114 static boolean ObjHit(int, int, int);
115 static void DeletePacMan(int, int);
116 static void MovePacMen(void);
117
118 // bitmap for laser beam detection
119 static Bitmap *laser_bitmap = NULL;
120
121 // variables for laser control
122 static int last_LX = 0, last_LY = 0, last_hit_mask = 0;
123 static int hold_x = -1, hold_y = -1;
124
125 // variables for pacman control
126 static int pacman_nr = -1;
127
128 // various game engine delay counters
129 static DelayCounter rotate_delay = { AUTO_ROTATE_DELAY };
130 static DelayCounter pacman_delay = { PACMAN_MOVE_DELAY };
131 static DelayCounter energy_delay = { ENERGY_DELAY };
132 static DelayCounter overload_delay = { 0 };
133
134 // element mask positions for scanning pixels of MM elements
135 #define MM_MASK_MCDUFFIN_RIGHT  0
136 #define MM_MASK_MCDUFFIN_UP     1
137 #define MM_MASK_MCDUFFIN_LEFT   2
138 #define MM_MASK_MCDUFFIN_DOWN   3
139 #define MM_MASK_GRID_1          4
140 #define MM_MASK_GRID_2          5
141 #define MM_MASK_GRID_3          6
142 #define MM_MASK_GRID_4          7
143 #define MM_MASK_RECTANGLE       8
144 #define MM_MASK_CIRCLE          9
145
146 #define NUM_MM_MASKS            10
147
148 // element masks for scanning pixels of MM elements
149 static const char mm_masks[NUM_MM_MASKS][16][16 + 1] =
150 {
151   {
152     "                ",
153     "    XXXXX       ",
154     "   XXXXXXX      ",
155     "  XXXXXXXXXXX   ",
156     "  XXXXXXXXXXXXX ",
157     "  XXXXXXXXXXXXXX",
158     "  XXXXXXXXXXXXXX",
159     "  XXXXXXXXXXXXX ",
160     "  XXXXXXXXXXXXX ",
161     "  XXXXXXXXXXXXX ",
162     "  XXXXXXXXXXXXX ",
163     "  XXXXXXXXXXXXX ",
164     "  XXXXXXXXXXXXX ",
165     "  XXXXXXXXXXXXX ",
166     "  XXXXXXXXXXXX  ",
167     "  XXXXXXXXXXXX  ",
168   },
169   {
170     "                ",
171     "    XXXXXXXX    ",
172     "  XXXXXXXXXXXX  ",
173     " XXXXXXXXXXXXXX ",
174     " XXXXXXXXXXXXXX ",
175     " XXXXXXXXXXXXXX ",
176     " XXXXXXXXXXXXXX ",
177     " XXXXXXXXXXXXXX ",
178     " XXXXXXXXXXXXXX ",
179     " XXXXXXXXXXXXXX ",
180     " XXXXXXXXXXXXXX ",
181     " XXXXXXXXXXXXXX ",
182     " XXXXXXXXXXXXXX ",
183     "  XXXXXXXXXXXXX ",
184     "  XXXXXXXXXXXX  ",
185     "  XXXXXXXXXXXX  ",
186   },
187   {
188     "                ",
189     "    XXXXXX      ",
190     "  XXXXXXXXX     ",
191     " XXXXXXXXXXX    ",
192     "XXXXXXXXXXXXX   ",
193     "XXXXXXXXXXXXX   ",
194     "XXXXXXXXXXXXXX  ",
195     " XXXXXXXXXXXXX  ",
196     " XXXXXXXXXXXXX  ",
197     " XXXXXXXXXXXXX  ",
198     " XXXXXXXXXXXXX  ",
199     " XXXXXXXXXXXXX  ",
200     " XXXXXXXXXXXXX  ",
201     " XXXXXXXXXXXXX  ",
202     "  XXXXXXXXXXXX  ",
203     "  XXXXXXXXXXXX  ",
204   },
205   {
206     "                ",
207     "    XXXXXX      ",
208     "   XXXXXXXX     ",
209     "  XXXXXXXXXX    ",
210     "  XXXXXXXXXXX   ",
211     "  XXXXXXXXXXX   ",
212     "  XXXXXXXXXXXX  ",
213     "  XXXXXXXXXXXX  ",
214     "  XXXXXXXXXXXX  ",
215     "  XXXXXXXXXXXX  ",
216     "  XXXXXXXXXXXX  ",
217     "  XXXXXXXXXXXX  ",
218     "  XXXXXXXXXXXX  ",
219     "  XXXXXXXXXXXX  ",
220     "  XXXXXXXXXXXX  ",
221     "  XXXXX  XXXXX  ",
222   },
223   {
224     " XXXXXX  XXXXXX ",
225     "XXXXXXXXXXXXXXXX",
226     "XXXXXXXXXXXXXXXX",
227     "XXXXXXXXXXXXXXXX",
228     "XXXXXXXXXXXXXXXX",
229     "XXXXXXXXXXXXXXXX",
230     "XXXXXXXXXXXXXXXX",
231     "                ",
232     "                ",
233     "XXXXXXXXXXXXXXXX",
234     "XXXXXXXXXXXXXXXX",
235     "XXXXXXXXXXXXXXXX",
236     "XXXXXXXXXXXXXXXX",
237     "XXXXXXXXXXXXXXXX",
238     "XXXXXXXXXXXXXXXX",
239     " XXXXXX  XXXXXX ",
240   },
241   {
242     " XXXXXX  XXXXXX ",
243     "XXXXXXX  XXXXXXX",
244     "XXXXXXX  XXXXXXX",
245     "XXXXXXX  XXXXXXX",
246     "XXXXXXX  XXXXXXX",
247     "XXXXXXX  XXXXXXX",
248     "XXXXXXX  XXXXXXX",
249     " XXXXXX  XXXXXX ",
250     " XXXXXX  XXXXXX ",
251     "XXXXXXX  XXXXXXX",
252     "XXXXXXX  XXXXXXX",
253     "XXXXXXX  XXXXXXX",
254     "XXXXXXX  XXXXXXX",
255     "XXXXXXX  XXXXXXX",
256     "XXXXXXX  XXXXXXX",
257     " XXXXXX  XXXXXX ",
258   },
259   {
260     "     XX  XXXXX  ",
261     "    XXX  XXXX   ",
262     "   XXXX  XXX   X",
263     "  XXXXXXXXX   XX",
264     " XXXXXXXXX   XXX",
265     "XXXXXXXXX   XXXX",
266     "XXXXXXXX   XXXXX",
267     "   XXXX   XXX   ",
268     "   XXX   XXXX   ",
269     "XXXXX   XXXXXXXX",
270     "XXXX   XXXXXXXXX",
271     "XXX   XXXXXXXXX ",
272     "XX   XXXXXXXXX  ",
273     "X   XXX  XXXX   ",
274     "   XXXX  XXX    ",
275     "  XXXXX  XX     ",
276   },
277   {
278     "  XXXXX  XX     ",
279     "   XXXX  XXX    ",
280     "X   XXX  XXXX   ",
281     "XX   XXXXXXXXX  ",
282     "XXX   XXXXXXXXX ",
283     "XXXX   XXXXXXXXX",
284     "XXXXX   XXXXXXXX",
285     "   XXX   XXXX   ",
286     "   XXXX   XXX   ",
287     "XXXXXXXX   XXXXX",
288     "XXXXXXXXX   XXXX",
289     " XXXXXXXXX   XXX",
290     "  XXXXXXXXX   XX",
291     "   XXXX  XXX   X",
292     "    XXX  XXXX   ",
293     "     XX  XXXXX  ",
294   },
295   {
296     "XXXXXXXXXXXXXXXX",
297     "XXXXXXXXXXXXXXXX",
298     "XXXXXXXXXXXXXXXX",
299     "XXXXXXXXXXXXXXXX",
300     "XXXXXXXXXXXXXXXX",
301     "XXXXXXXXXXXXXXXX",
302     "XXXXXXXXXXXXXXXX",
303     "XXXXXXXXXXXXXXXX",
304     "XXXXXXXXXXXXXXXX",
305     "XXXXXXXXXXXXXXXX",
306     "XXXXXXXXXXXXXXXX",
307     "XXXXXXXXXXXXXXXX",
308     "XXXXXXXXXXXXXXXX",
309     "XXXXXXXXXXXXXXXX",
310     "XXXXXXXXXXXXXXXX",
311     "XXXXXXXXXXXXXXXX",
312   },
313   {
314     "                ",
315     "      XXXX      ",
316     "    XXXXXXXX    ",
317     "   XXXXXXXXXX   ",
318     "  XXXXXXXXXXXX  ",
319     "  XXXXXXXXXXXX  ",
320     " XXXXXXXXXXXXXX ",
321     " XXXXXXXXXXXXXX ",
322     " XXXXXXXXXXXXXX ",
323     " XXXXXXXXXXXXXX ",
324     "  XXXXXXXXXXXX  ",
325     "  XXXXXXXXXXXX  ",
326     "   XXXXXXXXXX   ",
327     "    XXXXXXXX    ",
328     "      XXXX      ",
329     "                ",
330   },
331 };
332
333 static int get_element_angle(int element)
334 {
335   int element_phase = get_element_phase(element);
336
337   if (IS_MIRROR_FIXED(element) ||
338       IS_MCDUFFIN(element) ||
339       IS_LASER(element) ||
340       IS_RECEIVER(element))
341     return 4 * element_phase;
342   else
343     return element_phase;
344 }
345
346 static int get_opposite_angle(int angle)
347 {
348   int opposite_angle = angle + ANG_RAY_180;
349
350   // make sure "opposite_angle" is in valid interval [0, 15]
351   return (opposite_angle + 16) % 16;
352 }
353
354 static int get_mirrored_angle(int laser_angle, int mirror_angle)
355 {
356   int reflected_angle = 16 - laser_angle + mirror_angle;
357
358   // make sure "reflected_angle" is in valid interval [0, 15]
359   return (reflected_angle + 16) % 16;
360 }
361
362 static void DrawLaserLines(struct XY *points, int num_points, int mode)
363 {
364   Pixel pixel_drawto = (mode == DL_LASER_ENABLED ? pen_ray     : pen_bg);
365   Pixel pixel_buffer = (mode == DL_LASER_ENABLED ? WHITE_PIXEL : BLACK_PIXEL);
366
367   DrawLines(drawto_mm, points, num_points, pixel_drawto);
368
369   BEGIN_NO_HEADLESS
370   {
371     DrawLines(laser_bitmap, points, num_points, pixel_buffer);
372   }
373   END_NO_HEADLESS
374 }
375
376 static boolean CheckLaserPixel(int x, int y)
377 {
378   Pixel pixel;
379
380   BEGIN_NO_HEADLESS
381   {
382     pixel = ReadPixel(laser_bitmap, x, y);
383   }
384   END_NO_HEADLESS
385
386   return (pixel == WHITE_PIXEL);
387 }
388
389 static void CheckExitMM(void)
390 {
391   int exit_element = EL_EMPTY;
392   int exit_x = 0;
393   int exit_y = 0;
394   int x, y;
395   static int xy[4][2] =
396   {
397     { +1,  0 },
398     {  0, -1 },
399     { -1,  0 },
400     {  0, +1 }
401   };
402
403   for (y = 0; y < lev_fieldy; y++)
404   {
405     for (x = 0; x < lev_fieldx; x++)
406     {
407       if (Tile[x][y] == EL_EXIT_CLOSED)
408       {
409         // initiate opening animation of exit door
410         Tile[x][y] = EL_EXIT_OPENING;
411
412         exit_element = EL_EXIT_OPEN;
413         exit_x = x;
414         exit_y = y;
415       }
416       else if (IS_RECEIVER(Tile[x][y]))
417       {
418         // remove field that blocks receiver
419         int phase = Tile[x][y] - EL_RECEIVER_START;
420         int blocking_x, blocking_y;
421
422         blocking_x = x + xy[phase][0];
423         blocking_y = y + xy[phase][1];
424
425         if (IN_LEV_FIELD(blocking_x, blocking_y))
426         {
427           Tile[blocking_x][blocking_y] = EL_EMPTY;
428
429           DrawField_MM(blocking_x, blocking_y);
430         }
431
432         exit_element = EL_RECEIVER;
433         exit_x = x;
434         exit_y = y;
435       }
436     }
437   }
438
439   if (exit_element != EL_EMPTY)
440     PlayLevelSound_MM(exit_x, exit_y, exit_element, MM_ACTION_OPENING);
441 }
442
443 static void SetLaserColor(int brightness)
444 {
445   int color_min = 0x00;
446   int color_max = brightness;           // (0x00 <= brightness <= 0xFF)
447   int color_up   = color_max * laser.overload_value / MAX_LASER_OVERLOAD;
448   int color_down = color_max - color_up;
449
450   pen_ray =
451     GetPixelFromRGB(window,
452                     (game_mm.laser_red   ? color_max  : color_up),
453                     (game_mm.laser_green ? color_down : color_min),
454                     (game_mm.laser_blue  ? color_down : color_min));
455 }
456
457 static void InitMovDir_MM(int x, int y)
458 {
459   int element = Tile[x][y];
460   static int direction[3][4] =
461   {
462     { MV_RIGHT, MV_UP,    MV_LEFT,  MV_DOWN },
463     { MV_LEFT,  MV_DOWN,  MV_RIGHT, MV_UP   },
464     { MV_LEFT,  MV_RIGHT, MV_UP,    MV_DOWN }
465   };
466
467   switch (element)
468   {
469     case EL_PACMAN_RIGHT:
470     case EL_PACMAN_UP:
471     case EL_PACMAN_LEFT:
472     case EL_PACMAN_DOWN:
473       Tile[x][y] = EL_PACMAN;
474       MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
475       break;
476
477     default:
478       break;
479   }
480 }
481
482 static void InitField(int x, int y, boolean init_game)
483 {
484   int element = Tile[x][y];
485
486   switch (element)
487   {
488     case EL_DF_EMPTY:
489       Tile[x][y] = EL_EMPTY;
490       break;
491
492     case EL_KETTLE:
493     case EL_CELL:
494       if (init_game && native_mm_level.auto_count_kettles)
495         game_mm.kettles_still_needed++;
496       break;
497
498     case EL_LIGHTBULB_OFF:
499       game_mm.lights_still_needed++;
500       break;
501
502     default:
503       if (IS_MIRROR(element) ||
504           IS_BEAMER_OLD(element) ||
505           IS_BEAMER(element) ||
506           IS_POLAR(element) ||
507           IS_POLAR_CROSS(element) ||
508           IS_DF_MIRROR(element) ||
509           IS_DF_MIRROR_AUTO(element) ||
510           IS_GRID_STEEL_AUTO(element) ||
511           IS_GRID_WOOD_AUTO(element) ||
512           IS_FIBRE_OPTIC(element))
513       {
514         if (IS_BEAMER_OLD(element))
515         {
516           Tile[x][y] = EL_BEAMER_BLUE_START + (element - EL_BEAMER_START);
517           element = Tile[x][y];
518         }
519
520         if (!IS_FIBRE_OPTIC(element))
521         {
522           static int steps_grid_auto = 0;
523
524           if (game_mm.num_cycle == 0)   // initialize cycle steps for grids
525             steps_grid_auto = RND(16) * (RND(2) ? -1 : +1);
526
527           if (IS_GRID_STEEL_AUTO(element) ||
528               IS_GRID_WOOD_AUTO(element))
529             game_mm.cycle[game_mm.num_cycle].steps = steps_grid_auto;
530           else
531             game_mm.cycle[game_mm.num_cycle].steps = RND(16) * (RND(2) ? -1 : +1);
532
533           game_mm.cycle[game_mm.num_cycle].x = x;
534           game_mm.cycle[game_mm.num_cycle].y = y;
535           game_mm.num_cycle++;
536         }
537
538         if (IS_BEAMER(element) || IS_FIBRE_OPTIC(element))
539         {
540           int beamer_nr = BEAMER_NR(element);
541           int nr = laser.beamer[beamer_nr][0].num;
542
543           laser.beamer[beamer_nr][nr].x = x;
544           laser.beamer[beamer_nr][nr].y = y;
545           laser.beamer[beamer_nr][nr].num = 1;
546         }
547       }
548       else if (IS_PACMAN(element))
549       {
550         InitMovDir_MM(x, y);
551       }
552       else if (IS_MCDUFFIN(element) || IS_LASER(element))
553       {
554         if (init_game)
555         {
556           laser.start_edge.x = x;
557           laser.start_edge.y = y;
558           laser.start_angle = get_element_angle(element);
559         }
560
561         if (IS_MCDUFFIN(element))
562         {
563           game_mm.laser_red   = native_mm_level.mm_laser_red;
564           game_mm.laser_green = native_mm_level.mm_laser_green;
565           game_mm.laser_blue  = native_mm_level.mm_laser_blue;
566         }
567         else
568         {
569           game_mm.laser_red   = native_mm_level.df_laser_red;
570           game_mm.laser_green = native_mm_level.df_laser_green;
571           game_mm.laser_blue  = native_mm_level.df_laser_blue;
572         }
573
574         game_mm.has_mcduffin = (IS_MCDUFFIN(element));
575       }
576
577       break;
578   }
579 }
580
581 static void InitCycleElements_RotateSingleStep(void)
582 {
583   int i;
584
585   if (game_mm.num_cycle == 0)   // no elements to cycle
586     return;
587
588   for (i = 0; i < game_mm.num_cycle; i++)
589   {
590     int x = game_mm.cycle[i].x;
591     int y = game_mm.cycle[i].y;
592     int step = SIGN(game_mm.cycle[i].steps);
593     int last_element = Tile[x][y];
594     int next_element = get_rotated_element(last_element, step);
595
596     if (!game_mm.cycle[i].steps)
597       continue;
598
599     Tile[x][y] = next_element;
600
601     game_mm.cycle[i].steps -= step;
602   }
603 }
604
605 static void InitLaser(void)
606 {
607   int start_element = Tile[laser.start_edge.x][laser.start_edge.y];
608   int step = (IS_LASER(start_element) ? 4 : 0);
609
610   LX = laser.start_edge.x * TILEX;
611   if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
612     LX += 14;
613   else
614     LX += (laser.start_angle == ANG_RAY_RIGHT ? 28 + step : 0 - step);
615
616   LY = laser.start_edge.y * TILEY;
617   if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
618     LY += (laser.start_angle == ANG_RAY_DOWN ? 28 + step : 0 - step);
619   else
620     LY += 14;
621
622   XS = 2 * Step[laser.start_angle].x;
623   YS = 2 * Step[laser.start_angle].y;
624
625   laser.current_angle = laser.start_angle;
626
627   laser.num_damages = 0;
628   laser.num_edges = 0;
629   laser.num_beamers = 0;
630   laser.beamer_edge[0] = 0;
631
632   laser.dest_element = EL_EMPTY;
633   laser.wall_mask = 0;
634
635   AddLaserEdge(LX, LY);         // set laser starting edge
636
637   SetLaserColor(0xFF);
638 }
639
640 void InitGameEngine_MM(void)
641 {
642   int i, x, y;
643
644   BEGIN_NO_HEADLESS
645   {
646     // initialize laser bitmap to current playfield (screen) size
647     ReCreateBitmap(&laser_bitmap, drawto_mm->width, drawto_mm->height);
648     ClearRectangle(laser_bitmap, 0, 0, drawto_mm->width, drawto_mm->height);
649   }
650   END_NO_HEADLESS
651
652   // set global game control values
653   game_mm.num_cycle = 0;
654   game_mm.num_pacman = 0;
655
656   game_mm.score = 0;
657   game_mm.energy_left = 0;      // later set to "native_mm_level.time"
658   game_mm.kettles_still_needed =
659     (native_mm_level.auto_count_kettles ? 0 : native_mm_level.kettles_needed);
660   game_mm.lights_still_needed = 0;
661   game_mm.num_keys = 0;
662   game_mm.ball_choice_pos = 0;
663
664   game_mm.laser_red = FALSE;
665   game_mm.laser_green = FALSE;
666   game_mm.laser_blue = TRUE;
667   game_mm.has_mcduffin = TRUE;
668
669   game_mm.level_solved = FALSE;
670   game_mm.game_over = FALSE;
671   game_mm.game_over_cause = 0;
672   game_mm.game_over_message = NULL;
673
674   game_mm.laser_overload_value = 0;
675   game_mm.laser_enabled = FALSE;
676
677   // set global laser control values (must be set before "InitLaser()")
678   laser.start_edge.x = 0;
679   laser.start_edge.y = 0;
680   laser.start_angle = 0;
681
682   for (i = 0; i < MAX_NUM_BEAMERS; i++)
683     laser.beamer[i][0].num = laser.beamer[i][1].num = 0;
684
685   laser.overloaded = FALSE;
686   laser.overload_value = 0;
687   laser.fuse_off = FALSE;
688   laser.fuse_x = laser.fuse_y = -1;
689
690   laser.dest_element = EL_EMPTY;
691   laser.dest_element_last = EL_EMPTY;
692   laser.dest_element_last_x = -1;
693   laser.dest_element_last_y = -1;
694   laser.wall_mask = 0;
695
696   last_LX = 0;
697   last_LY = 0;
698   last_hit_mask = 0;
699
700   hold_x = -1;
701   hold_y = -1;
702
703   pacman_nr = -1;
704
705   CT = Ct = 0;
706
707   rotate_delay.count = 0;
708   pacman_delay.count = 0;
709   energy_delay.count = 0;
710   overload_delay.count = 0;
711
712   ClickElement(-1, -1, -1);
713
714   for (x = 0; x < lev_fieldx; x++)
715   {
716     for (y = 0; y < lev_fieldy; y++)
717     {
718       Tile[x][y] = Ur[x][y];
719       Hit[x][y] = Box[x][y] = 0;
720       Angle[x][y] = 0;
721       MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
722       Store[x][y] = Store2[x][y] = 0;
723       Stop[x][y] = FALSE;
724
725       InitField(x, y, TRUE);
726     }
727   }
728
729   DrawLevel_MM();
730 }
731
732 void InitGameActions_MM(void)
733 {
734   int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
735   int cycle_steps_done = 0;
736   int i;
737
738   InitLaser();
739
740   for (i = 0; i <= num_init_game_frames; i++)
741   {
742     if (i == num_init_game_frames)
743       StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
744     else if (setup.sound_loops)
745       PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
746     else
747       PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
748
749     game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
750
751     UpdateAndDisplayGameControlValues();
752
753     while (cycle_steps_done < NUM_INIT_CYCLE_STEPS * i / num_init_game_frames)
754     {
755       InitCycleElements_RotateSingleStep();
756
757       cycle_steps_done++;
758     }
759
760     AdvanceFrameCounter();
761     AdvanceGfxFrame();
762
763     DrawLevel_MM();
764
765     BackToFront_MM();
766
767 #ifdef DEBUG
768     if (setup.quick_doors)
769       continue;
770 #endif
771   }
772
773   ScanLaser();
774
775   if (game_mm.kettles_still_needed == 0)
776     CheckExitMM();
777
778   SetTileCursorXY(laser.start_edge.x, laser.start_edge.y);
779   SetTileCursorActive(TRUE);
780
781   // restart all delay counters after initially cycling game elements
782   ResetFrameCounter(&rotate_delay);
783   ResetFrameCounter(&pacman_delay);
784   ResetFrameCounter(&energy_delay);
785   ResetFrameCounter(&overload_delay);
786 }
787
788 static void FadeOutLaser(void)
789 {
790   int i;
791
792   for (i = 15; i >= 0; i--)
793   {
794     SetLaserColor(0x11 * i);
795
796     DrawLaser(0, DL_LASER_ENABLED);
797
798     BackToFront_MM();
799     Delay_WithScreenUpdates(50);
800   }
801
802   DrawLaser(0, DL_LASER_DISABLED);
803
804   StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
805 }
806
807 static void GameOver_MM(int game_over_cause)
808 {
809   game_mm.game_over = TRUE;
810   game_mm.game_over_cause = game_over_cause;
811   game_mm.game_over_message = (game_mm.has_mcduffin ?
812                                (game_over_cause == GAME_OVER_BOMB ?
813                                 "Bomb killed Mc Duffin!" :
814                                 game_over_cause == GAME_OVER_NO_ENERGY ?
815                                 "Out of magic energy!" :
816                                 game_over_cause == GAME_OVER_OVERLOADED ?
817                                 "Magic spell hit Mc Duffin!" :
818                                 NULL) :
819                                (game_over_cause == GAME_OVER_BOMB ?
820                                 "Bomb destroyed laser cannon!" :
821                                 game_over_cause == GAME_OVER_NO_ENERGY ?
822                                 "Out of laser energy!" :
823                                 game_over_cause == GAME_OVER_OVERLOADED ?
824                                 "Laser beam hit laser cannon!" :
825                                 NULL));
826
827   SetTileCursorActive(FALSE);
828 }
829
830 static void AddLaserEdge(int lx, int ly)
831 {
832   int clx = dSX + lx;
833   int cly = dSY + ly;
834
835   if (clx < -2 || cly < -2 || clx >= SXSIZE + 2 || cly >= SYSIZE + 2)
836   {
837     Warn("AddLaserEdge: out of bounds: %d, %d", lx, ly);
838
839     return;
840   }
841
842   laser.edge[laser.num_edges].x = cSX2 + lx;
843   laser.edge[laser.num_edges].y = cSY2 + ly;
844   laser.num_edges++;
845
846   laser.redraw = TRUE;
847 }
848
849 static void AddDamagedField(int ex, int ey)
850 {
851   // prevent adding the same field position again
852   if (laser.num_damages > 0 &&
853       laser.damage[laser.num_damages - 1].x == ex &&
854       laser.damage[laser.num_damages - 1].y == ey &&
855       laser.damage[laser.num_damages - 1].edge == laser.num_edges)
856     return;
857
858   laser.damage[laser.num_damages].is_mirror = FALSE;
859   laser.damage[laser.num_damages].angle = laser.current_angle;
860   laser.damage[laser.num_damages].edge = laser.num_edges;
861   laser.damage[laser.num_damages].x = ex;
862   laser.damage[laser.num_damages].y = ey;
863   laser.num_damages++;
864 }
865
866 static boolean StepBehind(void)
867 {
868   if (laser.num_edges)
869   {
870     int x = LX - XS;
871     int y = LY - YS;
872     int last_x = laser.edge[laser.num_edges - 1].x - cSX2;
873     int last_y = laser.edge[laser.num_edges - 1].y - cSY2;
874
875     return ((x - last_x) * XS < 0 || (y - last_y) * YS < 0);
876   }
877
878   return FALSE;
879 }
880
881 static int getMaskFromElement(int element)
882 {
883   if (IS_GRID(element))
884     return MM_MASK_GRID_1 + get_element_phase(element);
885   else if (IS_MCDUFFIN(element))
886     return MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element);
887   else if (IS_RECTANGLE(element) || IS_DF_GRID(element))
888     return MM_MASK_RECTANGLE;
889   else
890     return MM_MASK_CIRCLE;
891 }
892
893 static int ScanPixel(void)
894 {
895   int hit_mask = 0;
896
897 #if 0
898   Debug("game:mm:ScanPixel", "start scanning at (%d, %d) [%d, %d] [%d, %d]",
899         LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
900 #endif
901
902   // follow laser beam until it hits something (at least the screen border)
903   while (hit_mask == HIT_MASK_NO_HIT)
904   {
905     int i;
906
907 #if 0
908     // for safety
909     if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
910         SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
911     {
912       Debug("game:mm:ScanPixel", "touched screen border!");
913
914       return HIT_MASK_ALL;
915     }
916 #endif
917
918     // check if laser scan has crossed element boundaries (not just mini tiles)
919     boolean cross_x = (LX / TILEX != (LX + 2) / TILEX);
920     boolean cross_y = (LY / TILEY != (LY + 2) / TILEY);
921
922     if (cross_x && cross_y)
923     {
924       int elx1 = (LX - XS) / TILEX;
925       int ely1 = (LY + YS) / TILEY;
926       int elx2 = (LX + XS) / TILEX;
927       int ely2 = (LY - YS) / TILEY;
928
929       // add element corners left and right from the laser beam to damage list
930
931       if (IN_LEV_FIELD(elx1, ely1) && Tile[elx1][ely1] != EL_EMPTY)
932         AddDamagedField(elx1, ely1);
933
934       if (IN_LEV_FIELD(elx2, ely2) && Tile[elx2][ely2] != EL_EMPTY)
935         AddDamagedField(elx2, ely2);
936     }
937
938     for (i = 0; i < 4; i++)
939     {
940       int px = LX + (i % 2) * 2;
941       int py = LY + (i / 2) * 2;
942       int dx = px % TILEX;
943       int dy = py % TILEY;
944       int lx = (px + TILEX) / TILEX - 1;  // ...+TILEX...-1 to get correct
945       int ly = (py + TILEY) / TILEY - 1;  // negative values!
946       Pixel pixel;
947
948       if (IN_LEV_FIELD(lx, ly))
949       {
950         int element = Tile[lx][ly];
951
952         if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
953         {
954           pixel = 0;
955         }
956         else if (IS_WALL(element) || IS_WALL_CHANGING(element))
957         {
958           int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
959
960           pixel = ((element & (1 << pos)) ? 1 : 0);
961         }
962         else
963         {
964           int pos = getMaskFromElement(element);
965
966           pixel = (mm_masks[pos][dy / 2][dx / 2] == 'X' ? 1 : 0);
967         }
968       }
969       else
970       {
971         pixel = (cSX + px < REAL_SX || cSX + px >= REAL_SX + FULL_SXSIZE ||
972                  cSY + py < REAL_SY || cSY + py >= REAL_SY + FULL_SYSIZE);
973       }
974
975       if ((Sign[laser.current_angle] & (1 << i)) && pixel)
976         hit_mask |= (1 << i);
977     }
978
979     if (hit_mask == HIT_MASK_NO_HIT)
980     {
981       // hit nothing -- go on with another step
982       LX += XS;
983       LY += YS;
984     }
985   }
986
987   return hit_mask;
988 }
989
990 static void DeactivateLaserTargetElement(void)
991 {
992   if (laser.dest_element_last == EL_BOMB_ACTIVE ||
993       laser.dest_element_last == EL_MINE_ACTIVE ||
994       laser.dest_element_last == EL_GRAY_BALL_ACTIVE ||
995       laser.dest_element_last == EL_GRAY_BALL_OPENING)
996   {
997     int x = laser.dest_element_last_x;
998     int y = laser.dest_element_last_y;
999     int element = laser.dest_element_last;
1000
1001     if (Tile[x][y] == element)
1002       Tile[x][y] = (element == EL_BOMB_ACTIVE ? EL_BOMB :
1003                     element == EL_MINE_ACTIVE ? EL_MINE : EL_GRAY_BALL);
1004
1005     if (Tile[x][y] == EL_GRAY_BALL)
1006       MovDelay[x][y] = 0;
1007
1008     laser.dest_element_last = EL_EMPTY;
1009     laser.dest_element_last_x = -1;
1010     laser.dest_element_last_y = -1;
1011   }
1012 }
1013
1014 static void ScanLaser(void)
1015 {
1016   int element = EL_EMPTY;
1017   int last_element = EL_EMPTY;
1018   int end = 0, rf = laser.num_edges;
1019
1020   // do not scan laser again after the game was lost for whatever reason
1021   if (game_mm.game_over)
1022     return;
1023
1024   // do not scan laser if fuse is off
1025   if (laser.fuse_off)
1026     return;
1027
1028   DeactivateLaserTargetElement();
1029
1030   laser.overloaded = FALSE;
1031   laser.stops_inside_element = FALSE;
1032
1033   DrawLaser(0, DL_LASER_ENABLED);
1034
1035 #if 0
1036   Debug("game:mm:ScanLaser",
1037         "Start scanning with LX == %d, LY == %d, XS == %d, YS == %d",
1038         LX, LY, XS, YS);
1039 #endif
1040
1041   while (1)
1042   {
1043     int hit_mask;
1044
1045     if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
1046     {
1047       end = 1;
1048       laser.overloaded = TRUE;
1049
1050       break;
1051     }
1052
1053     hit_mask = ScanPixel();
1054
1055 #if 0
1056     Debug("game:mm:ScanLaser",
1057           "Hit something at LX == %d, LY == %d, XS == %d, YS == %d",
1058           LX, LY, XS, YS);
1059 #endif
1060
1061     // hit something -- check out what it was
1062     ELX = (LX + XS) / TILEX;
1063     ELY = (LY + YS) / TILEY;
1064
1065 #if 0
1066     Debug("game:mm:ScanLaser", "hit_mask (1) == '%x' (%d, %d) (%d, %d)",
1067           hit_mask, LX, LY, ELX, ELY);
1068 #endif
1069
1070     if (!IN_LEV_FIELD(ELX, ELY) || !IN_PIX_FIELD(LX, LY))
1071     {
1072       element = EL_EMPTY;
1073       laser.dest_element = element;
1074
1075       break;
1076     }
1077
1078     // check if laser scan has hit two diagonally adjacent element corners
1079     boolean diag_1 = ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1);
1080     boolean diag_2 = ((hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2);
1081
1082     // check if laser scan has crossed element boundaries (not just mini tiles)
1083     boolean cross_x = (LX / TILEX != (LX + 2) / TILEX);
1084     boolean cross_y = (LY / TILEY != (LY + 2) / TILEY);
1085
1086     // handle special case of laser hitting two diagonally adjacent elements
1087     // (with or without a third corner element behind these two elements)
1088     if ((diag_1 || diag_2) && cross_x && cross_y)
1089     {
1090       // compare the two diagonally adjacent elements
1091       int xoffset = 2;
1092       int yoffset = 2 * (diag_1 ? -1 : +1);
1093       int elx1 = (LX - xoffset) / TILEX;
1094       int ely1 = (LY + yoffset) / TILEY;
1095       int elx2 = (LX + xoffset) / TILEX;
1096       int ely2 = (LY - yoffset) / TILEY;
1097       int e1 = Tile[elx1][ely1];
1098       int e2 = Tile[elx2][ely2];
1099       boolean use_element_1 = FALSE;
1100
1101       if (IS_WALL_ICE(e1) || IS_WALL_ICE(e2))
1102       {
1103         if (IS_WALL_ICE(e1) && IS_WALL_ICE(e2))
1104           use_element_1 = (RND(2) ? TRUE : FALSE);
1105         else if (IS_WALL_ICE(e1))
1106           use_element_1 = TRUE;
1107       }
1108       else if (IS_WALL_AMOEBA(e1) || IS_WALL_AMOEBA(e2))
1109       {
1110         // if both tiles match, we can just select the first one
1111         if (IS_WALL_AMOEBA(e1))
1112           use_element_1 = TRUE;
1113       }
1114       else if (IS_ABSORBING_BLOCK(e1) || IS_ABSORBING_BLOCK(e2))
1115       {
1116         // if both tiles match, we can just select the first one
1117         if (IS_ABSORBING_BLOCK(e1))
1118           use_element_1 = TRUE;
1119       }
1120
1121       ELX = (use_element_1 ? elx1 : elx2);
1122       ELY = (use_element_1 ? ely1 : ely2);
1123     }
1124
1125 #if 0
1126     Debug("game:mm:ScanLaser", "hit_mask (2) == '%x' (%d, %d) (%d, %d)",
1127           hit_mask, LX, LY, ELX, ELY);
1128 #endif
1129
1130     last_element = element;
1131
1132     element = Tile[ELX][ELY];
1133     laser.dest_element = element;
1134
1135 #if 0
1136     Debug("game:mm:ScanLaser",
1137           "Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]",
1138           element, ELX, ELY,
1139           LX, LY,
1140           LX % TILEX, LY % TILEY,
1141           hit_mask);
1142 #endif
1143
1144 #if 0
1145     if (!IN_LEV_FIELD(ELX, ELY))
1146       Debug("game:mm:ScanLaser", "WARNING! (1) %d, %d (%d)",
1147             ELX, ELY, element);
1148 #endif
1149
1150     // special case: leaving fixed MM steel grid (upwards) with non-90° angle
1151     if (element == EL_EMPTY &&
1152         IS_GRID_STEEL(last_element) &&
1153         laser.current_angle % 4)                // angle is not 90°
1154       element = last_element;
1155
1156     if (element == EL_EMPTY)
1157     {
1158       if (!HitOnlyAnEdge(hit_mask))
1159         break;
1160     }
1161     else if (element == EL_FUSE_ON)
1162     {
1163       if (HitPolarizer(element, hit_mask))
1164         break;
1165     }
1166     else if (IS_GRID(element) || IS_DF_GRID(element))
1167     {
1168       if (HitPolarizer(element, hit_mask))
1169         break;
1170     }
1171     else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
1172              element == EL_GATE_STONE || element == EL_GATE_WOOD)
1173     {
1174       if (HitBlock(element, hit_mask))
1175       {
1176         rf = 1;
1177
1178         break;
1179       }
1180     }
1181     else if (IS_MCDUFFIN(element))
1182     {
1183       if (HitLaserSource(element, hit_mask))
1184         break;
1185     }
1186     else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
1187              IS_RECEIVER(element))
1188     {
1189       if (HitLaserDestination(element, hit_mask))
1190         break;
1191     }
1192     else if (IS_WALL(element))
1193     {
1194       if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
1195       {
1196         if (HitReflectingWalls(element, hit_mask))
1197           break;
1198       }
1199       else
1200       {
1201         if (HitAbsorbingWalls(element, hit_mask))
1202           break;
1203       }
1204     }
1205     else
1206     {
1207       if (HitElement(element, hit_mask))
1208         break;
1209     }
1210
1211     if (rf)
1212       DrawLaser(rf - 1, DL_LASER_ENABLED);
1213     rf = laser.num_edges;
1214
1215     if (!IS_DF_WALL_STEEL(element))
1216     {
1217       // only used for scanning DF steel walls; reset for all other elements
1218       last_LX = 0;
1219       last_LY = 0;
1220       last_hit_mask = 0;
1221     }
1222   }
1223
1224 #if 0
1225   if (laser.dest_element != Tile[ELX][ELY])
1226   {
1227     Debug("game:mm:ScanLaser",
1228           "ALARM: laser.dest_element == %d, Tile[ELX][ELY] == %d",
1229           laser.dest_element, Tile[ELX][ELY]);
1230   }
1231 #endif
1232
1233   if (!end && !laser.stops_inside_element && !StepBehind())
1234   {
1235 #if 0
1236     Debug("game:mm:ScanLaser", "Go one step back");
1237 #endif
1238
1239     LX -= XS;
1240     LY -= YS;
1241
1242     AddLaserEdge(LX, LY);
1243   }
1244
1245   if (rf)
1246     DrawLaser(rf - 1, DL_LASER_ENABLED);
1247
1248   Ct = CT = FrameCounter;
1249
1250 #if 0
1251     if (!IN_LEV_FIELD(ELX, ELY))
1252       Debug("game:mm:ScanLaser", "WARNING! (2) %d, %d", ELX, ELY);
1253 #endif
1254 }
1255
1256 static void ScanLaser_FromLastMirror(void)
1257 {
1258   int start_pos = (laser.num_damages > 0 ? laser.num_damages - 1 : 0);
1259   int i;
1260
1261   for (i = start_pos; i >= 0; i--)
1262     if (laser.damage[i].is_mirror)
1263       break;
1264
1265   int start_edge = (i > 0 ? laser.damage[i].edge - 1 : 0);
1266
1267   DrawLaser(start_edge, DL_LASER_DISABLED);
1268
1269   ScanLaser();
1270 }
1271
1272 static void DrawLaserExt(int start_edge, int num_edges, int mode)
1273 {
1274   int element;
1275   int elx, ely;
1276
1277 #if 0
1278   Debug("game:mm:DrawLaserExt", "start_edge, num_edges, mode == %d, %d, %d",
1279         start_edge, num_edges, mode);
1280 #endif
1281
1282   if (start_edge < 0)
1283   {
1284     Warn("DrawLaserExt: start_edge < 0");
1285
1286     return;
1287   }
1288
1289   if (num_edges < 0)
1290   {
1291     Warn("DrawLaserExt: num_edges < 0");
1292
1293     return;
1294   }
1295
1296 #if 0
1297   if (mode == DL_LASER_DISABLED)
1298   {
1299     Debug("game:mm:DrawLaserExt", "Delete laser from edge %d", start_edge);
1300   }
1301 #endif
1302
1303   // now draw the laser to the backbuffer and (if enabled) to the screen
1304   DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1305
1306   redraw_mask |= REDRAW_FIELD;
1307
1308   if (mode == DL_LASER_ENABLED)
1309     return;
1310
1311   // after the laser was deleted, the "damaged" graphics must be restored
1312   if (laser.num_damages)
1313   {
1314     int damage_start = 0;
1315     int i;
1316
1317     // determine the starting edge, from which graphics need to be restored
1318     if (start_edge > 0)
1319     {
1320       for (i = 0; i < laser.num_damages; i++)
1321       {
1322         if (laser.damage[i].edge == start_edge + 1)
1323         {
1324           damage_start = i;
1325
1326           break;
1327         }
1328       }
1329     }
1330
1331     // restore graphics from this starting edge to the end of damage list
1332     for (i = damage_start; i < laser.num_damages; i++)
1333     {
1334       int lx = laser.damage[i].x;
1335       int ly = laser.damage[i].y;
1336       int element = Tile[lx][ly];
1337
1338       if (Hit[lx][ly] == laser.damage[i].edge)
1339         if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1340                i == damage_start))
1341           Hit[lx][ly] = 0;
1342       if (Box[lx][ly] == laser.damage[i].edge)
1343         Box[lx][ly] = 0;
1344
1345       if (IS_DRAWABLE(element))
1346         DrawField_MM(lx, ly);
1347     }
1348
1349     elx = laser.damage[damage_start].x;
1350     ely = laser.damage[damage_start].y;
1351     element = Tile[elx][ely];
1352
1353 #if 0
1354     if (IS_BEAMER(element))
1355     {
1356       int i;
1357
1358       for (i = 0; i < laser.num_beamers; i++)
1359         Debug("game:mm:DrawLaserExt", "-> %d", laser.beamer_edge[i]);
1360
1361       Debug("game:mm:DrawLaserExt", "IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]",
1362             mode, elx, ely, Hit[elx][ely], start_edge);
1363       Debug("game:mm:DrawLaserExt", "IS_BEAMER: %d / %d",
1364             get_element_angle(element), laser.damage[damage_start].angle);
1365     }
1366 #endif
1367
1368     if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1369         laser.num_beamers > 0 &&
1370         start_edge == laser.beamer_edge[laser.num_beamers - 1])
1371     {
1372       // element is outgoing beamer
1373       laser.num_damages = damage_start + 1;
1374
1375       if (IS_BEAMER(element))
1376         laser.current_angle = get_element_angle(element);
1377     }
1378     else
1379     {
1380       // element is incoming beamer or other element
1381       laser.num_damages = damage_start;
1382       laser.current_angle = laser.damage[laser.num_damages].angle;
1383     }
1384   }
1385   else
1386   {
1387     // no damages but McDuffin himself (who needs to be redrawn anyway)
1388
1389     elx = laser.start_edge.x;
1390     ely = laser.start_edge.y;
1391     element = Tile[elx][ely];
1392   }
1393
1394   laser.num_edges = start_edge + 1;
1395   if (start_edge == 0)
1396     laser.current_angle = laser.start_angle;
1397
1398   LX = laser.edge[start_edge].x - cSX2;
1399   LY = laser.edge[start_edge].y - cSY2;
1400   XS = 2 * Step[laser.current_angle].x;
1401   YS = 2 * Step[laser.current_angle].y;
1402
1403 #if 0
1404   Debug("game:mm:DrawLaserExt", "Set (LX, LY) to (%d, %d) [%d]",
1405         LX, LY, element);
1406 #endif
1407
1408   if (start_edge > 0)
1409   {
1410     if (IS_BEAMER(element) ||
1411         IS_FIBRE_OPTIC(element) ||
1412         IS_PACMAN(element) ||
1413         IS_POLAR(element) ||
1414         IS_POLAR_CROSS(element) ||
1415         element == EL_FUSE_ON)
1416     {
1417       int step_size;
1418
1419 #if 0
1420       Debug("game:mm:DrawLaserExt", "element == %d", element);
1421 #endif
1422
1423       if (IS_22_5_ANGLE(laser.current_angle))   // neither 90° nor 45° angle
1424         step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1425       else
1426         step_size = 8;
1427
1428       if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1429           ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1430            (laser.num_beamers == 0 ||
1431             start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1432       {
1433         // element is incoming beamer or other element
1434         step_size = -step_size;
1435         laser.num_edges--;
1436       }
1437
1438 #if 0
1439       if (IS_BEAMER(element))
1440         Debug("game:mm:DrawLaserExt",
1441               "start_edge == %d, laser.beamer_edge == %d",
1442               start_edge, laser.beamer_edge);
1443 #endif
1444
1445       LX += step_size * XS;
1446       LY += step_size * YS;
1447     }
1448     else if (element != EL_EMPTY)
1449     {
1450       LX -= 3 * XS;
1451       LY -= 3 * YS;
1452       laser.num_edges--;
1453     }
1454   }
1455
1456 #if 0
1457   Debug("game:mm:DrawLaserExt", "Finally: (LX, LY) to (%d, %d) [%d]",
1458         LX, LY, element);
1459 #endif
1460 }
1461
1462 void DrawLaser(int start_edge, int mode)
1463 {
1464   // do not draw laser if fuse is off
1465   if (laser.fuse_off && mode == DL_LASER_ENABLED)
1466     return;
1467
1468   if (mode == DL_LASER_DISABLED)
1469     DeactivateLaserTargetElement();
1470
1471   if (laser.num_edges - start_edge < 0)
1472   {
1473     Warn("DrawLaser: laser.num_edges - start_edge < 0");
1474
1475     return;
1476   }
1477
1478   // check if laser is interrupted by beamer element
1479   if (laser.num_beamers > 0 &&
1480       start_edge < laser.beamer_edge[laser.num_beamers - 1])
1481   {
1482     if (mode == DL_LASER_ENABLED)
1483     {
1484       int i;
1485       int tmp_start_edge = start_edge;
1486
1487       // draw laser segments forward from the start to the last beamer
1488       for (i = 0; i < laser.num_beamers; i++)
1489       {
1490         int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1491
1492         if (tmp_num_edges <= 0)
1493           continue;
1494
1495 #if 0
1496         Debug("game:mm:DrawLaser", "DL_LASER_ENABLED: i==%d: %d, %d",
1497               i, laser.beamer_edge[i], tmp_start_edge);
1498 #endif
1499
1500         DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1501
1502         tmp_start_edge = laser.beamer_edge[i];
1503       }
1504
1505       // draw last segment from last beamer to the end
1506       DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1507                    DL_LASER_ENABLED);
1508     }
1509     else
1510     {
1511       int i;
1512       int last_num_edges = laser.num_edges;
1513       int num_beamers = laser.num_beamers;
1514
1515       // delete laser segments backward from the end to the first beamer
1516       for (i = num_beamers - 1; i >= 0; i--)
1517       {
1518         int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1519
1520         if (laser.beamer_edge[i] - start_edge <= 0)
1521           break;
1522
1523         DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1524
1525         last_num_edges = laser.beamer_edge[i];
1526         laser.num_beamers--;
1527       }
1528
1529 #if 0
1530       if (last_num_edges - start_edge <= 0)
1531         Debug("game:mm:DrawLaser", "DL_LASER_DISABLED: %d, %d",
1532               last_num_edges, start_edge);
1533 #endif
1534
1535       // special case when rotating first beamer: delete laser edge on beamer
1536       // (but do not start scanning on previous edge to prevent mirror sound)
1537       if (last_num_edges - start_edge == 1 && start_edge > 0)
1538         DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1539
1540       // delete first segment from start to the first beamer
1541       DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1542     }
1543   }
1544   else
1545   {
1546     DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1547   }
1548
1549   game_mm.laser_enabled = mode;
1550 }
1551
1552 void DrawLaser_MM(void)
1553 {
1554   DrawLaser(0, game_mm.laser_enabled);
1555 }
1556
1557 static boolean HitElement(int element, int hit_mask)
1558 {
1559   if (HitOnlyAnEdge(hit_mask))
1560     return FALSE;
1561
1562   if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1563     element = MovingOrBlocked2Element_MM(ELX, ELY);
1564
1565 #if 0
1566   Debug("game:mm:HitElement", "(1): element == %d", element);
1567 #endif
1568
1569 #if 0
1570   if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1571     Debug("game:mm:HitElement", "(%d): EXACT MATCH @ (%d, %d)",
1572           element, ELX, ELY);
1573   else
1574     Debug("game:mm:HitElement", "(%d): FUZZY MATCH @ (%d, %d)",
1575           element, ELX, ELY);
1576 #endif
1577
1578   AddDamagedField(ELX, ELY);
1579
1580   // this is more precise: check if laser would go through the center
1581   if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1582   {
1583     int skip_count = 0;
1584
1585     // prevent cutting through laser emitter with laser beam
1586     if (IS_LASER(element))
1587       return TRUE;
1588
1589     // skip the whole element before continuing the scan
1590     do
1591     {
1592       LX += XS;
1593       LY += YS;
1594
1595       skip_count++;
1596     }
1597     while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1598
1599     if ((LX/TILEX > ELX || LY/TILEY > ELY) && skip_count > 1)
1600     {
1601       /* skipping scan positions to the right and down skips one scan
1602          position too much, because this is only the top left scan position
1603          of totally four scan positions (plus one to the right, one to the
1604          bottom and one to the bottom right) */
1605       /* ... but only roll back scan position if more than one step done */
1606
1607       LX -= XS;
1608       LY -= YS;
1609     }
1610
1611     return FALSE;
1612   }
1613
1614 #if 0
1615   Debug("game:mm:HitElement", "(2): element == %d", element);
1616 #endif
1617
1618   if (LX + 5 * XS < 0 ||
1619       LY + 5 * YS < 0)
1620   {
1621     LX += 2 * XS;
1622     LY += 2 * YS;
1623
1624     return FALSE;
1625   }
1626
1627 #if 0
1628   Debug("game:mm:HitElement", "(3): element == %d", element);
1629 #endif
1630
1631   if (IS_POLAR(element) &&
1632       ((element - EL_POLAR_START) % 2 ||
1633        (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1634   {
1635     PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1636
1637     laser.num_damages--;
1638
1639     return TRUE;
1640   }
1641
1642   if (IS_POLAR_CROSS(element) &&
1643       (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1644   {
1645     PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1646
1647     laser.num_damages--;
1648
1649     return TRUE;
1650   }
1651
1652   if (!IS_BEAMER(element) &&
1653       !IS_FIBRE_OPTIC(element) &&
1654       !IS_GRID_WOOD(element) &&
1655       element != EL_FUEL_EMPTY)
1656   {
1657 #if 0
1658     if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1659       Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
1660     else
1661       Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
1662 #endif
1663
1664     LX = ELX * TILEX + 14;
1665     LY = ELY * TILEY + 14;
1666
1667     AddLaserEdge(LX, LY);
1668   }
1669
1670   if (IS_MIRROR(element) ||
1671       IS_MIRROR_FIXED(element) ||
1672       IS_POLAR(element) ||
1673       IS_POLAR_CROSS(element) ||
1674       IS_DF_MIRROR(element) ||
1675       IS_DF_MIRROR_AUTO(element) ||
1676       element == EL_PRISM ||
1677       element == EL_REFRACTOR)
1678   {
1679     int current_angle = laser.current_angle;
1680     int step_size;
1681
1682     laser.num_damages--;
1683
1684     AddDamagedField(ELX, ELY);
1685
1686     laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1687
1688     if (!Hit[ELX][ELY])
1689       Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1690
1691     if (IS_MIRROR(element) ||
1692         IS_MIRROR_FIXED(element) ||
1693         IS_DF_MIRROR(element) ||
1694         IS_DF_MIRROR_AUTO(element))
1695       laser.current_angle = get_mirrored_angle(laser.current_angle,
1696                                                get_element_angle(element));
1697
1698     if (element == EL_PRISM || element == EL_REFRACTOR)
1699       laser.current_angle = RND(16);
1700
1701     XS = 2 * Step[laser.current_angle].x;
1702     YS = 2 * Step[laser.current_angle].y;
1703
1704     if (!IS_22_5_ANGLE(laser.current_angle))    // 90° or 45° angle
1705       step_size = 8;
1706     else
1707       step_size = 4;
1708
1709     LX += step_size * XS;
1710     LY += step_size * YS;
1711
1712     // draw sparkles on mirror
1713     if ((IS_MIRROR(element) ||
1714          IS_MIRROR_FIXED(element) ||
1715          element == EL_PRISM) &&
1716         current_angle != laser.current_angle)
1717     {
1718       MovDelay[ELX][ELY] = 11;          // start animation
1719     }
1720
1721     if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1722         current_angle != laser.current_angle)
1723       PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1724
1725     laser.overloaded =
1726       (get_opposite_angle(laser.current_angle) ==
1727        laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1728
1729     return (laser.overloaded ? TRUE : FALSE);
1730   }
1731
1732   if (element == EL_FUEL_FULL)
1733   {
1734     laser.stops_inside_element = TRUE;
1735
1736     return TRUE;
1737   }
1738
1739   if (element == EL_BOMB || element == EL_MINE || element == EL_GRAY_BALL)
1740   {
1741     PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1742
1743     Tile[ELX][ELY] = (element == EL_BOMB ? EL_BOMB_ACTIVE :
1744                       element == EL_MINE ? EL_MINE_ACTIVE :
1745                       EL_GRAY_BALL_ACTIVE);
1746
1747     GfxFrame[ELX][ELY] = 0;             // restart animation
1748
1749     laser.dest_element_last = Tile[ELX][ELY];
1750     laser.dest_element_last_x = ELX;
1751     laser.dest_element_last_y = ELY;
1752
1753     if (element == EL_MINE)
1754       laser.overloaded = TRUE;
1755   }
1756
1757   if (element == EL_KETTLE ||
1758       element == EL_CELL ||
1759       element == EL_KEY ||
1760       element == EL_LIGHTBALL ||
1761       element == EL_PACMAN ||
1762       IS_PACMAN(element) ||
1763       IS_ENVELOPE(element))
1764   {
1765     if (!IS_PACMAN(element) &&
1766         !IS_ENVELOPE(element))
1767       Bang_MM(ELX, ELY);
1768
1769     if (element == EL_PACMAN)
1770       Bang_MM(ELX, ELY);
1771
1772     if (element == EL_KETTLE || element == EL_CELL)
1773     {
1774       if (game_mm.kettles_still_needed > 0)
1775         game_mm.kettles_still_needed--;
1776
1777       game.snapshot.collected_item = TRUE;
1778
1779       if (game_mm.kettles_still_needed == 0)
1780       {
1781         CheckExitMM();
1782
1783         DrawLaser(0, DL_LASER_ENABLED);
1784       }
1785     }
1786     else if (element == EL_KEY)
1787     {
1788       game_mm.num_keys++;
1789     }
1790     else if (IS_PACMAN(element))
1791     {
1792       DeletePacMan(ELX, ELY);
1793     }
1794     else if (IS_ENVELOPE(element))
1795     {
1796       Tile[ELX][ELY] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(Tile[ELX][ELY]);
1797     }
1798
1799     RaiseScoreElement_MM(element);
1800
1801     return FALSE;
1802   }
1803
1804   if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1805   {
1806     PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1807
1808     DrawLaser(0, DL_LASER_ENABLED);
1809
1810     if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
1811     {
1812       Tile[ELX][ELY] = EL_LIGHTBULB_ON;
1813       game_mm.lights_still_needed--;
1814     }
1815     else
1816     {
1817       Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
1818       game_mm.lights_still_needed++;
1819     }
1820
1821     DrawField_MM(ELX, ELY);
1822     DrawLaser(0, DL_LASER_ENABLED);
1823
1824     /*
1825     BackToFront();
1826     */
1827     laser.stops_inside_element = TRUE;
1828
1829     return TRUE;
1830   }
1831
1832 #if 0
1833   Debug("game:mm:HitElement", "(4): element == %d", element);
1834 #endif
1835
1836   if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1837       laser.num_beamers < MAX_NUM_BEAMERS &&
1838       laser.beamer[BEAMER_NR(element)][1].num)
1839   {
1840     int beamer_angle = get_element_angle(element);
1841     int beamer_nr = BEAMER_NR(element);
1842     int step_size;
1843
1844 #if 0
1845     Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
1846 #endif
1847
1848     laser.num_damages--;
1849
1850     if (IS_FIBRE_OPTIC(element) ||
1851         laser.current_angle == get_opposite_angle(beamer_angle))
1852     {
1853       int pos;
1854
1855       LX = ELX * TILEX + 14;
1856       LY = ELY * TILEY + 14;
1857
1858       AddLaserEdge(LX, LY);
1859       AddDamagedField(ELX, ELY);
1860
1861       laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1862
1863       if (!Hit[ELX][ELY])
1864         Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1865
1866       pos = (ELX == laser.beamer[beamer_nr][0].x &&
1867              ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1868       ELX = laser.beamer[beamer_nr][pos].x;
1869       ELY = laser.beamer[beamer_nr][pos].y;
1870       LX = ELX * TILEX + 14;
1871       LY = ELY * TILEY + 14;
1872
1873       if (IS_BEAMER(element))
1874       {
1875         laser.current_angle = get_element_angle(Tile[ELX][ELY]);
1876         XS = 2 * Step[laser.current_angle].x;
1877         YS = 2 * Step[laser.current_angle].y;
1878       }
1879
1880       laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1881
1882       AddLaserEdge(LX, LY);
1883       AddDamagedField(ELX, ELY);
1884
1885       laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1886
1887       if (!Hit[ELX][ELY])
1888         Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1889
1890       if (laser.current_angle == (laser.current_angle >> 1) << 1)
1891         step_size = 8;
1892       else
1893         step_size = 4;
1894
1895       LX += step_size * XS;
1896       LY += step_size * YS;
1897
1898       laser.num_beamers++;
1899
1900       return FALSE;
1901     }
1902   }
1903
1904   return TRUE;
1905 }
1906
1907 static boolean HitOnlyAnEdge(int hit_mask)
1908 {
1909   // check if the laser hit only the edge of an element and, if so, go on
1910
1911 #if 0
1912   Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
1913         LX, LY, hit_mask);
1914 #endif
1915
1916   if ((hit_mask == HIT_MASK_TOPLEFT ||
1917        hit_mask == HIT_MASK_TOPRIGHT ||
1918        hit_mask == HIT_MASK_BOTTOMLEFT ||
1919        hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1920       laser.current_angle % 4)                  // angle is not 90°
1921   {
1922     int dx, dy;
1923
1924     if (hit_mask == HIT_MASK_TOPLEFT)
1925     {
1926       dx = -1;
1927       dy = -1;
1928     }
1929     else if (hit_mask == HIT_MASK_TOPRIGHT)
1930     {
1931       dx = +1;
1932       dy = -1;
1933     }
1934     else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1935     {
1936       dx = -1;
1937       dy = +1;
1938     }
1939     else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
1940     {
1941       dx = +1;
1942       dy = +1;
1943     }
1944
1945     AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1946
1947     LX += XS;
1948     LY += YS;
1949
1950 #if 0
1951     Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
1952 #endif
1953
1954     return TRUE;
1955   }
1956
1957 #if 0
1958   Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
1959 #endif
1960
1961   return FALSE;
1962 }
1963
1964 static boolean HitPolarizer(int element, int hit_mask)
1965 {
1966   if (HitOnlyAnEdge(hit_mask))
1967     return FALSE;
1968
1969   if (IS_DF_GRID(element))
1970   {
1971     int grid_angle = get_element_angle(element);
1972
1973 #if 0
1974     Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
1975           grid_angle, laser.current_angle);
1976 #endif
1977
1978     AddLaserEdge(LX, LY);
1979     AddDamagedField(ELX, ELY);
1980
1981     if (!Hit[ELX][ELY])
1982       Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1983
1984     if (laser.current_angle == grid_angle ||
1985         laser.current_angle == get_opposite_angle(grid_angle))
1986     {
1987       // skip the whole element before continuing the scan
1988       do
1989       {
1990         LX += XS;
1991         LY += YS;
1992       }
1993       while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1994
1995       if (LX/TILEX > ELX || LY/TILEY > ELY)
1996       {
1997         /* skipping scan positions to the right and down skips one scan
1998            position too much, because this is only the top left scan position
1999            of totally four scan positions (plus one to the right, one to the
2000            bottom and one to the bottom right) */
2001
2002         LX -= XS;
2003         LY -= YS;
2004       }
2005
2006       AddLaserEdge(LX, LY);
2007
2008       LX += XS;
2009       LY += YS;
2010
2011 #if 0
2012       Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
2013             LX, LY,
2014             LX / TILEX, LY / TILEY,
2015             LX % TILEX, LY % TILEY);
2016 #endif
2017
2018       return FALSE;
2019     }
2020     else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
2021     {
2022       return HitReflectingWalls(element, hit_mask);
2023     }
2024     else
2025     {
2026       return HitAbsorbingWalls(element, hit_mask);
2027     }
2028   }
2029   else if (IS_GRID_STEEL(element))
2030   {
2031     // may be required if graphics for steel grid redefined
2032     AddDamagedField(ELX, ELY);
2033
2034     return HitReflectingWalls(element, hit_mask);
2035   }
2036   else  // IS_GRID_WOOD
2037   {
2038     // may be required if graphics for wooden grid redefined
2039     AddDamagedField(ELX, ELY);
2040
2041     return HitAbsorbingWalls(element, hit_mask);
2042   }
2043
2044   return TRUE;
2045 }
2046
2047 static boolean HitBlock(int element, int hit_mask)
2048 {
2049   boolean check = FALSE;
2050
2051   if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
2052       game_mm.num_keys == 0)
2053     check = TRUE;
2054
2055   if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2056   {
2057     int i, x, y;
2058     int ex = ELX * TILEX + 14;
2059     int ey = ELY * TILEY + 14;
2060
2061     check = TRUE;
2062
2063     for (i = 1; i < 32; i++)
2064     {
2065       x = LX + i * XS;
2066       y = LY + i * YS;
2067
2068       if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
2069         check = FALSE;
2070     }
2071   }
2072
2073   if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
2074     return HitAbsorbingWalls(element, hit_mask);
2075
2076   if (check)
2077   {
2078     AddLaserEdge(LX - XS, LY - YS);
2079     AddDamagedField(ELX, ELY);
2080
2081     if (!Box[ELX][ELY])
2082       Box[ELX][ELY] = laser.num_edges;
2083
2084     return HitReflectingWalls(element, hit_mask);
2085   }
2086
2087   if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
2088   {
2089     int xs = XS / 2, ys = YS / 2;
2090
2091     if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 ||
2092         (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2)
2093     {
2094       laser.overloaded = (element == EL_GATE_STONE);
2095
2096       return TRUE;
2097     }
2098
2099     if (ABS(xs) == 1 && ABS(ys) == 1 &&
2100         (hit_mask == HIT_MASK_TOP ||
2101          hit_mask == HIT_MASK_LEFT ||
2102          hit_mask == HIT_MASK_RIGHT ||
2103          hit_mask == HIT_MASK_BOTTOM))
2104       AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2105                                   hit_mask == HIT_MASK_BOTTOM),
2106                       ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2107                                   hit_mask == HIT_MASK_RIGHT));
2108     AddLaserEdge(LX, LY);
2109
2110     Bang_MM(ELX, ELY);
2111
2112     game_mm.num_keys--;
2113
2114     if (element == EL_GATE_STONE && Box[ELX][ELY])
2115     {
2116       DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
2117       /*
2118       BackToFront();
2119       */
2120       ScanLaser();
2121
2122       return TRUE;
2123     }
2124
2125     return FALSE;
2126   }
2127
2128   if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2129   {
2130     int xs = XS / 2, ys = YS / 2;
2131
2132     if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 ||
2133         (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2)
2134     {
2135       laser.overloaded = (element == EL_BLOCK_STONE);
2136
2137       return TRUE;
2138     }
2139
2140     if (ABS(xs) == 1 && ABS(ys) == 1 &&
2141         (hit_mask == HIT_MASK_TOP ||
2142          hit_mask == HIT_MASK_LEFT ||
2143          hit_mask == HIT_MASK_RIGHT ||
2144          hit_mask == HIT_MASK_BOTTOM))
2145       AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2146                                   hit_mask == HIT_MASK_BOTTOM),
2147                       ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2148                                   hit_mask == HIT_MASK_RIGHT));
2149     AddDamagedField(ELX, ELY);
2150
2151     LX = ELX * TILEX + 14;
2152     LY = ELY * TILEY + 14;
2153
2154     AddLaserEdge(LX, LY);
2155
2156     laser.stops_inside_element = TRUE;
2157
2158     return TRUE;
2159   }
2160
2161   return TRUE;
2162 }
2163
2164 static boolean HitLaserSource(int element, int hit_mask)
2165 {
2166   if (HitOnlyAnEdge(hit_mask))
2167     return FALSE;
2168
2169   PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2170
2171   laser.overloaded = TRUE;
2172
2173   return TRUE;
2174 }
2175
2176 static boolean HitLaserDestination(int element, int hit_mask)
2177 {
2178   if (HitOnlyAnEdge(hit_mask))
2179     return FALSE;
2180
2181   if (element != EL_EXIT_OPEN &&
2182       !(IS_RECEIVER(element) &&
2183         game_mm.kettles_still_needed == 0 &&
2184         laser.current_angle == get_opposite_angle(get_element_angle(element))))
2185   {
2186     PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2187
2188     return TRUE;
2189   }
2190
2191   if (IS_RECEIVER(element) ||
2192       (IS_22_5_ANGLE(laser.current_angle) &&
2193        (ELX != (LX + 6 * XS) / TILEX ||
2194         ELY != (LY + 6 * YS) / TILEY ||
2195         LX + 6 * XS < 0 ||
2196         LY + 6 * YS < 0)))
2197   {
2198     LX -= XS;
2199     LY -= YS;
2200   }
2201   else
2202   {
2203     LX = ELX * TILEX + 14;
2204     LY = ELY * TILEY + 14;
2205
2206     laser.stops_inside_element = TRUE;
2207   }
2208
2209   AddLaserEdge(LX, LY);
2210   AddDamagedField(ELX, ELY);
2211
2212   if (game_mm.lights_still_needed == 0)
2213   {
2214     game_mm.level_solved = TRUE;
2215
2216     SetTileCursorActive(FALSE);
2217   }
2218
2219   return TRUE;
2220 }
2221
2222 static boolean HitReflectingWalls(int element, int hit_mask)
2223 {
2224   // check if laser hits side of a wall with an angle that is not 90°
2225   if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2226                                             hit_mask == HIT_MASK_LEFT ||
2227                                             hit_mask == HIT_MASK_RIGHT ||
2228                                             hit_mask == HIT_MASK_BOTTOM))
2229   {
2230     PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2231
2232     LX -= XS;
2233     LY -= YS;
2234
2235     if (!IS_DF_GRID(element))
2236       AddLaserEdge(LX, LY);
2237
2238     // check if laser hits wall with an angle of 45°
2239     if (!IS_22_5_ANGLE(laser.current_angle))
2240     {
2241       if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2242       {
2243         LX += 2 * XS;
2244         laser.current_angle = get_mirrored_angle(laser.current_angle,
2245                                                  ANG_MIRROR_0);
2246       }
2247       else      // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2248       {
2249         LY += 2 * YS;
2250         laser.current_angle = get_mirrored_angle(laser.current_angle,
2251                                                  ANG_MIRROR_90);
2252       }
2253
2254       AddLaserEdge(LX, LY);
2255
2256       XS = 2 * Step[laser.current_angle].x;
2257       YS = 2 * Step[laser.current_angle].y;
2258
2259       return FALSE;
2260     }
2261     else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2262     {
2263       laser.current_angle = get_mirrored_angle(laser.current_angle,
2264                                                ANG_MIRROR_0);
2265       if (ABS(XS) == 4)
2266       {
2267         LX += 2 * XS;
2268         if (!IS_DF_GRID(element))
2269           AddLaserEdge(LX, LY);
2270       }
2271       else
2272       {
2273         LX += XS;
2274         if (!IS_DF_GRID(element))
2275           AddLaserEdge(LX, LY + YS / 2);
2276
2277         LX += XS;
2278         if (!IS_DF_GRID(element))
2279           AddLaserEdge(LX, LY);
2280       }
2281
2282       YS = 2 * Step[laser.current_angle].y;
2283
2284       return FALSE;
2285     }
2286     else        // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2287     {
2288       laser.current_angle = get_mirrored_angle(laser.current_angle,
2289                                                ANG_MIRROR_90);
2290       if (ABS(YS) == 4)
2291       {
2292         LY += 2 * YS;
2293         if (!IS_DF_GRID(element))
2294           AddLaserEdge(LX, LY);
2295       }
2296       else
2297       {
2298         LY += YS;
2299         if (!IS_DF_GRID(element))
2300           AddLaserEdge(LX + XS / 2, LY);
2301
2302         LY += YS;
2303         if (!IS_DF_GRID(element))
2304           AddLaserEdge(LX, LY);
2305       }
2306
2307       XS = 2 * Step[laser.current_angle].x;
2308
2309       return FALSE;
2310     }
2311   }
2312
2313   // reflection at the edge of reflecting DF style wall
2314   if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2315   {
2316     if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2317          hit_mask == HIT_MASK_TOPRIGHT) ||
2318         ((laser.current_angle == 5 || laser.current_angle == 7) &&
2319          hit_mask == HIT_MASK_TOPLEFT) ||
2320         ((laser.current_angle == 9 || laser.current_angle == 11) &&
2321          hit_mask == HIT_MASK_BOTTOMLEFT) ||
2322         ((laser.current_angle == 13 || laser.current_angle == 15) &&
2323          hit_mask == HIT_MASK_BOTTOMRIGHT))
2324     {
2325       int mirror_angle =
2326         (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2327          ANG_MIRROR_135 : ANG_MIRROR_45);
2328
2329       PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2330
2331       AddDamagedField(ELX, ELY);
2332       AddLaserEdge(LX, LY);
2333
2334       laser.current_angle = get_mirrored_angle(laser.current_angle,
2335                                                mirror_angle);
2336       XS = 8 / -XS;
2337       YS = 8 / -YS;
2338
2339       LX += XS;
2340       LY += YS;
2341
2342       AddLaserEdge(LX, LY);
2343
2344       return FALSE;
2345     }
2346   }
2347
2348   // reflection inside an edge of reflecting DF style wall
2349   if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2350   {
2351     if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2352          hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2353         ((laser.current_angle == 5 || laser.current_angle == 7) &&
2354          hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2355         ((laser.current_angle == 9 || laser.current_angle == 11) &&
2356          hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2357         ((laser.current_angle == 13 || laser.current_angle == 15) &&
2358          hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2359     {
2360       int mirror_angle =
2361         (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2362          hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2363          ANG_MIRROR_135 : ANG_MIRROR_45);
2364
2365       PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2366
2367       /*
2368       AddDamagedField(ELX, ELY);
2369       */
2370
2371       AddLaserEdge(LX - XS, LY - YS);
2372       AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2373                    LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2374
2375       laser.current_angle = get_mirrored_angle(laser.current_angle,
2376                                                mirror_angle);
2377       XS = 8 / -XS;
2378       YS = 8 / -YS;
2379
2380       LX += XS;
2381       LY += YS;
2382
2383       AddLaserEdge(LX, LY);
2384
2385       return FALSE;
2386     }
2387   }
2388
2389   // check if laser hits DF style wall with an angle of 90°
2390   if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2391   {
2392     if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2393          (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2394         (IS_VERT_ANGLE(laser.current_angle) &&
2395          (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2396     {
2397       // laser at last step touched nothing or the same side of the wall
2398       if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2399       {
2400         AddDamagedField(ELX, ELY);
2401
2402         LX += 8 * XS;
2403         LY += 8 * YS;
2404
2405         last_LX = LX;
2406         last_LY = LY;
2407         last_hit_mask = hit_mask;
2408
2409         return FALSE;
2410       }
2411     }
2412   }
2413
2414   if (!HitOnlyAnEdge(hit_mask))
2415   {
2416     laser.overloaded = TRUE;
2417
2418     return TRUE;
2419   }
2420
2421   return FALSE;
2422 }
2423
2424 static boolean HitAbsorbingWalls(int element, int hit_mask)
2425 {
2426   if (HitOnlyAnEdge(hit_mask))
2427     return FALSE;
2428
2429   if (ABS(XS) == 4 &&
2430       (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2431   {
2432     AddLaserEdge(LX - XS, LY - YS);
2433
2434     LX = LX + XS / 2;
2435     LY = LY + YS;
2436   }
2437
2438   if (ABS(YS) == 4 &&
2439       (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2440   {
2441     AddLaserEdge(LX - XS, LY - YS);
2442
2443     LX = LX + XS;
2444     LY = LY + YS / 2;
2445   }
2446
2447   if (IS_WALL_WOOD(element) ||
2448       IS_DF_WALL_WOOD(element) ||
2449       IS_GRID_WOOD(element) ||
2450       IS_GRID_WOOD_FIXED(element) ||
2451       IS_GRID_WOOD_AUTO(element) ||
2452       element == EL_FUSE_ON ||
2453       element == EL_BLOCK_WOOD ||
2454       element == EL_GATE_WOOD)
2455   {
2456     PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2457
2458     return TRUE;
2459   }
2460
2461   if (IS_WALL_ICE(element))
2462   {
2463     int lx = LX + XS;
2464     int ly = LY + YS;
2465     int mask;
2466
2467     // check if laser hit adjacent edges of two diagonal tiles
2468     if (ELX != lx / TILEX)
2469       lx = LX - XS;
2470     if (ELY != ly / TILEY)
2471       ly = LY - YS;
2472
2473     mask =     lx / MINI_TILEX - ELX * 2 + 1;    // Quadrant (horizontal)
2474     mask <<= ((ly / MINI_TILEY - ELY * 2) > 0 ? 2 : 0);  // || (vertical)
2475
2476     // check if laser hits wall with an angle of 90°
2477     if (IS_90_ANGLE(laser.current_angle))
2478       mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2479
2480     if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2481     {
2482       int i;
2483
2484       for (i = 0; i < 4; i++)
2485       {
2486         if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2487           mask = 15 - (8 >> i);
2488         else if (ABS(XS) == 4 &&
2489                  mask == (1 << i) &&
2490                  (XS > 0) == (i % 2) &&
2491                  (YS < 0) == (i / 2))
2492           mask = 3 + (i / 2) * 9;
2493         else if (ABS(YS) == 4 &&
2494                  mask == (1 << i) &&
2495                  (XS < 0) == (i % 2) &&
2496                  (YS > 0) == (i / 2))
2497           mask = 5 + (i % 2) * 5;
2498       }
2499     }
2500
2501     laser.wall_mask = mask;
2502   }
2503   else if (IS_WALL_AMOEBA(element))
2504   {
2505     int elx = (LX - 2 * XS) / TILEX;
2506     int ely = (LY - 2 * YS) / TILEY;
2507     int element2 = Tile[elx][ely];
2508     int mask;
2509
2510     if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2511     {
2512       laser.dest_element = EL_EMPTY;
2513
2514       return TRUE;
2515     }
2516
2517     ELX = elx;
2518     ELY = ely;
2519
2520     mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2521     mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2522
2523     if (IS_90_ANGLE(laser.current_angle))
2524       mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2525
2526     laser.dest_element = element2 | EL_WALL_AMOEBA_BASE;
2527
2528     laser.wall_mask = mask;
2529   }
2530
2531   return TRUE;
2532 }
2533
2534 static void OpenExit(int x, int y)
2535 {
2536   int delay = 6;
2537
2538   if (!MovDelay[x][y])          // next animation frame
2539     MovDelay[x][y] = 4 * delay;
2540
2541   if (MovDelay[x][y])           // wait some time before next frame
2542   {
2543     int phase;
2544
2545     MovDelay[x][y]--;
2546     phase = MovDelay[x][y] / delay;
2547
2548     if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2549       DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2550
2551     if (!MovDelay[x][y])
2552     {
2553       Tile[x][y] = EL_EXIT_OPEN;
2554       DrawField_MM(x, y);
2555     }
2556   }
2557 }
2558
2559 static void OpenGrayBall(int x, int y)
2560 {
2561   int delay = 2;
2562
2563   if (!MovDelay[x][y])          // next animation frame
2564   {
2565     if (IS_WALL(Store[x][y]))
2566     {
2567       DrawWalls_MM(x, y, Store[x][y]);
2568
2569       // copy wall tile to spare bitmap for "melting" animation
2570       BlitBitmap(drawto_mm, bitmap_db_field, cSX + x * TILEX, cSY + y * TILEY,
2571                  TILEX, TILEY, x * TILEX, y * TILEY);
2572
2573       DrawElement_MM(x, y, EL_GRAY_BALL);
2574     }
2575
2576     MovDelay[x][y] = 50 * delay;
2577   }
2578
2579   if (MovDelay[x][y])           // wait some time before next frame
2580   {
2581     MovDelay[x][y]--;
2582
2583     if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2584     {
2585       Bitmap *bitmap;
2586       int gx, gy;
2587       int dx = RND(26), dy = RND(26);
2588
2589       if (IS_WALL(Store[x][y]))
2590       {
2591         // copy wall tile from spare bitmap for "melting" animation
2592         bitmap = bitmap_db_field;
2593         gx = x * TILEX;
2594         gy = y * TILEY;
2595       }
2596       else
2597       {
2598         int graphic = el2gfx(Store[x][y]);
2599
2600         getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2601       }
2602
2603       BlitBitmap(bitmap, drawto_mm, gx + dx, gy + dy, 6, 6,
2604                  cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2605
2606       laser.redraw = TRUE;
2607
2608       MarkTileDirty(x, y);
2609     }
2610
2611     if (!MovDelay[x][y])
2612     {
2613       Tile[x][y] = Store[x][y];
2614       Store[x][y] = Store2[x][y] = 0;
2615       MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2616
2617       InitField(x, y, FALSE);
2618       DrawField_MM(x, y);
2619
2620       ScanLaser_FromLastMirror();
2621     }
2622   }
2623 }
2624
2625 static void OpenEnvelope(int x, int y)
2626 {
2627   int num_frames = 8;           // seven frames plus final empty space
2628
2629   if (!MovDelay[x][y])          // next animation frame
2630     MovDelay[x][y] = num_frames;
2631
2632   if (MovDelay[x][y])           // wait some time before next frame
2633   {
2634     int nr = ENVELOPE_OPENING_NR(Tile[x][y]);
2635
2636     MovDelay[x][y]--;
2637
2638     if (MovDelay[x][y] > 0 && IN_SCR_FIELD(x, y))
2639     {
2640       int graphic = el_act2gfx(EL_ENVELOPE_1 + nr, MM_ACTION_COLLECTING);
2641       int frame = num_frames - MovDelay[x][y] - 1;
2642
2643       DrawGraphicAnimation_MM(x, y, graphic, frame);
2644
2645       laser.redraw = TRUE;
2646     }
2647
2648     if (MovDelay[x][y] == 0)
2649     {
2650       Tile[x][y] = EL_EMPTY;
2651
2652       DrawField_MM(x, y);
2653
2654       ScanLaser();
2655
2656       ShowEnvelope(nr);
2657     }
2658   }
2659 }
2660
2661 static void MeltIce(int x, int y)
2662 {
2663   int frames = 5;
2664   int delay = 5;
2665
2666   if (!MovDelay[x][y])          // next animation frame
2667     MovDelay[x][y] = frames * delay;
2668
2669   if (MovDelay[x][y])           // wait some time before next frame
2670   {
2671     int phase;
2672     int wall_mask = Store2[x][y];
2673     int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_ICE_BASE;
2674
2675     MovDelay[x][y]--;
2676     phase = frames - MovDelay[x][y] / delay - 1;
2677
2678     if (!MovDelay[x][y])
2679     {
2680       Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2681       Store[x][y] = Store2[x][y] = 0;
2682
2683       DrawWalls_MM(x, y, Tile[x][y]);
2684
2685       if (Tile[x][y] == EL_WALL_ICE_BASE)
2686         Tile[x][y] = EL_EMPTY;
2687
2688       ScanLaser_FromLastMirror();
2689     }
2690     else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2691     {
2692       DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2693
2694       laser.redraw = TRUE;
2695     }
2696   }
2697 }
2698
2699 static void GrowAmoeba(int x, int y)
2700 {
2701   int frames = 5;
2702   int delay = 1;
2703
2704   if (!MovDelay[x][y])          // next animation frame
2705     MovDelay[x][y] = frames * delay;
2706
2707   if (MovDelay[x][y])           // wait some time before next frame
2708   {
2709     int phase;
2710     int wall_mask = Store2[x][y];
2711     int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_AMOEBA_BASE;
2712
2713     MovDelay[x][y]--;
2714     phase = MovDelay[x][y] / delay;
2715
2716     if (!MovDelay[x][y])
2717     {
2718       Tile[x][y] = real_element;
2719       Store[x][y] = Store2[x][y] = 0;
2720
2721       DrawWalls_MM(x, y, Tile[x][y]);
2722       DrawLaser(0, DL_LASER_ENABLED);
2723     }
2724     else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2725     {
2726       DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2727     }
2728   }
2729 }
2730
2731 static void DrawFieldAnimated_MM(int x, int y)
2732 {
2733   DrawField_MM(x, y);
2734
2735   laser.redraw = TRUE;
2736 }
2737
2738 static void DrawFieldAnimatedIfNeeded_MM(int x, int y)
2739 {
2740   int element = Tile[x][y];
2741   int graphic = el2gfx(element);
2742
2743   if (!getGraphicInfo_NewFrame(x, y, graphic))
2744     return;
2745
2746   DrawField_MM(x, y);
2747
2748   laser.redraw = TRUE;
2749 }
2750
2751 static void DrawFieldTwinkle(int x, int y)
2752 {
2753   if (MovDelay[x][y] != 0)      // wait some time before next frame
2754   {
2755     MovDelay[x][y]--;
2756
2757     DrawField_MM(x, y);
2758
2759     if (MovDelay[x][y] != 0)
2760     {
2761       int graphic = IMG_TWINKLE_WHITE;
2762       int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
2763
2764       DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
2765     }
2766
2767     laser.redraw = TRUE;
2768   }
2769 }
2770
2771 static void Explode_MM(int x, int y, int phase, int mode)
2772 {
2773   int num_phase = 9, delay = 2;
2774   int last_phase = num_phase * delay;
2775   int half_phase = (num_phase / 2) * delay;
2776   int center_element;
2777
2778   laser.redraw = TRUE;
2779
2780   if (phase == EX_PHASE_START)          // initialize 'Store[][]' field
2781   {
2782     center_element = Tile[x][y];
2783
2784     if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2785     {
2786       // put moving element to center field (and let it explode there)
2787       center_element = MovingOrBlocked2Element_MM(x, y);
2788       RemoveMovingField_MM(x, y);
2789
2790       Tile[x][y] = center_element;
2791     }
2792
2793     if (center_element != EL_GRAY_BALL_ACTIVE)
2794       Store[x][y] = EL_EMPTY;
2795     Store2[x][y] = center_element;
2796
2797     Tile[x][y] = EL_EXPLODING_OPAQUE;
2798
2799     GfxElement[x][y] = (center_element == EL_BOMB_ACTIVE ? EL_BOMB :
2800                         center_element == EL_GRAY_BALL_ACTIVE ? EL_GRAY_BALL :
2801                         IS_MCDUFFIN(center_element) ? EL_MCDUFFIN :
2802                         center_element);
2803
2804     MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2805
2806     ExplodePhase[x][y] = 1;
2807
2808     return;
2809   }
2810
2811   if (phase == 1)
2812     GfxFrame[x][y] = 0;         // restart explosion animation
2813
2814   ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
2815
2816   center_element = Store2[x][y];
2817
2818   if (phase == half_phase && Store[x][y] == EL_EMPTY)
2819   {
2820     Tile[x][y] = EL_EXPLODING_TRANSP;
2821
2822     if (x == ELX && y == ELY)
2823       ScanLaser();
2824   }
2825
2826   if (phase == last_phase)
2827   {
2828     if (center_element == EL_BOMB_ACTIVE)
2829     {
2830       DrawLaser(0, DL_LASER_DISABLED);
2831       InitLaser();
2832
2833       Bang_MM(laser.start_edge.x, laser.start_edge.y);
2834
2835       laser.overloaded = FALSE;
2836     }
2837     else if (IS_MCDUFFIN(center_element) || IS_LASER(center_element))
2838     {
2839       GameOver_MM(GAME_OVER_BOMB);
2840     }
2841
2842     Tile[x][y] = Store[x][y];
2843
2844     Store[x][y] = Store2[x][y] = 0;
2845     MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2846
2847     InitField(x, y, FALSE);
2848     DrawField_MM(x, y);
2849
2850     if (center_element == EL_GRAY_BALL_ACTIVE)
2851       ScanLaser_FromLastMirror();
2852   }
2853   else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2854   {
2855     int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
2856     int frame = getGraphicAnimationFrameXY(graphic, x, y);
2857
2858     DrawGraphicAnimation_MM(x, y, graphic, frame);
2859
2860     MarkTileDirty(x, y);
2861   }
2862 }
2863
2864 static void Bang_MM(int x, int y)
2865 {
2866   int element = Tile[x][y];
2867
2868   if (IS_PACMAN(element))
2869     PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2870   else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
2871     PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2872   else if (element == EL_KEY)
2873     PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2874   else
2875     PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2876
2877   Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
2878 }
2879
2880 static void TurnRound(int x, int y)
2881 {
2882   static struct
2883   {
2884     int x, y;
2885   } move_xy[] =
2886   {
2887     { 0, 0 },
2888     {-1, 0 },
2889     {+1, 0 },
2890     { 0, 0 },
2891     { 0, -1 },
2892     { 0, 0 }, { 0, 0 }, { 0, 0 },
2893     { 0, +1 }
2894   };
2895   static struct
2896   {
2897     int left, right, back;
2898   } turn[] =
2899   {
2900     { 0,        0,              0 },
2901     { MV_DOWN,  MV_UP,          MV_RIGHT },
2902     { MV_UP,    MV_DOWN,        MV_LEFT },
2903     { 0,        0,              0 },
2904     { MV_LEFT,  MV_RIGHT,       MV_DOWN },
2905     { 0,0,0 },  { 0,0,0 },      { 0,0,0 },
2906     { MV_RIGHT, MV_LEFT,        MV_UP }
2907   };
2908
2909   int element = Tile[x][y];
2910   int old_move_dir = MovDir[x][y];
2911   int right_dir = turn[old_move_dir].right;
2912   int back_dir = turn[old_move_dir].back;
2913   int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2914   int right_x = x + right_dx, right_y = y + right_dy;
2915
2916   if (element == EL_PACMAN)
2917   {
2918     boolean can_turn_right = FALSE;
2919
2920     if (IN_LEV_FIELD(right_x, right_y) &&
2921         IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2922       can_turn_right = TRUE;
2923
2924     if (can_turn_right)
2925       MovDir[x][y] = right_dir;
2926     else
2927       MovDir[x][y] = back_dir;
2928
2929     MovDelay[x][y] = 0;
2930   }
2931 }
2932
2933 static void StartMoving_MM(int x, int y)
2934 {
2935   int element = Tile[x][y];
2936
2937   if (Stop[x][y])
2938     return;
2939
2940   if (CAN_MOVE(element))
2941   {
2942     int newx, newy;
2943
2944     if (MovDelay[x][y])         // wait some time before next movement
2945     {
2946       MovDelay[x][y]--;
2947
2948       if (MovDelay[x][y])
2949         return;
2950     }
2951
2952     // now make next step
2953
2954     Moving2Blocked_MM(x, y, &newx, &newy);      // get next screen position
2955
2956     if (element == EL_PACMAN &&
2957         IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2958         !ObjHit(newx, newy, HIT_POS_CENTER))
2959     {
2960       Store[newx][newy] = Tile[newx][newy];
2961       Tile[newx][newy] = EL_EMPTY;
2962
2963       DrawField_MM(newx, newy);
2964     }
2965     else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2966              ObjHit(newx, newy, HIT_POS_CENTER))
2967     {
2968       // object was running against a wall
2969
2970       TurnRound(x, y);
2971
2972       return;
2973     }
2974
2975     InitMovingField_MM(x, y, MovDir[x][y]);
2976   }
2977
2978   if (MovDir[x][y])
2979     ContinueMoving_MM(x, y);
2980 }
2981
2982 static void ContinueMoving_MM(int x, int y)
2983 {
2984   int element = Tile[x][y];
2985   int direction = MovDir[x][y];
2986   int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2987   int dy = (direction == MV_UP   ? -1 : direction == MV_DOWN  ? +1 : 0);
2988   int horiz_move = (dx!=0);
2989   int newx = x + dx, newy = y + dy;
2990   int step = (horiz_move ? dx : dy) * TILEX / 8;
2991
2992   MovPos[x][y] += step;
2993
2994   if (ABS(MovPos[x][y]) >= TILEX)       // object reached its destination
2995   {
2996     Tile[x][y] = EL_EMPTY;
2997     Tile[newx][newy] = element;
2998
2999     MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3000     MovDelay[newx][newy] = 0;
3001
3002     if (!CAN_MOVE(element))
3003       MovDir[newx][newy] = 0;
3004
3005     DrawField_MM(x, y);
3006     DrawField_MM(newx, newy);
3007
3008     Stop[newx][newy] = TRUE;
3009
3010     if (element == EL_PACMAN)
3011     {
3012       if (Store[newx][newy] == EL_BOMB)
3013         Bang_MM(newx, newy);
3014
3015       if (IS_WALL_AMOEBA(Store[newx][newy]) &&
3016           (LX + 2 * XS) / TILEX == newx &&
3017           (LY + 2 * YS) / TILEY == newy)
3018       {
3019         laser.num_edges--;
3020         ScanLaser();
3021       }
3022     }
3023   }
3024   else                          // still moving on
3025   {
3026     DrawField_MM(x, y);
3027   }
3028
3029   laser.redraw = TRUE;
3030 }
3031
3032 boolean ClickElement(int x, int y, int button)
3033 {
3034   static DelayCounter click_delay = { CLICK_DELAY };
3035   static boolean new_button = TRUE;
3036   boolean element_clicked = FALSE;
3037   int element;
3038
3039   if (button == -1)
3040   {
3041     // initialize static variables
3042     click_delay.count = 0;
3043     click_delay.value = CLICK_DELAY;
3044     new_button = TRUE;
3045
3046     return FALSE;
3047   }
3048
3049   // do not rotate objects hit by the laser after the game was solved
3050   if (game_mm.level_solved && Hit[x][y])
3051     return FALSE;
3052
3053   if (button == MB_RELEASED)
3054   {
3055     new_button = TRUE;
3056     click_delay.value = CLICK_DELAY;
3057
3058     // release eventually hold auto-rotating mirror
3059     RotateMirror(x, y, MB_RELEASED);
3060
3061     return FALSE;
3062   }
3063
3064   if (!FrameReached(&click_delay) && !new_button)
3065     return FALSE;
3066
3067   if (button == MB_MIDDLEBUTTON)        // middle button has no function
3068     return FALSE;
3069
3070   if (!IN_LEV_FIELD(x, y))
3071     return FALSE;
3072
3073   if (Tile[x][y] == EL_EMPTY)
3074     return FALSE;
3075
3076   element = Tile[x][y];
3077
3078   if (IS_MIRROR(element) ||
3079       IS_BEAMER(element) ||
3080       IS_POLAR(element) ||
3081       IS_POLAR_CROSS(element) ||
3082       IS_DF_MIRROR(element) ||
3083       IS_DF_MIRROR_AUTO(element))
3084   {
3085     RotateMirror(x, y, button);
3086
3087     element_clicked = TRUE;
3088   }
3089   else if (IS_MCDUFFIN(element))
3090   {
3091     if (!laser.fuse_off)
3092     {
3093       DrawLaser(0, DL_LASER_DISABLED);
3094
3095       /*
3096       BackToFront();
3097       */
3098     }
3099
3100     element = get_rotated_element(element, BUTTON_ROTATION(button));
3101     laser.start_angle = get_element_angle(element);
3102
3103     InitLaser();
3104
3105     Tile[x][y] = element;
3106     DrawField_MM(x, y);
3107
3108     /*
3109     BackToFront();
3110     */
3111
3112     if (!laser.fuse_off)
3113       ScanLaser();
3114
3115     element_clicked = TRUE;
3116   }
3117   else if (element == EL_FUSE_ON && laser.fuse_off)
3118   {
3119     if (x != laser.fuse_x || y != laser.fuse_y)
3120       return FALSE;
3121
3122     laser.fuse_off = FALSE;
3123     laser.fuse_x = laser.fuse_y = -1;
3124
3125     DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
3126     ScanLaser();
3127
3128     element_clicked = TRUE;
3129   }
3130   else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
3131   {
3132     laser.fuse_off = TRUE;
3133     laser.fuse_x = x;
3134     laser.fuse_y = y;
3135     laser.overloaded = FALSE;
3136
3137     DrawLaser(0, DL_LASER_DISABLED);
3138     DrawGraphic_MM(x, y, IMG_MM_FUSE);
3139
3140     element_clicked = TRUE;
3141   }
3142   else if (element == EL_LIGHTBALL)
3143   {
3144     Bang_MM(x, y);
3145     RaiseScoreElement_MM(element);
3146     DrawLaser(0, DL_LASER_ENABLED);
3147
3148     element_clicked = TRUE;
3149   }
3150   else if (IS_ENVELOPE(element))
3151   {
3152     Tile[x][y] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(element);
3153
3154     element_clicked = TRUE;
3155   }
3156
3157   click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
3158   new_button = FALSE;
3159
3160   return element_clicked;
3161 }
3162
3163 static void RotateMirror(int x, int y, int button)
3164 {
3165   if (button == MB_RELEASED)
3166   {
3167     // release eventually hold auto-rotating mirror
3168     hold_x = -1;
3169     hold_y = -1;
3170
3171     return;
3172   }
3173
3174   if (IS_MIRROR(Tile[x][y]) ||
3175       IS_POLAR_CROSS(Tile[x][y]) ||
3176       IS_POLAR(Tile[x][y]) ||
3177       IS_BEAMER(Tile[x][y]) ||
3178       IS_DF_MIRROR(Tile[x][y]) ||
3179       IS_GRID_STEEL_AUTO(Tile[x][y]) ||
3180       IS_GRID_WOOD_AUTO(Tile[x][y]))
3181   {
3182     Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
3183   }
3184   else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
3185   {
3186     if (button == MB_LEFTBUTTON)
3187     {
3188       // left mouse button only for manual adjustment, no auto-rotating;
3189       // freeze mirror for until mouse button released
3190       hold_x = x;
3191       hold_y = y;
3192     }
3193     else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
3194     {
3195       Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
3196     }
3197   }
3198
3199   if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
3200   {
3201     int edge = Hit[x][y];
3202
3203     DrawField_MM(x, y);
3204
3205     if (edge > 0)
3206     {
3207       DrawLaser(edge - 1, DL_LASER_DISABLED);
3208       ScanLaser();
3209     }
3210   }
3211   else if (ObjHit(x, y, HIT_POS_CENTER))
3212   {
3213     int edge = Hit[x][y];
3214
3215     if (edge == 0)
3216     {
3217       Warn("RotateMirror: inconsistent field Hit[][]!\n");
3218
3219       edge = 1;
3220     }
3221
3222     DrawLaser(edge - 1, DL_LASER_DISABLED);
3223     ScanLaser();
3224   }
3225   else
3226   {
3227     int check = 1;
3228
3229     if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
3230       check = 2;
3231
3232     DrawField_MM(x, y);
3233
3234     if ((IS_BEAMER(Tile[x][y]) ||
3235          IS_POLAR(Tile[x][y]) ||
3236          IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
3237     {
3238       if (IS_BEAMER(Tile[x][y]))
3239       {
3240 #if 0
3241         Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3242               LX, LY, laser.beamer_edge, laser.beamer[1].num);
3243 #endif
3244
3245         if (check == 1)
3246           laser.num_edges--;
3247       }
3248
3249       ScanLaser();
3250
3251       check = 0;
3252     }
3253
3254     if (check == 2)
3255       DrawLaser(0, DL_LASER_ENABLED);
3256   }
3257 }
3258
3259 static void AutoRotateMirrors(void)
3260 {
3261   int x, y;
3262
3263   if (!FrameReached(&rotate_delay))
3264     return;
3265
3266   for (x = 0; x < lev_fieldx; x++)
3267   {
3268     for (y = 0; y < lev_fieldy; y++)
3269     {
3270       int element = Tile[x][y];
3271
3272       // do not rotate objects hit by the laser after the game was solved
3273       if (game_mm.level_solved && Hit[x][y])
3274         continue;
3275
3276       if (IS_DF_MIRROR_AUTO(element) ||
3277           IS_GRID_WOOD_AUTO(element) ||
3278           IS_GRID_STEEL_AUTO(element) ||
3279           element == EL_REFRACTOR)
3280         RotateMirror(x, y, MB_RIGHTBUTTON);
3281     }
3282   }
3283 }
3284
3285 static boolean ObjHit(int obx, int oby, int bits)
3286 {
3287   int i;
3288
3289   obx *= TILEX;
3290   oby *= TILEY;
3291
3292   if (bits & HIT_POS_CENTER)
3293   {
3294     if (CheckLaserPixel(cSX + obx + 15,
3295                         cSY + oby + 15))
3296       return TRUE;
3297   }
3298
3299   if (bits & HIT_POS_EDGE)
3300   {
3301     for (i = 0; i < 4; i++)
3302       if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3303                           cSY + oby + 31 * (i / 2)))
3304         return TRUE;
3305   }
3306
3307   if (bits & HIT_POS_BETWEEN)
3308   {
3309     for (i = 0; i < 4; i++)
3310       if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3311                           cSY + 4 + oby + 22 * (i / 2)))
3312         return TRUE;
3313   }
3314
3315   return FALSE;
3316 }
3317
3318 static void DeletePacMan(int px, int py)
3319 {
3320   int i, j;
3321
3322   Bang_MM(px, py);
3323
3324   if (game_mm.num_pacman <= 1)
3325   {
3326     game_mm.num_pacman = 0;
3327     return;
3328   }
3329
3330   for (i = 0; i < game_mm.num_pacman; i++)
3331     if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3332       break;
3333
3334   game_mm.num_pacman--;
3335
3336   for (j = i; j < game_mm.num_pacman; j++)
3337   {
3338     game_mm.pacman[j].x   = game_mm.pacman[j + 1].x;
3339     game_mm.pacman[j].y   = game_mm.pacman[j + 1].y;
3340     game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3341   }
3342 }
3343
3344 static void GameActions_MM_Ext(void)
3345 {
3346   int element;
3347   int x, y, i;
3348
3349   int r, d;
3350
3351   for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3352     Stop[x][y] = FALSE;
3353
3354   for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3355   {
3356     element = Tile[x][y];
3357
3358     if (!IS_MOVING(x, y) && CAN_MOVE(element))
3359       StartMoving_MM(x, y);
3360     else if (IS_MOVING(x, y))
3361       ContinueMoving_MM(x, y);
3362     else if (IS_EXPLODING(element))
3363       Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3364     else if (element == EL_EXIT_OPENING)
3365       OpenExit(x, y);
3366     else if (element == EL_GRAY_BALL_OPENING)
3367       OpenGrayBall(x, y);
3368     else if (IS_ENVELOPE_OPENING(element))
3369       OpenEnvelope(x, y);
3370     else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE_BASE)
3371       MeltIce(x, y);
3372     else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA_BASE)
3373       GrowAmoeba(x, y);
3374     else if (IS_MIRROR(element) ||
3375              IS_MIRROR_FIXED(element) ||
3376              element == EL_PRISM)
3377       DrawFieldTwinkle(x, y);
3378     else if (element == EL_GRAY_BALL_ACTIVE ||
3379              element == EL_BOMB_ACTIVE ||
3380              element == EL_MINE_ACTIVE)
3381       DrawFieldAnimated_MM(x, y);
3382     else if (!IS_BLOCKED(x, y))
3383       DrawFieldAnimatedIfNeeded_MM(x, y);
3384   }
3385
3386   AutoRotateMirrors();
3387
3388 #if 1
3389   // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3390
3391   // redraw after Explode_MM() ...
3392   if (laser.redraw)
3393     DrawLaser(0, DL_LASER_ENABLED);
3394   laser.redraw = FALSE;
3395 #endif
3396
3397   CT = FrameCounter;
3398
3399   if (game_mm.num_pacman && FrameReached(&pacman_delay))
3400   {
3401     MovePacMen();
3402
3403     if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3404     {
3405       DrawLaser(0, DL_LASER_DISABLED);
3406       ScanLaser();
3407     }
3408   }
3409
3410   // skip all following game actions if game is over
3411   if (game_mm.game_over)
3412     return;
3413
3414   if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3415   {
3416     FadeOutLaser();
3417
3418     GameOver_MM(GAME_OVER_NO_ENERGY);
3419
3420     return;
3421   }
3422
3423   if (FrameReached(&energy_delay))
3424   {
3425     if (game_mm.energy_left > 0)
3426       game_mm.energy_left--;
3427
3428     // when out of energy, wait another frame to play "out of time" sound
3429   }
3430
3431   element = laser.dest_element;
3432
3433 #if 0
3434   if (element != Tile[ELX][ELY])
3435   {
3436     Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3437           element, Tile[ELX][ELY]);
3438   }
3439 #endif
3440
3441   if (!laser.overloaded && laser.overload_value == 0 &&
3442       element != EL_BOMB &&
3443       element != EL_BOMB_ACTIVE &&
3444       element != EL_MINE &&
3445       element != EL_MINE_ACTIVE &&
3446       element != EL_GRAY_BALL &&
3447       element != EL_GRAY_BALL_ACTIVE &&
3448       element != EL_BLOCK_STONE &&
3449       element != EL_BLOCK_WOOD &&
3450       element != EL_FUSE_ON &&
3451       element != EL_FUEL_FULL &&
3452       !IS_WALL_ICE(element) &&
3453       !IS_WALL_AMOEBA(element))
3454     return;
3455
3456   overload_delay.value = HEALTH_DELAY(laser.overloaded);
3457
3458   if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3459        (!laser.overloaded && laser.overload_value > 0)) &&
3460       FrameReached(&overload_delay))
3461   {
3462     if (laser.overloaded)
3463       laser.overload_value++;
3464     else
3465       laser.overload_value--;
3466
3467     if (game_mm.cheat_no_overload)
3468     {
3469       laser.overloaded = FALSE;
3470       laser.overload_value = 0;
3471     }
3472
3473     game_mm.laser_overload_value = laser.overload_value;
3474
3475     if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3476     {
3477       SetLaserColor(0xFF);
3478
3479       DrawLaser(0, DL_LASER_ENABLED);
3480     }
3481
3482     if (!laser.overloaded)
3483       StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3484     else if (setup.sound_loops)
3485       PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3486     else
3487       PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3488
3489     if (laser.overload_value == MAX_LASER_OVERLOAD)
3490     {
3491       UpdateAndDisplayGameControlValues();
3492
3493       FadeOutLaser();
3494
3495       GameOver_MM(GAME_OVER_OVERLOADED);
3496
3497       return;
3498     }
3499   }
3500
3501   if (laser.fuse_off)
3502     return;
3503
3504   CT -= Ct;
3505
3506   if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3507   {
3508     if (game_mm.cheat_no_explosion)
3509       return;
3510
3511     Bang_MM(ELX, ELY);
3512
3513     laser.dest_element = EL_EXPLODING_OPAQUE;
3514
3515     return;
3516   }
3517
3518   if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3519   {
3520     laser.fuse_off = TRUE;
3521     laser.fuse_x = ELX;
3522     laser.fuse_y = ELY;
3523
3524     DrawLaser(0, DL_LASER_DISABLED);
3525     DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3526   }
3527
3528   if (element == EL_GRAY_BALL && CT > native_mm_level.time_ball)
3529   {
3530     if (!Store2[ELX][ELY])      // check if content element not yet determined
3531     {
3532       int last_anim_random_frame = gfx.anim_random_frame;
3533       int element_pos;
3534
3535       if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3536         gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3537
3538       element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3539                                       native_mm_level.ball_choice_mode, 0,
3540                                       game_mm.ball_choice_pos);
3541
3542       if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3543         gfx.anim_random_frame = last_anim_random_frame;
3544
3545       game_mm.ball_choice_pos++;
3546
3547       int new_element = native_mm_level.ball_content[element_pos];
3548       int new_element_base = map_wall_to_base_element(new_element);
3549
3550       if (IS_WALL(new_element_base))
3551       {
3552         // always use completely filled wall element
3553         new_element = new_element_base | 0x000f;
3554       }
3555       else if (native_mm_level.rotate_ball_content &&
3556                get_num_elements(new_element) > 1)
3557       {
3558         // randomly rotate newly created game element
3559         new_element = get_rotated_element(new_element, RND(16));
3560       }
3561
3562       Store[ELX][ELY] = new_element;
3563       Store2[ELX][ELY] = TRUE;
3564     }
3565
3566     if (native_mm_level.explode_ball)
3567       Bang_MM(ELX, ELY);
3568     else
3569       Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3570
3571     laser.dest_element = laser.dest_element_last = Tile[ELX][ELY];
3572
3573     return;
3574   }
3575
3576   if (IS_WALL_ICE(element) && CT > 50)
3577   {
3578     PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3579
3580     Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE_BASE + EL_WALL_CHANGING_BASE;
3581     Store[ELX][ELY] = EL_WALL_ICE_BASE;
3582     Store2[ELX][ELY] = laser.wall_mask;
3583
3584     laser.dest_element = Tile[ELX][ELY];
3585
3586     return;
3587   }
3588
3589   if (IS_WALL_AMOEBA(element) && CT > 60)
3590   {
3591     int k1, k2, k3;
3592     int element2 = Tile[ELX][ELY];
3593
3594     if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3595       return;
3596
3597     for (i = laser.num_damages - 1; i >= 0; i--)
3598       if (laser.damage[i].is_mirror)
3599         break;
3600
3601     r = laser.num_edges;
3602     d = laser.num_damages;
3603     k1 = i;
3604
3605     if (k1 > 0)
3606     {
3607       int x, y;
3608
3609       DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3610
3611       laser.num_edges++;
3612       DrawLaser(0, DL_LASER_ENABLED);
3613       laser.num_edges--;
3614
3615       x = laser.damage[k1].x;
3616       y = laser.damage[k1].y;
3617
3618       DrawField_MM(x, y);
3619     }
3620
3621     for (i = 0; i < 4; i++)
3622     {
3623       if (laser.wall_mask & (1 << i))
3624       {
3625         if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3626                             cSY + ELY * TILEY + 31 * (i / 2)))
3627           break;
3628
3629         if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3630                             cSY + ELY * TILEY + 14 + (i / 2) * 2))
3631           break;
3632       }
3633     }
3634
3635     k2 = i;
3636
3637     for (i = 0; i < 4; i++)
3638     {
3639       if (laser.wall_mask & (1 << i))
3640       {
3641         if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3642                             cSY + ELY * TILEY + 31 * (i / 2)))
3643           break;
3644       }
3645     }
3646
3647     k3 = i;
3648
3649     if (laser.num_beamers > 0 ||
3650         k1 < 1 || k2 < 4 || k3 < 4 ||
3651         CheckLaserPixel(cSX + ELX * TILEX + 14,
3652                         cSY + ELY * TILEY + 14))
3653     {
3654       laser.num_edges = r;
3655       laser.num_damages = d;
3656
3657       DrawLaser(0, DL_LASER_DISABLED);
3658     }
3659
3660     Tile[ELX][ELY] = element | laser.wall_mask;
3661
3662     int x = ELX, y = ELY;
3663     int wall_mask = laser.wall_mask;
3664
3665     ScanLaser();
3666     DrawLaser(0, DL_LASER_ENABLED);
3667
3668     PlayLevelSound_MM(x, y, element, MM_ACTION_GROWING);
3669
3670     Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA_BASE + EL_WALL_CHANGING_BASE;
3671     Store[x][y] = EL_WALL_AMOEBA_BASE;
3672     Store2[x][y] = wall_mask;
3673
3674     return;
3675   }
3676
3677   if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3678       laser.stops_inside_element && CT > native_mm_level.time_block)
3679   {
3680     int x, y;
3681     int k;
3682
3683     if (ABS(XS) > ABS(YS))
3684       k = 0;
3685     else
3686       k = 1;
3687     if (XS < YS)
3688       k += 2;
3689
3690     for (i = 0; i < 4; i++)
3691     {
3692       if (i)
3693         k++;
3694       if (k > 3)
3695         k = 0;
3696
3697       x = ELX + Step[k * 4].x;
3698       y = ELY + Step[k * 4].y;
3699
3700       if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3701         continue;
3702
3703       if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3704         continue;
3705
3706       break;
3707     }
3708
3709     if (i > 3)
3710     {
3711       laser.overloaded = (element == EL_BLOCK_STONE);
3712
3713       return;
3714     }
3715
3716     PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3717
3718     Tile[ELX][ELY] = 0;
3719     Tile[x][y] = element;
3720
3721     DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3722     DrawField_MM(x, y);
3723
3724     if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3725     {
3726       DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3727       DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3728     }
3729
3730     ScanLaser();
3731
3732     return;
3733   }
3734
3735   if (element == EL_FUEL_FULL && CT > 10)
3736   {
3737     int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
3738     int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
3739
3740     for (i = start; i <= num_init_game_frames; i++)
3741     {
3742       if (i == num_init_game_frames)
3743         StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3744       else if (setup.sound_loops)
3745         PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3746       else
3747         PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3748
3749       game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
3750
3751       UpdateAndDisplayGameControlValues();
3752
3753       BackToFront_MM();
3754     }
3755
3756     Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
3757
3758     DrawField_MM(ELX, ELY);
3759
3760     DrawLaser(0, DL_LASER_ENABLED);
3761
3762     return;
3763   }
3764 }
3765
3766 void GameActions_MM(struct MouseActionInfo action)
3767 {
3768   boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3769   boolean button_released = (action.button == MB_RELEASED);
3770
3771   GameActions_MM_Ext();
3772
3773   CheckSingleStepMode_MM(element_clicked, button_released);
3774 }
3775
3776 static void MovePacMen(void)
3777 {
3778   int mx, my, ox, oy, nx, ny;
3779   int element;
3780   int l;
3781
3782   if (++pacman_nr >= game_mm.num_pacman)
3783     pacman_nr = 0;
3784
3785   game_mm.pacman[pacman_nr].dir--;
3786
3787   for (l = 1; l < 5; l++)
3788   {
3789     game_mm.pacman[pacman_nr].dir++;
3790
3791     if (game_mm.pacman[pacman_nr].dir > 4)
3792       game_mm.pacman[pacman_nr].dir = 1;
3793
3794     if (game_mm.pacman[pacman_nr].dir % 2)
3795     {
3796       mx = 0;
3797       my = game_mm.pacman[pacman_nr].dir - 2;
3798     }
3799     else
3800     {
3801       my = 0;
3802       mx = 3 - game_mm.pacman[pacman_nr].dir;
3803     }
3804
3805     ox = game_mm.pacman[pacman_nr].x;
3806     oy = game_mm.pacman[pacman_nr].y;
3807     nx = ox + mx;
3808     ny = oy + my;
3809     element = Tile[nx][ny];
3810
3811     if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3812       continue;
3813
3814     if (!IS_EATABLE4PACMAN(element))
3815       continue;
3816
3817     if (ObjHit(nx, ny, HIT_POS_CENTER))
3818       continue;
3819
3820     Tile[ox][oy] = EL_EMPTY;
3821     Tile[nx][ny] =
3822       EL_PACMAN_RIGHT - 1 +
3823       (game_mm.pacman[pacman_nr].dir - 1 +
3824        (game_mm.pacman[pacman_nr].dir % 2) * 2);
3825
3826     game_mm.pacman[pacman_nr].x = nx;
3827     game_mm.pacman[pacman_nr].y = ny;
3828
3829     DrawGraphic_MM(ox, oy, IMG_EMPTY);
3830
3831     if (element != EL_EMPTY)
3832     {
3833       int graphic = el2gfx(Tile[nx][ny]);
3834       Bitmap *bitmap;
3835       int src_x, src_y;
3836       int i;
3837
3838       getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3839
3840       CT = FrameCounter;
3841       ox = cSX + ox * TILEX;
3842       oy = cSY + oy * TILEY;
3843
3844       for (i = 1; i < 33; i += 2)
3845         BlitBitmap(bitmap, window,
3846                    src_x, src_y, TILEX, TILEY,
3847                    ox + i * mx, oy + i * my);
3848       Ct = Ct + FrameCounter - CT;
3849     }
3850
3851     DrawField_MM(nx, ny);
3852     BackToFront_MM();
3853
3854     if (!laser.fuse_off)
3855     {
3856       DrawLaser(0, DL_LASER_ENABLED);
3857
3858       if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3859       {
3860         AddDamagedField(nx, ny);
3861
3862         laser.damage[laser.num_damages - 1].edge = 0;
3863       }
3864     }
3865
3866     if (element == EL_BOMB)
3867       DeletePacMan(nx, ny);
3868
3869     if (IS_WALL_AMOEBA(element) &&
3870         (LX + 2 * XS) / TILEX == nx &&
3871         (LY + 2 * YS) / TILEY == ny)
3872     {
3873       laser.num_edges--;
3874       ScanLaser();
3875     }
3876
3877     break;
3878   }
3879 }
3880
3881 static void InitMovingField_MM(int x, int y, int direction)
3882 {
3883   int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3884   int newy = y + (direction == MV_UP   ? -1 : direction == MV_DOWN  ? +1 : 0);
3885
3886   MovDir[x][y] = direction;
3887   MovDir[newx][newy] = direction;
3888
3889   if (Tile[newx][newy] == EL_EMPTY)
3890     Tile[newx][newy] = EL_BLOCKED;
3891 }
3892
3893 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3894 {
3895   int direction = MovDir[x][y];
3896   int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3897   int newy = y + (direction == MV_UP   ? -1 : direction == MV_DOWN  ? +1 : 0);
3898
3899   *goes_to_x = newx;
3900   *goes_to_y = newy;
3901 }
3902
3903 static void Blocked2Moving_MM(int x, int y,
3904                               int *comes_from_x, int *comes_from_y)
3905 {
3906   int oldx = x, oldy = y;
3907   int direction = MovDir[x][y];
3908
3909   if (direction == MV_LEFT)
3910     oldx++;
3911   else if (direction == MV_RIGHT)
3912     oldx--;
3913   else if (direction == MV_UP)
3914     oldy++;
3915   else if (direction == MV_DOWN)
3916     oldy--;
3917
3918   *comes_from_x = oldx;
3919   *comes_from_y = oldy;
3920 }
3921
3922 static int MovingOrBlocked2Element_MM(int x, int y)
3923 {
3924   int element = Tile[x][y];
3925
3926   if (element == EL_BLOCKED)
3927   {
3928     int oldx, oldy;
3929
3930     Blocked2Moving_MM(x, y, &oldx, &oldy);
3931
3932     return Tile[oldx][oldy];
3933   }
3934
3935   return element;
3936 }
3937
3938 static void RemoveMovingField_MM(int x, int y)
3939 {
3940   int oldx = x, oldy = y, newx = x, newy = y;
3941
3942   if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3943     return;
3944
3945   if (IS_MOVING(x, y))
3946   {
3947     Moving2Blocked_MM(x, y, &newx, &newy);
3948     if (Tile[newx][newy] != EL_BLOCKED)
3949       return;
3950   }
3951   else if (Tile[x][y] == EL_BLOCKED)
3952   {
3953     Blocked2Moving_MM(x, y, &oldx, &oldy);
3954     if (!IS_MOVING(oldx, oldy))
3955       return;
3956   }
3957
3958   Tile[oldx][oldy] = EL_EMPTY;
3959   Tile[newx][newy] = EL_EMPTY;
3960   MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3961   MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3962
3963   DrawLevelField_MM(oldx, oldy);
3964   DrawLevelField_MM(newx, newy);
3965 }
3966
3967 static void RaiseScore_MM(int value)
3968 {
3969   game_mm.score += value;
3970 }
3971
3972 void RaiseScoreElement_MM(int element)
3973 {
3974   switch (element)
3975   {
3976     case EL_PACMAN:
3977     case EL_PACMAN_RIGHT:
3978     case EL_PACMAN_UP:
3979     case EL_PACMAN_LEFT:
3980     case EL_PACMAN_DOWN:
3981       RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
3982       break;
3983
3984     case EL_KEY:
3985       RaiseScore_MM(native_mm_level.score[SC_KEY]);
3986       break;
3987
3988     case EL_KETTLE:
3989     case EL_CELL:
3990       RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
3991       break;
3992
3993     case EL_LIGHTBALL:
3994       RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
3995       break;
3996
3997     default:
3998       break;
3999   }
4000 }
4001
4002
4003 // ----------------------------------------------------------------------------
4004 // Mirror Magic game engine snapshot handling functions
4005 // ----------------------------------------------------------------------------
4006
4007 void SaveEngineSnapshotValues_MM(void)
4008 {
4009   int x, y;
4010
4011   engine_snapshot_mm.game_mm = game_mm;
4012   engine_snapshot_mm.laser = laser;
4013
4014   for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4015   {
4016     for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4017     {
4018       engine_snapshot_mm.Ur[x][y]    = Ur[x][y];
4019       engine_snapshot_mm.Hit[x][y]   = Hit[x][y];
4020       engine_snapshot_mm.Box[x][y]   = Box[x][y];
4021       engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4022     }
4023   }
4024
4025   engine_snapshot_mm.LX = LX;
4026   engine_snapshot_mm.LY = LY;
4027   engine_snapshot_mm.XS = XS;
4028   engine_snapshot_mm.YS = YS;
4029   engine_snapshot_mm.ELX = ELX;
4030   engine_snapshot_mm.ELY = ELY;
4031   engine_snapshot_mm.CT = CT;
4032   engine_snapshot_mm.Ct = Ct;
4033
4034   engine_snapshot_mm.last_LX = last_LX;
4035   engine_snapshot_mm.last_LY = last_LY;
4036   engine_snapshot_mm.last_hit_mask = last_hit_mask;
4037   engine_snapshot_mm.hold_x = hold_x;
4038   engine_snapshot_mm.hold_y = hold_y;
4039   engine_snapshot_mm.pacman_nr = pacman_nr;
4040
4041   engine_snapshot_mm.rotate_delay = rotate_delay;
4042   engine_snapshot_mm.pacman_delay = pacman_delay;
4043   engine_snapshot_mm.energy_delay = energy_delay;
4044   engine_snapshot_mm.overload_delay = overload_delay;
4045 }
4046
4047 void LoadEngineSnapshotValues_MM(void)
4048 {
4049   int x, y;
4050
4051   // stored engine snapshot buffers already restored at this point
4052
4053   game_mm = engine_snapshot_mm.game_mm;
4054   laser   = engine_snapshot_mm.laser;
4055
4056   for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4057   {
4058     for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4059     {
4060       Ur[x][y]    = engine_snapshot_mm.Ur[x][y];
4061       Hit[x][y]   = engine_snapshot_mm.Hit[x][y];
4062       Box[x][y]   = engine_snapshot_mm.Box[x][y];
4063       Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4064     }
4065   }
4066
4067   LX  = engine_snapshot_mm.LX;
4068   LY  = engine_snapshot_mm.LY;
4069   XS  = engine_snapshot_mm.XS;
4070   YS  = engine_snapshot_mm.YS;
4071   ELX = engine_snapshot_mm.ELX;
4072   ELY = engine_snapshot_mm.ELY;
4073   CT  = engine_snapshot_mm.CT;
4074   Ct  = engine_snapshot_mm.Ct;
4075
4076   last_LX       = engine_snapshot_mm.last_LX;
4077   last_LY       = engine_snapshot_mm.last_LY;
4078   last_hit_mask = engine_snapshot_mm.last_hit_mask;
4079   hold_x        = engine_snapshot_mm.hold_x;
4080   hold_y        = engine_snapshot_mm.hold_y;
4081   pacman_nr     = engine_snapshot_mm.pacman_nr;
4082
4083   rotate_delay   = engine_snapshot_mm.rotate_delay;
4084   pacman_delay   = engine_snapshot_mm.pacman_delay;
4085   energy_delay   = engine_snapshot_mm.energy_delay;
4086   overload_delay = engine_snapshot_mm.overload_delay;
4087
4088   RedrawPlayfield_MM();
4089 }
4090
4091 static int getAngleFromTouchDelta(int dx, int dy,  int base)
4092 {
4093   double pi = 3.141592653;
4094   double rad = atan2((double)-dy, (double)dx);
4095   double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4096   double deg = rad2 * 180.0 / pi;
4097
4098   return (int)(deg * base / 360.0 + 0.5) % base;
4099 }
4100
4101 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4102 {
4103   // calculate start (source) position to be at the middle of the tile
4104   int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4105   int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4106   int dx = dst_mx - src_mx;
4107   int dy = dst_my - src_my;
4108   int element;
4109   int base = 16;
4110   int phases = 16;
4111   int angle_old = -1;
4112   int angle_new = -1;
4113   int button = 0;
4114   int i;
4115
4116   if (!IN_LEV_FIELD(x, y))
4117     return 0;
4118
4119   element = Tile[x][y];
4120
4121   if (!IS_MCDUFFIN(element) &&
4122       !IS_MIRROR(element) &&
4123       !IS_BEAMER(element) &&
4124       !IS_POLAR(element) &&
4125       !IS_POLAR_CROSS(element) &&
4126       !IS_DF_MIRROR(element))
4127     return 0;
4128
4129   angle_old = get_element_angle(element);
4130
4131   if (IS_MCDUFFIN(element))
4132   {
4133     angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4134                  dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4135                  dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4136                  dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4137                  -1);
4138   }
4139   else if (IS_MIRROR(element) ||
4140            IS_DF_MIRROR(element))
4141   {
4142     for (i = 0; i < laser.num_damages; i++)
4143     {
4144       if (laser.damage[i].x == x &&
4145           laser.damage[i].y == y &&
4146           ObjHit(x, y, HIT_POS_CENTER))
4147       {
4148         angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4149         angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4150
4151         break;
4152       }
4153     }
4154   }
4155
4156   if (angle_new == -1)
4157   {
4158     if (IS_MIRROR(element) ||
4159         IS_DF_MIRROR(element) ||
4160         IS_POLAR(element))
4161       base = 32;
4162
4163     if (IS_POLAR_CROSS(element))
4164       phases = 4;
4165
4166     angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4167   }
4168
4169   button = (angle_new == angle_old ? 0 :
4170             (angle_new - angle_old + phases) % phases < (phases / 2) ?
4171             MB_LEFTBUTTON : MB_RIGHTBUTTON);
4172
4173   return button;
4174 }