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