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