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