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