added Deflektor style fixed mirror game elements to MM engine
[rocksndiamonds.git] / src / game_mm / mm_game.c
1 // ============================================================================
2 // Mirror Magic -- McDuffin's Revenge
3 // ----------------------------------------------------------------------------
4 // (c) 1994-2017 by Artsoft Entertainment
5 //                  Holger Schemel
6 //                  info@artsoft.org
7 //                  https://www.artsoft.org/
8 // ----------------------------------------------------------------------------
9 // mm_game.c
10 // ============================================================================
11
12 #include <math.h>
13
14 #include "main_mm.h"
15
16 #include "mm_main.h"
17 #include "mm_game.h"
18 #include "mm_tools.h"
19
20 // graphic position values for game controls
21 #define ENERGY_XSIZE            32
22 #define ENERGY_YSIZE            MAX_LASER_ENERGY
23 #define OVERLOAD_XSIZE          ENERGY_XSIZE
24 #define OVERLOAD_YSIZE          MAX_LASER_OVERLOAD
25
26 // values for Explode_MM()
27 #define EX_PHASE_START          0
28 #define EX_TYPE_NONE            0
29 #define EX_TYPE_NORMAL          (1 << 0)
30
31 // special positions in the game control window (relative to control window)
32 #define XX_LEVEL                36
33 #define YY_LEVEL                23
34 #define XX_KETTLES              29
35 #define YY_KETTLES              63
36 #define XX_SCORE                22
37 #define YY_SCORE                101
38 #define XX_ENERGY               8
39 #define YY_ENERGY               158
40 #define XX_OVERLOAD             60
41 #define YY_OVERLOAD             YY_ENERGY
42
43 // special positions in the game control window (relative to main window)
44 #define DX_LEVEL                (DX + XX_LEVEL)
45 #define DY_LEVEL                (DY + YY_LEVEL)
46 #define DX_KETTLES              (DX + XX_KETTLES)
47 #define DY_KETTLES              (DY + YY_KETTLES)
48 #define DX_SCORE                (DX + XX_SCORE)
49 #define DY_SCORE                (DY + YY_SCORE)
50 #define DX_ENERGY               (DX + XX_ENERGY)
51 #define DY_ENERGY               (DY + YY_ENERGY)
52 #define DX_OVERLOAD             (DX + XX_OVERLOAD)
53 #define DY_OVERLOAD             (DY + YY_OVERLOAD)
54
55 #define IS_LOOP_SOUND(s)        ((s) == SND_FUEL)
56 #define IS_MUSIC_SOUND(s)       ((s) == SND_TYGER || (s) == SND_VOYAGER)
57
58 // game button identifiers
59 #define GAME_CTRL_ID_LEFT       0
60 #define GAME_CTRL_ID_MIDDLE     1
61 #define GAME_CTRL_ID_RIGHT      2
62
63 #define NUM_GAME_BUTTONS        3
64
65 // values for DrawLaser()
66 #define DL_LASER_DISABLED       0
67 #define DL_LASER_ENABLED        1
68
69 // values for 'click_delay_value' in ClickElement()
70 #define CLICK_DELAY_FIRST       12      // delay (frames) after first click
71 #define CLICK_DELAY             6       // delay (frames) for pressed butten
72
73 #define AUTO_ROTATE_DELAY       CLICK_DELAY
74 #define INIT_GAME_ACTIONS_DELAY (ONE_SECOND_DELAY / GAME_FRAME_DELAY)
75 #define NUM_INIT_CYCLE_STEPS    16
76 #define PACMAN_MOVE_DELAY       12
77 #define ENERGY_DELAY            (ONE_SECOND_DELAY / GAME_FRAME_DELAY)
78 #define HEALTH_DEC_DELAY        3
79 #define HEALTH_INC_DELAY        9
80 #define HEALTH_DELAY(x)         ((x) ? HEALTH_DEC_DELAY : HEALTH_INC_DELAY)
81
82 #define BEGIN_NO_HEADLESS                       \
83   {                                             \
84     boolean last_headless = program.headless;   \
85                                                 \
86     program.headless = FALSE;                   \
87
88 #define END_NO_HEADLESS                         \
89     program.headless = last_headless;           \
90   }                                             \
91
92 // forward declaration for internal use
93 static int MovingOrBlocked2Element_MM(int, int);
94 static void Bang_MM(int, int);
95 static void RaiseScore_MM(int);
96 static void RaiseScoreElement_MM(int);
97 static void RemoveMovingField_MM(int, int);
98 static void InitMovingField_MM(int, int, int);
99 static void ContinueMoving_MM(int, int);
100
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       IS_DF_MIRROR_FIXED(element) ||
1696       element == EL_PRISM ||
1697       element == EL_REFRACTOR)
1698   {
1699     int current_angle = laser.current_angle;
1700     int step_size;
1701
1702     laser.num_damages--;
1703
1704     AddDamagedField(ELX, ELY);
1705
1706     laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1707
1708     if (!Hit[ELX][ELY])
1709       Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1710
1711     if (IS_MIRROR(element) ||
1712         IS_MIRROR_FIXED(element) ||
1713         IS_DF_MIRROR(element) ||
1714         IS_DF_MIRROR_AUTO(element) ||
1715         IS_DF_MIRROR_FIXED(element))
1716       laser.current_angle = get_mirrored_angle(laser.current_angle,
1717                                                get_element_angle(element));
1718
1719     if (element == EL_PRISM || element == EL_REFRACTOR)
1720       laser.current_angle = RND(16);
1721
1722     XS = 2 * Step[laser.current_angle].x;
1723     YS = 2 * Step[laser.current_angle].y;
1724
1725     if (!IS_22_5_ANGLE(laser.current_angle))    // 90° or 45° angle
1726       step_size = 8;
1727     else
1728       step_size = 4;
1729
1730     LX += step_size * XS;
1731     LY += step_size * YS;
1732
1733     // draw sparkles on mirror
1734     if ((IS_MIRROR(element) ||
1735          IS_MIRROR_FIXED(element) ||
1736          element == EL_PRISM) &&
1737         current_angle != laser.current_angle)
1738     {
1739       MovDelay[ELX][ELY] = 11;          // start animation
1740     }
1741
1742     if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1743         current_angle != laser.current_angle)
1744       PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1745
1746     laser.overloaded =
1747       (get_opposite_angle(laser.current_angle) ==
1748        laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1749
1750     return (laser.overloaded ? TRUE : FALSE);
1751   }
1752
1753   if (element == EL_FUEL_FULL)
1754   {
1755     laser.stops_inside_element = TRUE;
1756
1757     return TRUE;
1758   }
1759
1760   if (element == EL_BOMB || element == EL_MINE || element == EL_GRAY_BALL)
1761   {
1762     PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1763
1764     Tile[ELX][ELY] = (element == EL_BOMB ? EL_BOMB_ACTIVE :
1765                       element == EL_MINE ? EL_MINE_ACTIVE :
1766                       EL_GRAY_BALL_ACTIVE);
1767
1768     GfxFrame[ELX][ELY] = 0;             // restart animation
1769
1770     laser.dest_element_last = Tile[ELX][ELY];
1771     laser.dest_element_last_x = ELX;
1772     laser.dest_element_last_y = ELY;
1773
1774     if (element == EL_MINE)
1775       laser.overloaded = TRUE;
1776   }
1777
1778   if (element == EL_KETTLE ||
1779       element == EL_CELL ||
1780       element == EL_KEY ||
1781       element == EL_LIGHTBALL ||
1782       element == EL_PACMAN ||
1783       IS_PACMAN(element) ||
1784       IS_ENVELOPE(element))
1785   {
1786     if (!IS_PACMAN(element) &&
1787         !IS_ENVELOPE(element))
1788       Bang_MM(ELX, ELY);
1789
1790     if (element == EL_PACMAN)
1791       Bang_MM(ELX, ELY);
1792
1793     if (element == EL_KETTLE || element == EL_CELL)
1794     {
1795       if (game_mm.kettles_still_needed > 0)
1796         game_mm.kettles_still_needed--;
1797
1798       game.snapshot.collected_item = TRUE;
1799
1800       if (game_mm.kettles_still_needed == 0)
1801       {
1802         CheckExitMM();
1803
1804         DrawLaser(0, DL_LASER_ENABLED);
1805       }
1806     }
1807     else if (element == EL_KEY)
1808     {
1809       game_mm.num_keys++;
1810     }
1811     else if (IS_PACMAN(element))
1812     {
1813       DeletePacMan(ELX, ELY);
1814     }
1815     else if (IS_ENVELOPE(element))
1816     {
1817       Tile[ELX][ELY] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(Tile[ELX][ELY]);
1818     }
1819
1820     RaiseScoreElement_MM(element);
1821
1822     return FALSE;
1823   }
1824
1825   if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1826   {
1827     PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1828
1829     DrawLaser(0, DL_LASER_ENABLED);
1830
1831     if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
1832     {
1833       Tile[ELX][ELY] = EL_LIGHTBULB_ON;
1834       game_mm.lights_still_needed--;
1835     }
1836     else
1837     {
1838       Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
1839       game_mm.lights_still_needed++;
1840     }
1841
1842     DrawField_MM(ELX, ELY);
1843     DrawLaser(0, DL_LASER_ENABLED);
1844
1845     /*
1846     BackToFront();
1847     */
1848     laser.stops_inside_element = TRUE;
1849
1850     return TRUE;
1851   }
1852
1853 #if 0
1854   Debug("game:mm:HitElement", "(4): element == %d", element);
1855 #endif
1856
1857   if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1858       laser.num_beamers < MAX_NUM_BEAMERS &&
1859       laser.beamer[BEAMER_NR(element)][1].num)
1860   {
1861     int beamer_angle = get_element_angle(element);
1862     int beamer_nr = BEAMER_NR(element);
1863     int step_size;
1864
1865 #if 0
1866     Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
1867 #endif
1868
1869     laser.num_damages--;
1870
1871     if (IS_FIBRE_OPTIC(element) ||
1872         laser.current_angle == get_opposite_angle(beamer_angle))
1873     {
1874       int pos;
1875
1876       LX = ELX * TILEX + 14;
1877       LY = ELY * TILEY + 14;
1878
1879       AddLaserEdge(LX, LY);
1880       AddDamagedField(ELX, ELY);
1881
1882       laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1883
1884       if (!Hit[ELX][ELY])
1885         Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1886
1887       pos = (ELX == laser.beamer[beamer_nr][0].x &&
1888              ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1889       ELX = laser.beamer[beamer_nr][pos].x;
1890       ELY = laser.beamer[beamer_nr][pos].y;
1891       LX = ELX * TILEX + 14;
1892       LY = ELY * TILEY + 14;
1893
1894       if (IS_BEAMER(element))
1895       {
1896         laser.current_angle = get_element_angle(Tile[ELX][ELY]);
1897         XS = 2 * Step[laser.current_angle].x;
1898         YS = 2 * Step[laser.current_angle].y;
1899       }
1900
1901       laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1902
1903       AddLaserEdge(LX, LY);
1904       AddDamagedField(ELX, ELY);
1905
1906       laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1907
1908       if (!Hit[ELX][ELY])
1909         Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1910
1911       if (laser.current_angle == (laser.current_angle >> 1) << 1)
1912         step_size = 8;
1913       else
1914         step_size = 4;
1915
1916       LX += step_size * XS;
1917       LY += step_size * YS;
1918
1919       laser.num_beamers++;
1920
1921       return FALSE;
1922     }
1923   }
1924
1925   return TRUE;
1926 }
1927
1928 static boolean HitOnlyAnEdge(int hit_mask)
1929 {
1930   // check if the laser hit only the edge of an element and, if so, go on
1931
1932 #if 0
1933   Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
1934         LX, LY, hit_mask);
1935 #endif
1936
1937   if ((hit_mask == HIT_MASK_TOPLEFT ||
1938        hit_mask == HIT_MASK_TOPRIGHT ||
1939        hit_mask == HIT_MASK_BOTTOMLEFT ||
1940        hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1941       laser.current_angle % 4)                  // angle is not 90°
1942   {
1943     int dx, dy;
1944
1945     if (hit_mask == HIT_MASK_TOPLEFT)
1946     {
1947       dx = -1;
1948       dy = -1;
1949     }
1950     else if (hit_mask == HIT_MASK_TOPRIGHT)
1951     {
1952       dx = +1;
1953       dy = -1;
1954     }
1955     else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1956     {
1957       dx = -1;
1958       dy = +1;
1959     }
1960     else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
1961     {
1962       dx = +1;
1963       dy = +1;
1964     }
1965
1966     AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1967
1968     LX += XS;
1969     LY += YS;
1970
1971 #if 0
1972     Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
1973 #endif
1974
1975     return TRUE;
1976   }
1977
1978 #if 0
1979   Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
1980 #endif
1981
1982   return FALSE;
1983 }
1984
1985 static boolean HitPolarizer(int element, int hit_mask)
1986 {
1987   if (HitOnlyAnEdge(hit_mask))
1988     return FALSE;
1989
1990   if (IS_DF_GRID(element))
1991   {
1992     int grid_angle = get_element_angle(element);
1993
1994 #if 0
1995     Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
1996           grid_angle, laser.current_angle);
1997 #endif
1998
1999     AddLaserEdge(LX, LY);
2000     AddDamagedField(ELX, ELY);
2001
2002     if (!Hit[ELX][ELY])
2003       Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
2004
2005     if (laser.current_angle == grid_angle ||
2006         laser.current_angle == get_opposite_angle(grid_angle))
2007     {
2008       // skip the whole element before continuing the scan
2009       do
2010       {
2011         LX += XS;
2012         LY += YS;
2013       }
2014       while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
2015
2016       if (LX/TILEX > ELX || LY/TILEY > ELY)
2017       {
2018         /* skipping scan positions to the right and down skips one scan
2019            position too much, because this is only the top left scan position
2020            of totally four scan positions (plus one to the right, one to the
2021            bottom and one to the bottom right) */
2022
2023         LX -= XS;
2024         LY -= YS;
2025       }
2026
2027       AddLaserEdge(LX, LY);
2028
2029       LX += XS;
2030       LY += YS;
2031
2032 #if 0
2033       Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
2034             LX, LY,
2035             LX / TILEX, LY / TILEY,
2036             LX % TILEX, LY % TILEY);
2037 #endif
2038
2039       return FALSE;
2040     }
2041     else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
2042     {
2043       return HitReflectingWalls(element, hit_mask);
2044     }
2045     else
2046     {
2047       return HitAbsorbingWalls(element, hit_mask);
2048     }
2049   }
2050   else if (IS_GRID_STEEL(element))
2051   {
2052     // may be required if graphics for steel grid redefined
2053     AddDamagedField(ELX, ELY);
2054
2055     return HitReflectingWalls(element, hit_mask);
2056   }
2057   else  // IS_GRID_WOOD
2058   {
2059     // may be required if graphics for wooden grid redefined
2060     AddDamagedField(ELX, ELY);
2061
2062     return HitAbsorbingWalls(element, hit_mask);
2063   }
2064
2065   return TRUE;
2066 }
2067
2068 static boolean HitBlock(int element, int hit_mask)
2069 {
2070   boolean check = FALSE;
2071
2072   if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
2073       game_mm.num_keys == 0)
2074     check = TRUE;
2075
2076   if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2077   {
2078     int i, x, y;
2079     int ex = ELX * TILEX + 14;
2080     int ey = ELY * TILEY + 14;
2081
2082     check = TRUE;
2083
2084     for (i = 1; i < 32; i++)
2085     {
2086       x = LX + i * XS;
2087       y = LY + i * YS;
2088
2089       if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
2090         check = FALSE;
2091     }
2092   }
2093
2094   if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
2095     return HitAbsorbingWalls(element, hit_mask);
2096
2097   if (check)
2098   {
2099     AddLaserEdge(LX - XS, LY - YS);
2100     AddDamagedField(ELX, ELY);
2101
2102     if (!Box[ELX][ELY])
2103       Box[ELX][ELY] = laser.num_edges;
2104
2105     return HitReflectingWalls(element, hit_mask);
2106   }
2107
2108   if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
2109   {
2110     int xs = XS / 2, ys = YS / 2;
2111
2112     if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 ||
2113         (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2)
2114     {
2115       laser.overloaded = (element == EL_GATE_STONE);
2116
2117       return TRUE;
2118     }
2119
2120     if (ABS(xs) == 1 && ABS(ys) == 1 &&
2121         (hit_mask == HIT_MASK_TOP ||
2122          hit_mask == HIT_MASK_LEFT ||
2123          hit_mask == HIT_MASK_RIGHT ||
2124          hit_mask == HIT_MASK_BOTTOM))
2125       AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2126                                   hit_mask == HIT_MASK_BOTTOM),
2127                       ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2128                                   hit_mask == HIT_MASK_RIGHT));
2129     AddLaserEdge(LX, LY);
2130
2131     Bang_MM(ELX, ELY);
2132
2133     game_mm.num_keys--;
2134
2135     if (element == EL_GATE_STONE && Box[ELX][ELY])
2136     {
2137       DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
2138       /*
2139       BackToFront();
2140       */
2141       ScanLaser();
2142
2143       return TRUE;
2144     }
2145
2146     return FALSE;
2147   }
2148
2149   if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2150   {
2151     int xs = XS / 2, ys = YS / 2;
2152
2153     if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 ||
2154         (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2)
2155     {
2156       laser.overloaded = (element == EL_BLOCK_STONE);
2157
2158       return TRUE;
2159     }
2160
2161     if (ABS(xs) == 1 && ABS(ys) == 1 &&
2162         (hit_mask == HIT_MASK_TOP ||
2163          hit_mask == HIT_MASK_LEFT ||
2164          hit_mask == HIT_MASK_RIGHT ||
2165          hit_mask == HIT_MASK_BOTTOM))
2166       AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2167                                   hit_mask == HIT_MASK_BOTTOM),
2168                       ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2169                                   hit_mask == HIT_MASK_RIGHT));
2170     AddDamagedField(ELX, ELY);
2171
2172     LX = ELX * TILEX + 14;
2173     LY = ELY * TILEY + 14;
2174
2175     AddLaserEdge(LX, LY);
2176
2177     laser.stops_inside_element = TRUE;
2178
2179     return TRUE;
2180   }
2181
2182   return TRUE;
2183 }
2184
2185 static boolean HitLaserSource(int element, int hit_mask)
2186 {
2187   if (HitOnlyAnEdge(hit_mask))
2188     return FALSE;
2189
2190   PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2191
2192   laser.overloaded = TRUE;
2193
2194   return TRUE;
2195 }
2196
2197 static boolean HitLaserDestination(int element, int hit_mask)
2198 {
2199   if (HitOnlyAnEdge(hit_mask))
2200     return FALSE;
2201
2202   if (element != EL_EXIT_OPEN &&
2203       !(IS_RECEIVER(element) &&
2204         game_mm.kettles_still_needed == 0 &&
2205         laser.current_angle == get_opposite_angle(get_element_angle(element))))
2206   {
2207     PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2208
2209     return TRUE;
2210   }
2211
2212   if (IS_RECEIVER(element) ||
2213       (IS_22_5_ANGLE(laser.current_angle) &&
2214        (ELX != (LX + 6 * XS) / TILEX ||
2215         ELY != (LY + 6 * YS) / TILEY ||
2216         LX + 6 * XS < 0 ||
2217         LY + 6 * YS < 0)))
2218   {
2219     LX -= XS;
2220     LY -= YS;
2221   }
2222   else
2223   {
2224     LX = ELX * TILEX + 14;
2225     LY = ELY * TILEY + 14;
2226
2227     laser.stops_inside_element = TRUE;
2228   }
2229
2230   AddLaserEdge(LX, LY);
2231   AddDamagedField(ELX, ELY);
2232
2233   if (game_mm.lights_still_needed == 0)
2234   {
2235     game_mm.level_solved = TRUE;
2236
2237     SetTileCursorActive(FALSE);
2238   }
2239
2240   return TRUE;
2241 }
2242
2243 static boolean HitReflectingWalls(int element, int hit_mask)
2244 {
2245   // check if laser hits side of a wall with an angle that is not 90°
2246   if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2247                                             hit_mask == HIT_MASK_LEFT ||
2248                                             hit_mask == HIT_MASK_RIGHT ||
2249                                             hit_mask == HIT_MASK_BOTTOM))
2250   {
2251     PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2252
2253     LX -= XS;
2254     LY -= YS;
2255
2256     if (!IS_DF_GRID(element))
2257       AddLaserEdge(LX, LY);
2258
2259     // check if laser hits wall with an angle of 45°
2260     if (!IS_22_5_ANGLE(laser.current_angle))
2261     {
2262       if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2263       {
2264         LX += 2 * XS;
2265         laser.current_angle = get_mirrored_angle(laser.current_angle,
2266                                                  ANG_MIRROR_0);
2267       }
2268       else      // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2269       {
2270         LY += 2 * YS;
2271         laser.current_angle = get_mirrored_angle(laser.current_angle,
2272                                                  ANG_MIRROR_90);
2273       }
2274
2275       AddLaserEdge(LX, LY);
2276
2277       XS = 2 * Step[laser.current_angle].x;
2278       YS = 2 * Step[laser.current_angle].y;
2279
2280       return FALSE;
2281     }
2282     else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2283     {
2284       laser.current_angle = get_mirrored_angle(laser.current_angle,
2285                                                ANG_MIRROR_0);
2286       if (ABS(XS) == 4)
2287       {
2288         LX += 2 * XS;
2289         if (!IS_DF_GRID(element))
2290           AddLaserEdge(LX, LY);
2291       }
2292       else
2293       {
2294         LX += XS;
2295         if (!IS_DF_GRID(element))
2296           AddLaserEdge(LX, LY + YS / 2);
2297
2298         LX += XS;
2299         if (!IS_DF_GRID(element))
2300           AddLaserEdge(LX, LY);
2301       }
2302
2303       YS = 2 * Step[laser.current_angle].y;
2304
2305       return FALSE;
2306     }
2307     else        // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2308     {
2309       laser.current_angle = get_mirrored_angle(laser.current_angle,
2310                                                ANG_MIRROR_90);
2311       if (ABS(YS) == 4)
2312       {
2313         LY += 2 * YS;
2314         if (!IS_DF_GRID(element))
2315           AddLaserEdge(LX, LY);
2316       }
2317       else
2318       {
2319         LY += YS;
2320         if (!IS_DF_GRID(element))
2321           AddLaserEdge(LX + XS / 2, LY);
2322
2323         LY += YS;
2324         if (!IS_DF_GRID(element))
2325           AddLaserEdge(LX, LY);
2326       }
2327
2328       XS = 2 * Step[laser.current_angle].x;
2329
2330       return FALSE;
2331     }
2332   }
2333
2334   // reflection at the edge of reflecting DF style wall
2335   if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2336   {
2337     if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2338          hit_mask == HIT_MASK_TOPRIGHT) ||
2339         ((laser.current_angle == 5 || laser.current_angle == 7) &&
2340          hit_mask == HIT_MASK_TOPLEFT) ||
2341         ((laser.current_angle == 9 || laser.current_angle == 11) &&
2342          hit_mask == HIT_MASK_BOTTOMLEFT) ||
2343         ((laser.current_angle == 13 || laser.current_angle == 15) &&
2344          hit_mask == HIT_MASK_BOTTOMRIGHT))
2345     {
2346       int mirror_angle =
2347         (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2348          ANG_MIRROR_135 : ANG_MIRROR_45);
2349
2350       PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2351
2352       AddDamagedField(ELX, ELY);
2353       AddLaserEdge(LX, LY);
2354
2355       laser.current_angle = get_mirrored_angle(laser.current_angle,
2356                                                mirror_angle);
2357       XS = 8 / -XS;
2358       YS = 8 / -YS;
2359
2360       LX += XS;
2361       LY += YS;
2362
2363       AddLaserEdge(LX, LY);
2364
2365       return FALSE;
2366     }
2367   }
2368
2369   // reflection inside an edge of reflecting DF style wall
2370   if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2371   {
2372     if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2373          hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2374         ((laser.current_angle == 5 || laser.current_angle == 7) &&
2375          hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2376         ((laser.current_angle == 9 || laser.current_angle == 11) &&
2377          hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2378         ((laser.current_angle == 13 || laser.current_angle == 15) &&
2379          hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2380     {
2381       int mirror_angle =
2382         (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2383          hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2384          ANG_MIRROR_135 : ANG_MIRROR_45);
2385
2386       PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2387
2388       /*
2389       AddDamagedField(ELX, ELY);
2390       */
2391
2392       AddLaserEdge(LX - XS, LY - YS);
2393       AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2394                    LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2395
2396       laser.current_angle = get_mirrored_angle(laser.current_angle,
2397                                                mirror_angle);
2398       XS = 8 / -XS;
2399       YS = 8 / -YS;
2400
2401       LX += XS;
2402       LY += YS;
2403
2404       AddLaserEdge(LX, LY);
2405
2406       return FALSE;
2407     }
2408   }
2409
2410   // check if laser hits DF style wall with an angle of 90°
2411   if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2412   {
2413     if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2414          (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2415         (IS_VERT_ANGLE(laser.current_angle) &&
2416          (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2417     {
2418       // laser at last step touched nothing or the same side of the wall
2419       if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2420       {
2421         AddDamagedField(ELX, ELY);
2422
2423         LX += 8 * XS;
2424         LY += 8 * YS;
2425
2426         last_LX = LX;
2427         last_LY = LY;
2428         last_hit_mask = hit_mask;
2429
2430         return FALSE;
2431       }
2432     }
2433   }
2434
2435   if (!HitOnlyAnEdge(hit_mask))
2436   {
2437     laser.overloaded = TRUE;
2438
2439     return TRUE;
2440   }
2441
2442   return FALSE;
2443 }
2444
2445 static boolean HitAbsorbingWalls(int element, int hit_mask)
2446 {
2447   if (HitOnlyAnEdge(hit_mask))
2448     return FALSE;
2449
2450   if (ABS(XS) == 4 &&
2451       (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2452   {
2453     AddLaserEdge(LX - XS, LY - YS);
2454
2455     LX = LX + XS / 2;
2456     LY = LY + YS;
2457   }
2458
2459   if (ABS(YS) == 4 &&
2460       (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2461   {
2462     AddLaserEdge(LX - XS, LY - YS);
2463
2464     LX = LX + XS;
2465     LY = LY + YS / 2;
2466   }
2467
2468   if (IS_WALL_WOOD(element) ||
2469       IS_DF_WALL_WOOD(element) ||
2470       IS_GRID_WOOD(element) ||
2471       IS_GRID_WOOD_FIXED(element) ||
2472       IS_GRID_WOOD_AUTO(element) ||
2473       element == EL_FUSE_ON ||
2474       element == EL_BLOCK_WOOD ||
2475       element == EL_GATE_WOOD)
2476   {
2477     PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2478
2479     return TRUE;
2480   }
2481
2482   if (IS_WALL_ICE(element))
2483   {
2484     int lx = LX + XS;
2485     int ly = LY + YS;
2486     int mask;
2487
2488     // check if laser hit adjacent edges of two diagonal tiles
2489     if (ELX != lx / TILEX)
2490       lx = LX - XS;
2491     if (ELY != ly / TILEY)
2492       ly = LY - YS;
2493
2494     mask =     lx / MINI_TILEX - ELX * 2 + 1;    // Quadrant (horizontal)
2495     mask <<= ((ly / MINI_TILEY - ELY * 2) > 0 ? 2 : 0);  // || (vertical)
2496
2497     // check if laser hits wall with an angle of 90°
2498     if (IS_90_ANGLE(laser.current_angle))
2499       mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2500
2501     if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2502     {
2503       int i;
2504
2505       for (i = 0; i < 4; i++)
2506       {
2507         if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2508           mask = 15 - (8 >> i);
2509         else if (ABS(XS) == 4 &&
2510                  mask == (1 << i) &&
2511                  (XS > 0) == (i % 2) &&
2512                  (YS < 0) == (i / 2))
2513           mask = 3 + (i / 2) * 9;
2514         else if (ABS(YS) == 4 &&
2515                  mask == (1 << i) &&
2516                  (XS < 0) == (i % 2) &&
2517                  (YS > 0) == (i / 2))
2518           mask = 5 + (i % 2) * 5;
2519       }
2520     }
2521
2522     laser.wall_mask = mask;
2523   }
2524   else if (IS_WALL_AMOEBA(element))
2525   {
2526     int elx = (LX - 2 * XS) / TILEX;
2527     int ely = (LY - 2 * YS) / TILEY;
2528     int element2 = Tile[elx][ely];
2529     int mask;
2530
2531     if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2532     {
2533       laser.dest_element = EL_EMPTY;
2534
2535       return TRUE;
2536     }
2537
2538     ELX = elx;
2539     ELY = ely;
2540
2541     mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2542     mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2543
2544     if (IS_90_ANGLE(laser.current_angle))
2545       mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2546
2547     laser.dest_element = element2 | EL_WALL_AMOEBA_BASE;
2548
2549     laser.wall_mask = mask;
2550   }
2551
2552   return TRUE;
2553 }
2554
2555 static void OpenExit(int x, int y)
2556 {
2557   int delay = 6;
2558
2559   if (!MovDelay[x][y])          // next animation frame
2560     MovDelay[x][y] = 4 * delay;
2561
2562   if (MovDelay[x][y])           // wait some time before next frame
2563   {
2564     int phase;
2565
2566     MovDelay[x][y]--;
2567     phase = MovDelay[x][y] / delay;
2568
2569     if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2570       DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2571
2572     if (!MovDelay[x][y])
2573     {
2574       Tile[x][y] = EL_EXIT_OPEN;
2575       DrawField_MM(x, y);
2576     }
2577   }
2578 }
2579
2580 static void OpenGrayBall(int x, int y)
2581 {
2582   int delay = 2;
2583
2584   if (!MovDelay[x][y])          // next animation frame
2585   {
2586     if (IS_WALL(Store[x][y]))
2587     {
2588       DrawWalls_MM(x, y, Store[x][y]);
2589
2590       // copy wall tile to spare bitmap for "melting" animation
2591       BlitBitmap(drawto_mm, bitmap_db_field, cSX + x * TILEX, cSY + y * TILEY,
2592                  TILEX, TILEY, x * TILEX, y * TILEY);
2593
2594       DrawElement_MM(x, y, EL_GRAY_BALL);
2595     }
2596
2597     MovDelay[x][y] = 50 * delay;
2598   }
2599
2600   if (MovDelay[x][y])           // wait some time before next frame
2601   {
2602     MovDelay[x][y]--;
2603
2604     if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2605     {
2606       Bitmap *bitmap;
2607       int gx, gy;
2608       int dx = RND(26), dy = RND(26);
2609
2610       if (IS_WALL(Store[x][y]))
2611       {
2612         // copy wall tile from spare bitmap for "melting" animation
2613         bitmap = bitmap_db_field;
2614         gx = x * TILEX;
2615         gy = y * TILEY;
2616       }
2617       else
2618       {
2619         int graphic = el2gfx(Store[x][y]);
2620
2621         getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2622       }
2623
2624       BlitBitmap(bitmap, drawto_mm, gx + dx, gy + dy, 6, 6,
2625                  cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2626
2627       laser.redraw = TRUE;
2628
2629       MarkTileDirty(x, y);
2630     }
2631
2632     if (!MovDelay[x][y])
2633     {
2634       Tile[x][y] = Store[x][y];
2635       Store[x][y] = Store2[x][y] = 0;
2636       MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2637
2638       InitField(x, y, FALSE);
2639       DrawField_MM(x, y);
2640
2641       ScanLaser_FromLastMirror();
2642     }
2643   }
2644 }
2645
2646 static void OpenEnvelope(int x, int y)
2647 {
2648   int num_frames = 8;           // seven frames plus final empty space
2649
2650   if (!MovDelay[x][y])          // next animation frame
2651     MovDelay[x][y] = num_frames;
2652
2653   if (MovDelay[x][y])           // wait some time before next frame
2654   {
2655     int nr = ENVELOPE_OPENING_NR(Tile[x][y]);
2656
2657     MovDelay[x][y]--;
2658
2659     if (MovDelay[x][y] > 0 && IN_SCR_FIELD(x, y))
2660     {
2661       int graphic = el_act2gfx(EL_ENVELOPE_1 + nr, MM_ACTION_COLLECTING);
2662       int frame = num_frames - MovDelay[x][y] - 1;
2663
2664       DrawGraphicAnimation_MM(x, y, graphic, frame);
2665
2666       laser.redraw = TRUE;
2667     }
2668
2669     if (MovDelay[x][y] == 0)
2670     {
2671       Tile[x][y] = EL_EMPTY;
2672
2673       DrawField_MM(x, y);
2674
2675       ScanLaser();
2676
2677       ShowEnvelope(nr);
2678     }
2679   }
2680 }
2681
2682 static void MeltIce(int x, int y)
2683 {
2684   int frames = 5;
2685   int delay = 5;
2686
2687   if (!MovDelay[x][y])          // next animation frame
2688     MovDelay[x][y] = frames * delay;
2689
2690   if (MovDelay[x][y])           // wait some time before next frame
2691   {
2692     int phase;
2693     int wall_mask = Store2[x][y];
2694     int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_ICE_BASE;
2695
2696     MovDelay[x][y]--;
2697     phase = frames - MovDelay[x][y] / delay - 1;
2698
2699     if (!MovDelay[x][y])
2700     {
2701       Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2702       Store[x][y] = Store2[x][y] = 0;
2703
2704       DrawWalls_MM(x, y, Tile[x][y]);
2705
2706       if (Tile[x][y] == EL_WALL_ICE_BASE)
2707         Tile[x][y] = EL_EMPTY;
2708
2709       ScanLaser_FromLastMirror();
2710     }
2711     else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2712     {
2713       DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2714
2715       laser.redraw = TRUE;
2716     }
2717   }
2718 }
2719
2720 static void GrowAmoeba(int x, int y)
2721 {
2722   int frames = 5;
2723   int delay = 1;
2724
2725   if (!MovDelay[x][y])          // next animation frame
2726     MovDelay[x][y] = frames * delay;
2727
2728   if (MovDelay[x][y])           // wait some time before next frame
2729   {
2730     int phase;
2731     int wall_mask = Store2[x][y];
2732     int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_AMOEBA_BASE;
2733
2734     MovDelay[x][y]--;
2735     phase = MovDelay[x][y] / delay;
2736
2737     if (!MovDelay[x][y])
2738     {
2739       Tile[x][y] = real_element;
2740       Store[x][y] = Store2[x][y] = 0;
2741
2742       DrawWalls_MM(x, y, Tile[x][y]);
2743       DrawLaser(0, DL_LASER_ENABLED);
2744     }
2745     else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2746     {
2747       DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2748     }
2749   }
2750 }
2751
2752 static void DrawFieldAnimated_MM(int x, int y)
2753 {
2754   DrawField_MM(x, y);
2755
2756   laser.redraw = TRUE;
2757 }
2758
2759 static void DrawFieldAnimatedIfNeeded_MM(int x, int y)
2760 {
2761   int element = Tile[x][y];
2762   int graphic = el2gfx(element);
2763
2764   if (!getGraphicInfo_NewFrame(x, y, graphic))
2765     return;
2766
2767   DrawField_MM(x, y);
2768
2769   laser.redraw = TRUE;
2770 }
2771
2772 static void DrawFieldTwinkle(int x, int y)
2773 {
2774   if (MovDelay[x][y] != 0)      // wait some time before next frame
2775   {
2776     MovDelay[x][y]--;
2777
2778     DrawField_MM(x, y);
2779
2780     if (MovDelay[x][y] != 0)
2781     {
2782       int graphic = IMG_TWINKLE_WHITE;
2783       int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
2784
2785       DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
2786     }
2787
2788     laser.redraw = TRUE;
2789   }
2790 }
2791
2792 static void Explode_MM(int x, int y, int phase, int mode)
2793 {
2794   int num_phase = 9, delay = 2;
2795   int last_phase = num_phase * delay;
2796   int half_phase = (num_phase / 2) * delay;
2797   int center_element;
2798
2799   laser.redraw = TRUE;
2800
2801   if (phase == EX_PHASE_START)          // initialize 'Store[][]' field
2802   {
2803     center_element = Tile[x][y];
2804
2805     if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2806     {
2807       // put moving element to center field (and let it explode there)
2808       center_element = MovingOrBlocked2Element_MM(x, y);
2809       RemoveMovingField_MM(x, y);
2810
2811       Tile[x][y] = center_element;
2812     }
2813
2814     if (center_element != EL_GRAY_BALL_ACTIVE)
2815       Store[x][y] = EL_EMPTY;
2816     Store2[x][y] = center_element;
2817
2818     Tile[x][y] = EL_EXPLODING_OPAQUE;
2819
2820     GfxElement[x][y] = (center_element == EL_BOMB_ACTIVE ? EL_BOMB :
2821                         center_element == EL_GRAY_BALL_ACTIVE ? EL_GRAY_BALL :
2822                         IS_MCDUFFIN(center_element) ? EL_MCDUFFIN :
2823                         center_element);
2824
2825     MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2826
2827     ExplodePhase[x][y] = 1;
2828
2829     return;
2830   }
2831
2832   if (phase == 1)
2833     GfxFrame[x][y] = 0;         // restart explosion animation
2834
2835   ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
2836
2837   center_element = Store2[x][y];
2838
2839   if (phase == half_phase && Store[x][y] == EL_EMPTY)
2840   {
2841     Tile[x][y] = EL_EXPLODING_TRANSP;
2842
2843     if (x == ELX && y == ELY)
2844       ScanLaser();
2845   }
2846
2847   if (phase == last_phase)
2848   {
2849     if (center_element == EL_BOMB_ACTIVE)
2850     {
2851       DrawLaser(0, DL_LASER_DISABLED);
2852       InitLaser();
2853
2854       Bang_MM(laser.start_edge.x, laser.start_edge.y);
2855
2856       laser.overloaded = FALSE;
2857     }
2858     else if (IS_MCDUFFIN(center_element) || IS_LASER(center_element))
2859     {
2860       GameOver_MM(GAME_OVER_BOMB);
2861     }
2862
2863     Tile[x][y] = Store[x][y];
2864
2865     Store[x][y] = Store2[x][y] = 0;
2866     MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2867
2868     InitField(x, y, FALSE);
2869     DrawField_MM(x, y);
2870
2871     if (center_element == EL_GRAY_BALL_ACTIVE)
2872       ScanLaser_FromLastMirror();
2873   }
2874   else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2875   {
2876     int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
2877     int frame = getGraphicAnimationFrameXY(graphic, x, y);
2878
2879     DrawGraphicAnimation_MM(x, y, graphic, frame);
2880
2881     MarkTileDirty(x, y);
2882   }
2883 }
2884
2885 static void Bang_MM(int x, int y)
2886 {
2887   int element = Tile[x][y];
2888
2889   if (IS_PACMAN(element))
2890     PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2891   else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
2892     PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2893   else if (element == EL_KEY)
2894     PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2895   else
2896     PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2897
2898   Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
2899 }
2900
2901 static void TurnRound(int x, int y)
2902 {
2903   static struct
2904   {
2905     int x, y;
2906   } move_xy[] =
2907   {
2908     {  0,  0 },
2909     { -1,  0 },
2910     { +1,  0 },
2911     {  0,  0 },
2912     {  0, -1 },
2913     {  0,  0 }, { 0, 0 }, { 0, 0 },
2914     {  0, +1 }
2915   };
2916   static struct
2917   {
2918     int left, right, back;
2919   } turn[] =
2920   {
2921     { 0,        0,              0        },
2922     { MV_DOWN,  MV_UP,          MV_RIGHT },
2923     { MV_UP,    MV_DOWN,        MV_LEFT  },
2924     { 0,        0,              0        },
2925     { MV_LEFT,  MV_RIGHT,       MV_DOWN  },
2926     { 0,        0,              0        },
2927     { 0,        0,              0        },
2928     { 0,        0,              0        },
2929     { MV_RIGHT, MV_LEFT,        MV_UP    }
2930   };
2931
2932   int element = Tile[x][y];
2933   int old_move_dir = MovDir[x][y];
2934   int right_dir = turn[old_move_dir].right;
2935   int back_dir = turn[old_move_dir].back;
2936   int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2937   int right_x = x + right_dx, right_y = y + right_dy;
2938
2939   if (element == EL_PACMAN)
2940   {
2941     boolean can_turn_right = FALSE;
2942
2943     if (IN_LEV_FIELD(right_x, right_y) &&
2944         IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2945       can_turn_right = TRUE;
2946
2947     if (can_turn_right)
2948       MovDir[x][y] = right_dir;
2949     else
2950       MovDir[x][y] = back_dir;
2951
2952     MovDelay[x][y] = 0;
2953   }
2954 }
2955
2956 static void StartMoving_MM(int x, int y)
2957 {
2958   int element = Tile[x][y];
2959
2960   if (Stop[x][y])
2961     return;
2962
2963   if (CAN_MOVE(element))
2964   {
2965     int newx, newy;
2966
2967     if (MovDelay[x][y])         // wait some time before next movement
2968     {
2969       MovDelay[x][y]--;
2970
2971       if (MovDelay[x][y])
2972         return;
2973     }
2974
2975     // now make next step
2976
2977     Moving2Blocked(x, y, &newx, &newy); // get next screen position
2978
2979     if (element == EL_PACMAN &&
2980         IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2981         !ObjHit(newx, newy, HIT_POS_CENTER))
2982     {
2983       Store[newx][newy] = Tile[newx][newy];
2984       Tile[newx][newy] = EL_EMPTY;
2985
2986       DrawField_MM(newx, newy);
2987     }
2988     else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2989              ObjHit(newx, newy, HIT_POS_CENTER))
2990     {
2991       // object was running against a wall
2992
2993       TurnRound(x, y);
2994
2995       return;
2996     }
2997
2998     InitMovingField_MM(x, y, MovDir[x][y]);
2999   }
3000
3001   if (MovDir[x][y])
3002     ContinueMoving_MM(x, y);
3003 }
3004
3005 static void ContinueMoving_MM(int x, int y)
3006 {
3007   int element = Tile[x][y];
3008   int direction = MovDir[x][y];
3009   int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3010   int dy = (direction == MV_UP   ? -1 : direction == MV_DOWN  ? +1 : 0);
3011   int horiz_move = (dx!=0);
3012   int newx = x + dx, newy = y + dy;
3013   int step = (horiz_move ? dx : dy) * TILEX / 8;
3014
3015   MovPos[x][y] += step;
3016
3017   if (ABS(MovPos[x][y]) >= TILEX)       // object reached its destination
3018   {
3019     Tile[x][y] = EL_EMPTY;
3020     Tile[newx][newy] = element;
3021
3022     MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3023     MovDelay[newx][newy] = 0;
3024
3025     if (!CAN_MOVE(element))
3026       MovDir[newx][newy] = 0;
3027
3028     DrawField_MM(x, y);
3029     DrawField_MM(newx, newy);
3030
3031     Stop[newx][newy] = TRUE;
3032
3033     if (element == EL_PACMAN)
3034     {
3035       if (Store[newx][newy] == EL_BOMB)
3036         Bang_MM(newx, newy);
3037
3038       if (IS_WALL_AMOEBA(Store[newx][newy]) &&
3039           (LX + 2 * XS) / TILEX == newx &&
3040           (LY + 2 * YS) / TILEY == newy)
3041       {
3042         laser.num_edges--;
3043         ScanLaser();
3044       }
3045     }
3046   }
3047   else                          // still moving on
3048   {
3049     DrawField_MM(x, y);
3050   }
3051
3052   laser.redraw = TRUE;
3053 }
3054
3055 boolean ClickElement(int x, int y, int button)
3056 {
3057   static DelayCounter click_delay = { CLICK_DELAY };
3058   static boolean new_button = TRUE;
3059   boolean element_clicked = FALSE;
3060   int element;
3061
3062   if (button == -1)
3063   {
3064     // initialize static variables
3065     click_delay.count = 0;
3066     click_delay.value = CLICK_DELAY;
3067     new_button = TRUE;
3068
3069     return FALSE;
3070   }
3071
3072   // do not rotate objects hit by the laser after the game was solved
3073   if (game_mm.level_solved && Hit[x][y])
3074     return FALSE;
3075
3076   if (button == MB_RELEASED)
3077   {
3078     new_button = TRUE;
3079     click_delay.value = CLICK_DELAY;
3080
3081     // release eventually hold auto-rotating mirror
3082     RotateMirror(x, y, MB_RELEASED);
3083
3084     return FALSE;
3085   }
3086
3087   if (!FrameReached(&click_delay) && !new_button)
3088     return FALSE;
3089
3090   if (button == MB_MIDDLEBUTTON)        // middle button has no function
3091     return FALSE;
3092
3093   if (!IN_LEV_FIELD(x, y))
3094     return FALSE;
3095
3096   if (Tile[x][y] == EL_EMPTY)
3097     return FALSE;
3098
3099   element = Tile[x][y];
3100
3101   if (IS_MIRROR(element) ||
3102       IS_BEAMER(element) ||
3103       IS_POLAR(element) ||
3104       IS_POLAR_CROSS(element) ||
3105       IS_DF_MIRROR(element) ||
3106       IS_DF_MIRROR_AUTO(element))
3107   {
3108     RotateMirror(x, y, button);
3109
3110     element_clicked = TRUE;
3111   }
3112   else if (IS_MCDUFFIN(element))
3113   {
3114     boolean has_laser = (x == laser.start_edge.x && y == laser.start_edge.y);
3115
3116     if (has_laser && !laser.fuse_off)
3117       DrawLaser(0, DL_LASER_DISABLED);
3118
3119     element = get_rotated_element(element, BUTTON_ROTATION(button));
3120
3121     Tile[x][y] = element;
3122     DrawField_MM(x, y);
3123
3124     if (has_laser)
3125     {
3126       laser.start_angle = get_element_angle(element);
3127
3128       InitLaser();
3129
3130       if (!laser.fuse_off)
3131         ScanLaser();
3132     }
3133
3134     element_clicked = TRUE;
3135   }
3136   else if (element == EL_FUSE_ON && laser.fuse_off)
3137   {
3138     if (x != laser.fuse_x || y != laser.fuse_y)
3139       return FALSE;
3140
3141     laser.fuse_off = FALSE;
3142     laser.fuse_x = laser.fuse_y = -1;
3143
3144     DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
3145     ScanLaser();
3146
3147     element_clicked = TRUE;
3148   }
3149   else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
3150   {
3151     laser.fuse_off = TRUE;
3152     laser.fuse_x = x;
3153     laser.fuse_y = y;
3154     laser.overloaded = FALSE;
3155
3156     DrawLaser(0, DL_LASER_DISABLED);
3157     DrawGraphic_MM(x, y, IMG_MM_FUSE);
3158
3159     element_clicked = TRUE;
3160   }
3161   else if (element == EL_LIGHTBALL)
3162   {
3163     Bang_MM(x, y);
3164     RaiseScoreElement_MM(element);
3165     DrawLaser(0, DL_LASER_ENABLED);
3166
3167     element_clicked = TRUE;
3168   }
3169   else if (IS_ENVELOPE(element))
3170   {
3171     Tile[x][y] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(element);
3172
3173     element_clicked = TRUE;
3174   }
3175
3176   click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
3177   new_button = FALSE;
3178
3179   return element_clicked;
3180 }
3181
3182 static void RotateMirror(int x, int y, int button)
3183 {
3184   if (button == MB_RELEASED)
3185   {
3186     // release eventually hold auto-rotating mirror
3187     hold_x = -1;
3188     hold_y = -1;
3189
3190     return;
3191   }
3192
3193   if (IS_MIRROR(Tile[x][y]) ||
3194       IS_POLAR_CROSS(Tile[x][y]) ||
3195       IS_POLAR(Tile[x][y]) ||
3196       IS_BEAMER(Tile[x][y]) ||
3197       IS_DF_MIRROR(Tile[x][y]) ||
3198       IS_GRID_STEEL_AUTO(Tile[x][y]) ||
3199       IS_GRID_WOOD_AUTO(Tile[x][y]))
3200   {
3201     Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
3202   }
3203   else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
3204   {
3205     if (button == MB_LEFTBUTTON)
3206     {
3207       // left mouse button only for manual adjustment, no auto-rotating;
3208       // freeze mirror for until mouse button released
3209       hold_x = x;
3210       hold_y = y;
3211     }
3212     else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
3213     {
3214       Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
3215     }
3216   }
3217
3218   if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
3219   {
3220     int edge = Hit[x][y];
3221
3222     DrawField_MM(x, y);
3223
3224     if (edge > 0)
3225     {
3226       DrawLaser(edge - 1, DL_LASER_DISABLED);
3227       ScanLaser();
3228     }
3229   }
3230   else if (ObjHit(x, y, HIT_POS_CENTER))
3231   {
3232     int edge = Hit[x][y];
3233
3234     if (edge == 0)
3235     {
3236       Warn("RotateMirror: inconsistent field Hit[][]!\n");
3237
3238       edge = 1;
3239     }
3240
3241     DrawLaser(edge - 1, DL_LASER_DISABLED);
3242     ScanLaser();
3243   }
3244   else
3245   {
3246     int check = 1;
3247
3248     if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
3249       check = 2;
3250
3251     DrawField_MM(x, y);
3252
3253     if ((IS_BEAMER(Tile[x][y]) ||
3254          IS_POLAR(Tile[x][y]) ||
3255          IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
3256     {
3257       if (IS_BEAMER(Tile[x][y]))
3258       {
3259 #if 0
3260         Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3261               LX, LY, laser.beamer_edge, laser.beamer[1].num);
3262 #endif
3263
3264         if (check == 1)
3265           laser.num_edges--;
3266       }
3267
3268       ScanLaser();
3269
3270       check = 0;
3271     }
3272
3273     if (check == 2)
3274       DrawLaser(0, DL_LASER_ENABLED);
3275   }
3276 }
3277
3278 static void AutoRotateMirrors(void)
3279 {
3280   int x, y;
3281
3282   if (!FrameReached(&rotate_delay))
3283     return;
3284
3285   for (x = 0; x < lev_fieldx; x++)
3286   {
3287     for (y = 0; y < lev_fieldy; y++)
3288     {
3289       int element = Tile[x][y];
3290
3291       // do not rotate objects hit by the laser after the game was solved
3292       if (game_mm.level_solved && Hit[x][y])
3293         continue;
3294
3295       if (IS_DF_MIRROR_AUTO(element) ||
3296           IS_GRID_WOOD_AUTO(element) ||
3297           IS_GRID_STEEL_AUTO(element) ||
3298           element == EL_REFRACTOR)
3299       {
3300         RotateMirror(x, y, MB_RIGHTBUTTON);
3301
3302         laser.redraw = TRUE;
3303       }
3304     }
3305   }
3306 }
3307
3308 static boolean ObjHit(int obx, int oby, int bits)
3309 {
3310   int i;
3311
3312   obx *= TILEX;
3313   oby *= TILEY;
3314
3315   if (bits & HIT_POS_CENTER)
3316   {
3317     if (CheckLaserPixel(cSX + obx + 15,
3318                         cSY + oby + 15))
3319       return TRUE;
3320   }
3321
3322   if (bits & HIT_POS_EDGE)
3323   {
3324     for (i = 0; i < 4; i++)
3325       if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3326                           cSY + oby + 31 * (i / 2)))
3327         return TRUE;
3328   }
3329
3330   if (bits & HIT_POS_BETWEEN)
3331   {
3332     for (i = 0; i < 4; i++)
3333       if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3334                           cSY + 4 + oby + 22 * (i / 2)))
3335         return TRUE;
3336   }
3337
3338   return FALSE;
3339 }
3340
3341 static void DeletePacMan(int px, int py)
3342 {
3343   int i, j;
3344
3345   Bang_MM(px, py);
3346
3347   if (game_mm.num_pacman <= 1)
3348   {
3349     game_mm.num_pacman = 0;
3350     return;
3351   }
3352
3353   for (i = 0; i < game_mm.num_pacman; i++)
3354     if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3355       break;
3356
3357   game_mm.num_pacman--;
3358
3359   for (j = i; j < game_mm.num_pacman; j++)
3360   {
3361     game_mm.pacman[j].x   = game_mm.pacman[j + 1].x;
3362     game_mm.pacman[j].y   = game_mm.pacman[j + 1].y;
3363     game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3364   }
3365 }
3366
3367 static void GameActions_MM_Ext(void)
3368 {
3369   int element;
3370   int x, y, i;
3371
3372   int r, d;
3373
3374   for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3375     Stop[x][y] = FALSE;
3376
3377   for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3378   {
3379     element = Tile[x][y];
3380
3381     if (!IS_MOVING(x, y) && CAN_MOVE(element))
3382       StartMoving_MM(x, y);
3383     else if (IS_MOVING(x, y))
3384       ContinueMoving_MM(x, y);
3385     else if (IS_EXPLODING(element))
3386       Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3387     else if (element == EL_EXIT_OPENING)
3388       OpenExit(x, y);
3389     else if (element == EL_GRAY_BALL_OPENING)
3390       OpenGrayBall(x, y);
3391     else if (IS_ENVELOPE_OPENING(element))
3392       OpenEnvelope(x, y);
3393     else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE_BASE)
3394       MeltIce(x, y);
3395     else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA_BASE)
3396       GrowAmoeba(x, y);
3397     else if (IS_MIRROR(element) ||
3398              IS_MIRROR_FIXED(element) ||
3399              element == EL_PRISM)
3400       DrawFieldTwinkle(x, y);
3401     else if (element == EL_GRAY_BALL_ACTIVE ||
3402              element == EL_BOMB_ACTIVE ||
3403              element == EL_MINE_ACTIVE)
3404       DrawFieldAnimated_MM(x, y);
3405     else if (!IS_BLOCKED(x, y))
3406       DrawFieldAnimatedIfNeeded_MM(x, y);
3407   }
3408
3409   AutoRotateMirrors();
3410
3411 #if 1
3412   // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3413
3414   // redraw after Explode_MM() ...
3415   if (laser.redraw)
3416     DrawLaser(0, DL_LASER_ENABLED);
3417   laser.redraw = FALSE;
3418 #endif
3419
3420   CT = FrameCounter;
3421
3422   if (game_mm.num_pacman && FrameReached(&pacman_delay))
3423   {
3424     MovePacMen();
3425
3426     if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3427     {
3428       DrawLaser(0, DL_LASER_DISABLED);
3429       ScanLaser();
3430     }
3431   }
3432
3433   // skip all following game actions if game is over
3434   if (game_mm.game_over)
3435     return;
3436
3437   if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3438   {
3439     FadeOutLaser();
3440
3441     GameOver_MM(GAME_OVER_NO_ENERGY);
3442
3443     return;
3444   }
3445
3446   if (FrameReached(&energy_delay))
3447   {
3448     if (game_mm.energy_left > 0)
3449       game_mm.energy_left--;
3450
3451     // when out of energy, wait another frame to play "out of time" sound
3452   }
3453
3454   element = laser.dest_element;
3455
3456 #if 0
3457   if (element != Tile[ELX][ELY])
3458   {
3459     Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3460           element, Tile[ELX][ELY]);
3461   }
3462 #endif
3463
3464   if (!laser.overloaded && laser.overload_value == 0 &&
3465       element != EL_BOMB &&
3466       element != EL_BOMB_ACTIVE &&
3467       element != EL_MINE &&
3468       element != EL_MINE_ACTIVE &&
3469       element != EL_GRAY_BALL &&
3470       element != EL_GRAY_BALL_ACTIVE &&
3471       element != EL_BLOCK_STONE &&
3472       element != EL_BLOCK_WOOD &&
3473       element != EL_FUSE_ON &&
3474       element != EL_FUEL_FULL &&
3475       !IS_WALL_ICE(element) &&
3476       !IS_WALL_AMOEBA(element))
3477     return;
3478
3479   overload_delay.value = HEALTH_DELAY(laser.overloaded);
3480
3481   if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3482        (!laser.overloaded && laser.overload_value > 0)) &&
3483       FrameReached(&overload_delay))
3484   {
3485     if (laser.overloaded)
3486       laser.overload_value++;
3487     else
3488       laser.overload_value--;
3489
3490     if (game_mm.cheat_no_overload)
3491     {
3492       laser.overloaded = FALSE;
3493       laser.overload_value = 0;
3494     }
3495
3496     game_mm.laser_overload_value = laser.overload_value;
3497
3498     if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3499     {
3500       SetLaserColor(0xFF);
3501
3502       DrawLaser(0, DL_LASER_ENABLED);
3503     }
3504
3505     if (!laser.overloaded)
3506       StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3507     else if (setup.sound_loops)
3508       PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3509     else
3510       PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3511
3512     if (laser.overload_value == MAX_LASER_OVERLOAD)
3513     {
3514       UpdateAndDisplayGameControlValues();
3515
3516       FadeOutLaser();
3517
3518       GameOver_MM(GAME_OVER_OVERLOADED);
3519
3520       return;
3521     }
3522   }
3523
3524   if (laser.fuse_off)
3525     return;
3526
3527   CT -= Ct;
3528
3529   if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3530   {
3531     if (game_mm.cheat_no_explosion)
3532       return;
3533
3534     Bang_MM(ELX, ELY);
3535
3536     laser.dest_element = EL_EXPLODING_OPAQUE;
3537
3538     return;
3539   }
3540
3541   if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3542   {
3543     laser.fuse_off = TRUE;
3544     laser.fuse_x = ELX;
3545     laser.fuse_y = ELY;
3546
3547     DrawLaser(0, DL_LASER_DISABLED);
3548     DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3549   }
3550
3551   if (element == EL_GRAY_BALL && CT > native_mm_level.time_ball)
3552   {
3553     if (!Store2[ELX][ELY])      // check if content element not yet determined
3554     {
3555       int last_anim_random_frame = gfx.anim_random_frame;
3556       int element_pos;
3557
3558       if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3559         gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3560
3561       element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3562                                       native_mm_level.ball_choice_mode, 0,
3563                                       game_mm.ball_choice_pos);
3564
3565       if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3566         gfx.anim_random_frame = last_anim_random_frame;
3567
3568       game_mm.ball_choice_pos++;
3569
3570       int new_element = native_mm_level.ball_content[element_pos];
3571       int new_element_base = map_wall_to_base_element(new_element);
3572
3573       if (IS_WALL(new_element_base))
3574       {
3575         // always use completely filled wall element
3576         new_element = new_element_base | 0x000f;
3577       }
3578       else if (native_mm_level.rotate_ball_content &&
3579                get_num_elements(new_element) > 1)
3580       {
3581         // randomly rotate newly created game element
3582         new_element = get_rotated_element(new_element, RND(16));
3583       }
3584
3585       Store[ELX][ELY] = new_element;
3586       Store2[ELX][ELY] = TRUE;
3587     }
3588
3589     if (native_mm_level.explode_ball)
3590       Bang_MM(ELX, ELY);
3591     else
3592       Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3593
3594     laser.dest_element = laser.dest_element_last = Tile[ELX][ELY];
3595
3596     return;
3597   }
3598
3599   if (IS_WALL_ICE(element) && CT > 50)
3600   {
3601     PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3602
3603     Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE_BASE + EL_WALL_CHANGING_BASE;
3604     Store[ELX][ELY] = EL_WALL_ICE_BASE;
3605     Store2[ELX][ELY] = laser.wall_mask;
3606
3607     laser.dest_element = Tile[ELX][ELY];
3608
3609     return;
3610   }
3611
3612   if (IS_WALL_AMOEBA(element) && CT > 60)
3613   {
3614     int k1, k2, k3;
3615     int element2 = Tile[ELX][ELY];
3616
3617     if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3618       return;
3619
3620     for (i = laser.num_damages - 1; i >= 0; i--)
3621       if (laser.damage[i].is_mirror)
3622         break;
3623
3624     r = laser.num_edges;
3625     d = laser.num_damages;
3626     k1 = i;
3627
3628     if (k1 > 0)
3629     {
3630       int x, y;
3631
3632       DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3633
3634       laser.num_edges++;
3635       DrawLaser(0, DL_LASER_ENABLED);
3636       laser.num_edges--;
3637
3638       x = laser.damage[k1].x;
3639       y = laser.damage[k1].y;
3640
3641       DrawField_MM(x, y);
3642     }
3643
3644     for (i = 0; i < 4; i++)
3645     {
3646       if (laser.wall_mask & (1 << i))
3647       {
3648         if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3649                             cSY + ELY * TILEY + 31 * (i / 2)))
3650           break;
3651
3652         if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3653                             cSY + ELY * TILEY + 14 + (i / 2) * 2))
3654           break;
3655       }
3656     }
3657
3658     k2 = i;
3659
3660     for (i = 0; i < 4; i++)
3661     {
3662       if (laser.wall_mask & (1 << i))
3663       {
3664         if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3665                             cSY + ELY * TILEY + 31 * (i / 2)))
3666           break;
3667       }
3668     }
3669
3670     k3 = i;
3671
3672     if (laser.num_beamers > 0 ||
3673         k1 < 1 || k2 < 4 || k3 < 4 ||
3674         CheckLaserPixel(cSX + ELX * TILEX + 14,
3675                         cSY + ELY * TILEY + 14))
3676     {
3677       laser.num_edges = r;
3678       laser.num_damages = d;
3679
3680       DrawLaser(0, DL_LASER_DISABLED);
3681     }
3682
3683     Tile[ELX][ELY] = element | laser.wall_mask;
3684
3685     int x = ELX, y = ELY;
3686     int wall_mask = laser.wall_mask;
3687
3688     ScanLaser();
3689     DrawLaser(0, DL_LASER_ENABLED);
3690
3691     PlayLevelSound_MM(x, y, element, MM_ACTION_GROWING);
3692
3693     Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA_BASE + EL_WALL_CHANGING_BASE;
3694     Store[x][y] = EL_WALL_AMOEBA_BASE;
3695     Store2[x][y] = wall_mask;
3696
3697     return;
3698   }
3699
3700   if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3701       laser.stops_inside_element && CT > native_mm_level.time_block)
3702   {
3703     int x, y;
3704     int k;
3705
3706     if (ABS(XS) > ABS(YS))
3707       k = 0;
3708     else
3709       k = 1;
3710     if (XS < YS)
3711       k += 2;
3712
3713     for (i = 0; i < 4; i++)
3714     {
3715       if (i)
3716         k++;
3717       if (k > 3)
3718         k = 0;
3719
3720       x = ELX + Step[k * 4].x;
3721       y = ELY + Step[k * 4].y;
3722
3723       if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3724         continue;
3725
3726       if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3727         continue;
3728
3729       break;
3730     }
3731
3732     if (i > 3)
3733     {
3734       laser.overloaded = (element == EL_BLOCK_STONE);
3735
3736       return;
3737     }
3738
3739     PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3740
3741     Tile[ELX][ELY] = 0;
3742     Tile[x][y] = element;
3743
3744     DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3745     DrawField_MM(x, y);
3746
3747     if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3748     {
3749       DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3750       DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3751     }
3752
3753     ScanLaser();
3754
3755     return;
3756   }
3757
3758   if (element == EL_FUEL_FULL && CT > 10)
3759   {
3760     int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
3761     int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
3762
3763     for (i = start; i <= num_init_game_frames; i++)
3764     {
3765       if (i == num_init_game_frames)
3766         StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3767       else if (setup.sound_loops)
3768         PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3769       else
3770         PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3771
3772       game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
3773
3774       UpdateAndDisplayGameControlValues();
3775
3776       BackToFront_MM();
3777     }
3778
3779     Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
3780
3781     DrawField_MM(ELX, ELY);
3782
3783     DrawLaser(0, DL_LASER_ENABLED);
3784
3785     return;
3786   }
3787 }
3788
3789 void GameActions_MM(struct MouseActionInfo action)
3790 {
3791   boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3792   boolean button_released = (action.button == MB_RELEASED);
3793
3794   GameActions_MM_Ext();
3795
3796   CheckSingleStepMode_MM(element_clicked, button_released);
3797 }
3798
3799 static void MovePacMen(void)
3800 {
3801   int mx, my, ox, oy, nx, ny;
3802   int element;
3803   int l;
3804
3805   if (++pacman_nr >= game_mm.num_pacman)
3806     pacman_nr = 0;
3807
3808   game_mm.pacman[pacman_nr].dir--;
3809
3810   for (l = 1; l < 5; l++)
3811   {
3812     game_mm.pacman[pacman_nr].dir++;
3813
3814     if (game_mm.pacman[pacman_nr].dir > 4)
3815       game_mm.pacman[pacman_nr].dir = 1;
3816
3817     if (game_mm.pacman[pacman_nr].dir % 2)
3818     {
3819       mx = 0;
3820       my = game_mm.pacman[pacman_nr].dir - 2;
3821     }
3822     else
3823     {
3824       my = 0;
3825       mx = 3 - game_mm.pacman[pacman_nr].dir;
3826     }
3827
3828     ox = game_mm.pacman[pacman_nr].x;
3829     oy = game_mm.pacman[pacman_nr].y;
3830     nx = ox + mx;
3831     ny = oy + my;
3832     element = Tile[nx][ny];
3833
3834     if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3835       continue;
3836
3837     if (!IS_EATABLE4PACMAN(element))
3838       continue;
3839
3840     if (ObjHit(nx, ny, HIT_POS_CENTER))
3841       continue;
3842
3843     Tile[ox][oy] = EL_EMPTY;
3844     Tile[nx][ny] =
3845       EL_PACMAN_RIGHT - 1 +
3846       (game_mm.pacman[pacman_nr].dir - 1 +
3847        (game_mm.pacman[pacman_nr].dir % 2) * 2);
3848
3849     game_mm.pacman[pacman_nr].x = nx;
3850     game_mm.pacman[pacman_nr].y = ny;
3851
3852     DrawGraphic_MM(ox, oy, IMG_EMPTY);
3853
3854     if (element != EL_EMPTY)
3855     {
3856       int graphic = el2gfx(Tile[nx][ny]);
3857       Bitmap *bitmap;
3858       int src_x, src_y;
3859       int i;
3860
3861       getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3862
3863       CT = FrameCounter;
3864       ox = cSX + ox * TILEX;
3865       oy = cSY + oy * TILEY;
3866
3867       for (i = 1; i < 33; i += 2)
3868         BlitBitmap(bitmap, window,
3869                    src_x, src_y, TILEX, TILEY,
3870                    ox + i * mx, oy + i * my);
3871       Ct = Ct + FrameCounter - CT;
3872     }
3873
3874     DrawField_MM(nx, ny);
3875     BackToFront_MM();
3876
3877     if (!laser.fuse_off)
3878     {
3879       DrawLaser(0, DL_LASER_ENABLED);
3880
3881       if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3882       {
3883         AddDamagedField(nx, ny);
3884
3885         laser.damage[laser.num_damages - 1].edge = 0;
3886       }
3887     }
3888
3889     if (element == EL_BOMB)
3890       DeletePacMan(nx, ny);
3891
3892     if (IS_WALL_AMOEBA(element) &&
3893         (LX + 2 * XS) / TILEX == nx &&
3894         (LY + 2 * YS) / TILEY == ny)
3895     {
3896       laser.num_edges--;
3897       ScanLaser();
3898     }
3899
3900     break;
3901   }
3902 }
3903
3904 static void InitMovingField_MM(int x, int y, int direction)
3905 {
3906   int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3907   int newy = y + (direction == MV_UP   ? -1 : direction == MV_DOWN  ? +1 : 0);
3908
3909   MovDir[x][y] = direction;
3910   MovDir[newx][newy] = direction;
3911
3912   if (Tile[newx][newy] == EL_EMPTY)
3913     Tile[newx][newy] = EL_BLOCKED;
3914 }
3915
3916 static int MovingOrBlocked2Element_MM(int x, int y)
3917 {
3918   int element = Tile[x][y];
3919
3920   if (element == EL_BLOCKED)
3921   {
3922     int oldx, oldy;
3923
3924     Blocked2Moving(x, y, &oldx, &oldy);
3925
3926     return Tile[oldx][oldy];
3927   }
3928
3929   return element;
3930 }
3931
3932 static void RemoveMovingField_MM(int x, int y)
3933 {
3934   int oldx = x, oldy = y, newx = x, newy = y;
3935
3936   if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3937     return;
3938
3939   if (IS_MOVING(x, y))
3940   {
3941     Moving2Blocked(x, y, &newx, &newy);
3942     if (Tile[newx][newy] != EL_BLOCKED)
3943       return;
3944   }
3945   else if (Tile[x][y] == EL_BLOCKED)
3946   {
3947     Blocked2Moving(x, y, &oldx, &oldy);
3948     if (!IS_MOVING(oldx, oldy))
3949       return;
3950   }
3951
3952   Tile[oldx][oldy] = EL_EMPTY;
3953   Tile[newx][newy] = EL_EMPTY;
3954   MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3955   MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3956
3957   DrawLevelField_MM(oldx, oldy);
3958   DrawLevelField_MM(newx, newy);
3959 }
3960
3961 static void RaiseScore_MM(int value)
3962 {
3963   game_mm.score += value;
3964 }
3965
3966 void RaiseScoreElement_MM(int element)
3967 {
3968   switch (element)
3969   {
3970     case EL_PACMAN:
3971     case EL_PACMAN_RIGHT:
3972     case EL_PACMAN_UP:
3973     case EL_PACMAN_LEFT:
3974     case EL_PACMAN_DOWN:
3975       RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
3976       break;
3977
3978     case EL_KEY:
3979       RaiseScore_MM(native_mm_level.score[SC_KEY]);
3980       break;
3981
3982     case EL_KETTLE:
3983     case EL_CELL:
3984       RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
3985       break;
3986
3987     case EL_LIGHTBALL:
3988       RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
3989       break;
3990
3991     default:
3992       break;
3993   }
3994 }
3995
3996
3997 // ----------------------------------------------------------------------------
3998 // Mirror Magic game engine snapshot handling functions
3999 // ----------------------------------------------------------------------------
4000
4001 void SaveEngineSnapshotValues_MM(void)
4002 {
4003   int x, y;
4004
4005   engine_snapshot_mm.game_mm = game_mm;
4006   engine_snapshot_mm.laser = laser;
4007
4008   for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4009   {
4010     for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4011     {
4012       engine_snapshot_mm.Ur[x][y]    = Ur[x][y];
4013       engine_snapshot_mm.Hit[x][y]   = Hit[x][y];
4014       engine_snapshot_mm.Box[x][y]   = Box[x][y];
4015       engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4016     }
4017   }
4018
4019   engine_snapshot_mm.LX = LX;
4020   engine_snapshot_mm.LY = LY;
4021   engine_snapshot_mm.XS = XS;
4022   engine_snapshot_mm.YS = YS;
4023   engine_snapshot_mm.ELX = ELX;
4024   engine_snapshot_mm.ELY = ELY;
4025   engine_snapshot_mm.CT = CT;
4026   engine_snapshot_mm.Ct = Ct;
4027
4028   engine_snapshot_mm.last_LX = last_LX;
4029   engine_snapshot_mm.last_LY = last_LY;
4030   engine_snapshot_mm.last_hit_mask = last_hit_mask;
4031   engine_snapshot_mm.hold_x = hold_x;
4032   engine_snapshot_mm.hold_y = hold_y;
4033   engine_snapshot_mm.pacman_nr = pacman_nr;
4034
4035   engine_snapshot_mm.rotate_delay = rotate_delay;
4036   engine_snapshot_mm.pacman_delay = pacman_delay;
4037   engine_snapshot_mm.energy_delay = energy_delay;
4038   engine_snapshot_mm.overload_delay = overload_delay;
4039 }
4040
4041 void LoadEngineSnapshotValues_MM(void)
4042 {
4043   int x, y;
4044
4045   // stored engine snapshot buffers already restored at this point
4046
4047   game_mm = engine_snapshot_mm.game_mm;
4048   laser   = engine_snapshot_mm.laser;
4049
4050   for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4051   {
4052     for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4053     {
4054       Ur[x][y]    = engine_snapshot_mm.Ur[x][y];
4055       Hit[x][y]   = engine_snapshot_mm.Hit[x][y];
4056       Box[x][y]   = engine_snapshot_mm.Box[x][y];
4057       Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4058     }
4059   }
4060
4061   LX  = engine_snapshot_mm.LX;
4062   LY  = engine_snapshot_mm.LY;
4063   XS  = engine_snapshot_mm.XS;
4064   YS  = engine_snapshot_mm.YS;
4065   ELX = engine_snapshot_mm.ELX;
4066   ELY = engine_snapshot_mm.ELY;
4067   CT  = engine_snapshot_mm.CT;
4068   Ct  = engine_snapshot_mm.Ct;
4069
4070   last_LX       = engine_snapshot_mm.last_LX;
4071   last_LY       = engine_snapshot_mm.last_LY;
4072   last_hit_mask = engine_snapshot_mm.last_hit_mask;
4073   hold_x        = engine_snapshot_mm.hold_x;
4074   hold_y        = engine_snapshot_mm.hold_y;
4075   pacman_nr     = engine_snapshot_mm.pacman_nr;
4076
4077   rotate_delay   = engine_snapshot_mm.rotate_delay;
4078   pacman_delay   = engine_snapshot_mm.pacman_delay;
4079   energy_delay   = engine_snapshot_mm.energy_delay;
4080   overload_delay = engine_snapshot_mm.overload_delay;
4081
4082   RedrawPlayfield_MM();
4083 }
4084
4085 static int getAngleFromTouchDelta(int dx, int dy,  int base)
4086 {
4087   double pi = 3.141592653;
4088   double rad = atan2((double)-dy, (double)dx);
4089   double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4090   double deg = rad2 * 180.0 / pi;
4091
4092   return (int)(deg * base / 360.0 + 0.5) % base;
4093 }
4094
4095 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4096 {
4097   // calculate start (source) position to be at the middle of the tile
4098   int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4099   int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4100   int dx = dst_mx - src_mx;
4101   int dy = dst_my - src_my;
4102   int element;
4103   int base = 16;
4104   int phases = 16;
4105   int angle_old = -1;
4106   int angle_new = -1;
4107   int button = 0;
4108   int i;
4109
4110   if (!IN_LEV_FIELD(x, y))
4111     return 0;
4112
4113   element = Tile[x][y];
4114
4115   if (!IS_MCDUFFIN(element) &&
4116       !IS_MIRROR(element) &&
4117       !IS_BEAMER(element) &&
4118       !IS_POLAR(element) &&
4119       !IS_POLAR_CROSS(element) &&
4120       !IS_DF_MIRROR(element))
4121     return 0;
4122
4123   angle_old = get_element_angle(element);
4124
4125   if (IS_MCDUFFIN(element))
4126   {
4127     angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4128                  dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4129                  dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4130                  dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4131                  -1);
4132   }
4133   else if (IS_MIRROR(element) ||
4134            IS_DF_MIRROR(element))
4135   {
4136     for (i = 0; i < laser.num_damages; i++)
4137     {
4138       if (laser.damage[i].x == x &&
4139           laser.damage[i].y == y &&
4140           ObjHit(x, y, HIT_POS_CENTER))
4141       {
4142         angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4143         angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4144
4145         break;
4146       }
4147     }
4148   }
4149
4150   if (angle_new == -1)
4151   {
4152     if (IS_MIRROR(element) ||
4153         IS_DF_MIRROR(element) ||
4154         IS_POLAR(element))
4155       base = 32;
4156
4157     if (IS_POLAR_CROSS(element))
4158       phases = 4;
4159
4160     angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4161   }
4162
4163   button = (angle_new == angle_old ? 0 :
4164             (angle_new - angle_old + phases) % phases < (phases / 2) ?
4165             MB_LEFTBUTTON : MB_RIGHTBUTTON);
4166
4167   return button;
4168 }