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