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