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