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