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