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