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