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