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