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