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