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