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