changed code for more generic explosion 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     Store[x][y] = EL_EMPTY;
2731     Store2[x][y] = center_element;
2732
2733     Tile[x][y] = EL_EXPLODING_OPAQUE;
2734
2735     GfxElement[x][y] = (center_element == EL_BOMB_ACTIVE ? EL_BOMB :
2736                         IS_MCDUFFIN(center_element) ? EL_MCDUFFIN :
2737                         center_element);
2738
2739     MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2740
2741     ExplodePhase[x][y] = 1;
2742
2743     return;
2744   }
2745
2746   if (phase == 1)
2747     GfxFrame[x][y] = 0;         // restart explosion animation
2748
2749   ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
2750
2751   center_element = Store2[x][y];
2752
2753   if (phase == half_phase && Store[x][y] == EL_EMPTY)
2754   {
2755     Tile[x][y] = EL_EXPLODING_TRANSP;
2756
2757     if (x == ELX && y == ELY)
2758       ScanLaser();
2759   }
2760
2761   if (phase == last_phase)
2762   {
2763     if (center_element == EL_BOMB_ACTIVE)
2764     {
2765       DrawLaser(0, DL_LASER_DISABLED);
2766       InitLaser();
2767
2768       Bang_MM(laser.start_edge.x, laser.start_edge.y);
2769
2770       GameOver_MM(GAME_OVER_DELAYED);
2771
2772       laser.overloaded = FALSE;
2773     }
2774     else if (IS_MCDUFFIN(center_element))
2775     {
2776       GameOver_MM(GAME_OVER_BOMB);
2777     }
2778
2779     Tile[x][y] = Store[x][y];
2780
2781     Store[x][y] = Store2[x][y] = 0;
2782     MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2783
2784     InitField(x, y, FALSE);
2785     DrawField_MM(x, y);
2786   }
2787   else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2788   {
2789     int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
2790     int frame = getGraphicAnimationFrameXY(graphic, x, y);
2791
2792     DrawGraphicAnimation_MM(x, y, graphic, frame);
2793
2794     MarkTileDirty(x, y);
2795   }
2796 }
2797
2798 static void Bang_MM(int x, int y)
2799 {
2800   int element = Tile[x][y];
2801
2802   if (IS_PACMAN(element))
2803     PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2804   else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
2805     PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2806   else if (element == EL_KEY)
2807     PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2808   else
2809     PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2810
2811   Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
2812 }
2813
2814 static void TurnRound(int x, int y)
2815 {
2816   static struct
2817   {
2818     int x, y;
2819   } move_xy[] =
2820   {
2821     { 0, 0 },
2822     {-1, 0 },
2823     {+1, 0 },
2824     { 0, 0 },
2825     { 0, -1 },
2826     { 0, 0 }, { 0, 0 }, { 0, 0 },
2827     { 0, +1 }
2828   };
2829   static struct
2830   {
2831     int left, right, back;
2832   } turn[] =
2833   {
2834     { 0,        0,              0 },
2835     { MV_DOWN,  MV_UP,          MV_RIGHT },
2836     { MV_UP,    MV_DOWN,        MV_LEFT },
2837     { 0,        0,              0 },
2838     { MV_LEFT,  MV_RIGHT,       MV_DOWN },
2839     { 0,0,0 },  { 0,0,0 },      { 0,0,0 },
2840     { MV_RIGHT, MV_LEFT,        MV_UP }
2841   };
2842
2843   int element = Tile[x][y];
2844   int old_move_dir = MovDir[x][y];
2845   int right_dir = turn[old_move_dir].right;
2846   int back_dir = turn[old_move_dir].back;
2847   int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2848   int right_x = x + right_dx, right_y = y + right_dy;
2849
2850   if (element == EL_PACMAN)
2851   {
2852     boolean can_turn_right = FALSE;
2853
2854     if (IN_LEV_FIELD(right_x, right_y) &&
2855         IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2856       can_turn_right = TRUE;
2857
2858     if (can_turn_right)
2859       MovDir[x][y] = right_dir;
2860     else
2861       MovDir[x][y] = back_dir;
2862
2863     MovDelay[x][y] = 0;
2864   }
2865 }
2866
2867 static void StartMoving_MM(int x, int y)
2868 {
2869   int element = Tile[x][y];
2870
2871   if (Stop[x][y])
2872     return;
2873
2874   if (CAN_MOVE(element))
2875   {
2876     int newx, newy;
2877
2878     if (MovDelay[x][y])         // wait some time before next movement
2879     {
2880       MovDelay[x][y]--;
2881
2882       if (MovDelay[x][y])
2883         return;
2884     }
2885
2886     // now make next step
2887
2888     Moving2Blocked_MM(x, y, &newx, &newy);      // get next screen position
2889
2890     if (element == EL_PACMAN &&
2891         IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2892         !ObjHit(newx, newy, HIT_POS_CENTER))
2893     {
2894       Store[newx][newy] = Tile[newx][newy];
2895       Tile[newx][newy] = EL_EMPTY;
2896
2897       DrawField_MM(newx, newy);
2898     }
2899     else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2900              ObjHit(newx, newy, HIT_POS_CENTER))
2901     {
2902       // object was running against a wall
2903
2904       TurnRound(x, y);
2905
2906       return;
2907     }
2908
2909     InitMovingField_MM(x, y, MovDir[x][y]);
2910   }
2911
2912   if (MovDir[x][y])
2913     ContinueMoving_MM(x, y);
2914 }
2915
2916 static void ContinueMoving_MM(int x, int y)
2917 {
2918   int element = Tile[x][y];
2919   int direction = MovDir[x][y];
2920   int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2921   int dy = (direction == MV_UP   ? -1 : direction == MV_DOWN  ? +1 : 0);
2922   int horiz_move = (dx!=0);
2923   int newx = x + dx, newy = y + dy;
2924   int step = (horiz_move ? dx : dy) * TILEX / 8;
2925
2926   MovPos[x][y] += step;
2927
2928   if (ABS(MovPos[x][y]) >= TILEX)       // object reached its destination
2929   {
2930     Tile[x][y] = EL_EMPTY;
2931     Tile[newx][newy] = element;
2932
2933     MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2934     MovDelay[newx][newy] = 0;
2935
2936     if (!CAN_MOVE(element))
2937       MovDir[newx][newy] = 0;
2938
2939     DrawField_MM(x, y);
2940     DrawField_MM(newx, newy);
2941
2942     Stop[newx][newy] = TRUE;
2943
2944     if (element == EL_PACMAN)
2945     {
2946       if (Store[newx][newy] == EL_BOMB)
2947         Bang_MM(newx, newy);
2948
2949       if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2950           (LX + 2 * XS) / TILEX == newx &&
2951           (LY + 2 * YS) / TILEY == newy)
2952       {
2953         laser.num_edges--;
2954         ScanLaser();
2955       }
2956     }
2957   }
2958   else                          // still moving on
2959   {
2960     DrawField_MM(x, y);
2961   }
2962
2963   laser.redraw = TRUE;
2964 }
2965
2966 boolean ClickElement(int x, int y, int button)
2967 {
2968   static DelayCounter click_delay = { CLICK_DELAY };
2969   static boolean new_button = TRUE;
2970   boolean element_clicked = FALSE;
2971   int element;
2972
2973   if (button == -1)
2974   {
2975     // initialize static variables
2976     click_delay.count = 0;
2977     click_delay.value = CLICK_DELAY;
2978     new_button = TRUE;
2979
2980     return FALSE;
2981   }
2982
2983   // do not rotate objects hit by the laser after the game was solved
2984   if (game_mm.level_solved && Hit[x][y])
2985     return FALSE;
2986
2987   if (button == MB_RELEASED)
2988   {
2989     new_button = TRUE;
2990     click_delay.value = CLICK_DELAY;
2991
2992     // release eventually hold auto-rotating mirror
2993     RotateMirror(x, y, MB_RELEASED);
2994
2995     return FALSE;
2996   }
2997
2998   if (!FrameReached(&click_delay) && !new_button)
2999     return FALSE;
3000
3001   if (button == MB_MIDDLEBUTTON)        // middle button has no function
3002     return FALSE;
3003
3004   if (!IN_LEV_FIELD(x, y))
3005     return FALSE;
3006
3007   if (Tile[x][y] == EL_EMPTY)
3008     return FALSE;
3009
3010   element = Tile[x][y];
3011
3012   if (IS_MIRROR(element) ||
3013       IS_BEAMER(element) ||
3014       IS_POLAR(element) ||
3015       IS_POLAR_CROSS(element) ||
3016       IS_DF_MIRROR(element) ||
3017       IS_DF_MIRROR_AUTO(element))
3018   {
3019     RotateMirror(x, y, button);
3020
3021     element_clicked = TRUE;
3022   }
3023   else if (IS_MCDUFFIN(element))
3024   {
3025     if (!laser.fuse_off)
3026     {
3027       DrawLaser(0, DL_LASER_DISABLED);
3028
3029       /*
3030       BackToFront();
3031       */
3032     }
3033
3034     element = get_rotated_element(element, BUTTON_ROTATION(button));
3035     laser.start_angle = get_element_angle(element);
3036
3037     InitLaser();
3038
3039     Tile[x][y] = element;
3040     DrawField_MM(x, y);
3041
3042     /*
3043     BackToFront();
3044     */
3045
3046     if (!laser.fuse_off)
3047       ScanLaser();
3048
3049     element_clicked = TRUE;
3050   }
3051   else if (element == EL_FUSE_ON && laser.fuse_off)
3052   {
3053     if (x != laser.fuse_x || y != laser.fuse_y)
3054       return FALSE;
3055
3056     laser.fuse_off = FALSE;
3057     laser.fuse_x = laser.fuse_y = -1;
3058
3059     DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
3060     ScanLaser();
3061
3062     element_clicked = TRUE;
3063   }
3064   else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
3065   {
3066     laser.fuse_off = TRUE;
3067     laser.fuse_x = x;
3068     laser.fuse_y = y;
3069     laser.overloaded = FALSE;
3070
3071     DrawLaser(0, DL_LASER_DISABLED);
3072     DrawGraphic_MM(x, y, IMG_MM_FUSE);
3073
3074     element_clicked = TRUE;
3075   }
3076   else if (element == EL_LIGHTBALL)
3077   {
3078     Bang_MM(x, y);
3079     RaiseScoreElement_MM(element);
3080     DrawLaser(0, DL_LASER_ENABLED);
3081
3082     element_clicked = TRUE;
3083   }
3084   else if (IS_ENVELOPE(element))
3085   {
3086     Tile[x][y] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(element);
3087
3088     element_clicked = TRUE;
3089   }
3090
3091   click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
3092   new_button = FALSE;
3093
3094   return element_clicked;
3095 }
3096
3097 static void RotateMirror(int x, int y, int button)
3098 {
3099   if (button == MB_RELEASED)
3100   {
3101     // release eventually hold auto-rotating mirror
3102     hold_x = -1;
3103     hold_y = -1;
3104
3105     return;
3106   }
3107
3108   if (IS_MIRROR(Tile[x][y]) ||
3109       IS_POLAR_CROSS(Tile[x][y]) ||
3110       IS_POLAR(Tile[x][y]) ||
3111       IS_BEAMER(Tile[x][y]) ||
3112       IS_DF_MIRROR(Tile[x][y]) ||
3113       IS_GRID_STEEL_AUTO(Tile[x][y]) ||
3114       IS_GRID_WOOD_AUTO(Tile[x][y]))
3115   {
3116     Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
3117   }
3118   else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
3119   {
3120     if (button == MB_LEFTBUTTON)
3121     {
3122       // left mouse button only for manual adjustment, no auto-rotating;
3123       // freeze mirror for until mouse button released
3124       hold_x = x;
3125       hold_y = y;
3126     }
3127     else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
3128     {
3129       Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
3130     }
3131   }
3132
3133   if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
3134   {
3135     int edge = Hit[x][y];
3136
3137     DrawField_MM(x, y);
3138
3139     if (edge > 0)
3140     {
3141       DrawLaser(edge - 1, DL_LASER_DISABLED);
3142       ScanLaser();
3143     }
3144   }
3145   else if (ObjHit(x, y, HIT_POS_CENTER))
3146   {
3147     int edge = Hit[x][y];
3148
3149     if (edge == 0)
3150     {
3151       Warn("RotateMirror: inconsistent field Hit[][]!\n");
3152
3153       edge = 1;
3154     }
3155
3156     DrawLaser(edge - 1, DL_LASER_DISABLED);
3157     ScanLaser();
3158   }
3159   else
3160   {
3161     int check = 1;
3162
3163     if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
3164       check = 2;
3165
3166     DrawField_MM(x, y);
3167
3168     if ((IS_BEAMER(Tile[x][y]) ||
3169          IS_POLAR(Tile[x][y]) ||
3170          IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
3171     {
3172       if (IS_BEAMER(Tile[x][y]))
3173       {
3174 #if 0
3175         Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3176               LX, LY, laser.beamer_edge, laser.beamer[1].num);
3177 #endif
3178
3179         if (check == 1)
3180           laser.num_edges--;
3181       }
3182
3183       ScanLaser();
3184
3185       check = 0;
3186     }
3187
3188     if (check == 2)
3189       DrawLaser(0, DL_LASER_ENABLED);
3190   }
3191 }
3192
3193 static void AutoRotateMirrors(void)
3194 {
3195   int x, y;
3196
3197   if (!FrameReached(&rotate_delay))
3198     return;
3199
3200   for (x = 0; x < lev_fieldx; x++)
3201   {
3202     for (y = 0; y < lev_fieldy; y++)
3203     {
3204       int element = Tile[x][y];
3205
3206       // do not rotate objects hit by the laser after the game was solved
3207       if (game_mm.level_solved && Hit[x][y])
3208         continue;
3209
3210       if (IS_DF_MIRROR_AUTO(element) ||
3211           IS_GRID_WOOD_AUTO(element) ||
3212           IS_GRID_STEEL_AUTO(element) ||
3213           element == EL_REFRACTOR)
3214         RotateMirror(x, y, MB_RIGHTBUTTON);
3215     }
3216   }
3217 }
3218
3219 static boolean ObjHit(int obx, int oby, int bits)
3220 {
3221   int i;
3222
3223   obx *= TILEX;
3224   oby *= TILEY;
3225
3226   if (bits & HIT_POS_CENTER)
3227   {
3228     if (CheckLaserPixel(cSX + obx + 15,
3229                         cSY + oby + 15))
3230       return TRUE;
3231   }
3232
3233   if (bits & HIT_POS_EDGE)
3234   {
3235     for (i = 0; i < 4; i++)
3236       if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3237                           cSY + oby + 31 * (i / 2)))
3238         return TRUE;
3239   }
3240
3241   if (bits & HIT_POS_BETWEEN)
3242   {
3243     for (i = 0; i < 4; i++)
3244       if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3245                           cSY + 4 + oby + 22 * (i / 2)))
3246         return TRUE;
3247   }
3248
3249   return FALSE;
3250 }
3251
3252 static void DeletePacMan(int px, int py)
3253 {
3254   int i, j;
3255
3256   Bang_MM(px, py);
3257
3258   if (game_mm.num_pacman <= 1)
3259   {
3260     game_mm.num_pacman = 0;
3261     return;
3262   }
3263
3264   for (i = 0; i < game_mm.num_pacman; i++)
3265     if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3266       break;
3267
3268   game_mm.num_pacman--;
3269
3270   for (j = i; j < game_mm.num_pacman; j++)
3271   {
3272     game_mm.pacman[j].x   = game_mm.pacman[j + 1].x;
3273     game_mm.pacman[j].y   = game_mm.pacman[j + 1].y;
3274     game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3275   }
3276 }
3277
3278 static void GameActions_MM_Ext(void)
3279 {
3280   int element;
3281   int x, y, i;
3282
3283   int r, d;
3284
3285   for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3286     Stop[x][y] = FALSE;
3287
3288   for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3289   {
3290     element = Tile[x][y];
3291
3292     if (!IS_MOVING(x, y) && CAN_MOVE(element))
3293       StartMoving_MM(x, y);
3294     else if (IS_MOVING(x, y))
3295       ContinueMoving_MM(x, y);
3296     else if (IS_EXPLODING(element))
3297       Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3298     else if (element == EL_EXIT_OPENING)
3299       OpenExit(x, y);
3300     else if (element == EL_GRAY_BALL_OPENING)
3301       OpenGrayBall(x, y);
3302     else if (IS_ENVELOPE_OPENING(element))
3303       OpenEnvelope(x, y);
3304     else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE_BASE)
3305       MeltIce(x, y);
3306     else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA_BASE)
3307       GrowAmoeba(x, y);
3308     else if (IS_MIRROR(element) ||
3309              IS_MIRROR_FIXED(element) ||
3310              element == EL_PRISM)
3311       DrawFieldTwinkle(x, y);
3312     else if (element == EL_GRAY_BALL_ACTIVE ||
3313              element == EL_BOMB_ACTIVE ||
3314              element == EL_MINE_ACTIVE)
3315       DrawFieldAnimated_MM(x, y);
3316     else if (!IS_BLOCKED(x, y))
3317       DrawFieldAnimatedIfNeeded_MM(x, y);
3318   }
3319
3320   AutoRotateMirrors();
3321
3322 #if 1
3323   // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3324
3325   // redraw after Explode_MM() ...
3326   if (laser.redraw)
3327     DrawLaser(0, DL_LASER_ENABLED);
3328   laser.redraw = FALSE;
3329 #endif
3330
3331   CT = FrameCounter;
3332
3333   if (game_mm.num_pacman && FrameReached(&pacman_delay))
3334   {
3335     MovePacMen();
3336
3337     if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3338     {
3339       DrawLaser(0, DL_LASER_DISABLED);
3340       ScanLaser();
3341     }
3342   }
3343
3344   // skip all following game actions if game is over
3345   if (game_mm.game_over)
3346     return;
3347
3348   if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3349   {
3350     FadeOutLaser();
3351
3352     GameOver_MM(GAME_OVER_NO_ENERGY);
3353
3354     return;
3355   }
3356
3357   if (FrameReached(&energy_delay))
3358   {
3359     if (game_mm.energy_left > 0)
3360       game_mm.energy_left--;
3361
3362     // when out of energy, wait another frame to play "out of time" sound
3363   }
3364
3365   element = laser.dest_element;
3366
3367 #if 0
3368   if (element != Tile[ELX][ELY])
3369   {
3370     Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3371           element, Tile[ELX][ELY]);
3372   }
3373 #endif
3374
3375   if (!laser.overloaded && laser.overload_value == 0 &&
3376       element != EL_BOMB &&
3377       element != EL_BOMB_ACTIVE &&
3378       element != EL_MINE &&
3379       element != EL_MINE_ACTIVE &&
3380       element != EL_GRAY_BALL &&
3381       element != EL_GRAY_BALL_ACTIVE &&
3382       element != EL_BLOCK_STONE &&
3383       element != EL_BLOCK_WOOD &&
3384       element != EL_FUSE_ON &&
3385       element != EL_FUEL_FULL &&
3386       !IS_WALL_ICE(element) &&
3387       !IS_WALL_AMOEBA(element))
3388     return;
3389
3390   overload_delay.value = HEALTH_DELAY(laser.overloaded);
3391
3392   if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3393        (!laser.overloaded && laser.overload_value > 0)) &&
3394       FrameReached(&overload_delay))
3395   {
3396     if (laser.overloaded)
3397       laser.overload_value++;
3398     else
3399       laser.overload_value--;
3400
3401     if (game_mm.cheat_no_overload)
3402     {
3403       laser.overloaded = FALSE;
3404       laser.overload_value = 0;
3405     }
3406
3407     game_mm.laser_overload_value = laser.overload_value;
3408
3409     if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3410     {
3411       SetLaserColor(0xFF);
3412
3413       DrawLaser(0, DL_LASER_ENABLED);
3414     }
3415
3416     if (!laser.overloaded)
3417       StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3418     else if (setup.sound_loops)
3419       PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3420     else
3421       PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3422
3423     if (laser.overload_value == MAX_LASER_OVERLOAD)
3424     {
3425       UpdateAndDisplayGameControlValues();
3426
3427       FadeOutLaser();
3428
3429       GameOver_MM(GAME_OVER_OVERLOADED);
3430
3431       return;
3432     }
3433   }
3434
3435   if (laser.fuse_off)
3436     return;
3437
3438   CT -= Ct;
3439
3440   if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3441   {
3442     if (game_mm.cheat_no_explosion)
3443       return;
3444
3445     Bang_MM(ELX, ELY);
3446
3447     laser.dest_element = EL_EXPLODING_OPAQUE;
3448
3449     return;
3450   }
3451
3452   if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3453   {
3454     laser.fuse_off = TRUE;
3455     laser.fuse_x = ELX;
3456     laser.fuse_y = ELY;
3457
3458     DrawLaser(0, DL_LASER_DISABLED);
3459     DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3460   }
3461
3462   if (element == EL_GRAY_BALL && CT > native_mm_level.time_ball)
3463   {
3464     if (!Store2[ELX][ELY])      // check if content element not yet determined
3465     {
3466       int last_anim_random_frame = gfx.anim_random_frame;
3467       int element_pos;
3468
3469       if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3470         gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3471
3472       element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3473                                       native_mm_level.ball_choice_mode, 0,
3474                                       game_mm.ball_choice_pos);
3475
3476       if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3477         gfx.anim_random_frame = last_anim_random_frame;
3478
3479       game_mm.ball_choice_pos++;
3480
3481       int new_element = native_mm_level.ball_content[element_pos];
3482       int new_element_base = map_wall_to_base_element(new_element);
3483
3484       if (IS_WALL(new_element_base))
3485       {
3486         // always use completely filled wall element
3487         new_element = new_element_base | 0x000f;
3488       }
3489       else if (native_mm_level.rotate_ball_content &&
3490                get_num_elements(new_element) > 1)
3491       {
3492         // randomly rotate newly created game element
3493         new_element = get_rotated_element(new_element, RND(16));
3494       }
3495
3496       Store[ELX][ELY] = new_element;
3497       Store2[ELX][ELY] = TRUE;
3498     }
3499
3500     Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3501
3502     laser.dest_element_last = Tile[ELX][ELY];
3503
3504     return;
3505   }
3506
3507   if (IS_WALL_ICE(element) && CT > 50)
3508   {
3509     PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3510
3511     Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE_BASE + EL_WALL_CHANGING_BASE;
3512     Store[ELX][ELY] = EL_WALL_ICE_BASE;
3513     Store2[ELX][ELY] = laser.wall_mask;
3514
3515     laser.dest_element = Tile[ELX][ELY];
3516
3517     return;
3518   }
3519
3520   if (IS_WALL_AMOEBA(element) && CT > 60)
3521   {
3522     int k1, k2, k3;
3523     int element2 = Tile[ELX][ELY];
3524
3525     if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3526       return;
3527
3528     for (i = laser.num_damages - 1; i >= 0; i--)
3529       if (laser.damage[i].is_mirror)
3530         break;
3531
3532     r = laser.num_edges;
3533     d = laser.num_damages;
3534     k1 = i;
3535
3536     if (k1 > 0)
3537     {
3538       int x, y;
3539
3540       DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3541
3542       laser.num_edges++;
3543       DrawLaser(0, DL_LASER_ENABLED);
3544       laser.num_edges--;
3545
3546       x = laser.damage[k1].x;
3547       y = laser.damage[k1].y;
3548
3549       DrawField_MM(x, y);
3550     }
3551
3552     for (i = 0; i < 4; i++)
3553     {
3554       if (laser.wall_mask & (1 << i))
3555       {
3556         if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3557                             cSY + ELY * TILEY + 31 * (i / 2)))
3558           break;
3559
3560         if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3561                             cSY + ELY * TILEY + 14 + (i / 2) * 2))
3562           break;
3563       }
3564     }
3565
3566     k2 = i;
3567
3568     for (i = 0; i < 4; i++)
3569     {
3570       if (laser.wall_mask & (1 << i))
3571       {
3572         if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3573                             cSY + ELY * TILEY + 31 * (i / 2)))
3574           break;
3575       }
3576     }
3577
3578     k3 = i;
3579
3580     if (laser.num_beamers > 0 ||
3581         k1 < 1 || k2 < 4 || k3 < 4 ||
3582         CheckLaserPixel(cSX + ELX * TILEX + 14,
3583                         cSY + ELY * TILEY + 14))
3584     {
3585       laser.num_edges = r;
3586       laser.num_damages = d;
3587
3588       DrawLaser(0, DL_LASER_DISABLED);
3589     }
3590
3591     Tile[ELX][ELY] = element | laser.wall_mask;
3592
3593     int x = ELX, y = ELY;
3594     int wall_mask = laser.wall_mask;
3595
3596     ScanLaser();
3597     DrawLaser(0, DL_LASER_ENABLED);
3598
3599     PlayLevelSound_MM(x, y, element, MM_ACTION_GROWING);
3600
3601     Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA_BASE + EL_WALL_CHANGING_BASE;
3602     Store[x][y] = EL_WALL_AMOEBA_BASE;
3603     Store2[x][y] = wall_mask;
3604
3605     return;
3606   }
3607
3608   if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3609       laser.stops_inside_element && CT > native_mm_level.time_block)
3610   {
3611     int x, y;
3612     int k;
3613
3614     if (ABS(XS) > ABS(YS))
3615       k = 0;
3616     else
3617       k = 1;
3618     if (XS < YS)
3619       k += 2;
3620
3621     for (i = 0; i < 4; i++)
3622     {
3623       if (i)
3624         k++;
3625       if (k > 3)
3626         k = 0;
3627
3628       x = ELX + Step[k * 4].x;
3629       y = ELY + Step[k * 4].y;
3630
3631       if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3632         continue;
3633
3634       if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3635         continue;
3636
3637       break;
3638     }
3639
3640     if (i > 3)
3641     {
3642       laser.overloaded = (element == EL_BLOCK_STONE);
3643
3644       return;
3645     }
3646
3647     PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3648
3649     Tile[ELX][ELY] = 0;
3650     Tile[x][y] = element;
3651
3652     DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3653     DrawField_MM(x, y);
3654
3655     if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3656     {
3657       DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3658       DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3659     }
3660
3661     ScanLaser();
3662
3663     return;
3664   }
3665
3666   if (element == EL_FUEL_FULL && CT > 10)
3667   {
3668     int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
3669     int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
3670
3671     for (i = start; i <= num_init_game_frames; i++)
3672     {
3673       if (i == num_init_game_frames)
3674         StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3675       else if (setup.sound_loops)
3676         PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3677       else
3678         PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3679
3680       game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
3681
3682       UpdateAndDisplayGameControlValues();
3683
3684       BackToFront();
3685     }
3686
3687     Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
3688
3689     DrawField_MM(ELX, ELY);
3690
3691     DrawLaser(0, DL_LASER_ENABLED);
3692
3693     return;
3694   }
3695 }
3696
3697 void GameActions_MM(struct MouseActionInfo action)
3698 {
3699   boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3700   boolean button_released = (action.button == MB_RELEASED);
3701
3702   GameActions_MM_Ext();
3703
3704   CheckSingleStepMode_MM(element_clicked, button_released);
3705 }
3706
3707 static void MovePacMen(void)
3708 {
3709   int mx, my, ox, oy, nx, ny;
3710   int element;
3711   int l;
3712
3713   if (++pacman_nr >= game_mm.num_pacman)
3714     pacman_nr = 0;
3715
3716   game_mm.pacman[pacman_nr].dir--;
3717
3718   for (l = 1; l < 5; l++)
3719   {
3720     game_mm.pacman[pacman_nr].dir++;
3721
3722     if (game_mm.pacman[pacman_nr].dir > 4)
3723       game_mm.pacman[pacman_nr].dir = 1;
3724
3725     if (game_mm.pacman[pacman_nr].dir % 2)
3726     {
3727       mx = 0;
3728       my = game_mm.pacman[pacman_nr].dir - 2;
3729     }
3730     else
3731     {
3732       my = 0;
3733       mx = 3 - game_mm.pacman[pacman_nr].dir;
3734     }
3735
3736     ox = game_mm.pacman[pacman_nr].x;
3737     oy = game_mm.pacman[pacman_nr].y;
3738     nx = ox + mx;
3739     ny = oy + my;
3740     element = Tile[nx][ny];
3741
3742     if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3743       continue;
3744
3745     if (!IS_EATABLE4PACMAN(element))
3746       continue;
3747
3748     if (ObjHit(nx, ny, HIT_POS_CENTER))
3749       continue;
3750
3751     Tile[ox][oy] = EL_EMPTY;
3752     Tile[nx][ny] =
3753       EL_PACMAN_RIGHT - 1 +
3754       (game_mm.pacman[pacman_nr].dir - 1 +
3755        (game_mm.pacman[pacman_nr].dir % 2) * 2);
3756
3757     game_mm.pacman[pacman_nr].x = nx;
3758     game_mm.pacman[pacman_nr].y = ny;
3759
3760     DrawGraphic_MM(ox, oy, IMG_EMPTY);
3761
3762     if (element != EL_EMPTY)
3763     {
3764       int graphic = el2gfx(Tile[nx][ny]);
3765       Bitmap *bitmap;
3766       int src_x, src_y;
3767       int i;
3768
3769       getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3770
3771       CT = FrameCounter;
3772       ox = cSX + ox * TILEX;
3773       oy = cSY + oy * TILEY;
3774
3775       for (i = 1; i < 33; i += 2)
3776         BlitBitmap(bitmap, window,
3777                    src_x, src_y, TILEX, TILEY,
3778                    ox + i * mx, oy + i * my);
3779       Ct = Ct + FrameCounter - CT;
3780     }
3781
3782     DrawField_MM(nx, ny);
3783     BackToFront();
3784
3785     if (!laser.fuse_off)
3786     {
3787       DrawLaser(0, DL_LASER_ENABLED);
3788
3789       if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3790       {
3791         AddDamagedField(nx, ny);
3792
3793         laser.damage[laser.num_damages - 1].edge = 0;
3794       }
3795     }
3796
3797     if (element == EL_BOMB)
3798       DeletePacMan(nx, ny);
3799
3800     if (IS_WALL_AMOEBA(element) &&
3801         (LX + 2 * XS) / TILEX == nx &&
3802         (LY + 2 * YS) / TILEY == ny)
3803     {
3804       laser.num_edges--;
3805       ScanLaser();
3806     }
3807
3808     break;
3809   }
3810 }
3811
3812 static void InitMovingField_MM(int x, int y, int direction)
3813 {
3814   int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3815   int newy = y + (direction == MV_UP   ? -1 : direction == MV_DOWN  ? +1 : 0);
3816
3817   MovDir[x][y] = direction;
3818   MovDir[newx][newy] = direction;
3819
3820   if (Tile[newx][newy] == EL_EMPTY)
3821     Tile[newx][newy] = EL_BLOCKED;
3822 }
3823
3824 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3825 {
3826   int direction = MovDir[x][y];
3827   int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3828   int newy = y + (direction == MV_UP   ? -1 : direction == MV_DOWN  ? +1 : 0);
3829
3830   *goes_to_x = newx;
3831   *goes_to_y = newy;
3832 }
3833
3834 static void Blocked2Moving_MM(int x, int y,
3835                               int *comes_from_x, int *comes_from_y)
3836 {
3837   int oldx = x, oldy = y;
3838   int direction = MovDir[x][y];
3839
3840   if (direction == MV_LEFT)
3841     oldx++;
3842   else if (direction == MV_RIGHT)
3843     oldx--;
3844   else if (direction == MV_UP)
3845     oldy++;
3846   else if (direction == MV_DOWN)
3847     oldy--;
3848
3849   *comes_from_x = oldx;
3850   *comes_from_y = oldy;
3851 }
3852
3853 static int MovingOrBlocked2Element_MM(int x, int y)
3854 {
3855   int element = Tile[x][y];
3856
3857   if (element == EL_BLOCKED)
3858   {
3859     int oldx, oldy;
3860
3861     Blocked2Moving_MM(x, y, &oldx, &oldy);
3862
3863     return Tile[oldx][oldy];
3864   }
3865
3866   return element;
3867 }
3868
3869 static void RemoveMovingField_MM(int x, int y)
3870 {
3871   int oldx = x, oldy = y, newx = x, newy = y;
3872
3873   if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3874     return;
3875
3876   if (IS_MOVING(x, y))
3877   {
3878     Moving2Blocked_MM(x, y, &newx, &newy);
3879     if (Tile[newx][newy] != EL_BLOCKED)
3880       return;
3881   }
3882   else if (Tile[x][y] == EL_BLOCKED)
3883   {
3884     Blocked2Moving_MM(x, y, &oldx, &oldy);
3885     if (!IS_MOVING(oldx, oldy))
3886       return;
3887   }
3888
3889   Tile[oldx][oldy] = EL_EMPTY;
3890   Tile[newx][newy] = EL_EMPTY;
3891   MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3892   MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3893
3894   DrawLevelField_MM(oldx, oldy);
3895   DrawLevelField_MM(newx, newy);
3896 }
3897
3898 static void RaiseScore_MM(int value)
3899 {
3900   game_mm.score += value;
3901 }
3902
3903 void RaiseScoreElement_MM(int element)
3904 {
3905   switch (element)
3906   {
3907     case EL_PACMAN:
3908     case EL_PACMAN_RIGHT:
3909     case EL_PACMAN_UP:
3910     case EL_PACMAN_LEFT:
3911     case EL_PACMAN_DOWN:
3912       RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
3913       break;
3914
3915     case EL_KEY:
3916       RaiseScore_MM(native_mm_level.score[SC_KEY]);
3917       break;
3918
3919     case EL_KETTLE:
3920     case EL_CELL:
3921       RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
3922       break;
3923
3924     case EL_LIGHTBALL:
3925       RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
3926       break;
3927
3928     default:
3929       break;
3930   }
3931 }
3932
3933
3934 // ----------------------------------------------------------------------------
3935 // Mirror Magic game engine snapshot handling functions
3936 // ----------------------------------------------------------------------------
3937
3938 void SaveEngineSnapshotValues_MM(void)
3939 {
3940   int x, y;
3941
3942   engine_snapshot_mm.game_mm = game_mm;
3943   engine_snapshot_mm.laser = laser;
3944
3945   for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
3946   {
3947     for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
3948     {
3949       engine_snapshot_mm.Ur[x][y]    = Ur[x][y];
3950       engine_snapshot_mm.Hit[x][y]   = Hit[x][y];
3951       engine_snapshot_mm.Box[x][y]   = Box[x][y];
3952       engine_snapshot_mm.Angle[x][y] = Angle[x][y];
3953     }
3954   }
3955
3956   engine_snapshot_mm.LX = LX;
3957   engine_snapshot_mm.LY = LY;
3958   engine_snapshot_mm.XS = XS;
3959   engine_snapshot_mm.YS = YS;
3960   engine_snapshot_mm.ELX = ELX;
3961   engine_snapshot_mm.ELY = ELY;
3962   engine_snapshot_mm.CT = CT;
3963   engine_snapshot_mm.Ct = Ct;
3964
3965   engine_snapshot_mm.last_LX = last_LX;
3966   engine_snapshot_mm.last_LY = last_LY;
3967   engine_snapshot_mm.last_hit_mask = last_hit_mask;
3968   engine_snapshot_mm.hold_x = hold_x;
3969   engine_snapshot_mm.hold_y = hold_y;
3970   engine_snapshot_mm.pacman_nr = pacman_nr;
3971
3972   engine_snapshot_mm.rotate_delay = rotate_delay;
3973   engine_snapshot_mm.pacman_delay = pacman_delay;
3974   engine_snapshot_mm.energy_delay = energy_delay;
3975   engine_snapshot_mm.overload_delay = overload_delay;
3976 }
3977
3978 void LoadEngineSnapshotValues_MM(void)
3979 {
3980   int x, y;
3981
3982   // stored engine snapshot buffers already restored at this point
3983
3984   game_mm = engine_snapshot_mm.game_mm;
3985   laser   = engine_snapshot_mm.laser;
3986
3987   for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
3988   {
3989     for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
3990     {
3991       Ur[x][y]    = engine_snapshot_mm.Ur[x][y];
3992       Hit[x][y]   = engine_snapshot_mm.Hit[x][y];
3993       Box[x][y]   = engine_snapshot_mm.Box[x][y];
3994       Angle[x][y] = engine_snapshot_mm.Angle[x][y];
3995     }
3996   }
3997
3998   LX  = engine_snapshot_mm.LX;
3999   LY  = engine_snapshot_mm.LY;
4000   XS  = engine_snapshot_mm.XS;
4001   YS  = engine_snapshot_mm.YS;
4002   ELX = engine_snapshot_mm.ELX;
4003   ELY = engine_snapshot_mm.ELY;
4004   CT  = engine_snapshot_mm.CT;
4005   Ct  = engine_snapshot_mm.Ct;
4006
4007   last_LX       = engine_snapshot_mm.last_LX;
4008   last_LY       = engine_snapshot_mm.last_LY;
4009   last_hit_mask = engine_snapshot_mm.last_hit_mask;
4010   hold_x        = engine_snapshot_mm.hold_x;
4011   hold_y        = engine_snapshot_mm.hold_y;
4012   pacman_nr     = engine_snapshot_mm.pacman_nr;
4013
4014   rotate_delay   = engine_snapshot_mm.rotate_delay;
4015   pacman_delay   = engine_snapshot_mm.pacman_delay;
4016   energy_delay   = engine_snapshot_mm.energy_delay;
4017   overload_delay = engine_snapshot_mm.overload_delay;
4018
4019   RedrawPlayfield_MM();
4020 }
4021
4022 static int getAngleFromTouchDelta(int dx, int dy,  int base)
4023 {
4024   double pi = 3.141592653;
4025   double rad = atan2((double)-dy, (double)dx);
4026   double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4027   double deg = rad2 * 180.0 / pi;
4028
4029   return (int)(deg * base / 360.0 + 0.5) % base;
4030 }
4031
4032 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4033 {
4034   // calculate start (source) position to be at the middle of the tile
4035   int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4036   int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4037   int dx = dst_mx - src_mx;
4038   int dy = dst_my - src_my;
4039   int element;
4040   int base = 16;
4041   int phases = 16;
4042   int angle_old = -1;
4043   int angle_new = -1;
4044   int button = 0;
4045   int i;
4046
4047   if (!IN_LEV_FIELD(x, y))
4048     return 0;
4049
4050   element = Tile[x][y];
4051
4052   if (!IS_MCDUFFIN(element) &&
4053       !IS_MIRROR(element) &&
4054       !IS_BEAMER(element) &&
4055       !IS_POLAR(element) &&
4056       !IS_POLAR_CROSS(element) &&
4057       !IS_DF_MIRROR(element))
4058     return 0;
4059
4060   angle_old = get_element_angle(element);
4061
4062   if (IS_MCDUFFIN(element))
4063   {
4064     angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4065                  dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4066                  dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4067                  dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4068                  -1);
4069   }
4070   else if (IS_MIRROR(element) ||
4071            IS_DF_MIRROR(element))
4072   {
4073     for (i = 0; i < laser.num_damages; i++)
4074     {
4075       if (laser.damage[i].x == x &&
4076           laser.damage[i].y == y &&
4077           ObjHit(x, y, HIT_POS_CENTER))
4078       {
4079         angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4080         angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4081
4082         break;
4083       }
4084     }
4085   }
4086
4087   if (angle_new == -1)
4088   {
4089     if (IS_MIRROR(element) ||
4090         IS_DF_MIRROR(element) ||
4091         IS_POLAR(element))
4092       base = 32;
4093
4094     if (IS_POLAR_CROSS(element))
4095       phases = 4;
4096
4097     angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4098   }
4099
4100   button = (angle_new == angle_old ? 0 :
4101             (angle_new - angle_old + phases) % phases < (phases / 2) ?
4102             MB_LEFTBUTTON : MB_RIGHTBUTTON);
4103
4104   return button;
4105 }