added initializing gray ball content elements in 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     // prevent cutting through laser emitter with laser beam
1500     if (IS_LASER(element))
1501       return TRUE;
1502
1503     // skip the whole element before continuing the scan
1504     do
1505     {
1506       LX += XS;
1507       LY += YS;
1508     }
1509     while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1510
1511     if (LX/TILEX > ELX || LY/TILEY > ELY)
1512     {
1513       /* skipping scan positions to the right and down skips one scan
1514          position too much, because this is only the top left scan position
1515          of totally four scan positions (plus one to the right, one to the
1516          bottom and one to the bottom right) */
1517
1518       LX -= XS;
1519       LY -= YS;
1520     }
1521
1522     return FALSE;
1523   }
1524
1525 #if 0
1526   Debug("game:mm:HitElement", "(2): element == %d", element);
1527 #endif
1528
1529   if (LX + 5 * XS < 0 ||
1530       LY + 5 * YS < 0)
1531   {
1532     LX += 2 * XS;
1533     LY += 2 * YS;
1534
1535     return FALSE;
1536   }
1537
1538 #if 0
1539   Debug("game:mm:HitElement", "(3): element == %d", element);
1540 #endif
1541
1542   if (IS_POLAR(element) &&
1543       ((element - EL_POLAR_START) % 2 ||
1544        (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1545   {
1546     PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1547
1548     laser.num_damages--;
1549
1550     return TRUE;
1551   }
1552
1553   if (IS_POLAR_CROSS(element) &&
1554       (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1555   {
1556     PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1557
1558     laser.num_damages--;
1559
1560     return TRUE;
1561   }
1562
1563   if (!IS_BEAMER(element) &&
1564       !IS_FIBRE_OPTIC(element) &&
1565       !IS_GRID_WOOD(element) &&
1566       element != EL_FUEL_EMPTY)
1567   {
1568 #if 0
1569     if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1570       Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
1571     else
1572       Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
1573 #endif
1574
1575     LX = ELX * TILEX + 14;
1576     LY = ELY * TILEY + 14;
1577
1578     AddLaserEdge(LX, LY);
1579   }
1580
1581   if (IS_MIRROR(element) ||
1582       IS_MIRROR_FIXED(element) ||
1583       IS_POLAR(element) ||
1584       IS_POLAR_CROSS(element) ||
1585       IS_DF_MIRROR(element) ||
1586       IS_DF_MIRROR_AUTO(element) ||
1587       element == EL_PRISM ||
1588       element == EL_REFRACTOR)
1589   {
1590     int current_angle = laser.current_angle;
1591     int step_size;
1592
1593     laser.num_damages--;
1594
1595     AddDamagedField(ELX, ELY);
1596
1597     laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1598
1599     if (!Hit[ELX][ELY])
1600       Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1601
1602     if (IS_MIRROR(element) ||
1603         IS_MIRROR_FIXED(element) ||
1604         IS_DF_MIRROR(element) ||
1605         IS_DF_MIRROR_AUTO(element))
1606       laser.current_angle = get_mirrored_angle(laser.current_angle,
1607                                                get_element_angle(element));
1608
1609     if (element == EL_PRISM || element == EL_REFRACTOR)
1610       laser.current_angle = RND(16);
1611
1612     XS = 2 * Step[laser.current_angle].x;
1613     YS = 2 * Step[laser.current_angle].y;
1614
1615     if (!IS_22_5_ANGLE(laser.current_angle))    // 90° or 45° angle
1616       step_size = 8;
1617     else
1618       step_size = 4;
1619
1620     LX += step_size * XS;
1621     LY += step_size * YS;
1622
1623     // draw sparkles on mirror
1624     if ((IS_MIRROR(element) ||
1625          IS_MIRROR_FIXED(element) ||
1626          element == EL_PRISM) &&
1627         current_angle != laser.current_angle)
1628     {
1629       MovDelay[ELX][ELY] = 11;          // start animation
1630     }
1631
1632     if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1633         current_angle != laser.current_angle)
1634       PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1635
1636     laser.overloaded =
1637       (get_opposite_angle(laser.current_angle) ==
1638        laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1639
1640     return (laser.overloaded ? TRUE : FALSE);
1641   }
1642
1643   if (element == EL_FUEL_FULL)
1644   {
1645     laser.stops_inside_element = TRUE;
1646
1647     return TRUE;
1648   }
1649
1650   if (element == EL_BOMB || element == EL_MINE)
1651   {
1652     PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1653
1654     Tile[ELX][ELY] = (element == EL_BOMB ? EL_BOMB_ACTIVE : EL_MINE_ACTIVE);
1655
1656     laser.dest_element_last = Tile[ELX][ELY];
1657     laser.dest_element_last_x = ELX;
1658     laser.dest_element_last_y = ELY;
1659
1660     if (element == EL_MINE)
1661       laser.overloaded = TRUE;
1662   }
1663
1664   if (element == EL_KETTLE ||
1665       element == EL_CELL ||
1666       element == EL_KEY ||
1667       element == EL_LIGHTBALL ||
1668       element == EL_PACMAN ||
1669       IS_PACMAN(element) ||
1670       IS_ENVELOPE(element))
1671   {
1672     if (!IS_PACMAN(element) &&
1673         !IS_ENVELOPE(element))
1674       Bang_MM(ELX, ELY);
1675
1676     if (element == EL_PACMAN)
1677       Bang_MM(ELX, ELY);
1678
1679     if (element == EL_KETTLE || element == EL_CELL)
1680     {
1681       if (game_mm.kettles_still_needed > 0)
1682         game_mm.kettles_still_needed--;
1683
1684       game.snapshot.collected_item = TRUE;
1685
1686       if (game_mm.kettles_still_needed == 0)
1687       {
1688         CheckExitMM();
1689
1690         DrawLaser(0, DL_LASER_ENABLED);
1691       }
1692     }
1693     else if (element == EL_KEY)
1694     {
1695       game_mm.num_keys++;
1696     }
1697     else if (IS_PACMAN(element))
1698     {
1699       DeletePacMan(ELX, ELY);
1700     }
1701     else if (IS_ENVELOPE(element))
1702     {
1703       Tile[ELX][ELY] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(Tile[ELX][ELY]);
1704     }
1705
1706     RaiseScoreElement_MM(element);
1707
1708     return FALSE;
1709   }
1710
1711   if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1712   {
1713     PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1714
1715     DrawLaser(0, DL_LASER_ENABLED);
1716
1717     if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
1718     {
1719       Tile[ELX][ELY] = EL_LIGHTBULB_ON;
1720       game_mm.lights_still_needed--;
1721     }
1722     else
1723     {
1724       Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
1725       game_mm.lights_still_needed++;
1726     }
1727
1728     DrawField_MM(ELX, ELY);
1729     DrawLaser(0, DL_LASER_ENABLED);
1730
1731     /*
1732     BackToFront();
1733     */
1734     laser.stops_inside_element = TRUE;
1735
1736     return TRUE;
1737   }
1738
1739 #if 0
1740   Debug("game:mm:HitElement", "(4): element == %d", element);
1741 #endif
1742
1743   if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1744       laser.num_beamers < MAX_NUM_BEAMERS &&
1745       laser.beamer[BEAMER_NR(element)][1].num)
1746   {
1747     int beamer_angle = get_element_angle(element);
1748     int beamer_nr = BEAMER_NR(element);
1749     int step_size;
1750
1751 #if 0
1752     Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
1753 #endif
1754
1755     laser.num_damages--;
1756
1757     if (IS_FIBRE_OPTIC(element) ||
1758         laser.current_angle == get_opposite_angle(beamer_angle))
1759     {
1760       int pos;
1761
1762       LX = ELX * TILEX + 14;
1763       LY = ELY * TILEY + 14;
1764
1765       AddLaserEdge(LX, LY);
1766       AddDamagedField(ELX, ELY);
1767
1768       laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1769
1770       if (!Hit[ELX][ELY])
1771         Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1772
1773       pos = (ELX == laser.beamer[beamer_nr][0].x &&
1774              ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1775       ELX = laser.beamer[beamer_nr][pos].x;
1776       ELY = laser.beamer[beamer_nr][pos].y;
1777       LX = ELX * TILEX + 14;
1778       LY = ELY * TILEY + 14;
1779
1780       if (IS_BEAMER(element))
1781       {
1782         laser.current_angle = get_element_angle(Tile[ELX][ELY]);
1783         XS = 2 * Step[laser.current_angle].x;
1784         YS = 2 * Step[laser.current_angle].y;
1785       }
1786
1787       laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1788
1789       AddLaserEdge(LX, LY);
1790       AddDamagedField(ELX, ELY);
1791
1792       laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1793
1794       if (!Hit[ELX][ELY])
1795         Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1796
1797       if (laser.current_angle == (laser.current_angle >> 1) << 1)
1798         step_size = 8;
1799       else
1800         step_size = 4;
1801
1802       LX += step_size * XS;
1803       LY += step_size * YS;
1804
1805       laser.num_beamers++;
1806
1807       return FALSE;
1808     }
1809   }
1810
1811   return TRUE;
1812 }
1813
1814 boolean HitOnlyAnEdge(int hit_mask)
1815 {
1816   // check if the laser hit only the edge of an element and, if so, go on
1817
1818 #if 0
1819   Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
1820         LX, LY, hit_mask);
1821 #endif
1822
1823   if ((hit_mask == HIT_MASK_TOPLEFT ||
1824        hit_mask == HIT_MASK_TOPRIGHT ||
1825        hit_mask == HIT_MASK_BOTTOMLEFT ||
1826        hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1827       laser.current_angle % 4)                  // angle is not 90°
1828   {
1829     int dx, dy;
1830
1831     if (hit_mask == HIT_MASK_TOPLEFT)
1832     {
1833       dx = -1;
1834       dy = -1;
1835     }
1836     else if (hit_mask == HIT_MASK_TOPRIGHT)
1837     {
1838       dx = +1;
1839       dy = -1;
1840     }
1841     else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1842     {
1843       dx = -1;
1844       dy = +1;
1845     }
1846     else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
1847     {
1848       dx = +1;
1849       dy = +1;
1850     }
1851
1852     AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1853
1854     LX += XS;
1855     LY += YS;
1856
1857 #if 0
1858     Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
1859 #endif
1860
1861     return TRUE;
1862   }
1863
1864 #if 0
1865   Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
1866 #endif
1867
1868   return FALSE;
1869 }
1870
1871 boolean HitPolarizer(int element, int hit_mask)
1872 {
1873   if (HitOnlyAnEdge(hit_mask))
1874     return FALSE;
1875
1876   if (IS_DF_GRID(element))
1877   {
1878     int grid_angle = get_element_angle(element);
1879
1880 #if 0
1881     Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
1882           grid_angle, laser.current_angle);
1883 #endif
1884
1885     AddLaserEdge(LX, LY);
1886     AddDamagedField(ELX, ELY);
1887
1888     if (!Hit[ELX][ELY])
1889       Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1890
1891     if (laser.current_angle == grid_angle ||
1892         laser.current_angle == get_opposite_angle(grid_angle))
1893     {
1894       // skip the whole element before continuing the scan
1895       do
1896       {
1897         LX += XS;
1898         LY += YS;
1899       }
1900       while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1901
1902       if (LX/TILEX > ELX || LY/TILEY > ELY)
1903       {
1904         /* skipping scan positions to the right and down skips one scan
1905            position too much, because this is only the top left scan position
1906            of totally four scan positions (plus one to the right, one to the
1907            bottom and one to the bottom right) */
1908
1909         LX -= XS;
1910         LY -= YS;
1911       }
1912
1913       AddLaserEdge(LX, LY);
1914
1915       LX += XS;
1916       LY += YS;
1917
1918 #if 0
1919       Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
1920             LX, LY,
1921             LX / TILEX, LY / TILEY,
1922             LX % TILEX, LY % TILEY);
1923 #endif
1924
1925       return FALSE;
1926     }
1927     else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
1928     {
1929       return HitReflectingWalls(element, hit_mask);
1930     }
1931     else
1932     {
1933       return HitAbsorbingWalls(element, hit_mask);
1934     }
1935   }
1936   else if (IS_GRID_STEEL(element))
1937   {
1938     return HitReflectingWalls(element, hit_mask);
1939   }
1940   else  // IS_GRID_WOOD
1941   {
1942     return HitAbsorbingWalls(element, hit_mask);
1943   }
1944
1945   return TRUE;
1946 }
1947
1948 boolean HitBlock(int element, int hit_mask)
1949 {
1950   boolean check = FALSE;
1951
1952   if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
1953       game_mm.num_keys == 0)
1954     check = TRUE;
1955
1956   if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1957   {
1958     int i, x, y;
1959     int ex = ELX * TILEX + 14;
1960     int ey = ELY * TILEY + 14;
1961
1962     check = TRUE;
1963
1964     for (i = 1; i < 32; i++)
1965     {
1966       x = LX + i * XS;
1967       y = LY + i * YS;
1968
1969       if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
1970         check = FALSE;
1971     }
1972   }
1973
1974   if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
1975     return HitAbsorbingWalls(element, hit_mask);
1976
1977   if (check)
1978   {
1979     AddLaserEdge(LX - XS, LY - YS);
1980     AddDamagedField(ELX, ELY);
1981
1982     if (!Box[ELX][ELY])
1983       Box[ELX][ELY] = laser.num_edges;
1984
1985     return HitReflectingWalls(element, hit_mask);
1986   }
1987
1988   if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
1989   {
1990     int xs = XS / 2, ys = YS / 2;
1991     int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1992     int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1993
1994     if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1995         (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1996     {
1997       laser.overloaded = (element == EL_GATE_STONE);
1998
1999       return TRUE;
2000     }
2001
2002     if (ABS(xs) == 1 && ABS(ys) == 1 &&
2003         (hit_mask == HIT_MASK_TOP ||
2004          hit_mask == HIT_MASK_LEFT ||
2005          hit_mask == HIT_MASK_RIGHT ||
2006          hit_mask == HIT_MASK_BOTTOM))
2007       AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2008                                   hit_mask == HIT_MASK_BOTTOM),
2009                       ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2010                                   hit_mask == HIT_MASK_RIGHT));
2011     AddLaserEdge(LX, LY);
2012
2013     Bang_MM(ELX, ELY);
2014
2015     game_mm.num_keys--;
2016
2017     if (element == EL_GATE_STONE && Box[ELX][ELY])
2018     {
2019       DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
2020       /*
2021       BackToFront();
2022       */
2023       ScanLaser();
2024
2025       return TRUE;
2026     }
2027
2028     return FALSE;
2029   }
2030
2031   if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2032   {
2033     int xs = XS / 2, ys = YS / 2;
2034     int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
2035     int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
2036
2037     if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
2038         (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
2039     {
2040       laser.overloaded = (element == EL_BLOCK_STONE);
2041
2042       return TRUE;
2043     }
2044
2045     if (ABS(xs) == 1 && ABS(ys) == 1 &&
2046         (hit_mask == HIT_MASK_TOP ||
2047          hit_mask == HIT_MASK_LEFT ||
2048          hit_mask == HIT_MASK_RIGHT ||
2049          hit_mask == HIT_MASK_BOTTOM))
2050       AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2051                                   hit_mask == HIT_MASK_BOTTOM),
2052                       ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2053                                   hit_mask == HIT_MASK_RIGHT));
2054     AddDamagedField(ELX, ELY);
2055
2056     LX = ELX * TILEX + 14;
2057     LY = ELY * TILEY + 14;
2058
2059     AddLaserEdge(LX, LY);
2060
2061     laser.stops_inside_element = TRUE;
2062
2063     return TRUE;
2064   }
2065
2066   return TRUE;
2067 }
2068
2069 boolean HitLaserSource(int element, int hit_mask)
2070 {
2071   if (HitOnlyAnEdge(hit_mask))
2072     return FALSE;
2073
2074   PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2075
2076   laser.overloaded = TRUE;
2077
2078   return TRUE;
2079 }
2080
2081 boolean HitLaserDestination(int element, int hit_mask)
2082 {
2083   if (HitOnlyAnEdge(hit_mask))
2084     return FALSE;
2085
2086   if (element != EL_EXIT_OPEN &&
2087       !(IS_RECEIVER(element) &&
2088         game_mm.kettles_still_needed == 0 &&
2089         laser.current_angle == get_opposite_angle(get_element_angle(element))))
2090   {
2091     PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2092
2093     return TRUE;
2094   }
2095
2096   if (IS_RECEIVER(element) ||
2097       (IS_22_5_ANGLE(laser.current_angle) &&
2098        (ELX != (LX + 6 * XS) / TILEX ||
2099         ELY != (LY + 6 * YS) / TILEY ||
2100         LX + 6 * XS < 0 ||
2101         LY + 6 * YS < 0)))
2102   {
2103     LX -= XS;
2104     LY -= YS;
2105   }
2106   else
2107   {
2108     LX = ELX * TILEX + 14;
2109     LY = ELY * TILEY + 14;
2110
2111     laser.stops_inside_element = TRUE;
2112   }
2113
2114   AddLaserEdge(LX, LY);
2115   AddDamagedField(ELX, ELY);
2116
2117   if (game_mm.lights_still_needed == 0)
2118   {
2119     game_mm.level_solved = TRUE;
2120
2121     SetTileCursorActive(FALSE);
2122   }
2123
2124   return TRUE;
2125 }
2126
2127 boolean HitReflectingWalls(int element, int hit_mask)
2128 {
2129   // check if laser hits side of a wall with an angle that is not 90°
2130   if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2131                                             hit_mask == HIT_MASK_LEFT ||
2132                                             hit_mask == HIT_MASK_RIGHT ||
2133                                             hit_mask == HIT_MASK_BOTTOM))
2134   {
2135     PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2136
2137     LX -= XS;
2138     LY -= YS;
2139
2140     if (!IS_DF_GRID(element))
2141       AddLaserEdge(LX, LY);
2142
2143     // check if laser hits wall with an angle of 45°
2144     if (!IS_22_5_ANGLE(laser.current_angle))
2145     {
2146       if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2147       {
2148         LX += 2 * XS;
2149         laser.current_angle = get_mirrored_angle(laser.current_angle,
2150                                                  ANG_MIRROR_0);
2151       }
2152       else      // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2153       {
2154         LY += 2 * YS;
2155         laser.current_angle = get_mirrored_angle(laser.current_angle,
2156                                                  ANG_MIRROR_90);
2157       }
2158
2159       AddLaserEdge(LX, LY);
2160
2161       XS = 2 * Step[laser.current_angle].x;
2162       YS = 2 * Step[laser.current_angle].y;
2163
2164       return FALSE;
2165     }
2166     else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2167     {
2168       laser.current_angle = get_mirrored_angle(laser.current_angle,
2169                                                ANG_MIRROR_0);
2170       if (ABS(XS) == 4)
2171       {
2172         LX += 2 * XS;
2173         if (!IS_DF_GRID(element))
2174           AddLaserEdge(LX, LY);
2175       }
2176       else
2177       {
2178         LX += XS;
2179         if (!IS_DF_GRID(element))
2180           AddLaserEdge(LX, LY + YS / 2);
2181
2182         LX += XS;
2183         if (!IS_DF_GRID(element))
2184           AddLaserEdge(LX, LY);
2185       }
2186
2187       YS = 2 * Step[laser.current_angle].y;
2188
2189       return FALSE;
2190     }
2191     else        // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2192     {
2193       laser.current_angle = get_mirrored_angle(laser.current_angle,
2194                                                ANG_MIRROR_90);
2195       if (ABS(YS) == 4)
2196       {
2197         LY += 2 * YS;
2198         if (!IS_DF_GRID(element))
2199           AddLaserEdge(LX, LY);
2200       }
2201       else
2202       {
2203         LY += YS;
2204         if (!IS_DF_GRID(element))
2205           AddLaserEdge(LX + XS / 2, LY);
2206
2207         LY += YS;
2208         if (!IS_DF_GRID(element))
2209           AddLaserEdge(LX, LY);
2210       }
2211
2212       XS = 2 * Step[laser.current_angle].x;
2213
2214       return FALSE;
2215     }
2216   }
2217
2218   // reflection at the edge of reflecting DF style wall
2219   if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2220   {
2221     if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2222          hit_mask == HIT_MASK_TOPRIGHT) ||
2223         ((laser.current_angle == 5 || laser.current_angle == 7) &&
2224          hit_mask == HIT_MASK_TOPLEFT) ||
2225         ((laser.current_angle == 9 || laser.current_angle == 11) &&
2226          hit_mask == HIT_MASK_BOTTOMLEFT) ||
2227         ((laser.current_angle == 13 || laser.current_angle == 15) &&
2228          hit_mask == HIT_MASK_BOTTOMRIGHT))
2229     {
2230       int mirror_angle =
2231         (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2232          ANG_MIRROR_135 : ANG_MIRROR_45);
2233
2234       PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2235
2236       AddDamagedField(ELX, ELY);
2237       AddLaserEdge(LX, LY);
2238
2239       laser.current_angle = get_mirrored_angle(laser.current_angle,
2240                                                mirror_angle);
2241       XS = 8 / -XS;
2242       YS = 8 / -YS;
2243
2244       LX += XS;
2245       LY += YS;
2246
2247       AddLaserEdge(LX, LY);
2248
2249       return FALSE;
2250     }
2251   }
2252
2253   // reflection inside an edge of reflecting DF style wall
2254   if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2255   {
2256     if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2257          hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2258         ((laser.current_angle == 5 || laser.current_angle == 7) &&
2259          hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2260         ((laser.current_angle == 9 || laser.current_angle == 11) &&
2261          hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2262         ((laser.current_angle == 13 || laser.current_angle == 15) &&
2263          hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2264     {
2265       int mirror_angle =
2266         (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2267          hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2268          ANG_MIRROR_135 : ANG_MIRROR_45);
2269
2270       PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2271
2272       /*
2273       AddDamagedField(ELX, ELY);
2274       */
2275
2276       AddLaserEdge(LX - XS, LY - YS);
2277       AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2278                    LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2279
2280       laser.current_angle = get_mirrored_angle(laser.current_angle,
2281                                                mirror_angle);
2282       XS = 8 / -XS;
2283       YS = 8 / -YS;
2284
2285       LX += XS;
2286       LY += YS;
2287
2288       AddLaserEdge(LX, LY);
2289
2290       return FALSE;
2291     }
2292   }
2293
2294   // check if laser hits DF style wall with an angle of 90°
2295   if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2296   {
2297     if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2298          (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2299         (IS_VERT_ANGLE(laser.current_angle) &&
2300          (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2301     {
2302       // laser at last step touched nothing or the same side of the wall
2303       if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2304       {
2305         AddDamagedField(ELX, ELY);
2306
2307         LX += 8 * XS;
2308         LY += 8 * YS;
2309
2310         last_LX = LX;
2311         last_LY = LY;
2312         last_hit_mask = hit_mask;
2313
2314         return FALSE;
2315       }
2316     }
2317   }
2318
2319   if (!HitOnlyAnEdge(hit_mask))
2320   {
2321     laser.overloaded = TRUE;
2322
2323     return TRUE;
2324   }
2325
2326   return FALSE;
2327 }
2328
2329 boolean HitAbsorbingWalls(int element, int hit_mask)
2330 {
2331   if (HitOnlyAnEdge(hit_mask))
2332     return FALSE;
2333
2334   if (ABS(XS) == 4 &&
2335       (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2336   {
2337     AddLaserEdge(LX - XS, LY - YS);
2338
2339     LX = LX + XS / 2;
2340     LY = LY + YS;
2341   }
2342
2343   if (ABS(YS) == 4 &&
2344       (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2345   {
2346     AddLaserEdge(LX - XS, LY - YS);
2347
2348     LX = LX + XS;
2349     LY = LY + YS / 2;
2350   }
2351
2352   if (IS_WALL_WOOD(element) ||
2353       IS_DF_WALL_WOOD(element) ||
2354       IS_GRID_WOOD(element) ||
2355       IS_GRID_WOOD_FIXED(element) ||
2356       IS_GRID_WOOD_AUTO(element) ||
2357       element == EL_FUSE_ON ||
2358       element == EL_BLOCK_WOOD ||
2359       element == EL_GATE_WOOD)
2360   {
2361     PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2362
2363     return TRUE;
2364   }
2365
2366   if (IS_WALL_ICE(element))
2367   {
2368     int mask;
2369
2370     mask = (LX + XS) / MINI_TILEX - ELX * 2 + 1;    // Quadrant (horizontal)
2371     mask <<= (((LY + YS) / MINI_TILEY - ELY * 2) > 0) * 2;  // || (vertical)
2372
2373     // check if laser hits wall with an angle of 90°
2374     if (IS_90_ANGLE(laser.current_angle))
2375       mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2376
2377     if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2378     {
2379       int i;
2380
2381       for (i = 0; i < 4; i++)
2382       {
2383         if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2384           mask = 15 - (8 >> i);
2385         else if (ABS(XS) == 4 &&
2386                  mask == (1 << i) &&
2387                  (XS > 0) == (i % 2) &&
2388                  (YS < 0) == (i / 2))
2389           mask = 3 + (i / 2) * 9;
2390         else if (ABS(YS) == 4 &&
2391                  mask == (1 << i) &&
2392                  (XS < 0) == (i % 2) &&
2393                  (YS > 0) == (i / 2))
2394           mask = 5 + (i % 2) * 5;
2395       }
2396     }
2397
2398     laser.wall_mask = mask;
2399   }
2400   else if (IS_WALL_AMOEBA(element))
2401   {
2402     int elx = (LX - 2 * XS) / TILEX;
2403     int ely = (LY - 2 * YS) / TILEY;
2404     int element2 = Tile[elx][ely];
2405     int mask;
2406
2407     if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2408     {
2409       laser.dest_element = EL_EMPTY;
2410
2411       return TRUE;
2412     }
2413
2414     ELX = elx;
2415     ELY = ely;
2416
2417     mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2418     mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2419
2420     if (IS_90_ANGLE(laser.current_angle))
2421       mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2422
2423     laser.dest_element = element2 | EL_WALL_AMOEBA;
2424
2425     laser.wall_mask = mask;
2426   }
2427
2428   return TRUE;
2429 }
2430
2431 static void OpenExit(int x, int y)
2432 {
2433   int delay = 6;
2434
2435   if (!MovDelay[x][y])          // next animation frame
2436     MovDelay[x][y] = 4 * delay;
2437
2438   if (MovDelay[x][y])           // wait some time before next frame
2439   {
2440     int phase;
2441
2442     MovDelay[x][y]--;
2443     phase = MovDelay[x][y] / delay;
2444
2445     if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2446       DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2447
2448     if (!MovDelay[x][y])
2449     {
2450       Tile[x][y] = EL_EXIT_OPEN;
2451       DrawField_MM(x, y);
2452     }
2453   }
2454 }
2455
2456 static void OpenSurpriseBall(int x, int y)
2457 {
2458   int delay = 2;
2459
2460   if (!MovDelay[x][y])          // next animation frame
2461     MovDelay[x][y] = 50 * delay;
2462
2463   if (MovDelay[x][y])           // wait some time before next frame
2464   {
2465     MovDelay[x][y]--;
2466
2467     if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2468     {
2469       Bitmap *bitmap;
2470       int graphic = el2gfx(Store[x][y]);
2471       int gx, gy;
2472       int dx = RND(26), dy = RND(26);
2473
2474       getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2475
2476       BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
2477                  cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2478
2479       laser.redraw = TRUE;
2480
2481       MarkTileDirty(x, y);
2482     }
2483
2484     if (!MovDelay[x][y])
2485     {
2486       Tile[x][y] = Store[x][y];
2487       Store[x][y] = Store2[x][y] = 0;
2488       MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2489
2490       InitField(x, y, FALSE);
2491       DrawField_MM(x, y);
2492
2493       ScanLaser();
2494     }
2495   }
2496 }
2497
2498 static void OpenEnvelope(int x, int y)
2499 {
2500   int num_frames = 8;           // seven frames plus final empty space
2501
2502   if (!MovDelay[x][y])          // next animation frame
2503     MovDelay[x][y] = num_frames;
2504
2505   if (MovDelay[x][y])           // wait some time before next frame
2506   {
2507     int nr = ENVELOPE_OPENING_NR(Tile[x][y]);
2508
2509     MovDelay[x][y]--;
2510
2511     if (MovDelay[x][y] > 0 && IN_SCR_FIELD(x, y))
2512     {
2513       int graphic = el_act2gfx(EL_ENVELOPE_1 + nr, MM_ACTION_COLLECTING);
2514       int frame = num_frames - MovDelay[x][y] - 1;
2515
2516       DrawGraphicAnimation_MM(x, y, graphic, frame);
2517
2518       laser.redraw = TRUE;
2519     }
2520
2521     if (MovDelay[x][y] == 0)
2522     {
2523       Tile[x][y] = EL_EMPTY;
2524
2525       DrawField_MM(x, y);
2526
2527       ScanLaser();
2528
2529       ShowEnvelope_MM(nr);
2530     }
2531   }
2532 }
2533
2534 static void MeltIce(int x, int y)
2535 {
2536   int frames = 5;
2537   int delay = 5;
2538
2539   if (!MovDelay[x][y])          // next animation frame
2540     MovDelay[x][y] = frames * delay;
2541
2542   if (MovDelay[x][y])           // wait some time before next frame
2543   {
2544     int phase;
2545     int wall_mask = Store2[x][y];
2546     int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_ICE;
2547
2548     MovDelay[x][y]--;
2549     phase = frames - MovDelay[x][y] / delay - 1;
2550
2551     if (!MovDelay[x][y])
2552     {
2553       int i;
2554
2555       Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2556       Store[x][y] = Store2[x][y] = 0;
2557
2558       DrawWalls_MM(x, y, Tile[x][y]);
2559
2560       if (Tile[x][y] == EL_WALL_ICE)
2561         Tile[x][y] = EL_EMPTY;
2562
2563       for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
2564         if (laser.damage[i].is_mirror)
2565           break;
2566
2567       if (i > 0)
2568         DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
2569       else
2570         DrawLaser(0, DL_LASER_DISABLED);
2571
2572       ScanLaser();
2573     }
2574     else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2575     {
2576       DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2577
2578       laser.redraw = TRUE;
2579     }
2580   }
2581 }
2582
2583 static void GrowAmoeba(int x, int y)
2584 {
2585   int frames = 5;
2586   int delay = 1;
2587
2588   if (!MovDelay[x][y])          // next animation frame
2589     MovDelay[x][y] = frames * delay;
2590
2591   if (MovDelay[x][y])           // wait some time before next frame
2592   {
2593     int phase;
2594     int wall_mask = Store2[x][y];
2595     int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_AMOEBA;
2596
2597     MovDelay[x][y]--;
2598     phase = MovDelay[x][y] / delay;
2599
2600     if (!MovDelay[x][y])
2601     {
2602       Tile[x][y] = real_element;
2603       Store[x][y] = Store2[x][y] = 0;
2604
2605       DrawWalls_MM(x, y, Tile[x][y]);
2606       DrawLaser(0, DL_LASER_ENABLED);
2607     }
2608     else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2609     {
2610       DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2611     }
2612   }
2613 }
2614
2615 static void DrawFieldAnimated_MM(int x, int y)
2616 {
2617   DrawField_MM(x, y);
2618
2619   laser.redraw = TRUE;
2620 }
2621
2622 static void DrawFieldAnimatedIfNeeded_MM(int x, int y)
2623 {
2624   int element = Tile[x][y];
2625   int graphic = el2gfx(element);
2626
2627   if (!getGraphicInfo_NewFrame(x, y, graphic))
2628     return;
2629
2630   DrawField_MM(x, y);
2631
2632   laser.redraw = TRUE;
2633 }
2634
2635 static void DrawFieldTwinkle(int x, int y)
2636 {
2637   if (MovDelay[x][y] != 0)      // wait some time before next frame
2638   {
2639     MovDelay[x][y]--;
2640
2641     DrawField_MM(x, y);
2642
2643     if (MovDelay[x][y] != 0)
2644     {
2645       int graphic = IMG_TWINKLE_WHITE;
2646       int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
2647
2648       DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
2649     }
2650
2651     laser.redraw = TRUE;
2652   }
2653 }
2654
2655 static void Explode_MM(int x, int y, int phase, int mode)
2656 {
2657   int num_phase = 9, delay = 2;
2658   int last_phase = num_phase * delay;
2659   int half_phase = (num_phase / 2) * delay;
2660
2661   laser.redraw = TRUE;
2662
2663   if (phase == EX_PHASE_START)          // initialize 'Store[][]' field
2664   {
2665     int center_element = Tile[x][y];
2666
2667     if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2668     {
2669       // put moving element to center field (and let it explode there)
2670       center_element = MovingOrBlocked2Element_MM(x, y);
2671       RemoveMovingField_MM(x, y);
2672
2673       Tile[x][y] = center_element;
2674     }
2675
2676     Store[x][y] = center_element;
2677     Store2[x][y] = mode;
2678
2679     Tile[x][y] = EL_EXPLODING_OPAQUE;
2680
2681     GfxElement[x][y] = (center_element == EL_BOMB_ACTIVE ? EL_BOMB :
2682                         IS_MCDUFFIN(center_element) ? EL_MCDUFFIN :
2683                         center_element);
2684
2685     MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2686
2687     ExplodePhase[x][y] = 1;
2688
2689     return;
2690   }
2691
2692   if (phase == 1)
2693     GfxFrame[x][y] = 0;         // restart explosion animation
2694
2695   ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
2696
2697   if (phase == half_phase)
2698   {
2699     Tile[x][y] = EL_EXPLODING_TRANSP;
2700
2701     if (x == ELX && y == ELY)
2702       ScanLaser();
2703   }
2704
2705   if (phase == last_phase)
2706   {
2707     if (Store[x][y] == EL_BOMB_ACTIVE)
2708     {
2709       DrawLaser(0, DL_LASER_DISABLED);
2710       InitLaser();
2711
2712       Bang_MM(laser.start_edge.x, laser.start_edge.y);
2713
2714       GameOver_MM(GAME_OVER_DELAYED);
2715
2716       laser.overloaded = FALSE;
2717     }
2718     else if (IS_MCDUFFIN(Store[x][y]))
2719     {
2720       GameOver_MM(GAME_OVER_BOMB);
2721     }
2722
2723     Tile[x][y] = EL_EMPTY;
2724
2725     Store[x][y] = Store2[x][y] = 0;
2726     MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2727
2728     InitField(x, y, FALSE);
2729     DrawField_MM(x, y);
2730   }
2731   else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2732   {
2733     int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
2734     int frame = getGraphicAnimationFrameXY(graphic, x, y);
2735
2736     DrawGraphicAnimation_MM(x, y, graphic, frame);
2737
2738     MarkTileDirty(x, y);
2739   }
2740 }
2741
2742 static void Bang_MM(int x, int y)
2743 {
2744   int element = Tile[x][y];
2745
2746 #if 0
2747   DrawLaser(0, DL_LASER_ENABLED);
2748 #endif
2749
2750   if (IS_PACMAN(element))
2751     PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2752   else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
2753     PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2754   else if (element == EL_KEY)
2755     PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2756   else
2757     PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2758
2759   Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
2760 }
2761
2762 void TurnRound(int x, int y)
2763 {
2764   static struct
2765   {
2766     int x, y;
2767   } move_xy[] =
2768   {
2769     { 0, 0 },
2770     {-1, 0 },
2771     {+1, 0 },
2772     { 0, 0 },
2773     { 0, -1 },
2774     { 0, 0 }, { 0, 0 }, { 0, 0 },
2775     { 0, +1 }
2776   };
2777   static struct
2778   {
2779     int left, right, back;
2780   } turn[] =
2781   {
2782     { 0,        0,              0 },
2783     { MV_DOWN,  MV_UP,          MV_RIGHT },
2784     { MV_UP,    MV_DOWN,        MV_LEFT },
2785     { 0,        0,              0 },
2786     { MV_LEFT,  MV_RIGHT,       MV_DOWN },
2787     { 0,0,0 },  { 0,0,0 },      { 0,0,0 },
2788     { MV_RIGHT, MV_LEFT,        MV_UP }
2789   };
2790
2791   int element = Tile[x][y];
2792   int old_move_dir = MovDir[x][y];
2793   int right_dir = turn[old_move_dir].right;
2794   int back_dir = turn[old_move_dir].back;
2795   int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2796   int right_x = x + right_dx, right_y = y + right_dy;
2797
2798   if (element == EL_PACMAN)
2799   {
2800     boolean can_turn_right = FALSE;
2801
2802     if (IN_LEV_FIELD(right_x, right_y) &&
2803         IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2804       can_turn_right = TRUE;
2805
2806     if (can_turn_right)
2807       MovDir[x][y] = right_dir;
2808     else
2809       MovDir[x][y] = back_dir;
2810
2811     MovDelay[x][y] = 0;
2812   }
2813 }
2814
2815 static void StartMoving_MM(int x, int y)
2816 {
2817   int element = Tile[x][y];
2818
2819   if (Stop[x][y])
2820     return;
2821
2822   if (CAN_MOVE(element))
2823   {
2824     int newx, newy;
2825
2826     if (MovDelay[x][y])         // wait some time before next movement
2827     {
2828       MovDelay[x][y]--;
2829
2830       if (MovDelay[x][y])
2831         return;
2832     }
2833
2834     // now make next step
2835
2836     Moving2Blocked_MM(x, y, &newx, &newy);      // get next screen position
2837
2838     if (element == EL_PACMAN &&
2839         IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2840         !ObjHit(newx, newy, HIT_POS_CENTER))
2841     {
2842       Store[newx][newy] = Tile[newx][newy];
2843       Tile[newx][newy] = EL_EMPTY;
2844
2845       DrawField_MM(newx, newy);
2846     }
2847     else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2848              ObjHit(newx, newy, HIT_POS_CENTER))
2849     {
2850       // object was running against a wall
2851
2852       TurnRound(x, y);
2853
2854       return;
2855     }
2856
2857     InitMovingField_MM(x, y, MovDir[x][y]);
2858   }
2859
2860   if (MovDir[x][y])
2861     ContinueMoving_MM(x, y);
2862 }
2863
2864 static void ContinueMoving_MM(int x, int y)
2865 {
2866   int element = Tile[x][y];
2867   int direction = MovDir[x][y];
2868   int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2869   int dy = (direction == MV_UP   ? -1 : direction == MV_DOWN  ? +1 : 0);
2870   int horiz_move = (dx!=0);
2871   int newx = x + dx, newy = y + dy;
2872   int step = (horiz_move ? dx : dy) * TILEX / 8;
2873
2874   MovPos[x][y] += step;
2875
2876   if (ABS(MovPos[x][y]) >= TILEX)       // object reached its destination
2877   {
2878     Tile[x][y] = EL_EMPTY;
2879     Tile[newx][newy] = element;
2880
2881     MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2882     MovDelay[newx][newy] = 0;
2883
2884     if (!CAN_MOVE(element))
2885       MovDir[newx][newy] = 0;
2886
2887     DrawField_MM(x, y);
2888     DrawField_MM(newx, newy);
2889
2890     Stop[newx][newy] = TRUE;
2891
2892     if (element == EL_PACMAN)
2893     {
2894       if (Store[newx][newy] == EL_BOMB)
2895         Bang_MM(newx, newy);
2896
2897       if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2898           (LX + 2 * XS) / TILEX == newx &&
2899           (LY + 2 * YS) / TILEY == newy)
2900       {
2901         laser.num_edges--;
2902         ScanLaser();
2903       }
2904     }
2905   }
2906   else                          // still moving on
2907   {
2908     DrawField_MM(x, y);
2909   }
2910
2911   laser.redraw = TRUE;
2912 }
2913
2914 boolean ClickElement(int x, int y, int button)
2915 {
2916   static DelayCounter click_delay = { CLICK_DELAY };
2917   static boolean new_button = TRUE;
2918   boolean element_clicked = FALSE;
2919   int element;
2920
2921   if (button == -1)
2922   {
2923     // initialize static variables
2924     click_delay.count = 0;
2925     click_delay.value = CLICK_DELAY;
2926     new_button = TRUE;
2927
2928     return FALSE;
2929   }
2930
2931   // do not rotate objects hit by the laser after the game was solved
2932   if (game_mm.level_solved && Hit[x][y])
2933     return FALSE;
2934
2935   if (button == MB_RELEASED)
2936   {
2937     new_button = TRUE;
2938     click_delay.value = CLICK_DELAY;
2939
2940     // release eventually hold auto-rotating mirror
2941     RotateMirror(x, y, MB_RELEASED);
2942
2943     return FALSE;
2944   }
2945
2946   if (!FrameReached(&click_delay) && !new_button)
2947     return FALSE;
2948
2949   if (button == MB_MIDDLEBUTTON)        // middle button has no function
2950     return FALSE;
2951
2952   if (!IN_LEV_FIELD(x, y))
2953     return FALSE;
2954
2955   if (Tile[x][y] == EL_EMPTY)
2956     return FALSE;
2957
2958   element = Tile[x][y];
2959
2960   if (IS_MIRROR(element) ||
2961       IS_BEAMER(element) ||
2962       IS_POLAR(element) ||
2963       IS_POLAR_CROSS(element) ||
2964       IS_DF_MIRROR(element) ||
2965       IS_DF_MIRROR_AUTO(element))
2966   {
2967     RotateMirror(x, y, button);
2968
2969     element_clicked = TRUE;
2970   }
2971   else if (IS_MCDUFFIN(element))
2972   {
2973     if (!laser.fuse_off)
2974     {
2975       DrawLaser(0, DL_LASER_DISABLED);
2976
2977       /*
2978       BackToFront();
2979       */
2980     }
2981
2982     element = get_rotated_element(element, BUTTON_ROTATION(button));
2983     laser.start_angle = get_element_angle(element);
2984
2985     InitLaser();
2986
2987     Tile[x][y] = element;
2988     DrawField_MM(x, y);
2989
2990     /*
2991     BackToFront();
2992     */
2993
2994     if (!laser.fuse_off)
2995       ScanLaser();
2996
2997     element_clicked = TRUE;
2998   }
2999   else if (element == EL_FUSE_ON && laser.fuse_off)
3000   {
3001     if (x != laser.fuse_x || y != laser.fuse_y)
3002       return FALSE;
3003
3004     laser.fuse_off = FALSE;
3005     laser.fuse_x = laser.fuse_y = -1;
3006
3007     DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
3008     ScanLaser();
3009
3010     element_clicked = TRUE;
3011   }
3012   else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
3013   {
3014     laser.fuse_off = TRUE;
3015     laser.fuse_x = x;
3016     laser.fuse_y = y;
3017     laser.overloaded = FALSE;
3018
3019     DrawLaser(0, DL_LASER_DISABLED);
3020     DrawGraphic_MM(x, y, IMG_MM_FUSE);
3021
3022     element_clicked = TRUE;
3023   }
3024   else if (element == EL_LIGHTBALL)
3025   {
3026     Bang_MM(x, y);
3027     RaiseScoreElement_MM(element);
3028     DrawLaser(0, DL_LASER_ENABLED);
3029
3030     element_clicked = TRUE;
3031   }
3032   else if (IS_ENVELOPE(element))
3033   {
3034     Tile[x][y] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(element);
3035
3036     element_clicked = TRUE;
3037   }
3038
3039   click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
3040   new_button = FALSE;
3041
3042   return element_clicked;
3043 }
3044
3045 void RotateMirror(int x, int y, int button)
3046 {
3047   if (button == MB_RELEASED)
3048   {
3049     // release eventually hold auto-rotating mirror
3050     hold_x = -1;
3051     hold_y = -1;
3052
3053     return;
3054   }
3055
3056   if (IS_MIRROR(Tile[x][y]) ||
3057       IS_POLAR_CROSS(Tile[x][y]) ||
3058       IS_POLAR(Tile[x][y]) ||
3059       IS_BEAMER(Tile[x][y]) ||
3060       IS_DF_MIRROR(Tile[x][y]) ||
3061       IS_GRID_STEEL_AUTO(Tile[x][y]) ||
3062       IS_GRID_WOOD_AUTO(Tile[x][y]))
3063   {
3064     Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
3065   }
3066   else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
3067   {
3068     if (button == MB_LEFTBUTTON)
3069     {
3070       // left mouse button only for manual adjustment, no auto-rotating;
3071       // freeze mirror for until mouse button released
3072       hold_x = x;
3073       hold_y = y;
3074     }
3075     else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
3076     {
3077       Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
3078     }
3079   }
3080
3081   if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
3082   {
3083     int edge = Hit[x][y];
3084
3085     DrawField_MM(x, y);
3086
3087     if (edge > 0)
3088     {
3089       DrawLaser(edge - 1, DL_LASER_DISABLED);
3090       ScanLaser();
3091     }
3092   }
3093   else if (ObjHit(x, y, HIT_POS_CENTER))
3094   {
3095     int edge = Hit[x][y];
3096
3097     if (edge == 0)
3098     {
3099       Warn("RotateMirror: inconsistent field Hit[][]!\n");
3100
3101       edge = 1;
3102     }
3103
3104     DrawLaser(edge - 1, DL_LASER_DISABLED);
3105     ScanLaser();
3106   }
3107   else
3108   {
3109     int check = 1;
3110
3111     if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
3112       check = 2;
3113
3114     DrawField_MM(x, y);
3115
3116     if ((IS_BEAMER(Tile[x][y]) ||
3117          IS_POLAR(Tile[x][y]) ||
3118          IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
3119     {
3120       if (IS_BEAMER(Tile[x][y]))
3121       {
3122 #if 0
3123         Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3124               LX, LY, laser.beamer_edge, laser.beamer[1].num);
3125 #endif
3126
3127         if (check == 1)
3128           laser.num_edges--;
3129       }
3130
3131       ScanLaser();
3132
3133       check = 0;
3134     }
3135
3136     if (check == 2)
3137       DrawLaser(0, DL_LASER_ENABLED);
3138   }
3139 }
3140
3141 static void AutoRotateMirrors(void)
3142 {
3143   int x, y;
3144
3145   if (!FrameReached(&rotate_delay))
3146     return;
3147
3148   for (x = 0; x < lev_fieldx; x++)
3149   {
3150     for (y = 0; y < lev_fieldy; y++)
3151     {
3152       int element = Tile[x][y];
3153
3154       // do not rotate objects hit by the laser after the game was solved
3155       if (game_mm.level_solved && Hit[x][y])
3156         continue;
3157
3158       if (IS_DF_MIRROR_AUTO(element) ||
3159           IS_GRID_WOOD_AUTO(element) ||
3160           IS_GRID_STEEL_AUTO(element) ||
3161           element == EL_REFRACTOR)
3162         RotateMirror(x, y, MB_RIGHTBUTTON);
3163     }
3164   }
3165 }
3166
3167 boolean ObjHit(int obx, int oby, int bits)
3168 {
3169   int i;
3170
3171   obx *= TILEX;
3172   oby *= TILEY;
3173
3174   if (bits & HIT_POS_CENTER)
3175   {
3176     if (CheckLaserPixel(cSX + obx + 15,
3177                         cSY + oby + 15))
3178       return TRUE;
3179   }
3180
3181   if (bits & HIT_POS_EDGE)
3182   {
3183     for (i = 0; i < 4; i++)
3184       if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3185                           cSY + oby + 31 * (i / 2)))
3186         return TRUE;
3187   }
3188
3189   if (bits & HIT_POS_BETWEEN)
3190   {
3191     for (i = 0; i < 4; i++)
3192       if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3193                           cSY + 4 + oby + 22 * (i / 2)))
3194         return TRUE;
3195   }
3196
3197   return FALSE;
3198 }
3199
3200 void DeletePacMan(int px, int py)
3201 {
3202   int i, j;
3203
3204   Bang_MM(px, py);
3205
3206   if (game_mm.num_pacman <= 1)
3207   {
3208     game_mm.num_pacman = 0;
3209     return;
3210   }
3211
3212   for (i = 0; i < game_mm.num_pacman; i++)
3213     if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3214       break;
3215
3216   game_mm.num_pacman--;
3217
3218   for (j = i; j < game_mm.num_pacman; j++)
3219   {
3220     game_mm.pacman[j].x   = game_mm.pacman[j + 1].x;
3221     game_mm.pacman[j].y   = game_mm.pacman[j + 1].y;
3222     game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3223   }
3224 }
3225
3226 void ColorCycling(void)
3227 {
3228   static int CC, Cc = 0;
3229
3230   static int color, old = 0xF00, new = 0x010, mult = 1;
3231   static unsigned short red, green, blue;
3232
3233   if (color_status == STATIC_COLORS)
3234     return;
3235
3236   CC = FrameCounter;
3237
3238   if (CC < Cc || CC > Cc + 2)
3239   {
3240     Cc = CC;
3241
3242     color = old + new * mult;
3243     if (mult > 0)
3244       mult++;
3245     else
3246       mult--;
3247
3248     if (ABS(mult) == 16)
3249     {
3250       mult =- mult / 16;
3251       old = color;
3252       new = new << 4;
3253
3254       if (new > 0x100)
3255         new = 0x001;
3256     }
3257
3258     red   = 0x0e00 * ((color & 0xF00) >> 8);
3259     green = 0x0e00 * ((color & 0x0F0) >> 4);
3260     blue  = 0x0e00 * ((color & 0x00F));
3261     SetRGB(pen_magicolor[0], red, green, blue);
3262
3263     red   = 0x1111 * ((color & 0xF00) >> 8);
3264     green = 0x1111 * ((color & 0x0F0) >> 4);
3265     blue  = 0x1111 * ((color & 0x00F));
3266     SetRGB(pen_magicolor[1], red, green, blue);
3267   }
3268 }
3269
3270 static void GameActions_MM_Ext(void)
3271 {
3272   int element;
3273   int x, y, i;
3274
3275   int r, d;
3276
3277   for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3278     Stop[x][y] = FALSE;
3279
3280   for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3281   {
3282     element = Tile[x][y];
3283
3284     if (!IS_MOVING(x, y) && CAN_MOVE(element))
3285       StartMoving_MM(x, y);
3286     else if (IS_MOVING(x, y))
3287       ContinueMoving_MM(x, y);
3288     else if (IS_EXPLODING(element))
3289       Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3290     else if (element == EL_EXIT_OPENING)
3291       OpenExit(x, y);
3292     else if (element == EL_GRAY_BALL_OPENING)
3293       OpenSurpriseBall(x, y);
3294     else if (IS_ENVELOPE_OPENING(element))
3295       OpenEnvelope(x, y);
3296     else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
3297       MeltIce(x, y);
3298     else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
3299       GrowAmoeba(x, y);
3300     else if (IS_MIRROR(element) ||
3301              IS_MIRROR_FIXED(element) ||
3302              element == EL_PRISM)
3303       DrawFieldTwinkle(x, y);
3304     else if (element == EL_GRAY_BALL_OPENING ||
3305              element == EL_BOMB_ACTIVE ||
3306              element == EL_MINE_ACTIVE)
3307       DrawFieldAnimated_MM(x, y);
3308     else if (!IS_BLOCKED(x, y))
3309       DrawFieldAnimatedIfNeeded_MM(x, y);
3310   }
3311
3312   AutoRotateMirrors();
3313
3314 #if 1
3315   // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3316
3317   // redraw after Explode_MM() ...
3318   if (laser.redraw)
3319     DrawLaser(0, DL_LASER_ENABLED);
3320   laser.redraw = FALSE;
3321 #endif
3322
3323   CT = FrameCounter;
3324
3325   if (game_mm.num_pacman && FrameReached(&pacman_delay))
3326   {
3327     MovePacMen();
3328
3329     if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3330     {
3331       DrawLaser(0, DL_LASER_DISABLED);
3332       ScanLaser();
3333     }
3334   }
3335
3336   // skip all following game actions if game is over
3337   if (game_mm.game_over)
3338     return;
3339
3340   if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3341   {
3342     FadeOutLaser();
3343
3344     GameOver_MM(GAME_OVER_NO_ENERGY);
3345
3346     return;
3347   }
3348
3349   if (FrameReached(&energy_delay))
3350   {
3351     if (game_mm.energy_left > 0)
3352       game_mm.energy_left--;
3353
3354     // when out of energy, wait another frame to play "out of time" sound
3355   }
3356
3357   element = laser.dest_element;
3358
3359 #if 0
3360   if (element != Tile[ELX][ELY])
3361   {
3362     Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3363           element, Tile[ELX][ELY]);
3364   }
3365 #endif
3366
3367   if (!laser.overloaded && laser.overload_value == 0 &&
3368       element != EL_BOMB &&
3369       element != EL_BOMB_ACTIVE &&
3370       element != EL_MINE &&
3371       element != EL_MINE_ACTIVE &&
3372       element != EL_BALL_GRAY &&
3373       element != EL_BLOCK_STONE &&
3374       element != EL_BLOCK_WOOD &&
3375       element != EL_FUSE_ON &&
3376       element != EL_FUEL_FULL &&
3377       !IS_WALL_ICE(element) &&
3378       !IS_WALL_AMOEBA(element))
3379     return;
3380
3381   overload_delay.value = HEALTH_DELAY(laser.overloaded);
3382
3383   if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3384        (!laser.overloaded && laser.overload_value > 0)) &&
3385       FrameReached(&overload_delay))
3386   {
3387     if (laser.overloaded)
3388       laser.overload_value++;
3389     else
3390       laser.overload_value--;
3391
3392     if (game_mm.cheat_no_overload)
3393     {
3394       laser.overloaded = FALSE;
3395       laser.overload_value = 0;
3396     }
3397
3398     game_mm.laser_overload_value = laser.overload_value;
3399
3400     if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3401     {
3402       SetLaserColor(0xFF);
3403
3404       DrawLaser(0, DL_LASER_ENABLED);
3405     }
3406
3407     if (!laser.overloaded)
3408       StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3409     else if (setup.sound_loops)
3410       PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3411     else
3412       PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3413
3414     if (laser.overloaded)
3415     {
3416 #if 0
3417       BlitBitmap(pix[PIX_DOOR], drawto,
3418                  DOOR_GFX_PAGEX4 + XX_OVERLOAD,
3419                  DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
3420                  - laser.overload_value,
3421                  OVERLOAD_XSIZE, laser.overload_value,
3422                  DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
3423                  - laser.overload_value);
3424 #endif
3425       redraw_mask |= REDRAW_DOOR_1;
3426     }
3427     else
3428     {
3429 #if 0
3430       BlitBitmap(pix[PIX_DOOR], drawto,
3431                  DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
3432                  OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
3433                  DX_OVERLOAD, DY_OVERLOAD);
3434 #endif
3435       redraw_mask |= REDRAW_DOOR_1;
3436     }
3437
3438     if (laser.overload_value == MAX_LASER_OVERLOAD)
3439     {
3440       UpdateAndDisplayGameControlValues();
3441
3442       FadeOutLaser();
3443
3444       GameOver_MM(GAME_OVER_OVERLOADED);
3445
3446       return;
3447     }
3448   }
3449
3450   if (laser.fuse_off)
3451     return;
3452
3453   CT -= Ct;
3454
3455   if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3456   {
3457     if (game_mm.cheat_no_explosion)
3458       return;
3459
3460     Bang_MM(ELX, ELY);
3461
3462     laser.dest_element = EL_EXPLODING_OPAQUE;
3463
3464     return;
3465   }
3466
3467   if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3468   {
3469     laser.fuse_off = TRUE;
3470     laser.fuse_x = ELX;
3471     laser.fuse_y = ELY;
3472
3473     DrawLaser(0, DL_LASER_DISABLED);
3474     DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3475   }
3476
3477   if (element == EL_BALL_GRAY && CT > native_mm_level.time_ball)
3478   {
3479     if (!Store2[ELX][ELY])      // check if content element not yet determined
3480     {
3481       int last_anim_random_frame = gfx.anim_random_frame;
3482       int element_pos;
3483
3484       if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3485         gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3486
3487       element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3488                                       native_mm_level.ball_choice_mode, 0,
3489                                       game_mm.ball_choice_pos);
3490
3491       if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3492         gfx.anim_random_frame = last_anim_random_frame;
3493
3494       game_mm.ball_choice_pos++;
3495
3496       int new_element = native_mm_level.ball_content[element_pos];
3497
3498       // randomly rotate newly created game element, if needed
3499       if (native_mm_level.rotate_ball_content)
3500         new_element = get_rotated_element(new_element, RND(16));
3501
3502       Store[ELX][ELY] = new_element;
3503       Store2[ELX][ELY] = TRUE;
3504     }
3505
3506     Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3507
3508     // !!! CHECK AGAIN: Laser on Polarizer !!!
3509     ScanLaser();
3510
3511     laser.dest_element_last = Tile[ELX][ELY];
3512     laser.dest_element_last_x = ELX;
3513     laser.dest_element_last_y = ELY;
3514
3515     return;
3516
3517 #if 0
3518     int graphic;
3519
3520     switch (RND(5))
3521     {
3522       case 0:
3523         element = EL_MIRROR_START + RND(16);
3524         break;
3525       case 1:
3526         {
3527           int rnd = RND(3);
3528
3529           element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3530         }
3531         break;
3532       default:
3533         {
3534           int rnd = RND(3);
3535
3536           element = (rnd == 0 ? EL_FUSE_ON :
3537                      rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3538                      rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3539                      rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3540                      EL_MIRROR_FIXED_START + rnd - 25);
3541         }
3542         break;
3543     }
3544
3545     graphic = el2gfx(element);
3546
3547     for (i = 0; i < 50; i++)
3548     {
3549       int x = RND(26);
3550       int y = RND(26);
3551
3552 #if 0
3553       BlitBitmap(pix[PIX_BACK], drawto,
3554                  SX + (graphic % GFX_PER_LINE) * TILEX + x,
3555                  SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3556                  SX + ELX * TILEX + x,
3557                  SY + ELY * TILEY + y);
3558 #endif
3559       MarkTileDirty(ELX, ELY);
3560       BackToFront();
3561
3562       DrawLaser(0, DL_LASER_ENABLED);
3563
3564       Delay_WithScreenUpdates(50);
3565     }
3566
3567     Tile[ELX][ELY] = element;
3568     DrawField_MM(ELX, ELY);
3569
3570 #if 0
3571     Debug("game:mm:GameActions_MM_Ext", "NEW ELEMENT: (%d, %d)", ELX, ELY);
3572 #endif
3573
3574     // above stuff: GRAY BALL -> PRISM !!!
3575 /*
3576     LX = ELX * TILEX + 14;
3577     LY = ELY * TILEY + 14;
3578     if (laser.current_angle == (laser.current_angle >> 1) << 1)
3579       OK = 8;
3580     else
3581       OK = 4;
3582     LX -= OK * XS;
3583     LY -= OK * YS;
3584
3585     laser.num_edges -= 2;
3586     laser.num_damages--;
3587 */
3588
3589 #if 0
3590     for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3591       if (laser.damage[i].is_mirror)
3592         break;
3593
3594     if (i > 0)
3595       DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3596     else
3597       DrawLaser(0, DL_LASER_DISABLED);
3598 #else
3599     DrawLaser(0, DL_LASER_DISABLED);
3600 #endif
3601
3602     ScanLaser();
3603 #endif
3604
3605     return;
3606   }
3607
3608   if (IS_WALL_ICE(element) && CT > 50)
3609   {
3610     PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3611
3612     {
3613       Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3614       Store[ELX][ELY] = EL_WALL_ICE;
3615       Store2[ELX][ELY] = laser.wall_mask;
3616
3617       laser.dest_element = Tile[ELX][ELY];
3618
3619       return;
3620     }
3621
3622     for (i = 0; i < 5; i++)
3623     {
3624       int phase = i + 1;
3625
3626       if (i == 4)
3627       {
3628         Tile[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3629         phase = 0;
3630       }
3631
3632       DrawWallsAnimation_MM(ELX, ELY, Tile[ELX][ELY], phase, laser.wall_mask);
3633       BackToFront();
3634       Delay_WithScreenUpdates(100);
3635     }
3636
3637     if (Tile[ELX][ELY] == EL_WALL_ICE)
3638       Tile[ELX][ELY] = EL_EMPTY;
3639
3640 /*
3641     laser.num_edges--;
3642     LX = laser.edge[laser.num_edges].x - cSX2;
3643     LY = laser.edge[laser.num_edges].y - cSY2;
3644 */
3645
3646     for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
3647       if (laser.damage[i].is_mirror)
3648         break;
3649
3650     if (i > 0)
3651       DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3652     else
3653       DrawLaser(0, DL_LASER_DISABLED);
3654
3655     ScanLaser();
3656
3657     return;
3658   }
3659
3660   if (IS_WALL_AMOEBA(element) && CT > 60)
3661   {
3662     int k1, k2, k3, dx, dy, de, dm;
3663     int element2 = Tile[ELX][ELY];
3664
3665     if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3666       return;
3667
3668     for (i = laser.num_damages - 1; i >= 0; i--)
3669       if (laser.damage[i].is_mirror)
3670         break;
3671
3672     r = laser.num_edges;
3673     d = laser.num_damages;
3674     k1 = i;
3675
3676     if (k1 > 0)
3677     {
3678       int x, y;
3679
3680       DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3681
3682       laser.num_edges++;
3683       DrawLaser(0, DL_LASER_ENABLED);
3684       laser.num_edges--;
3685
3686       x = laser.damage[k1].x;
3687       y = laser.damage[k1].y;
3688
3689       DrawField_MM(x, y);
3690     }
3691
3692     for (i = 0; i < 4; i++)
3693     {
3694       if (laser.wall_mask & (1 << i))
3695       {
3696         if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3697                             cSY + ELY * TILEY + 31 * (i / 2)))
3698           break;
3699
3700         if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3701                             cSY + ELY * TILEY + 14 + (i / 2) * 2))
3702           break;
3703       }
3704     }
3705
3706     k2 = i;
3707
3708     for (i = 0; i < 4; i++)
3709     {
3710       if (laser.wall_mask & (1 << i))
3711       {
3712         if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3713                             cSY + ELY * TILEY + 31 * (i / 2)))
3714           break;
3715       }
3716     }
3717
3718     k3 = i;
3719
3720     if (laser.num_beamers > 0 ||
3721         k1 < 1 || k2 < 4 || k3 < 4 ||
3722         CheckLaserPixel(cSX + ELX * TILEX + 14,
3723                         cSY + ELY * TILEY + 14))
3724     {
3725       laser.num_edges = r;
3726       laser.num_damages = d;
3727
3728       DrawLaser(0, DL_LASER_DISABLED);
3729     }
3730
3731     Tile[ELX][ELY] = element | laser.wall_mask;
3732
3733     dx = ELX;
3734     dy = ELY;
3735     de = Tile[ELX][ELY];
3736     dm = laser.wall_mask;
3737
3738 #if 1
3739     {
3740       int x = ELX, y = ELY;
3741       int wall_mask = laser.wall_mask;
3742
3743       ScanLaser();
3744       DrawLaser(0, DL_LASER_ENABLED);
3745
3746       PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3747
3748       Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3749       Store[x][y] = EL_WALL_AMOEBA;
3750       Store2[x][y] = wall_mask;
3751
3752       return;
3753     }
3754 #endif
3755
3756     DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3757     ScanLaser();
3758     DrawLaser(0, DL_LASER_ENABLED);
3759
3760     PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3761
3762     for (i = 4; i >= 0; i--)
3763     {
3764       DrawWallsAnimation_MM(dx, dy, de, i, dm);
3765
3766       BackToFront();
3767       Delay_WithScreenUpdates(20);
3768     }
3769
3770     DrawLaser(0, DL_LASER_ENABLED);
3771
3772     return;
3773   }
3774
3775   if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3776       laser.stops_inside_element && CT > native_mm_level.time_block)
3777   {
3778     int x, y;
3779     int k;
3780
3781     if (ABS(XS) > ABS(YS))
3782       k = 0;
3783     else
3784       k = 1;
3785     if (XS < YS)
3786       k += 2;
3787
3788     for (i = 0; i < 4; i++)
3789     {
3790       if (i)
3791         k++;
3792       if (k > 3)
3793         k = 0;
3794
3795       x = ELX + Step[k * 4].x;
3796       y = ELY + Step[k * 4].y;
3797
3798       if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3799         continue;
3800
3801       if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3802         continue;
3803
3804       break;
3805     }
3806
3807     if (i > 3)
3808     {
3809       laser.overloaded = (element == EL_BLOCK_STONE);
3810
3811       return;
3812     }
3813
3814     PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3815
3816     Tile[ELX][ELY] = 0;
3817     Tile[x][y] = element;
3818
3819     DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3820     DrawField_MM(x, y);
3821
3822     if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3823     {
3824       DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3825       DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3826     }
3827
3828     ScanLaser();
3829
3830     return;
3831   }
3832
3833   if (element == EL_FUEL_FULL && CT > 10)
3834   {
3835     int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
3836     int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
3837
3838     for (i = start; i <= num_init_game_frames; i++)
3839     {
3840       if (i == num_init_game_frames)
3841         StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3842       else if (setup.sound_loops)
3843         PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3844       else
3845         PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3846
3847       game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
3848
3849       UpdateAndDisplayGameControlValues();
3850
3851       BackToFront();
3852     }
3853
3854     Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
3855
3856     DrawField_MM(ELX, ELY);
3857
3858     DrawLaser(0, DL_LASER_ENABLED);
3859
3860     return;
3861   }
3862
3863   return;
3864 }
3865
3866 void GameActions_MM(struct MouseActionInfo action)
3867 {
3868   boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3869   boolean button_released = (action.button == MB_RELEASED);
3870
3871   GameActions_MM_Ext();
3872
3873   CheckSingleStepMode_MM(element_clicked, button_released);
3874 }
3875
3876 void MovePacMen(void)
3877 {
3878   int mx, my, ox, oy, nx, ny;
3879   int element;
3880   int l;
3881
3882   if (++pacman_nr >= game_mm.num_pacman)
3883     pacman_nr = 0;
3884
3885   game_mm.pacman[pacman_nr].dir--;
3886
3887   for (l = 1; l < 5; l++)
3888   {
3889     game_mm.pacman[pacman_nr].dir++;
3890
3891     if (game_mm.pacman[pacman_nr].dir > 4)
3892       game_mm.pacman[pacman_nr].dir = 1;
3893
3894     if (game_mm.pacman[pacman_nr].dir % 2)
3895     {
3896       mx = 0;
3897       my = game_mm.pacman[pacman_nr].dir - 2;
3898     }
3899     else
3900     {
3901       my = 0;
3902       mx = 3 - game_mm.pacman[pacman_nr].dir;
3903     }
3904
3905     ox = game_mm.pacman[pacman_nr].x;
3906     oy = game_mm.pacman[pacman_nr].y;
3907     nx = ox + mx;
3908     ny = oy + my;
3909     element = Tile[nx][ny];
3910
3911     if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3912       continue;
3913
3914     if (!IS_EATABLE4PACMAN(element))
3915       continue;
3916
3917     if (ObjHit(nx, ny, HIT_POS_CENTER))
3918       continue;
3919
3920     Tile[ox][oy] = EL_EMPTY;
3921     Tile[nx][ny] =
3922       EL_PACMAN_RIGHT - 1 +
3923       (game_mm.pacman[pacman_nr].dir - 1 +
3924        (game_mm.pacman[pacman_nr].dir % 2) * 2);
3925
3926     game_mm.pacman[pacman_nr].x = nx;
3927     game_mm.pacman[pacman_nr].y = ny;
3928
3929     DrawGraphic_MM(ox, oy, IMG_EMPTY);
3930
3931     if (element != EL_EMPTY)
3932     {
3933       int graphic = el2gfx(Tile[nx][ny]);
3934       Bitmap *bitmap;
3935       int src_x, src_y;
3936       int i;
3937
3938       getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3939
3940       CT = FrameCounter;
3941       ox = cSX + ox * TILEX;
3942       oy = cSY + oy * TILEY;
3943
3944       for (i = 1; i < 33; i += 2)
3945         BlitBitmap(bitmap, window,
3946                    src_x, src_y, TILEX, TILEY,
3947                    ox + i * mx, oy + i * my);
3948       Ct = Ct + FrameCounter - CT;
3949     }
3950
3951     DrawField_MM(nx, ny);
3952     BackToFront();
3953
3954     if (!laser.fuse_off)
3955     {
3956       DrawLaser(0, DL_LASER_ENABLED);
3957
3958       if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3959       {
3960         AddDamagedField(nx, ny);
3961
3962         laser.damage[laser.num_damages - 1].edge = 0;
3963       }
3964     }
3965
3966     if (element == EL_BOMB)
3967       DeletePacMan(nx, ny);
3968
3969     if (IS_WALL_AMOEBA(element) &&
3970         (LX + 2 * XS) / TILEX == nx &&
3971         (LY + 2 * YS) / TILEY == ny)
3972     {
3973       laser.num_edges--;
3974       ScanLaser();
3975     }
3976
3977     break;
3978   }
3979 }
3980
3981 static void InitMovingField_MM(int x, int y, int direction)
3982 {
3983   int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3984   int newy = y + (direction == MV_UP   ? -1 : direction == MV_DOWN  ? +1 : 0);
3985
3986   MovDir[x][y] = direction;
3987   MovDir[newx][newy] = direction;
3988
3989   if (Tile[newx][newy] == EL_EMPTY)
3990     Tile[newx][newy] = EL_BLOCKED;
3991 }
3992
3993 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3994 {
3995   int direction = MovDir[x][y];
3996   int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3997   int newy = y + (direction == MV_UP   ? -1 : direction == MV_DOWN  ? +1 : 0);
3998
3999   *goes_to_x = newx;
4000   *goes_to_y = newy;
4001 }
4002
4003 static void Blocked2Moving_MM(int x, int y,
4004                               int *comes_from_x, int *comes_from_y)
4005 {
4006   int oldx = x, oldy = y;
4007   int direction = MovDir[x][y];
4008
4009   if (direction == MV_LEFT)
4010     oldx++;
4011   else if (direction == MV_RIGHT)
4012     oldx--;
4013   else if (direction == MV_UP)
4014     oldy++;
4015   else if (direction == MV_DOWN)
4016     oldy--;
4017
4018   *comes_from_x = oldx;
4019   *comes_from_y = oldy;
4020 }
4021
4022 static int MovingOrBlocked2Element_MM(int x, int y)
4023 {
4024   int element = Tile[x][y];
4025
4026   if (element == EL_BLOCKED)
4027   {
4028     int oldx, oldy;
4029
4030     Blocked2Moving_MM(x, y, &oldx, &oldy);
4031
4032     return Tile[oldx][oldy];
4033   }
4034
4035   return element;
4036 }
4037
4038 #if 0
4039 static void RemoveField(int x, int y)
4040 {
4041   Tile[x][y] = EL_EMPTY;
4042   MovPos[x][y] = 0;
4043   MovDir[x][y] = 0;
4044   MovDelay[x][y] = 0;
4045 }
4046 #endif
4047
4048 static void RemoveMovingField_MM(int x, int y)
4049 {
4050   int oldx = x, oldy = y, newx = x, newy = y;
4051
4052   if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
4053     return;
4054
4055   if (IS_MOVING(x, y))
4056   {
4057     Moving2Blocked_MM(x, y, &newx, &newy);
4058     if (Tile[newx][newy] != EL_BLOCKED)
4059       return;
4060   }
4061   else if (Tile[x][y] == EL_BLOCKED)
4062   {
4063     Blocked2Moving_MM(x, y, &oldx, &oldy);
4064     if (!IS_MOVING(oldx, oldy))
4065       return;
4066   }
4067
4068   Tile[oldx][oldy] = EL_EMPTY;
4069   Tile[newx][newy] = EL_EMPTY;
4070   MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
4071   MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
4072
4073   DrawLevelField_MM(oldx, oldy);
4074   DrawLevelField_MM(newx, newy);
4075 }
4076
4077 void PlaySoundLevel(int x, int y, int sound_nr)
4078 {
4079   int sx = SCREENX(x), sy = SCREENY(y);
4080   int volume, stereo;
4081   int silence_distance = 8;
4082
4083   if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
4084       (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
4085     return;
4086
4087   if (!IN_LEV_FIELD(x, y) ||
4088       sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
4089       sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
4090     return;
4091
4092   volume = SOUND_MAX_VOLUME;
4093
4094 #ifndef MSDOS
4095   stereo = (sx - SCR_FIELDX/2) * 12;
4096 #else
4097   stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
4098   if (stereo > SOUND_MAX_RIGHT)
4099     stereo = SOUND_MAX_RIGHT;
4100   if (stereo < SOUND_MAX_LEFT)
4101     stereo = SOUND_MAX_LEFT;
4102 #endif
4103
4104   if (!IN_SCR_FIELD(sx, sy))
4105   {
4106     int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
4107     int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
4108
4109     volume -= volume * (dx > dy ? dx : dy) / silence_distance;
4110   }
4111
4112   PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
4113 }
4114
4115 static void RaiseScore_MM(int value)
4116 {
4117   game_mm.score += value;
4118 }
4119
4120 void RaiseScoreElement_MM(int element)
4121 {
4122   switch (element)
4123   {
4124     case EL_PACMAN:
4125     case EL_PACMAN_RIGHT:
4126     case EL_PACMAN_UP:
4127     case EL_PACMAN_LEFT:
4128     case EL_PACMAN_DOWN:
4129       RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
4130       break;
4131
4132     case EL_KEY:
4133       RaiseScore_MM(native_mm_level.score[SC_KEY]);
4134       break;
4135
4136     case EL_KETTLE:
4137     case EL_CELL:
4138       RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
4139       break;
4140
4141     case EL_LIGHTBALL:
4142       RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
4143       break;
4144
4145     default:
4146       break;
4147   }
4148 }
4149
4150
4151 // ----------------------------------------------------------------------------
4152 // Mirror Magic game engine snapshot handling functions
4153 // ----------------------------------------------------------------------------
4154
4155 void SaveEngineSnapshotValues_MM(void)
4156 {
4157   int x, y;
4158
4159   engine_snapshot_mm.game_mm = game_mm;
4160   engine_snapshot_mm.laser = laser;
4161
4162   for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4163   {
4164     for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4165     {
4166       engine_snapshot_mm.Ur[x][y]    = Ur[x][y];
4167       engine_snapshot_mm.Hit[x][y]   = Hit[x][y];
4168       engine_snapshot_mm.Box[x][y]   = Box[x][y];
4169       engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4170     }
4171   }
4172
4173   engine_snapshot_mm.LX = LX;
4174   engine_snapshot_mm.LY = LY;
4175   engine_snapshot_mm.XS = XS;
4176   engine_snapshot_mm.YS = YS;
4177   engine_snapshot_mm.ELX = ELX;
4178   engine_snapshot_mm.ELY = ELY;
4179   engine_snapshot_mm.CT = CT;
4180   engine_snapshot_mm.Ct = Ct;
4181
4182   engine_snapshot_mm.last_LX = last_LX;
4183   engine_snapshot_mm.last_LY = last_LY;
4184   engine_snapshot_mm.last_hit_mask = last_hit_mask;
4185   engine_snapshot_mm.hold_x = hold_x;
4186   engine_snapshot_mm.hold_y = hold_y;
4187   engine_snapshot_mm.pacman_nr = pacman_nr;
4188
4189   engine_snapshot_mm.rotate_delay = rotate_delay;
4190   engine_snapshot_mm.pacman_delay = pacman_delay;
4191   engine_snapshot_mm.energy_delay = energy_delay;
4192   engine_snapshot_mm.overload_delay = overload_delay;
4193 }
4194
4195 void LoadEngineSnapshotValues_MM(void)
4196 {
4197   int x, y;
4198
4199   // stored engine snapshot buffers already restored at this point
4200
4201   game_mm = engine_snapshot_mm.game_mm;
4202   laser   = engine_snapshot_mm.laser;
4203
4204   for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4205   {
4206     for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4207     {
4208       Ur[x][y]    = engine_snapshot_mm.Ur[x][y];
4209       Hit[x][y]   = engine_snapshot_mm.Hit[x][y];
4210       Box[x][y]   = engine_snapshot_mm.Box[x][y];
4211       Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4212     }
4213   }
4214
4215   LX  = engine_snapshot_mm.LX;
4216   LY  = engine_snapshot_mm.LY;
4217   XS  = engine_snapshot_mm.XS;
4218   YS  = engine_snapshot_mm.YS;
4219   ELX = engine_snapshot_mm.ELX;
4220   ELY = engine_snapshot_mm.ELY;
4221   CT  = engine_snapshot_mm.CT;
4222   Ct  = engine_snapshot_mm.Ct;
4223
4224   last_LX       = engine_snapshot_mm.last_LX;
4225   last_LY       = engine_snapshot_mm.last_LY;
4226   last_hit_mask = engine_snapshot_mm.last_hit_mask;
4227   hold_x        = engine_snapshot_mm.hold_x;
4228   hold_y        = engine_snapshot_mm.hold_y;
4229   pacman_nr     = engine_snapshot_mm.pacman_nr;
4230
4231   rotate_delay   = engine_snapshot_mm.rotate_delay;
4232   pacman_delay   = engine_snapshot_mm.pacman_delay;
4233   energy_delay   = engine_snapshot_mm.energy_delay;
4234   overload_delay = engine_snapshot_mm.overload_delay;
4235
4236   RedrawPlayfield_MM();
4237 }
4238
4239 static int getAngleFromTouchDelta(int dx, int dy,  int base)
4240 {
4241   double pi = 3.141592653;
4242   double rad = atan2((double)-dy, (double)dx);
4243   double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4244   double deg = rad2 * 180.0 / pi;
4245
4246   return (int)(deg * base / 360.0 + 0.5) % base;
4247 }
4248
4249 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4250 {
4251   // calculate start (source) position to be at the middle of the tile
4252   int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4253   int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4254   int dx = dst_mx - src_mx;
4255   int dy = dst_my - src_my;
4256   int element;
4257   int base = 16;
4258   int phases = 16;
4259   int angle_old = -1;
4260   int angle_new = -1;
4261   int button = 0;
4262   int i;
4263
4264   if (!IN_LEV_FIELD(x, y))
4265     return 0;
4266
4267   element = Tile[x][y];
4268
4269   if (!IS_MCDUFFIN(element) &&
4270       !IS_MIRROR(element) &&
4271       !IS_BEAMER(element) &&
4272       !IS_POLAR(element) &&
4273       !IS_POLAR_CROSS(element) &&
4274       !IS_DF_MIRROR(element))
4275     return 0;
4276
4277   angle_old = get_element_angle(element);
4278
4279   if (IS_MCDUFFIN(element))
4280   {
4281     angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4282                  dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4283                  dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4284                  dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4285                  -1);
4286   }
4287   else if (IS_MIRROR(element) ||
4288            IS_DF_MIRROR(element))
4289   {
4290     for (i = 0; i < laser.num_damages; i++)
4291     {
4292       if (laser.damage[i].x == x &&
4293           laser.damage[i].y == y &&
4294           ObjHit(x, y, HIT_POS_CENTER))
4295       {
4296         angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4297         angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4298
4299         break;
4300       }
4301     }
4302   }
4303
4304   if (angle_new == -1)
4305   {
4306     if (IS_MIRROR(element) ||
4307         IS_DF_MIRROR(element) ||
4308         IS_POLAR(element))
4309       base = 32;
4310
4311     if (IS_POLAR_CROSS(element))
4312       phases = 4;
4313
4314     angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4315   }
4316
4317   button = (angle_new == angle_old ? 0 :
4318             (angle_new - angle_old + phases) % phases < (phases / 2) ?
4319             MB_LEFTBUTTON : MB_RIGHTBUTTON);
4320
4321   return button;
4322 }