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