fixed bug with laser passing between two diagonally placed DF polarizers
[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       // check if laser is still inside visible playfield area
1077       if (cSX + LX >= REAL_SX && cSX + LX < REAL_SX + FULL_SXSIZE &&
1078           cSY + LY >= REAL_SY && cSY + LY < REAL_SY + FULL_SYSIZE)
1079       {
1080         // go on with another step
1081         LX += XS;
1082         LY += YS;
1083
1084         continue;
1085       }
1086
1087       element = EL_EMPTY;
1088       laser.dest_element = element;
1089
1090       break;
1091     }
1092
1093     // check if laser scan has hit two diagonally adjacent element corners
1094     boolean diag_1 = ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1);
1095     boolean diag_2 = ((hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2);
1096
1097     // check if laser scan has crossed element boundaries (not just mini tiles)
1098     boolean cross_x = (LX / TILEX != (LX + 2) / TILEX);
1099     boolean cross_y = (LY / TILEY != (LY + 2) / TILEY);
1100
1101     // handle special case of laser hitting two diagonally adjacent elements
1102     // (with or without a third corner element behind these two elements)
1103     if ((diag_1 || diag_2) && cross_x && cross_y)
1104     {
1105       // compare the two diagonally adjacent elements
1106       int xoffset = 2;
1107       int yoffset = 2 * (diag_1 ? -1 : +1);
1108       int elx1 = (LX - xoffset) / TILEX;
1109       int ely1 = (LY + yoffset) / TILEY;
1110       int elx2 = (LX + xoffset) / TILEX;
1111       int ely2 = (LY - yoffset) / TILEY;
1112       int e1 = Tile[elx1][ely1];
1113       int e2 = Tile[elx2][ely2];
1114       boolean use_element_1 = FALSE;
1115
1116       if (IS_WALL_ICE(e1) || IS_WALL_ICE(e2))
1117       {
1118         if (IS_WALL_ICE(e1) && IS_WALL_ICE(e2))
1119           use_element_1 = (RND(2) ? TRUE : FALSE);
1120         else if (IS_WALL_ICE(e1))
1121           use_element_1 = TRUE;
1122       }
1123       else if (IS_WALL_AMOEBA(e1) || IS_WALL_AMOEBA(e2))
1124       {
1125         // if both tiles match, we can just select the first one
1126         if (IS_WALL_AMOEBA(e1))
1127           use_element_1 = TRUE;
1128       }
1129       else if (IS_ABSORBING_BLOCK(e1) || IS_ABSORBING_BLOCK(e2))
1130       {
1131         // if both tiles match, we can just select the first one
1132         if (IS_ABSORBING_BLOCK(e1))
1133           use_element_1 = TRUE;
1134       }
1135
1136       ELX = (use_element_1 ? elx1 : elx2);
1137       ELY = (use_element_1 ? ely1 : ely2);
1138     }
1139
1140 #if 0
1141     Debug("game:mm:ScanLaser", "hit_mask (2) == '%x' (%d, %d) (%d, %d)",
1142           hit_mask, LX, LY, ELX, ELY);
1143 #endif
1144
1145     last_element = element;
1146
1147     element = Tile[ELX][ELY];
1148     laser.dest_element = element;
1149
1150 #if 0
1151     Debug("game:mm:ScanLaser",
1152           "Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]",
1153           element, ELX, ELY,
1154           LX, LY,
1155           LX % TILEX, LY % TILEY,
1156           hit_mask);
1157 #endif
1158
1159 #if 0
1160     if (!IN_LEV_FIELD(ELX, ELY))
1161       Debug("game:mm:ScanLaser", "WARNING! (1) %d, %d (%d)",
1162             ELX, ELY, element);
1163 #endif
1164
1165     // special case: leaving fixed MM steel grid (upwards) with non-90° angle
1166     if (element == EL_EMPTY &&
1167         IS_GRID_STEEL(last_element) &&
1168         laser.current_angle % 4)                // angle is not 90°
1169       element = last_element;
1170
1171     if (element == EL_EMPTY)
1172     {
1173       if (!HitOnlyAnEdge(hit_mask))
1174         break;
1175     }
1176     else if (element == EL_FUSE_ON)
1177     {
1178       if (HitPolarizer(element, hit_mask))
1179         break;
1180     }
1181     else if (IS_GRID(element) || IS_DF_GRID(element))
1182     {
1183       if (HitPolarizer(element, hit_mask))
1184         break;
1185     }
1186     else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
1187              element == EL_GATE_STONE || element == EL_GATE_WOOD)
1188     {
1189       if (HitBlock(element, hit_mask))
1190       {
1191         rf = 1;
1192
1193         break;
1194       }
1195     }
1196     else if (IS_MCDUFFIN(element))
1197     {
1198       if (HitLaserSource(element, hit_mask))
1199         break;
1200     }
1201     else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
1202              IS_RECEIVER(element))
1203     {
1204       if (HitLaserDestination(element, hit_mask))
1205         break;
1206     }
1207     else if (IS_WALL(element))
1208     {
1209       if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
1210       {
1211         if (HitReflectingWalls(element, hit_mask))
1212           break;
1213       }
1214       else
1215       {
1216         if (HitAbsorbingWalls(element, hit_mask))
1217           break;
1218       }
1219     }
1220     else
1221     {
1222       if (HitElement(element, hit_mask))
1223         break;
1224     }
1225
1226     if (rf)
1227       DrawLaser(rf - 1, DL_LASER_ENABLED);
1228     rf = laser.num_edges;
1229
1230     if (!IS_DF_WALL_STEEL(element))
1231     {
1232       // only used for scanning DF steel walls; reset for all other elements
1233       last_LX = 0;
1234       last_LY = 0;
1235       last_hit_mask = 0;
1236     }
1237   }
1238
1239 #if 0
1240   if (laser.dest_element != Tile[ELX][ELY])
1241   {
1242     Debug("game:mm:ScanLaser",
1243           "ALARM: laser.dest_element == %d, Tile[ELX][ELY] == %d",
1244           laser.dest_element, Tile[ELX][ELY]);
1245   }
1246 #endif
1247
1248   if (!end && !laser.stops_inside_element && !StepBehind())
1249   {
1250 #if 0
1251     Debug("game:mm:ScanLaser", "Go one step back");
1252 #endif
1253
1254     LX -= XS;
1255     LY -= YS;
1256
1257     AddLaserEdge(LX, LY);
1258   }
1259
1260   if (rf)
1261     DrawLaser(rf - 1, DL_LASER_ENABLED);
1262
1263   Ct = CT = FrameCounter;
1264
1265 #if 0
1266     if (!IN_LEV_FIELD(ELX, ELY))
1267       Debug("game:mm:ScanLaser", "WARNING! (2) %d, %d", ELX, ELY);
1268 #endif
1269 }
1270
1271 static void ScanLaser_FromLastMirror(void)
1272 {
1273   int start_pos = (laser.num_damages > 0 ? laser.num_damages - 1 : 0);
1274   int i;
1275
1276   for (i = start_pos; i >= 0; i--)
1277     if (laser.damage[i].is_mirror)
1278       break;
1279
1280   int start_edge = (i > 0 ? laser.damage[i].edge - 1 : 0);
1281
1282   DrawLaser(start_edge, DL_LASER_DISABLED);
1283
1284   ScanLaser();
1285 }
1286
1287 static void DrawLaserExt(int start_edge, int num_edges, int mode)
1288 {
1289   int element;
1290   int elx, ely;
1291
1292 #if 0
1293   Debug("game:mm:DrawLaserExt", "start_edge, num_edges, mode == %d, %d, %d",
1294         start_edge, num_edges, mode);
1295 #endif
1296
1297   if (start_edge < 0)
1298   {
1299     Warn("DrawLaserExt: start_edge < 0");
1300
1301     return;
1302   }
1303
1304   if (num_edges < 0)
1305   {
1306     Warn("DrawLaserExt: num_edges < 0");
1307
1308     return;
1309   }
1310
1311 #if 0
1312   if (mode == DL_LASER_DISABLED)
1313   {
1314     Debug("game:mm:DrawLaserExt", "Delete laser from edge %d", start_edge);
1315   }
1316 #endif
1317
1318   // now draw the laser to the backbuffer and (if enabled) to the screen
1319   DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1320
1321   redraw_mask |= REDRAW_FIELD;
1322
1323   if (mode == DL_LASER_ENABLED)
1324     return;
1325
1326   // after the laser was deleted, the "damaged" graphics must be restored
1327   if (laser.num_damages)
1328   {
1329     int damage_start = 0;
1330     int i;
1331
1332     // determine the starting edge, from which graphics need to be restored
1333     if (start_edge > 0)
1334     {
1335       for (i = 0; i < laser.num_damages; i++)
1336       {
1337         if (laser.damage[i].edge == start_edge + 1)
1338         {
1339           damage_start = i;
1340
1341           break;
1342         }
1343       }
1344     }
1345
1346     // restore graphics from this starting edge to the end of damage list
1347     for (i = damage_start; i < laser.num_damages; i++)
1348     {
1349       int lx = laser.damage[i].x;
1350       int ly = laser.damage[i].y;
1351       int element = Tile[lx][ly];
1352
1353       if (Hit[lx][ly] == laser.damage[i].edge)
1354         if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1355                i == damage_start))
1356           Hit[lx][ly] = 0;
1357       if (Box[lx][ly] == laser.damage[i].edge)
1358         Box[lx][ly] = 0;
1359
1360       if (IS_DRAWABLE(element))
1361         DrawField_MM(lx, ly);
1362     }
1363
1364     elx = laser.damage[damage_start].x;
1365     ely = laser.damage[damage_start].y;
1366     element = Tile[elx][ely];
1367
1368 #if 0
1369     if (IS_BEAMER(element))
1370     {
1371       int i;
1372
1373       for (i = 0; i < laser.num_beamers; i++)
1374         Debug("game:mm:DrawLaserExt", "-> %d", laser.beamer_edge[i]);
1375
1376       Debug("game:mm:DrawLaserExt", "IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]",
1377             mode, elx, ely, Hit[elx][ely], start_edge);
1378       Debug("game:mm:DrawLaserExt", "IS_BEAMER: %d / %d",
1379             get_element_angle(element), laser.damage[damage_start].angle);
1380     }
1381 #endif
1382
1383     if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1384         laser.num_beamers > 0 &&
1385         start_edge == laser.beamer_edge[laser.num_beamers - 1])
1386     {
1387       // element is outgoing beamer
1388       laser.num_damages = damage_start + 1;
1389
1390       if (IS_BEAMER(element))
1391         laser.current_angle = get_element_angle(element);
1392     }
1393     else
1394     {
1395       // element is incoming beamer or other element
1396       laser.num_damages = damage_start;
1397       laser.current_angle = laser.damage[laser.num_damages].angle;
1398     }
1399   }
1400   else
1401   {
1402     // no damages but McDuffin himself (who needs to be redrawn anyway)
1403
1404     elx = laser.start_edge.x;
1405     ely = laser.start_edge.y;
1406     element = Tile[elx][ely];
1407   }
1408
1409   laser.num_edges = start_edge + 1;
1410   if (start_edge == 0)
1411     laser.current_angle = laser.start_angle;
1412
1413   LX = laser.edge[start_edge].x - cSX2;
1414   LY = laser.edge[start_edge].y - cSY2;
1415   XS = 2 * Step[laser.current_angle].x;
1416   YS = 2 * Step[laser.current_angle].y;
1417
1418 #if 0
1419   Debug("game:mm:DrawLaserExt", "Set (LX, LY) to (%d, %d) [%d]",
1420         LX, LY, element);
1421 #endif
1422
1423   if (start_edge > 0)
1424   {
1425     if (IS_BEAMER(element) ||
1426         IS_FIBRE_OPTIC(element) ||
1427         IS_PACMAN(element) ||
1428         IS_POLAR(element) ||
1429         IS_POLAR_CROSS(element) ||
1430         element == EL_FUSE_ON)
1431     {
1432       int step_size;
1433
1434 #if 0
1435       Debug("game:mm:DrawLaserExt", "element == %d", element);
1436 #endif
1437
1438       if (IS_22_5_ANGLE(laser.current_angle))   // neither 90° nor 45° angle
1439         step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1440       else
1441         step_size = 8;
1442
1443       if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1444           ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1445            (laser.num_beamers == 0 ||
1446             start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1447       {
1448         // element is incoming beamer or other element
1449         step_size = -step_size;
1450         laser.num_edges--;
1451       }
1452
1453 #if 0
1454       if (IS_BEAMER(element))
1455         Debug("game:mm:DrawLaserExt",
1456               "start_edge == %d, laser.beamer_edge == %d",
1457               start_edge, laser.beamer_edge);
1458 #endif
1459
1460       LX += step_size * XS;
1461       LY += step_size * YS;
1462     }
1463     else if (element != EL_EMPTY)
1464     {
1465       LX -= 3 * XS;
1466       LY -= 3 * YS;
1467       laser.num_edges--;
1468     }
1469   }
1470
1471 #if 0
1472   Debug("game:mm:DrawLaserExt", "Finally: (LX, LY) to (%d, %d) [%d]",
1473         LX, LY, element);
1474 #endif
1475 }
1476
1477 void DrawLaser(int start_edge, int mode)
1478 {
1479   // do not draw laser if fuse is off
1480   if (laser.fuse_off && mode == DL_LASER_ENABLED)
1481     return;
1482
1483   if (mode == DL_LASER_DISABLED)
1484     DeactivateLaserTargetElement();
1485
1486   if (laser.num_edges - start_edge < 0)
1487   {
1488     Warn("DrawLaser: laser.num_edges - start_edge < 0");
1489
1490     return;
1491   }
1492
1493   // check if laser is interrupted by beamer element
1494   if (laser.num_beamers > 0 &&
1495       start_edge < laser.beamer_edge[laser.num_beamers - 1])
1496   {
1497     if (mode == DL_LASER_ENABLED)
1498     {
1499       int i;
1500       int tmp_start_edge = start_edge;
1501
1502       // draw laser segments forward from the start to the last beamer
1503       for (i = 0; i < laser.num_beamers; i++)
1504       {
1505         int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1506
1507         if (tmp_num_edges <= 0)
1508           continue;
1509
1510 #if 0
1511         Debug("game:mm:DrawLaser", "DL_LASER_ENABLED: i==%d: %d, %d",
1512               i, laser.beamer_edge[i], tmp_start_edge);
1513 #endif
1514
1515         DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1516
1517         tmp_start_edge = laser.beamer_edge[i];
1518       }
1519
1520       // draw last segment from last beamer to the end
1521       DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1522                    DL_LASER_ENABLED);
1523     }
1524     else
1525     {
1526       int i;
1527       int last_num_edges = laser.num_edges;
1528       int num_beamers = laser.num_beamers;
1529
1530       // delete laser segments backward from the end to the first beamer
1531       for (i = num_beamers - 1; i >= 0; i--)
1532       {
1533         int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1534
1535         if (laser.beamer_edge[i] - start_edge <= 0)
1536           break;
1537
1538         DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1539
1540         last_num_edges = laser.beamer_edge[i];
1541         laser.num_beamers--;
1542       }
1543
1544 #if 0
1545       if (last_num_edges - start_edge <= 0)
1546         Debug("game:mm:DrawLaser", "DL_LASER_DISABLED: %d, %d",
1547               last_num_edges, start_edge);
1548 #endif
1549
1550       // special case when rotating first beamer: delete laser edge on beamer
1551       // (but do not start scanning on previous edge to prevent mirror sound)
1552       if (last_num_edges - start_edge == 1 && start_edge > 0)
1553         DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1554
1555       // delete first segment from start to the first beamer
1556       DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1557     }
1558   }
1559   else
1560   {
1561     DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1562   }
1563
1564   game_mm.laser_enabled = mode;
1565 }
1566
1567 void DrawLaser_MM(void)
1568 {
1569   DrawLaser(0, game_mm.laser_enabled);
1570 }
1571
1572 static boolean HitElement(int element, int hit_mask)
1573 {
1574   if (HitOnlyAnEdge(hit_mask))
1575     return FALSE;
1576
1577   if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1578     element = MovingOrBlocked2Element_MM(ELX, ELY);
1579
1580 #if 0
1581   Debug("game:mm:HitElement", "(1): element == %d", element);
1582 #endif
1583
1584 #if 0
1585   if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1586     Debug("game:mm:HitElement", "(%d): EXACT MATCH @ (%d, %d)",
1587           element, ELX, ELY);
1588   else
1589     Debug("game:mm:HitElement", "(%d): FUZZY MATCH @ (%d, %d)",
1590           element, ELX, ELY);
1591 #endif
1592
1593   AddDamagedField(ELX, ELY);
1594
1595   // this is more precise: check if laser would go through the center
1596   if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1597   {
1598     int skip_count = 0;
1599
1600     // prevent cutting through laser emitter with laser beam
1601     if (IS_LASER(element))
1602       return TRUE;
1603
1604     // skip the whole element before continuing the scan
1605     do
1606     {
1607       LX += XS;
1608       LY += YS;
1609
1610       skip_count++;
1611     }
1612     while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1613
1614     if ((LX/TILEX > ELX || LY/TILEY > ELY) && skip_count > 1)
1615     {
1616       /* skipping scan positions to the right and down skips one scan
1617          position too much, because this is only the top left scan position
1618          of totally four scan positions (plus one to the right, one to the
1619          bottom and one to the bottom right) */
1620       /* ... but only roll back scan position if more than one step done */
1621
1622       LX -= XS;
1623       LY -= YS;
1624     }
1625
1626     return FALSE;
1627   }
1628
1629 #if 0
1630   Debug("game:mm:HitElement", "(2): element == %d", element);
1631 #endif
1632
1633   if (LX + 5 * XS < 0 ||
1634       LY + 5 * YS < 0)
1635   {
1636     LX += 2 * XS;
1637     LY += 2 * YS;
1638
1639     return FALSE;
1640   }
1641
1642 #if 0
1643   Debug("game:mm:HitElement", "(3): element == %d", element);
1644 #endif
1645
1646   if (IS_POLAR(element) &&
1647       ((element - EL_POLAR_START) % 2 ||
1648        (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1649   {
1650     PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1651
1652     laser.num_damages--;
1653
1654     return TRUE;
1655   }
1656
1657   if (IS_POLAR_CROSS(element) &&
1658       (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1659   {
1660     PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1661
1662     laser.num_damages--;
1663
1664     return TRUE;
1665   }
1666
1667   if (!IS_BEAMER(element) &&
1668       !IS_FIBRE_OPTIC(element) &&
1669       !IS_GRID_WOOD(element) &&
1670       element != EL_FUEL_EMPTY)
1671   {
1672 #if 0
1673     if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1674       Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
1675     else
1676       Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
1677 #endif
1678
1679     LX = ELX * TILEX + 14;
1680     LY = ELY * TILEY + 14;
1681
1682     AddLaserEdge(LX, LY);
1683   }
1684
1685   if (IS_MIRROR(element) ||
1686       IS_MIRROR_FIXED(element) ||
1687       IS_POLAR(element) ||
1688       IS_POLAR_CROSS(element) ||
1689       IS_DF_MIRROR(element) ||
1690       IS_DF_MIRROR_AUTO(element) ||
1691       element == EL_PRISM ||
1692       element == EL_REFRACTOR)
1693   {
1694     int current_angle = laser.current_angle;
1695     int step_size;
1696
1697     laser.num_damages--;
1698
1699     AddDamagedField(ELX, ELY);
1700
1701     laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1702
1703     if (!Hit[ELX][ELY])
1704       Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1705
1706     if (IS_MIRROR(element) ||
1707         IS_MIRROR_FIXED(element) ||
1708         IS_DF_MIRROR(element) ||
1709         IS_DF_MIRROR_AUTO(element))
1710       laser.current_angle = get_mirrored_angle(laser.current_angle,
1711                                                get_element_angle(element));
1712
1713     if (element == EL_PRISM || element == EL_REFRACTOR)
1714       laser.current_angle = RND(16);
1715
1716     XS = 2 * Step[laser.current_angle].x;
1717     YS = 2 * Step[laser.current_angle].y;
1718
1719     if (!IS_22_5_ANGLE(laser.current_angle))    // 90° or 45° angle
1720       step_size = 8;
1721     else
1722       step_size = 4;
1723
1724     LX += step_size * XS;
1725     LY += step_size * YS;
1726
1727     // draw sparkles on mirror
1728     if ((IS_MIRROR(element) ||
1729          IS_MIRROR_FIXED(element) ||
1730          element == EL_PRISM) &&
1731         current_angle != laser.current_angle)
1732     {
1733       MovDelay[ELX][ELY] = 11;          // start animation
1734     }
1735
1736     if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1737         current_angle != laser.current_angle)
1738       PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1739
1740     laser.overloaded =
1741       (get_opposite_angle(laser.current_angle) ==
1742        laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1743
1744     return (laser.overloaded ? TRUE : FALSE);
1745   }
1746
1747   if (element == EL_FUEL_FULL)
1748   {
1749     laser.stops_inside_element = TRUE;
1750
1751     return TRUE;
1752   }
1753
1754   if (element == EL_BOMB || element == EL_MINE || element == EL_GRAY_BALL)
1755   {
1756     PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1757
1758     Tile[ELX][ELY] = (element == EL_BOMB ? EL_BOMB_ACTIVE :
1759                       element == EL_MINE ? EL_MINE_ACTIVE :
1760                       EL_GRAY_BALL_ACTIVE);
1761
1762     GfxFrame[ELX][ELY] = 0;             // restart animation
1763
1764     laser.dest_element_last = Tile[ELX][ELY];
1765     laser.dest_element_last_x = ELX;
1766     laser.dest_element_last_y = ELY;
1767
1768     if (element == EL_MINE)
1769       laser.overloaded = TRUE;
1770   }
1771
1772   if (element == EL_KETTLE ||
1773       element == EL_CELL ||
1774       element == EL_KEY ||
1775       element == EL_LIGHTBALL ||
1776       element == EL_PACMAN ||
1777       IS_PACMAN(element) ||
1778       IS_ENVELOPE(element))
1779   {
1780     if (!IS_PACMAN(element) &&
1781         !IS_ENVELOPE(element))
1782       Bang_MM(ELX, ELY);
1783
1784     if (element == EL_PACMAN)
1785       Bang_MM(ELX, ELY);
1786
1787     if (element == EL_KETTLE || element == EL_CELL)
1788     {
1789       if (game_mm.kettles_still_needed > 0)
1790         game_mm.kettles_still_needed--;
1791
1792       game.snapshot.collected_item = TRUE;
1793
1794       if (game_mm.kettles_still_needed == 0)
1795       {
1796         CheckExitMM();
1797
1798         DrawLaser(0, DL_LASER_ENABLED);
1799       }
1800     }
1801     else if (element == EL_KEY)
1802     {
1803       game_mm.num_keys++;
1804     }
1805     else if (IS_PACMAN(element))
1806     {
1807       DeletePacMan(ELX, ELY);
1808     }
1809     else if (IS_ENVELOPE(element))
1810     {
1811       Tile[ELX][ELY] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(Tile[ELX][ELY]);
1812     }
1813
1814     RaiseScoreElement_MM(element);
1815
1816     return FALSE;
1817   }
1818
1819   if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1820   {
1821     PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1822
1823     DrawLaser(0, DL_LASER_ENABLED);
1824
1825     if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
1826     {
1827       Tile[ELX][ELY] = EL_LIGHTBULB_ON;
1828       game_mm.lights_still_needed--;
1829     }
1830     else
1831     {
1832       Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
1833       game_mm.lights_still_needed++;
1834     }
1835
1836     DrawField_MM(ELX, ELY);
1837     DrawLaser(0, DL_LASER_ENABLED);
1838
1839     /*
1840     BackToFront();
1841     */
1842     laser.stops_inside_element = TRUE;
1843
1844     return TRUE;
1845   }
1846
1847 #if 0
1848   Debug("game:mm:HitElement", "(4): element == %d", element);
1849 #endif
1850
1851   if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1852       laser.num_beamers < MAX_NUM_BEAMERS &&
1853       laser.beamer[BEAMER_NR(element)][1].num)
1854   {
1855     int beamer_angle = get_element_angle(element);
1856     int beamer_nr = BEAMER_NR(element);
1857     int step_size;
1858
1859 #if 0
1860     Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
1861 #endif
1862
1863     laser.num_damages--;
1864
1865     if (IS_FIBRE_OPTIC(element) ||
1866         laser.current_angle == get_opposite_angle(beamer_angle))
1867     {
1868       int pos;
1869
1870       LX = ELX * TILEX + 14;
1871       LY = ELY * TILEY + 14;
1872
1873       AddLaserEdge(LX, LY);
1874       AddDamagedField(ELX, ELY);
1875
1876       laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1877
1878       if (!Hit[ELX][ELY])
1879         Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1880
1881       pos = (ELX == laser.beamer[beamer_nr][0].x &&
1882              ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1883       ELX = laser.beamer[beamer_nr][pos].x;
1884       ELY = laser.beamer[beamer_nr][pos].y;
1885       LX = ELX * TILEX + 14;
1886       LY = ELY * TILEY + 14;
1887
1888       if (IS_BEAMER(element))
1889       {
1890         laser.current_angle = get_element_angle(Tile[ELX][ELY]);
1891         XS = 2 * Step[laser.current_angle].x;
1892         YS = 2 * Step[laser.current_angle].y;
1893       }
1894
1895       laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1896
1897       AddLaserEdge(LX, LY);
1898       AddDamagedField(ELX, ELY);
1899
1900       laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1901
1902       if (!Hit[ELX][ELY])
1903         Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1904
1905       if (laser.current_angle == (laser.current_angle >> 1) << 1)
1906         step_size = 8;
1907       else
1908         step_size = 4;
1909
1910       LX += step_size * XS;
1911       LY += step_size * YS;
1912
1913       laser.num_beamers++;
1914
1915       return FALSE;
1916     }
1917   }
1918
1919   return TRUE;
1920 }
1921
1922 static boolean HitOnlyAnEdge(int hit_mask)
1923 {
1924   // check if the laser hit only the edge of an element and, if so, go on
1925
1926 #if 0
1927   Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
1928         LX, LY, hit_mask);
1929 #endif
1930
1931   if ((hit_mask == HIT_MASK_TOPLEFT ||
1932        hit_mask == HIT_MASK_TOPRIGHT ||
1933        hit_mask == HIT_MASK_BOTTOMLEFT ||
1934        hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1935       laser.current_angle % 4)                  // angle is not 90°
1936   {
1937     int dx, dy;
1938
1939     if (hit_mask == HIT_MASK_TOPLEFT)
1940     {
1941       dx = -1;
1942       dy = -1;
1943     }
1944     else if (hit_mask == HIT_MASK_TOPRIGHT)
1945     {
1946       dx = +1;
1947       dy = -1;
1948     }
1949     else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1950     {
1951       dx = -1;
1952       dy = +1;
1953     }
1954     else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
1955     {
1956       dx = +1;
1957       dy = +1;
1958     }
1959
1960     AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1961
1962     LX += XS;
1963     LY += YS;
1964
1965 #if 0
1966     Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
1967 #endif
1968
1969     return TRUE;
1970   }
1971
1972 #if 0
1973   Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
1974 #endif
1975
1976   return FALSE;
1977 }
1978
1979 static boolean HitPolarizer(int element, int hit_mask)
1980 {
1981   if (HitOnlyAnEdge(hit_mask))
1982     return FALSE;
1983
1984   if (IS_DF_GRID(element))
1985   {
1986     int grid_angle = get_element_angle(element);
1987
1988 #if 0
1989     Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
1990           grid_angle, laser.current_angle);
1991 #endif
1992
1993     AddLaserEdge(LX, LY);
1994     AddDamagedField(ELX, ELY);
1995
1996     if (!Hit[ELX][ELY])
1997       Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1998
1999     if (laser.current_angle == grid_angle ||
2000         laser.current_angle == get_opposite_angle(grid_angle))
2001     {
2002       // skip the whole element before continuing the scan
2003       do
2004       {
2005         LX += XS;
2006         LY += YS;
2007       }
2008       while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
2009
2010       if (LX/TILEX > ELX || LY/TILEY > ELY)
2011       {
2012         /* skipping scan positions to the right and down skips one scan
2013            position too much, because this is only the top left scan position
2014            of totally four scan positions (plus one to the right, one to the
2015            bottom and one to the bottom right) */
2016
2017         LX -= XS;
2018         LY -= YS;
2019       }
2020
2021       AddLaserEdge(LX, LY);
2022
2023       LX += XS;
2024       LY += YS;
2025
2026 #if 0
2027       Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
2028             LX, LY,
2029             LX / TILEX, LY / TILEY,
2030             LX % TILEX, LY % TILEY);
2031 #endif
2032
2033       return FALSE;
2034     }
2035     else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
2036     {
2037       return HitReflectingWalls(element, hit_mask);
2038     }
2039     else
2040     {
2041       return HitAbsorbingWalls(element, hit_mask);
2042     }
2043   }
2044   else if (IS_GRID_STEEL(element))
2045   {
2046     // may be required if graphics for steel grid redefined
2047     AddDamagedField(ELX, ELY);
2048
2049     return HitReflectingWalls(element, hit_mask);
2050   }
2051   else  // IS_GRID_WOOD
2052   {
2053     // may be required if graphics for wooden grid redefined
2054     AddDamagedField(ELX, ELY);
2055
2056     return HitAbsorbingWalls(element, hit_mask);
2057   }
2058
2059   return TRUE;
2060 }
2061
2062 static boolean HitBlock(int element, int hit_mask)
2063 {
2064   boolean check = FALSE;
2065
2066   if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
2067       game_mm.num_keys == 0)
2068     check = TRUE;
2069
2070   if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2071   {
2072     int i, x, y;
2073     int ex = ELX * TILEX + 14;
2074     int ey = ELY * TILEY + 14;
2075
2076     check = TRUE;
2077
2078     for (i = 1; i < 32; i++)
2079     {
2080       x = LX + i * XS;
2081       y = LY + i * YS;
2082
2083       if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
2084         check = FALSE;
2085     }
2086   }
2087
2088   if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
2089     return HitAbsorbingWalls(element, hit_mask);
2090
2091   if (check)
2092   {
2093     AddLaserEdge(LX - XS, LY - YS);
2094     AddDamagedField(ELX, ELY);
2095
2096     if (!Box[ELX][ELY])
2097       Box[ELX][ELY] = laser.num_edges;
2098
2099     return HitReflectingWalls(element, hit_mask);
2100   }
2101
2102   if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
2103   {
2104     int xs = XS / 2, ys = YS / 2;
2105
2106     if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 ||
2107         (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2)
2108     {
2109       laser.overloaded = (element == EL_GATE_STONE);
2110
2111       return TRUE;
2112     }
2113
2114     if (ABS(xs) == 1 && ABS(ys) == 1 &&
2115         (hit_mask == HIT_MASK_TOP ||
2116          hit_mask == HIT_MASK_LEFT ||
2117          hit_mask == HIT_MASK_RIGHT ||
2118          hit_mask == HIT_MASK_BOTTOM))
2119       AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2120                                   hit_mask == HIT_MASK_BOTTOM),
2121                       ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2122                                   hit_mask == HIT_MASK_RIGHT));
2123     AddLaserEdge(LX, LY);
2124
2125     Bang_MM(ELX, ELY);
2126
2127     game_mm.num_keys--;
2128
2129     if (element == EL_GATE_STONE && Box[ELX][ELY])
2130     {
2131       DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
2132       /*
2133       BackToFront();
2134       */
2135       ScanLaser();
2136
2137       return TRUE;
2138     }
2139
2140     return FALSE;
2141   }
2142
2143   if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2144   {
2145     int xs = XS / 2, ys = YS / 2;
2146
2147     if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 ||
2148         (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2)
2149     {
2150       laser.overloaded = (element == EL_BLOCK_STONE);
2151
2152       return TRUE;
2153     }
2154
2155     if (ABS(xs) == 1 && ABS(ys) == 1 &&
2156         (hit_mask == HIT_MASK_TOP ||
2157          hit_mask == HIT_MASK_LEFT ||
2158          hit_mask == HIT_MASK_RIGHT ||
2159          hit_mask == HIT_MASK_BOTTOM))
2160       AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2161                                   hit_mask == HIT_MASK_BOTTOM),
2162                       ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2163                                   hit_mask == HIT_MASK_RIGHT));
2164     AddDamagedField(ELX, ELY);
2165
2166     LX = ELX * TILEX + 14;
2167     LY = ELY * TILEY + 14;
2168
2169     AddLaserEdge(LX, LY);
2170
2171     laser.stops_inside_element = TRUE;
2172
2173     return TRUE;
2174   }
2175
2176   return TRUE;
2177 }
2178
2179 static boolean HitLaserSource(int element, int hit_mask)
2180 {
2181   if (HitOnlyAnEdge(hit_mask))
2182     return FALSE;
2183
2184   PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2185
2186   laser.overloaded = TRUE;
2187
2188   return TRUE;
2189 }
2190
2191 static boolean HitLaserDestination(int element, int hit_mask)
2192 {
2193   if (HitOnlyAnEdge(hit_mask))
2194     return FALSE;
2195
2196   if (element != EL_EXIT_OPEN &&
2197       !(IS_RECEIVER(element) &&
2198         game_mm.kettles_still_needed == 0 &&
2199         laser.current_angle == get_opposite_angle(get_element_angle(element))))
2200   {
2201     PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2202
2203     return TRUE;
2204   }
2205
2206   if (IS_RECEIVER(element) ||
2207       (IS_22_5_ANGLE(laser.current_angle) &&
2208        (ELX != (LX + 6 * XS) / TILEX ||
2209         ELY != (LY + 6 * YS) / TILEY ||
2210         LX + 6 * XS < 0 ||
2211         LY + 6 * YS < 0)))
2212   {
2213     LX -= XS;
2214     LY -= YS;
2215   }
2216   else
2217   {
2218     LX = ELX * TILEX + 14;
2219     LY = ELY * TILEY + 14;
2220
2221     laser.stops_inside_element = TRUE;
2222   }
2223
2224   AddLaserEdge(LX, LY);
2225   AddDamagedField(ELX, ELY);
2226
2227   if (game_mm.lights_still_needed == 0)
2228   {
2229     game_mm.level_solved = TRUE;
2230
2231     SetTileCursorActive(FALSE);
2232   }
2233
2234   return TRUE;
2235 }
2236
2237 static boolean HitReflectingWalls(int element, int hit_mask)
2238 {
2239   // check if laser hits side of a wall with an angle that is not 90°
2240   if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2241                                             hit_mask == HIT_MASK_LEFT ||
2242                                             hit_mask == HIT_MASK_RIGHT ||
2243                                             hit_mask == HIT_MASK_BOTTOM))
2244   {
2245     PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2246
2247     LX -= XS;
2248     LY -= YS;
2249
2250     if (!IS_DF_GRID(element))
2251       AddLaserEdge(LX, LY);
2252
2253     // check if laser hits wall with an angle of 45°
2254     if (!IS_22_5_ANGLE(laser.current_angle))
2255     {
2256       if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2257       {
2258         LX += 2 * XS;
2259         laser.current_angle = get_mirrored_angle(laser.current_angle,
2260                                                  ANG_MIRROR_0);
2261       }
2262       else      // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2263       {
2264         LY += 2 * YS;
2265         laser.current_angle = get_mirrored_angle(laser.current_angle,
2266                                                  ANG_MIRROR_90);
2267       }
2268
2269       AddLaserEdge(LX, LY);
2270
2271       XS = 2 * Step[laser.current_angle].x;
2272       YS = 2 * Step[laser.current_angle].y;
2273
2274       return FALSE;
2275     }
2276     else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2277     {
2278       laser.current_angle = get_mirrored_angle(laser.current_angle,
2279                                                ANG_MIRROR_0);
2280       if (ABS(XS) == 4)
2281       {
2282         LX += 2 * XS;
2283         if (!IS_DF_GRID(element))
2284           AddLaserEdge(LX, LY);
2285       }
2286       else
2287       {
2288         LX += XS;
2289         if (!IS_DF_GRID(element))
2290           AddLaserEdge(LX, LY + YS / 2);
2291
2292         LX += XS;
2293         if (!IS_DF_GRID(element))
2294           AddLaserEdge(LX, LY);
2295       }
2296
2297       YS = 2 * Step[laser.current_angle].y;
2298
2299       return FALSE;
2300     }
2301     else        // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2302     {
2303       laser.current_angle = get_mirrored_angle(laser.current_angle,
2304                                                ANG_MIRROR_90);
2305       if (ABS(YS) == 4)
2306       {
2307         LY += 2 * YS;
2308         if (!IS_DF_GRID(element))
2309           AddLaserEdge(LX, LY);
2310       }
2311       else
2312       {
2313         LY += YS;
2314         if (!IS_DF_GRID(element))
2315           AddLaserEdge(LX + XS / 2, LY);
2316
2317         LY += YS;
2318         if (!IS_DF_GRID(element))
2319           AddLaserEdge(LX, LY);
2320       }
2321
2322       XS = 2 * Step[laser.current_angle].x;
2323
2324       return FALSE;
2325     }
2326   }
2327
2328   // reflection at the edge of reflecting DF style wall
2329   if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2330   {
2331     if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2332          hit_mask == HIT_MASK_TOPRIGHT) ||
2333         ((laser.current_angle == 5 || laser.current_angle == 7) &&
2334          hit_mask == HIT_MASK_TOPLEFT) ||
2335         ((laser.current_angle == 9 || laser.current_angle == 11) &&
2336          hit_mask == HIT_MASK_BOTTOMLEFT) ||
2337         ((laser.current_angle == 13 || laser.current_angle == 15) &&
2338          hit_mask == HIT_MASK_BOTTOMRIGHT))
2339     {
2340       int mirror_angle =
2341         (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2342          ANG_MIRROR_135 : ANG_MIRROR_45);
2343
2344       PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2345
2346       AddDamagedField(ELX, ELY);
2347       AddLaserEdge(LX, LY);
2348
2349       laser.current_angle = get_mirrored_angle(laser.current_angle,
2350                                                mirror_angle);
2351       XS = 8 / -XS;
2352       YS = 8 / -YS;
2353
2354       LX += XS;
2355       LY += YS;
2356
2357       AddLaserEdge(LX, LY);
2358
2359       return FALSE;
2360     }
2361   }
2362
2363   // reflection inside an edge of reflecting DF style wall
2364   if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2365   {
2366     if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2367          hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2368         ((laser.current_angle == 5 || laser.current_angle == 7) &&
2369          hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2370         ((laser.current_angle == 9 || laser.current_angle == 11) &&
2371          hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2372         ((laser.current_angle == 13 || laser.current_angle == 15) &&
2373          hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2374     {
2375       int mirror_angle =
2376         (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2377          hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2378          ANG_MIRROR_135 : ANG_MIRROR_45);
2379
2380       PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2381
2382       /*
2383       AddDamagedField(ELX, ELY);
2384       */
2385
2386       AddLaserEdge(LX - XS, LY - YS);
2387       AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2388                    LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2389
2390       laser.current_angle = get_mirrored_angle(laser.current_angle,
2391                                                mirror_angle);
2392       XS = 8 / -XS;
2393       YS = 8 / -YS;
2394
2395       LX += XS;
2396       LY += YS;
2397
2398       AddLaserEdge(LX, LY);
2399
2400       return FALSE;
2401     }
2402   }
2403
2404   // check if laser hits DF style wall with an angle of 90°
2405   if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2406   {
2407     if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2408          (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2409         (IS_VERT_ANGLE(laser.current_angle) &&
2410          (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2411     {
2412       // laser at last step touched nothing or the same side of the wall
2413       if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2414       {
2415         AddDamagedField(ELX, ELY);
2416
2417         LX += 8 * XS;
2418         LY += 8 * YS;
2419
2420         last_LX = LX;
2421         last_LY = LY;
2422         last_hit_mask = hit_mask;
2423
2424         return FALSE;
2425       }
2426     }
2427   }
2428
2429   if (!HitOnlyAnEdge(hit_mask))
2430   {
2431     laser.overloaded = TRUE;
2432
2433     return TRUE;
2434   }
2435
2436   return FALSE;
2437 }
2438
2439 static boolean HitAbsorbingWalls(int element, int hit_mask)
2440 {
2441   if (HitOnlyAnEdge(hit_mask))
2442     return FALSE;
2443
2444   if (ABS(XS) == 4 &&
2445       (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2446   {
2447     AddLaserEdge(LX - XS, LY - YS);
2448
2449     LX = LX + XS / 2;
2450     LY = LY + YS;
2451   }
2452
2453   if (ABS(YS) == 4 &&
2454       (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2455   {
2456     AddLaserEdge(LX - XS, LY - YS);
2457
2458     LX = LX + XS;
2459     LY = LY + YS / 2;
2460   }
2461
2462   if (IS_WALL_WOOD(element) ||
2463       IS_DF_WALL_WOOD(element) ||
2464       IS_GRID_WOOD(element) ||
2465       IS_GRID_WOOD_FIXED(element) ||
2466       IS_GRID_WOOD_AUTO(element) ||
2467       element == EL_FUSE_ON ||
2468       element == EL_BLOCK_WOOD ||
2469       element == EL_GATE_WOOD)
2470   {
2471     PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2472
2473     return TRUE;
2474   }
2475
2476   if (IS_WALL_ICE(element))
2477   {
2478     int lx = LX + XS;
2479     int ly = LY + YS;
2480     int mask;
2481
2482     // check if laser hit adjacent edges of two diagonal tiles
2483     if (ELX != lx / TILEX)
2484       lx = LX - XS;
2485     if (ELY != ly / TILEY)
2486       ly = LY - YS;
2487
2488     mask =     lx / MINI_TILEX - ELX * 2 + 1;    // Quadrant (horizontal)
2489     mask <<= ((ly / MINI_TILEY - ELY * 2) > 0 ? 2 : 0);  // || (vertical)
2490
2491     // check if laser hits wall with an angle of 90°
2492     if (IS_90_ANGLE(laser.current_angle))
2493       mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2494
2495     if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2496     {
2497       int i;
2498
2499       for (i = 0; i < 4; i++)
2500       {
2501         if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2502           mask = 15 - (8 >> i);
2503         else if (ABS(XS) == 4 &&
2504                  mask == (1 << i) &&
2505                  (XS > 0) == (i % 2) &&
2506                  (YS < 0) == (i / 2))
2507           mask = 3 + (i / 2) * 9;
2508         else if (ABS(YS) == 4 &&
2509                  mask == (1 << i) &&
2510                  (XS < 0) == (i % 2) &&
2511                  (YS > 0) == (i / 2))
2512           mask = 5 + (i % 2) * 5;
2513       }
2514     }
2515
2516     laser.wall_mask = mask;
2517   }
2518   else if (IS_WALL_AMOEBA(element))
2519   {
2520     int elx = (LX - 2 * XS) / TILEX;
2521     int ely = (LY - 2 * YS) / TILEY;
2522     int element2 = Tile[elx][ely];
2523     int mask;
2524
2525     if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2526     {
2527       laser.dest_element = EL_EMPTY;
2528
2529       return TRUE;
2530     }
2531
2532     ELX = elx;
2533     ELY = ely;
2534
2535     mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2536     mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2537
2538     if (IS_90_ANGLE(laser.current_angle))
2539       mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2540
2541     laser.dest_element = element2 | EL_WALL_AMOEBA_BASE;
2542
2543     laser.wall_mask = mask;
2544   }
2545
2546   return TRUE;
2547 }
2548
2549 static void OpenExit(int x, int y)
2550 {
2551   int delay = 6;
2552
2553   if (!MovDelay[x][y])          // next animation frame
2554     MovDelay[x][y] = 4 * delay;
2555
2556   if (MovDelay[x][y])           // wait some time before next frame
2557   {
2558     int phase;
2559
2560     MovDelay[x][y]--;
2561     phase = MovDelay[x][y] / delay;
2562
2563     if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2564       DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2565
2566     if (!MovDelay[x][y])
2567     {
2568       Tile[x][y] = EL_EXIT_OPEN;
2569       DrawField_MM(x, y);
2570     }
2571   }
2572 }
2573
2574 static void OpenGrayBall(int x, int y)
2575 {
2576   int delay = 2;
2577
2578   if (!MovDelay[x][y])          // next animation frame
2579   {
2580     if (IS_WALL(Store[x][y]))
2581     {
2582       DrawWalls_MM(x, y, Store[x][y]);
2583
2584       // copy wall tile to spare bitmap for "melting" animation
2585       BlitBitmap(drawto_mm, bitmap_db_field, cSX + x * TILEX, cSY + y * TILEY,
2586                  TILEX, TILEY, x * TILEX, y * TILEY);
2587
2588       DrawElement_MM(x, y, EL_GRAY_BALL);
2589     }
2590
2591     MovDelay[x][y] = 50 * delay;
2592   }
2593
2594   if (MovDelay[x][y])           // wait some time before next frame
2595   {
2596     MovDelay[x][y]--;
2597
2598     if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2599     {
2600       Bitmap *bitmap;
2601       int gx, gy;
2602       int dx = RND(26), dy = RND(26);
2603
2604       if (IS_WALL(Store[x][y]))
2605       {
2606         // copy wall tile from spare bitmap for "melting" animation
2607         bitmap = bitmap_db_field;
2608         gx = x * TILEX;
2609         gy = y * TILEY;
2610       }
2611       else
2612       {
2613         int graphic = el2gfx(Store[x][y]);
2614
2615         getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2616       }
2617
2618       BlitBitmap(bitmap, drawto_mm, gx + dx, gy + dy, 6, 6,
2619                  cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2620
2621       laser.redraw = TRUE;
2622
2623       MarkTileDirty(x, y);
2624     }
2625
2626     if (!MovDelay[x][y])
2627     {
2628       Tile[x][y] = Store[x][y];
2629       Store[x][y] = Store2[x][y] = 0;
2630       MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2631
2632       InitField(x, y, FALSE);
2633       DrawField_MM(x, y);
2634
2635       ScanLaser_FromLastMirror();
2636     }
2637   }
2638 }
2639
2640 static void OpenEnvelope(int x, int y)
2641 {
2642   int num_frames = 8;           // seven frames plus final empty space
2643
2644   if (!MovDelay[x][y])          // next animation frame
2645     MovDelay[x][y] = num_frames;
2646
2647   if (MovDelay[x][y])           // wait some time before next frame
2648   {
2649     int nr = ENVELOPE_OPENING_NR(Tile[x][y]);
2650
2651     MovDelay[x][y]--;
2652
2653     if (MovDelay[x][y] > 0 && IN_SCR_FIELD(x, y))
2654     {
2655       int graphic = el_act2gfx(EL_ENVELOPE_1 + nr, MM_ACTION_COLLECTING);
2656       int frame = num_frames - MovDelay[x][y] - 1;
2657
2658       DrawGraphicAnimation_MM(x, y, graphic, frame);
2659
2660       laser.redraw = TRUE;
2661     }
2662
2663     if (MovDelay[x][y] == 0)
2664     {
2665       Tile[x][y] = EL_EMPTY;
2666
2667       DrawField_MM(x, y);
2668
2669       ScanLaser();
2670
2671       ShowEnvelope(nr);
2672     }
2673   }
2674 }
2675
2676 static void MeltIce(int x, int y)
2677 {
2678   int frames = 5;
2679   int delay = 5;
2680
2681   if (!MovDelay[x][y])          // next animation frame
2682     MovDelay[x][y] = frames * delay;
2683
2684   if (MovDelay[x][y])           // wait some time before next frame
2685   {
2686     int phase;
2687     int wall_mask = Store2[x][y];
2688     int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_ICE_BASE;
2689
2690     MovDelay[x][y]--;
2691     phase = frames - MovDelay[x][y] / delay - 1;
2692
2693     if (!MovDelay[x][y])
2694     {
2695       Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2696       Store[x][y] = Store2[x][y] = 0;
2697
2698       DrawWalls_MM(x, y, Tile[x][y]);
2699
2700       if (Tile[x][y] == EL_WALL_ICE_BASE)
2701         Tile[x][y] = EL_EMPTY;
2702
2703       ScanLaser_FromLastMirror();
2704     }
2705     else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2706     {
2707       DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2708
2709       laser.redraw = TRUE;
2710     }
2711   }
2712 }
2713
2714 static void GrowAmoeba(int x, int y)
2715 {
2716   int frames = 5;
2717   int delay = 1;
2718
2719   if (!MovDelay[x][y])          // next animation frame
2720     MovDelay[x][y] = frames * delay;
2721
2722   if (MovDelay[x][y])           // wait some time before next frame
2723   {
2724     int phase;
2725     int wall_mask = Store2[x][y];
2726     int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_AMOEBA_BASE;
2727
2728     MovDelay[x][y]--;
2729     phase = MovDelay[x][y] / delay;
2730
2731     if (!MovDelay[x][y])
2732     {
2733       Tile[x][y] = real_element;
2734       Store[x][y] = Store2[x][y] = 0;
2735
2736       DrawWalls_MM(x, y, Tile[x][y]);
2737       DrawLaser(0, DL_LASER_ENABLED);
2738     }
2739     else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2740     {
2741       DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2742     }
2743   }
2744 }
2745
2746 static void DrawFieldAnimated_MM(int x, int y)
2747 {
2748   DrawField_MM(x, y);
2749
2750   laser.redraw = TRUE;
2751 }
2752
2753 static void DrawFieldAnimatedIfNeeded_MM(int x, int y)
2754 {
2755   int element = Tile[x][y];
2756   int graphic = el2gfx(element);
2757
2758   if (!getGraphicInfo_NewFrame(x, y, graphic))
2759     return;
2760
2761   DrawField_MM(x, y);
2762
2763   laser.redraw = TRUE;
2764 }
2765
2766 static void DrawFieldTwinkle(int x, int y)
2767 {
2768   if (MovDelay[x][y] != 0)      // wait some time before next frame
2769   {
2770     MovDelay[x][y]--;
2771
2772     DrawField_MM(x, y);
2773
2774     if (MovDelay[x][y] != 0)
2775     {
2776       int graphic = IMG_TWINKLE_WHITE;
2777       int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
2778
2779       DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
2780     }
2781
2782     laser.redraw = TRUE;
2783   }
2784 }
2785
2786 static void Explode_MM(int x, int y, int phase, int mode)
2787 {
2788   int num_phase = 9, delay = 2;
2789   int last_phase = num_phase * delay;
2790   int half_phase = (num_phase / 2) * delay;
2791   int center_element;
2792
2793   laser.redraw = TRUE;
2794
2795   if (phase == EX_PHASE_START)          // initialize 'Store[][]' field
2796   {
2797     center_element = Tile[x][y];
2798
2799     if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2800     {
2801       // put moving element to center field (and let it explode there)
2802       center_element = MovingOrBlocked2Element_MM(x, y);
2803       RemoveMovingField_MM(x, y);
2804
2805       Tile[x][y] = center_element;
2806     }
2807
2808     if (center_element != EL_GRAY_BALL_ACTIVE)
2809       Store[x][y] = EL_EMPTY;
2810     Store2[x][y] = center_element;
2811
2812     Tile[x][y] = EL_EXPLODING_OPAQUE;
2813
2814     GfxElement[x][y] = (center_element == EL_BOMB_ACTIVE ? EL_BOMB :
2815                         center_element == EL_GRAY_BALL_ACTIVE ? EL_GRAY_BALL :
2816                         IS_MCDUFFIN(center_element) ? EL_MCDUFFIN :
2817                         center_element);
2818
2819     MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2820
2821     ExplodePhase[x][y] = 1;
2822
2823     return;
2824   }
2825
2826   if (phase == 1)
2827     GfxFrame[x][y] = 0;         // restart explosion animation
2828
2829   ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
2830
2831   center_element = Store2[x][y];
2832
2833   if (phase == half_phase && Store[x][y] == EL_EMPTY)
2834   {
2835     Tile[x][y] = EL_EXPLODING_TRANSP;
2836
2837     if (x == ELX && y == ELY)
2838       ScanLaser();
2839   }
2840
2841   if (phase == last_phase)
2842   {
2843     if (center_element == EL_BOMB_ACTIVE)
2844     {
2845       DrawLaser(0, DL_LASER_DISABLED);
2846       InitLaser();
2847
2848       Bang_MM(laser.start_edge.x, laser.start_edge.y);
2849
2850       laser.overloaded = FALSE;
2851     }
2852     else if (IS_MCDUFFIN(center_element) || IS_LASER(center_element))
2853     {
2854       GameOver_MM(GAME_OVER_BOMB);
2855     }
2856
2857     Tile[x][y] = Store[x][y];
2858
2859     Store[x][y] = Store2[x][y] = 0;
2860     MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2861
2862     InitField(x, y, FALSE);
2863     DrawField_MM(x, y);
2864
2865     if (center_element == EL_GRAY_BALL_ACTIVE)
2866       ScanLaser_FromLastMirror();
2867   }
2868   else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2869   {
2870     int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
2871     int frame = getGraphicAnimationFrameXY(graphic, x, y);
2872
2873     DrawGraphicAnimation_MM(x, y, graphic, frame);
2874
2875     MarkTileDirty(x, y);
2876   }
2877 }
2878
2879 static void Bang_MM(int x, int y)
2880 {
2881   int element = Tile[x][y];
2882
2883   if (IS_PACMAN(element))
2884     PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2885   else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
2886     PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2887   else if (element == EL_KEY)
2888     PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2889   else
2890     PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2891
2892   Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
2893 }
2894
2895 static void TurnRound(int x, int y)
2896 {
2897   static struct
2898   {
2899     int x, y;
2900   } move_xy[] =
2901   {
2902     {  0,  0 },
2903     { -1,  0 },
2904     { +1,  0 },
2905     {  0,  0 },
2906     {  0, -1 },
2907     {  0,  0 }, { 0, 0 }, { 0, 0 },
2908     {  0, +1 }
2909   };
2910   static struct
2911   {
2912     int left, right, back;
2913   } turn[] =
2914   {
2915     { 0,        0,              0        },
2916     { MV_DOWN,  MV_UP,          MV_RIGHT },
2917     { MV_UP,    MV_DOWN,        MV_LEFT  },
2918     { 0,        0,              0        },
2919     { MV_LEFT,  MV_RIGHT,       MV_DOWN  },
2920     { 0,        0,              0        },
2921     { 0,        0,              0        },
2922     { 0,        0,              0        },
2923     { MV_RIGHT, MV_LEFT,        MV_UP    }
2924   };
2925
2926   int element = Tile[x][y];
2927   int old_move_dir = MovDir[x][y];
2928   int right_dir = turn[old_move_dir].right;
2929   int back_dir = turn[old_move_dir].back;
2930   int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2931   int right_x = x + right_dx, right_y = y + right_dy;
2932
2933   if (element == EL_PACMAN)
2934   {
2935     boolean can_turn_right = FALSE;
2936
2937     if (IN_LEV_FIELD(right_x, right_y) &&
2938         IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2939       can_turn_right = TRUE;
2940
2941     if (can_turn_right)
2942       MovDir[x][y] = right_dir;
2943     else
2944       MovDir[x][y] = back_dir;
2945
2946     MovDelay[x][y] = 0;
2947   }
2948 }
2949
2950 static void StartMoving_MM(int x, int y)
2951 {
2952   int element = Tile[x][y];
2953
2954   if (Stop[x][y])
2955     return;
2956
2957   if (CAN_MOVE(element))
2958   {
2959     int newx, newy;
2960
2961     if (MovDelay[x][y])         // wait some time before next movement
2962     {
2963       MovDelay[x][y]--;
2964
2965       if (MovDelay[x][y])
2966         return;
2967     }
2968
2969     // now make next step
2970
2971     Moving2Blocked(x, y, &newx, &newy); // get next screen position
2972
2973     if (element == EL_PACMAN &&
2974         IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2975         !ObjHit(newx, newy, HIT_POS_CENTER))
2976     {
2977       Store[newx][newy] = Tile[newx][newy];
2978       Tile[newx][newy] = EL_EMPTY;
2979
2980       DrawField_MM(newx, newy);
2981     }
2982     else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2983              ObjHit(newx, newy, HIT_POS_CENTER))
2984     {
2985       // object was running against a wall
2986
2987       TurnRound(x, y);
2988
2989       return;
2990     }
2991
2992     InitMovingField_MM(x, y, MovDir[x][y]);
2993   }
2994
2995   if (MovDir[x][y])
2996     ContinueMoving_MM(x, y);
2997 }
2998
2999 static void ContinueMoving_MM(int x, int y)
3000 {
3001   int element = Tile[x][y];
3002   int direction = MovDir[x][y];
3003   int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3004   int dy = (direction == MV_UP   ? -1 : direction == MV_DOWN  ? +1 : 0);
3005   int horiz_move = (dx!=0);
3006   int newx = x + dx, newy = y + dy;
3007   int step = (horiz_move ? dx : dy) * TILEX / 8;
3008
3009   MovPos[x][y] += step;
3010
3011   if (ABS(MovPos[x][y]) >= TILEX)       // object reached its destination
3012   {
3013     Tile[x][y] = EL_EMPTY;
3014     Tile[newx][newy] = element;
3015
3016     MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3017     MovDelay[newx][newy] = 0;
3018
3019     if (!CAN_MOVE(element))
3020       MovDir[newx][newy] = 0;
3021
3022     DrawField_MM(x, y);
3023     DrawField_MM(newx, newy);
3024
3025     Stop[newx][newy] = TRUE;
3026
3027     if (element == EL_PACMAN)
3028     {
3029       if (Store[newx][newy] == EL_BOMB)
3030         Bang_MM(newx, newy);
3031
3032       if (IS_WALL_AMOEBA(Store[newx][newy]) &&
3033           (LX + 2 * XS) / TILEX == newx &&
3034           (LY + 2 * YS) / TILEY == newy)
3035       {
3036         laser.num_edges--;
3037         ScanLaser();
3038       }
3039     }
3040   }
3041   else                          // still moving on
3042   {
3043     DrawField_MM(x, y);
3044   }
3045
3046   laser.redraw = TRUE;
3047 }
3048
3049 boolean ClickElement(int x, int y, int button)
3050 {
3051   static DelayCounter click_delay = { CLICK_DELAY };
3052   static boolean new_button = TRUE;
3053   boolean element_clicked = FALSE;
3054   int element;
3055
3056   if (button == -1)
3057   {
3058     // initialize static variables
3059     click_delay.count = 0;
3060     click_delay.value = CLICK_DELAY;
3061     new_button = TRUE;
3062
3063     return FALSE;
3064   }
3065
3066   // do not rotate objects hit by the laser after the game was solved
3067   if (game_mm.level_solved && Hit[x][y])
3068     return FALSE;
3069
3070   if (button == MB_RELEASED)
3071   {
3072     new_button = TRUE;
3073     click_delay.value = CLICK_DELAY;
3074
3075     // release eventually hold auto-rotating mirror
3076     RotateMirror(x, y, MB_RELEASED);
3077
3078     return FALSE;
3079   }
3080
3081   if (!FrameReached(&click_delay) && !new_button)
3082     return FALSE;
3083
3084   if (button == MB_MIDDLEBUTTON)        // middle button has no function
3085     return FALSE;
3086
3087   if (!IN_LEV_FIELD(x, y))
3088     return FALSE;
3089
3090   if (Tile[x][y] == EL_EMPTY)
3091     return FALSE;
3092
3093   element = Tile[x][y];
3094
3095   if (IS_MIRROR(element) ||
3096       IS_BEAMER(element) ||
3097       IS_POLAR(element) ||
3098       IS_POLAR_CROSS(element) ||
3099       IS_DF_MIRROR(element) ||
3100       IS_DF_MIRROR_AUTO(element))
3101   {
3102     RotateMirror(x, y, button);
3103
3104     element_clicked = TRUE;
3105   }
3106   else if (IS_MCDUFFIN(element))
3107   {
3108     boolean has_laser = (x == laser.start_edge.x && y == laser.start_edge.y);
3109
3110     if (has_laser && !laser.fuse_off)
3111       DrawLaser(0, DL_LASER_DISABLED);
3112
3113     element = get_rotated_element(element, BUTTON_ROTATION(button));
3114
3115     Tile[x][y] = element;
3116     DrawField_MM(x, y);
3117
3118     if (has_laser)
3119     {
3120       laser.start_angle = get_element_angle(element);
3121
3122       InitLaser();
3123
3124       if (!laser.fuse_off)
3125         ScanLaser();
3126     }
3127
3128     element_clicked = TRUE;
3129   }
3130   else if (element == EL_FUSE_ON && laser.fuse_off)
3131   {
3132     if (x != laser.fuse_x || y != laser.fuse_y)
3133       return FALSE;
3134
3135     laser.fuse_off = FALSE;
3136     laser.fuse_x = laser.fuse_y = -1;
3137
3138     DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
3139     ScanLaser();
3140
3141     element_clicked = TRUE;
3142   }
3143   else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
3144   {
3145     laser.fuse_off = TRUE;
3146     laser.fuse_x = x;
3147     laser.fuse_y = y;
3148     laser.overloaded = FALSE;
3149
3150     DrawLaser(0, DL_LASER_DISABLED);
3151     DrawGraphic_MM(x, y, IMG_MM_FUSE);
3152
3153     element_clicked = TRUE;
3154   }
3155   else if (element == EL_LIGHTBALL)
3156   {
3157     Bang_MM(x, y);
3158     RaiseScoreElement_MM(element);
3159     DrawLaser(0, DL_LASER_ENABLED);
3160
3161     element_clicked = TRUE;
3162   }
3163   else if (IS_ENVELOPE(element))
3164   {
3165     Tile[x][y] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(element);
3166
3167     element_clicked = TRUE;
3168   }
3169
3170   click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
3171   new_button = FALSE;
3172
3173   return element_clicked;
3174 }
3175
3176 static void RotateMirror(int x, int y, int button)
3177 {
3178   if (button == MB_RELEASED)
3179   {
3180     // release eventually hold auto-rotating mirror
3181     hold_x = -1;
3182     hold_y = -1;
3183
3184     return;
3185   }
3186
3187   if (IS_MIRROR(Tile[x][y]) ||
3188       IS_POLAR_CROSS(Tile[x][y]) ||
3189       IS_POLAR(Tile[x][y]) ||
3190       IS_BEAMER(Tile[x][y]) ||
3191       IS_DF_MIRROR(Tile[x][y]) ||
3192       IS_GRID_STEEL_AUTO(Tile[x][y]) ||
3193       IS_GRID_WOOD_AUTO(Tile[x][y]))
3194   {
3195     Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
3196   }
3197   else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
3198   {
3199     if (button == MB_LEFTBUTTON)
3200     {
3201       // left mouse button only for manual adjustment, no auto-rotating;
3202       // freeze mirror for until mouse button released
3203       hold_x = x;
3204       hold_y = y;
3205     }
3206     else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
3207     {
3208       Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
3209     }
3210   }
3211
3212   if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
3213   {
3214     int edge = Hit[x][y];
3215
3216     DrawField_MM(x, y);
3217
3218     if (edge > 0)
3219     {
3220       DrawLaser(edge - 1, DL_LASER_DISABLED);
3221       ScanLaser();
3222     }
3223   }
3224   else if (ObjHit(x, y, HIT_POS_CENTER))
3225   {
3226     int edge = Hit[x][y];
3227
3228     if (edge == 0)
3229     {
3230       Warn("RotateMirror: inconsistent field Hit[][]!\n");
3231
3232       edge = 1;
3233     }
3234
3235     DrawLaser(edge - 1, DL_LASER_DISABLED);
3236     ScanLaser();
3237   }
3238   else
3239   {
3240     int check = 1;
3241
3242     if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
3243       check = 2;
3244
3245     DrawField_MM(x, y);
3246
3247     if ((IS_BEAMER(Tile[x][y]) ||
3248          IS_POLAR(Tile[x][y]) ||
3249          IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
3250     {
3251       if (IS_BEAMER(Tile[x][y]))
3252       {
3253 #if 0
3254         Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3255               LX, LY, laser.beamer_edge, laser.beamer[1].num);
3256 #endif
3257
3258         if (check == 1)
3259           laser.num_edges--;
3260       }
3261
3262       ScanLaser();
3263
3264       check = 0;
3265     }
3266
3267     if (check == 2)
3268       DrawLaser(0, DL_LASER_ENABLED);
3269   }
3270 }
3271
3272 static void AutoRotateMirrors(void)
3273 {
3274   int x, y;
3275
3276   if (!FrameReached(&rotate_delay))
3277     return;
3278
3279   for (x = 0; x < lev_fieldx; x++)
3280   {
3281     for (y = 0; y < lev_fieldy; y++)
3282     {
3283       int element = Tile[x][y];
3284
3285       // do not rotate objects hit by the laser after the game was solved
3286       if (game_mm.level_solved && Hit[x][y])
3287         continue;
3288
3289       if (IS_DF_MIRROR_AUTO(element) ||
3290           IS_GRID_WOOD_AUTO(element) ||
3291           IS_GRID_STEEL_AUTO(element) ||
3292           element == EL_REFRACTOR)
3293       {
3294         RotateMirror(x, y, MB_RIGHTBUTTON);
3295
3296         laser.redraw = TRUE;
3297       }
3298     }
3299   }
3300 }
3301
3302 static boolean ObjHit(int obx, int oby, int bits)
3303 {
3304   int i;
3305
3306   obx *= TILEX;
3307   oby *= TILEY;
3308
3309   if (bits & HIT_POS_CENTER)
3310   {
3311     if (CheckLaserPixel(cSX + obx + 15,
3312                         cSY + oby + 15))
3313       return TRUE;
3314   }
3315
3316   if (bits & HIT_POS_EDGE)
3317   {
3318     for (i = 0; i < 4; i++)
3319       if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3320                           cSY + oby + 31 * (i / 2)))
3321         return TRUE;
3322   }
3323
3324   if (bits & HIT_POS_BETWEEN)
3325   {
3326     for (i = 0; i < 4; i++)
3327       if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3328                           cSY + 4 + oby + 22 * (i / 2)))
3329         return TRUE;
3330   }
3331
3332   return FALSE;
3333 }
3334
3335 static void DeletePacMan(int px, int py)
3336 {
3337   int i, j;
3338
3339   Bang_MM(px, py);
3340
3341   if (game_mm.num_pacman <= 1)
3342   {
3343     game_mm.num_pacman = 0;
3344     return;
3345   }
3346
3347   for (i = 0; i < game_mm.num_pacman; i++)
3348     if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3349       break;
3350
3351   game_mm.num_pacman--;
3352
3353   for (j = i; j < game_mm.num_pacman; j++)
3354   {
3355     game_mm.pacman[j].x   = game_mm.pacman[j + 1].x;
3356     game_mm.pacman[j].y   = game_mm.pacman[j + 1].y;
3357     game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3358   }
3359 }
3360
3361 static void GameActions_MM_Ext(void)
3362 {
3363   int element;
3364   int x, y, i;
3365
3366   int r, d;
3367
3368   for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3369     Stop[x][y] = FALSE;
3370
3371   for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3372   {
3373     element = Tile[x][y];
3374
3375     if (!IS_MOVING(x, y) && CAN_MOVE(element))
3376       StartMoving_MM(x, y);
3377     else if (IS_MOVING(x, y))
3378       ContinueMoving_MM(x, y);
3379     else if (IS_EXPLODING(element))
3380       Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3381     else if (element == EL_EXIT_OPENING)
3382       OpenExit(x, y);
3383     else if (element == EL_GRAY_BALL_OPENING)
3384       OpenGrayBall(x, y);
3385     else if (IS_ENVELOPE_OPENING(element))
3386       OpenEnvelope(x, y);
3387     else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE_BASE)
3388       MeltIce(x, y);
3389     else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA_BASE)
3390       GrowAmoeba(x, y);
3391     else if (IS_MIRROR(element) ||
3392              IS_MIRROR_FIXED(element) ||
3393              element == EL_PRISM)
3394       DrawFieldTwinkle(x, y);
3395     else if (element == EL_GRAY_BALL_ACTIVE ||
3396              element == EL_BOMB_ACTIVE ||
3397              element == EL_MINE_ACTIVE)
3398       DrawFieldAnimated_MM(x, y);
3399     else if (!IS_BLOCKED(x, y))
3400       DrawFieldAnimatedIfNeeded_MM(x, y);
3401   }
3402
3403   AutoRotateMirrors();
3404
3405 #if 1
3406   // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3407
3408   // redraw after Explode_MM() ...
3409   if (laser.redraw)
3410     DrawLaser(0, DL_LASER_ENABLED);
3411   laser.redraw = FALSE;
3412 #endif
3413
3414   CT = FrameCounter;
3415
3416   if (game_mm.num_pacman && FrameReached(&pacman_delay))
3417   {
3418     MovePacMen();
3419
3420     if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3421     {
3422       DrawLaser(0, DL_LASER_DISABLED);
3423       ScanLaser();
3424     }
3425   }
3426
3427   // skip all following game actions if game is over
3428   if (game_mm.game_over)
3429     return;
3430
3431   if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3432   {
3433     FadeOutLaser();
3434
3435     GameOver_MM(GAME_OVER_NO_ENERGY);
3436
3437     return;
3438   }
3439
3440   if (FrameReached(&energy_delay))
3441   {
3442     if (game_mm.energy_left > 0)
3443       game_mm.energy_left--;
3444
3445     // when out of energy, wait another frame to play "out of time" sound
3446   }
3447
3448   element = laser.dest_element;
3449
3450 #if 0
3451   if (element != Tile[ELX][ELY])
3452   {
3453     Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3454           element, Tile[ELX][ELY]);
3455   }
3456 #endif
3457
3458   if (!laser.overloaded && laser.overload_value == 0 &&
3459       element != EL_BOMB &&
3460       element != EL_BOMB_ACTIVE &&
3461       element != EL_MINE &&
3462       element != EL_MINE_ACTIVE &&
3463       element != EL_GRAY_BALL &&
3464       element != EL_GRAY_BALL_ACTIVE &&
3465       element != EL_BLOCK_STONE &&
3466       element != EL_BLOCK_WOOD &&
3467       element != EL_FUSE_ON &&
3468       element != EL_FUEL_FULL &&
3469       !IS_WALL_ICE(element) &&
3470       !IS_WALL_AMOEBA(element))
3471     return;
3472
3473   overload_delay.value = HEALTH_DELAY(laser.overloaded);
3474
3475   if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3476        (!laser.overloaded && laser.overload_value > 0)) &&
3477       FrameReached(&overload_delay))
3478   {
3479     if (laser.overloaded)
3480       laser.overload_value++;
3481     else
3482       laser.overload_value--;
3483
3484     if (game_mm.cheat_no_overload)
3485     {
3486       laser.overloaded = FALSE;
3487       laser.overload_value = 0;
3488     }
3489
3490     game_mm.laser_overload_value = laser.overload_value;
3491
3492     if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3493     {
3494       SetLaserColor(0xFF);
3495
3496       DrawLaser(0, DL_LASER_ENABLED);
3497     }
3498
3499     if (!laser.overloaded)
3500       StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3501     else if (setup.sound_loops)
3502       PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3503     else
3504       PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3505
3506     if (laser.overload_value == MAX_LASER_OVERLOAD)
3507     {
3508       UpdateAndDisplayGameControlValues();
3509
3510       FadeOutLaser();
3511
3512       GameOver_MM(GAME_OVER_OVERLOADED);
3513
3514       return;
3515     }
3516   }
3517
3518   if (laser.fuse_off)
3519     return;
3520
3521   CT -= Ct;
3522
3523   if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3524   {
3525     if (game_mm.cheat_no_explosion)
3526       return;
3527
3528     Bang_MM(ELX, ELY);
3529
3530     laser.dest_element = EL_EXPLODING_OPAQUE;
3531
3532     return;
3533   }
3534
3535   if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3536   {
3537     laser.fuse_off = TRUE;
3538     laser.fuse_x = ELX;
3539     laser.fuse_y = ELY;
3540
3541     DrawLaser(0, DL_LASER_DISABLED);
3542     DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3543   }
3544
3545   if (element == EL_GRAY_BALL && CT > native_mm_level.time_ball)
3546   {
3547     if (!Store2[ELX][ELY])      // check if content element not yet determined
3548     {
3549       int last_anim_random_frame = gfx.anim_random_frame;
3550       int element_pos;
3551
3552       if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3553         gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3554
3555       element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3556                                       native_mm_level.ball_choice_mode, 0,
3557                                       game_mm.ball_choice_pos);
3558
3559       if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3560         gfx.anim_random_frame = last_anim_random_frame;
3561
3562       game_mm.ball_choice_pos++;
3563
3564       int new_element = native_mm_level.ball_content[element_pos];
3565       int new_element_base = map_wall_to_base_element(new_element);
3566
3567       if (IS_WALL(new_element_base))
3568       {
3569         // always use completely filled wall element
3570         new_element = new_element_base | 0x000f;
3571       }
3572       else if (native_mm_level.rotate_ball_content &&
3573                get_num_elements(new_element) > 1)
3574       {
3575         // randomly rotate newly created game element
3576         new_element = get_rotated_element(new_element, RND(16));
3577       }
3578
3579       Store[ELX][ELY] = new_element;
3580       Store2[ELX][ELY] = TRUE;
3581     }
3582
3583     if (native_mm_level.explode_ball)
3584       Bang_MM(ELX, ELY);
3585     else
3586       Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3587
3588     laser.dest_element = laser.dest_element_last = Tile[ELX][ELY];
3589
3590     return;
3591   }
3592
3593   if (IS_WALL_ICE(element) && CT > 50)
3594   {
3595     PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3596
3597     Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE_BASE + EL_WALL_CHANGING_BASE;
3598     Store[ELX][ELY] = EL_WALL_ICE_BASE;
3599     Store2[ELX][ELY] = laser.wall_mask;
3600
3601     laser.dest_element = Tile[ELX][ELY];
3602
3603     return;
3604   }
3605
3606   if (IS_WALL_AMOEBA(element) && CT > 60)
3607   {
3608     int k1, k2, k3;
3609     int element2 = Tile[ELX][ELY];
3610
3611     if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3612       return;
3613
3614     for (i = laser.num_damages - 1; i >= 0; i--)
3615       if (laser.damage[i].is_mirror)
3616         break;
3617
3618     r = laser.num_edges;
3619     d = laser.num_damages;
3620     k1 = i;
3621
3622     if (k1 > 0)
3623     {
3624       int x, y;
3625
3626       DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3627
3628       laser.num_edges++;
3629       DrawLaser(0, DL_LASER_ENABLED);
3630       laser.num_edges--;
3631
3632       x = laser.damage[k1].x;
3633       y = laser.damage[k1].y;
3634
3635       DrawField_MM(x, y);
3636     }
3637
3638     for (i = 0; i < 4; i++)
3639     {
3640       if (laser.wall_mask & (1 << i))
3641       {
3642         if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3643                             cSY + ELY * TILEY + 31 * (i / 2)))
3644           break;
3645
3646         if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3647                             cSY + ELY * TILEY + 14 + (i / 2) * 2))
3648           break;
3649       }
3650     }
3651
3652     k2 = i;
3653
3654     for (i = 0; i < 4; i++)
3655     {
3656       if (laser.wall_mask & (1 << i))
3657       {
3658         if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3659                             cSY + ELY * TILEY + 31 * (i / 2)))
3660           break;
3661       }
3662     }
3663
3664     k3 = i;
3665
3666     if (laser.num_beamers > 0 ||
3667         k1 < 1 || k2 < 4 || k3 < 4 ||
3668         CheckLaserPixel(cSX + ELX * TILEX + 14,
3669                         cSY + ELY * TILEY + 14))
3670     {
3671       laser.num_edges = r;
3672       laser.num_damages = d;
3673
3674       DrawLaser(0, DL_LASER_DISABLED);
3675     }
3676
3677     Tile[ELX][ELY] = element | laser.wall_mask;
3678
3679     int x = ELX, y = ELY;
3680     int wall_mask = laser.wall_mask;
3681
3682     ScanLaser();
3683     DrawLaser(0, DL_LASER_ENABLED);
3684
3685     PlayLevelSound_MM(x, y, element, MM_ACTION_GROWING);
3686
3687     Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA_BASE + EL_WALL_CHANGING_BASE;
3688     Store[x][y] = EL_WALL_AMOEBA_BASE;
3689     Store2[x][y] = wall_mask;
3690
3691     return;
3692   }
3693
3694   if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3695       laser.stops_inside_element && CT > native_mm_level.time_block)
3696   {
3697     int x, y;
3698     int k;
3699
3700     if (ABS(XS) > ABS(YS))
3701       k = 0;
3702     else
3703       k = 1;
3704     if (XS < YS)
3705       k += 2;
3706
3707     for (i = 0; i < 4; i++)
3708     {
3709       if (i)
3710         k++;
3711       if (k > 3)
3712         k = 0;
3713
3714       x = ELX + Step[k * 4].x;
3715       y = ELY + Step[k * 4].y;
3716
3717       if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3718         continue;
3719
3720       if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3721         continue;
3722
3723       break;
3724     }
3725
3726     if (i > 3)
3727     {
3728       laser.overloaded = (element == EL_BLOCK_STONE);
3729
3730       return;
3731     }
3732
3733     PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3734
3735     Tile[ELX][ELY] = 0;
3736     Tile[x][y] = element;
3737
3738     DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3739     DrawField_MM(x, y);
3740
3741     if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3742     {
3743       DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3744       DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3745     }
3746
3747     ScanLaser();
3748
3749     return;
3750   }
3751
3752   if (element == EL_FUEL_FULL && CT > 10)
3753   {
3754     int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
3755     int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
3756
3757     for (i = start; i <= num_init_game_frames; i++)
3758     {
3759       if (i == num_init_game_frames)
3760         StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3761       else if (setup.sound_loops)
3762         PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3763       else
3764         PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3765
3766       game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
3767
3768       UpdateAndDisplayGameControlValues();
3769
3770       BackToFront_MM();
3771     }
3772
3773     Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
3774
3775     DrawField_MM(ELX, ELY);
3776
3777     DrawLaser(0, DL_LASER_ENABLED);
3778
3779     return;
3780   }
3781 }
3782
3783 void GameActions_MM(struct MouseActionInfo action)
3784 {
3785   boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3786   boolean button_released = (action.button == MB_RELEASED);
3787
3788   GameActions_MM_Ext();
3789
3790   CheckSingleStepMode_MM(element_clicked, button_released);
3791 }
3792
3793 static void MovePacMen(void)
3794 {
3795   int mx, my, ox, oy, nx, ny;
3796   int element;
3797   int l;
3798
3799   if (++pacman_nr >= game_mm.num_pacman)
3800     pacman_nr = 0;
3801
3802   game_mm.pacman[pacman_nr].dir--;
3803
3804   for (l = 1; l < 5; l++)
3805   {
3806     game_mm.pacman[pacman_nr].dir++;
3807
3808     if (game_mm.pacman[pacman_nr].dir > 4)
3809       game_mm.pacman[pacman_nr].dir = 1;
3810
3811     if (game_mm.pacman[pacman_nr].dir % 2)
3812     {
3813       mx = 0;
3814       my = game_mm.pacman[pacman_nr].dir - 2;
3815     }
3816     else
3817     {
3818       my = 0;
3819       mx = 3 - game_mm.pacman[pacman_nr].dir;
3820     }
3821
3822     ox = game_mm.pacman[pacman_nr].x;
3823     oy = game_mm.pacman[pacman_nr].y;
3824     nx = ox + mx;
3825     ny = oy + my;
3826     element = Tile[nx][ny];
3827
3828     if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3829       continue;
3830
3831     if (!IS_EATABLE4PACMAN(element))
3832       continue;
3833
3834     if (ObjHit(nx, ny, HIT_POS_CENTER))
3835       continue;
3836
3837     Tile[ox][oy] = EL_EMPTY;
3838     Tile[nx][ny] =
3839       EL_PACMAN_RIGHT - 1 +
3840       (game_mm.pacman[pacman_nr].dir - 1 +
3841        (game_mm.pacman[pacman_nr].dir % 2) * 2);
3842
3843     game_mm.pacman[pacman_nr].x = nx;
3844     game_mm.pacman[pacman_nr].y = ny;
3845
3846     DrawGraphic_MM(ox, oy, IMG_EMPTY);
3847
3848     if (element != EL_EMPTY)
3849     {
3850       int graphic = el2gfx(Tile[nx][ny]);
3851       Bitmap *bitmap;
3852       int src_x, src_y;
3853       int i;
3854
3855       getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3856
3857       CT = FrameCounter;
3858       ox = cSX + ox * TILEX;
3859       oy = cSY + oy * TILEY;
3860
3861       for (i = 1; i < 33; i += 2)
3862         BlitBitmap(bitmap, window,
3863                    src_x, src_y, TILEX, TILEY,
3864                    ox + i * mx, oy + i * my);
3865       Ct = Ct + FrameCounter - CT;
3866     }
3867
3868     DrawField_MM(nx, ny);
3869     BackToFront_MM();
3870
3871     if (!laser.fuse_off)
3872     {
3873       DrawLaser(0, DL_LASER_ENABLED);
3874
3875       if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3876       {
3877         AddDamagedField(nx, ny);
3878
3879         laser.damage[laser.num_damages - 1].edge = 0;
3880       }
3881     }
3882
3883     if (element == EL_BOMB)
3884       DeletePacMan(nx, ny);
3885
3886     if (IS_WALL_AMOEBA(element) &&
3887         (LX + 2 * XS) / TILEX == nx &&
3888         (LY + 2 * YS) / TILEY == ny)
3889     {
3890       laser.num_edges--;
3891       ScanLaser();
3892     }
3893
3894     break;
3895   }
3896 }
3897
3898 static void InitMovingField_MM(int x, int y, int direction)
3899 {
3900   int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3901   int newy = y + (direction == MV_UP   ? -1 : direction == MV_DOWN  ? +1 : 0);
3902
3903   MovDir[x][y] = direction;
3904   MovDir[newx][newy] = direction;
3905
3906   if (Tile[newx][newy] == EL_EMPTY)
3907     Tile[newx][newy] = EL_BLOCKED;
3908 }
3909
3910 static int MovingOrBlocked2Element_MM(int x, int y)
3911 {
3912   int element = Tile[x][y];
3913
3914   if (element == EL_BLOCKED)
3915   {
3916     int oldx, oldy;
3917
3918     Blocked2Moving(x, y, &oldx, &oldy);
3919
3920     return Tile[oldx][oldy];
3921   }
3922
3923   return element;
3924 }
3925
3926 static void RemoveMovingField_MM(int x, int y)
3927 {
3928   int oldx = x, oldy = y, newx = x, newy = y;
3929
3930   if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3931     return;
3932
3933   if (IS_MOVING(x, y))
3934   {
3935     Moving2Blocked(x, y, &newx, &newy);
3936     if (Tile[newx][newy] != EL_BLOCKED)
3937       return;
3938   }
3939   else if (Tile[x][y] == EL_BLOCKED)
3940   {
3941     Blocked2Moving(x, y, &oldx, &oldy);
3942     if (!IS_MOVING(oldx, oldy))
3943       return;
3944   }
3945
3946   Tile[oldx][oldy] = EL_EMPTY;
3947   Tile[newx][newy] = EL_EMPTY;
3948   MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3949   MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3950
3951   DrawLevelField_MM(oldx, oldy);
3952   DrawLevelField_MM(newx, newy);
3953 }
3954
3955 static void RaiseScore_MM(int value)
3956 {
3957   game_mm.score += value;
3958 }
3959
3960 void RaiseScoreElement_MM(int element)
3961 {
3962   switch (element)
3963   {
3964     case EL_PACMAN:
3965     case EL_PACMAN_RIGHT:
3966     case EL_PACMAN_UP:
3967     case EL_PACMAN_LEFT:
3968     case EL_PACMAN_DOWN:
3969       RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
3970       break;
3971
3972     case EL_KEY:
3973       RaiseScore_MM(native_mm_level.score[SC_KEY]);
3974       break;
3975
3976     case EL_KETTLE:
3977     case EL_CELL:
3978       RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
3979       break;
3980
3981     case EL_LIGHTBALL:
3982       RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
3983       break;
3984
3985     default:
3986       break;
3987   }
3988 }
3989
3990
3991 // ----------------------------------------------------------------------------
3992 // Mirror Magic game engine snapshot handling functions
3993 // ----------------------------------------------------------------------------
3994
3995 void SaveEngineSnapshotValues_MM(void)
3996 {
3997   int x, y;
3998
3999   engine_snapshot_mm.game_mm = game_mm;
4000   engine_snapshot_mm.laser = laser;
4001
4002   for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4003   {
4004     for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4005     {
4006       engine_snapshot_mm.Ur[x][y]    = Ur[x][y];
4007       engine_snapshot_mm.Hit[x][y]   = Hit[x][y];
4008       engine_snapshot_mm.Box[x][y]   = Box[x][y];
4009       engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4010     }
4011   }
4012
4013   engine_snapshot_mm.LX = LX;
4014   engine_snapshot_mm.LY = LY;
4015   engine_snapshot_mm.XS = XS;
4016   engine_snapshot_mm.YS = YS;
4017   engine_snapshot_mm.ELX = ELX;
4018   engine_snapshot_mm.ELY = ELY;
4019   engine_snapshot_mm.CT = CT;
4020   engine_snapshot_mm.Ct = Ct;
4021
4022   engine_snapshot_mm.last_LX = last_LX;
4023   engine_snapshot_mm.last_LY = last_LY;
4024   engine_snapshot_mm.last_hit_mask = last_hit_mask;
4025   engine_snapshot_mm.hold_x = hold_x;
4026   engine_snapshot_mm.hold_y = hold_y;
4027   engine_snapshot_mm.pacman_nr = pacman_nr;
4028
4029   engine_snapshot_mm.rotate_delay = rotate_delay;
4030   engine_snapshot_mm.pacman_delay = pacman_delay;
4031   engine_snapshot_mm.energy_delay = energy_delay;
4032   engine_snapshot_mm.overload_delay = overload_delay;
4033 }
4034
4035 void LoadEngineSnapshotValues_MM(void)
4036 {
4037   int x, y;
4038
4039   // stored engine snapshot buffers already restored at this point
4040
4041   game_mm = engine_snapshot_mm.game_mm;
4042   laser   = engine_snapshot_mm.laser;
4043
4044   for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4045   {
4046     for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4047     {
4048       Ur[x][y]    = engine_snapshot_mm.Ur[x][y];
4049       Hit[x][y]   = engine_snapshot_mm.Hit[x][y];
4050       Box[x][y]   = engine_snapshot_mm.Box[x][y];
4051       Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4052     }
4053   }
4054
4055   LX  = engine_snapshot_mm.LX;
4056   LY  = engine_snapshot_mm.LY;
4057   XS  = engine_snapshot_mm.XS;
4058   YS  = engine_snapshot_mm.YS;
4059   ELX = engine_snapshot_mm.ELX;
4060   ELY = engine_snapshot_mm.ELY;
4061   CT  = engine_snapshot_mm.CT;
4062   Ct  = engine_snapshot_mm.Ct;
4063
4064   last_LX       = engine_snapshot_mm.last_LX;
4065   last_LY       = engine_snapshot_mm.last_LY;
4066   last_hit_mask = engine_snapshot_mm.last_hit_mask;
4067   hold_x        = engine_snapshot_mm.hold_x;
4068   hold_y        = engine_snapshot_mm.hold_y;
4069   pacman_nr     = engine_snapshot_mm.pacman_nr;
4070
4071   rotate_delay   = engine_snapshot_mm.rotate_delay;
4072   pacman_delay   = engine_snapshot_mm.pacman_delay;
4073   energy_delay   = engine_snapshot_mm.energy_delay;
4074   overload_delay = engine_snapshot_mm.overload_delay;
4075
4076   RedrawPlayfield_MM();
4077 }
4078
4079 static int getAngleFromTouchDelta(int dx, int dy,  int base)
4080 {
4081   double pi = 3.141592653;
4082   double rad = atan2((double)-dy, (double)dx);
4083   double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4084   double deg = rad2 * 180.0 / pi;
4085
4086   return (int)(deg * base / 360.0 + 0.5) % base;
4087 }
4088
4089 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4090 {
4091   // calculate start (source) position to be at the middle of the tile
4092   int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4093   int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4094   int dx = dst_mx - src_mx;
4095   int dy = dst_my - src_my;
4096   int element;
4097   int base = 16;
4098   int phases = 16;
4099   int angle_old = -1;
4100   int angle_new = -1;
4101   int button = 0;
4102   int i;
4103
4104   if (!IN_LEV_FIELD(x, y))
4105     return 0;
4106
4107   element = Tile[x][y];
4108
4109   if (!IS_MCDUFFIN(element) &&
4110       !IS_MIRROR(element) &&
4111       !IS_BEAMER(element) &&
4112       !IS_POLAR(element) &&
4113       !IS_POLAR_CROSS(element) &&
4114       !IS_DF_MIRROR(element))
4115     return 0;
4116
4117   angle_old = get_element_angle(element);
4118
4119   if (IS_MCDUFFIN(element))
4120   {
4121     angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4122                  dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4123                  dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4124                  dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4125                  -1);
4126   }
4127   else if (IS_MIRROR(element) ||
4128            IS_DF_MIRROR(element))
4129   {
4130     for (i = 0; i < laser.num_damages; i++)
4131     {
4132       if (laser.damage[i].x == x &&
4133           laser.damage[i].y == y &&
4134           ObjHit(x, y, HIT_POS_CENTER))
4135       {
4136         angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4137         angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4138
4139         break;
4140       }
4141     }
4142   }
4143
4144   if (angle_new == -1)
4145   {
4146     if (IS_MIRROR(element) ||
4147         IS_DF_MIRROR(element) ||
4148         IS_POLAR(element))
4149       base = 32;
4150
4151     if (IS_POLAR_CROSS(element))
4152       phases = 4;
4153
4154     angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4155   }
4156
4157   button = (angle_new == angle_old ? 0 :
4158             (angle_new - angle_old + phases) % phases < (phases / 2) ?
4159             MB_LEFTBUTTON : MB_RIGHTBUTTON);
4160
4161   return button;
4162 }