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