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