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