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