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