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