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