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