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