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