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