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