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