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