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