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