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