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