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